Pregunta

Background: I am compiling 2 dependent TypeScript files to js, which produces also source maps (one source map per file) using tsc 1.0

I'm using -m commonjs and then use browserify to generate a single bundle.js

However I noticed that I get the original source map references twice in the bundle, which doesn't seem to work.

Passing --debug doesn't seem to do the trick either.

I had a feeling this issue: https://github.com/substack/node-browserify/issues/325 is somewhat related, but I couldn't figure out how the issue was resolved.

Also https://github.com/substack/browser-pack was suggested, but again I don't fully understand how to use it, is it a replacement to browserify?

Bottom line, I would like to merge the 2 js files but "merge" the js to ts source maps using browserify. Is that possible?

¿Fue útil?

Solución 2

Using the minifyify browserify plugin I believe you can use TypeScript with Browserify and retain the source maps. After compiling the TypeScript files you should be able to pass the "entry" file (the one that imports the other one via commonjs syntax) through browserify with the minifyify plugin.

var browserify = require('browserify'),
    bundler = new browserify();

bundler.add('entry.js');
bundler.plugin('minifyify', {map: 'bundle.js.map'});
bundler.bundle({debug: true}, function (err, src, map) {
  if (err) console.log(err);
  fs.writeFileSync('bundle.js', src);
  fs.writeFileSync('bundle.js.map', map);
});

Otros consejos

tsify is a browserify plugin that is better and replaces e.g. typescriptifier.

npm install tsify browserify watchify

You use tsify like this:

browserify src/index.ts -p tsify --debug -o build/index.js

Notice that this supports browserify --debug switch, no extra tricks required. So you can also use it with watchify like this:

watchify src/index.ts -p tsify --debug -o build/index.js

Here is my working solution:

var settings = {
  projectName : "test"
};    

gulp.task("bundle", function() {

  var mainTsFilePath = "src/main.ts";
  var outputFolder   = "bundle/src/";
  var outputFileName = settings.projectName + ".min.js";
  var pkg            = require("./package.json");

  var banner = [
    "/**",
    " * <%= pkg.name %> v.<%= pkg.version %> - <%= pkg.description %>",
    " * Copyright (c) 2015 <%= pkg.author %>",
    " * <%= pkg.license %>",
    " */", ""
  ].join("\n");

  var bundler = browserify({
    debug: true,
    standalone : settings.projectName
  });

  // TS compiler options are in tsconfig.json file
  return bundler.add(mainTsFilePath)
                .plugin(tsify)
                .bundle()
                .pipe(source(outputFileName))
                .pipe(buffer())
                .pipe(sourcemaps.init({ loadMaps: true }))
                .pipe(uglify())
                .pipe(header(banner, { pkg : pkg } ))
                .pipe(sourcemaps.write('./'))
                .pipe(gulp.dest(outputFolder));
});

I created example project.

You can run it with $(npm bin)/gulp build --env=dev for development environment and source maps will be generated.

There is gulpfile.js:

'use strict';

var path = require('path'),
    gulp = require('gulp'),
    del = require('del'),
    typescript = require('gulp-typescript'),
    sourcemaps = require('gulp-sourcemaps'),
    browserify = require('browserify'),
    source = require('vinyl-source-stream'),
    buffer = require('vinyl-buffer'),
    uglify = require('gulp-uglify'),
    gutil = require('gulp-util'),
    inject = require('gulp-inject'),
    babel = require('gulp-babel'),
    argv = require('yargs').argv;

var devEnvironment = 'dev',
    prodEnvironment = 'prod',
    environment = argv.env || prodEnvironment,
    isDevelopment = environment === devEnvironment;

var projectPath = __dirname,
    srcDir = 'src',
    srcPath = path.join(projectPath, srcDir),
    buildDir = path.join('build', environment),
    buildPath = path.join(projectPath, buildDir),
    distDir = 'dist',
    distRelativePath = path.join(buildDir, distDir),
    distPath = path.join(buildPath, distDir);

var tsSrcPath = path.join(srcPath, 'typescript'),
    tsGlob = path.join(tsSrcPath, '**', '*.ts'),
    tsBuildPath = path.join(buildPath, 'tsc');

var indexHtmlName = 'index.html',
    indexJsName = 'index.js';

var distIndexJsPath = path.join(distPath, 'index.js'),
    distIndexHtmlPath = path.join(distPath, indexHtmlName);

var tsProject = typescript.createProject('tsconfig.json');

console.log('Environment: ' + environment);

gulp.task('clean', function () {
    return del([buildPath]);
});

gulp.task('tsc', ['clean'], function () {
    var stream = gulp.src([tsGlob]);
    if (isDevelopment) {
        stream = stream
            .pipe(sourcemaps.init());
    }
    stream = stream
        .pipe(typescript(tsProject))
        .pipe(babel({
            presets: ['es2015']
        }));
    if (isDevelopment) {
        stream = stream.pipe(sourcemaps.write({sourceRoot: tsSrcPath}));
    }
    return stream.pipe(gulp.dest(tsBuildPath));
});

gulp.task('bundle', ['tsc'], function () {
    var b = browserify({
        entries: path.join(tsBuildPath, indexJsName),
        debug: isDevelopment
    });

    var stream = b.bundle()
        .pipe(source(indexJsName))
        .pipe(buffer());
    if (!isDevelopment) {
        stream = stream.pipe(uglify());
    }
    return stream
        .on('error', gutil.log)
        .pipe(gulp.dest(distPath));
});

gulp.task('build', ['bundle'], function() {
    return gulp.src(path.join(srcPath, indexHtmlName))
        .pipe(inject(gulp.src([distIndexJsPath], {read: false}), {ignorePath: distRelativePath, addRootSlash: true}))
        .pipe(gulp.dest(distPath));
});

You should pay attention to lines:

  1. stream = stream.pipe(sourcemaps.write('', {sourceRoot: tsSrcPath})); - write inline source maps with sourceRoot pointing to your typescript sources path. Inline maps are written directly to .js files generated by tsc to build/dev/tsc.
  2. debug: isDevelopment - in development environment make browserify generate his own source maps for resulting bundle build/dev/dist/index.js file so it will have source maps referencing .js files from build/dev/tsc which in turn have source maps referencing .ts files from src/typescript.

With this setup you will be able to see and debug .ts files in browser:

Screenshot

I faced similar issue when trying to debug my Angular2 app running in Chrome in Visual Studio Code (Using Debugger for Chrome extension)

I use gulp as my task runner and my setup is as follows: Typescript files -> tsc -> intermediate es5 js -> browserify (plus uglify in production build) -> compiled bundle

My directory structure is as follows:

|- src
    |- my .ts files here
    |- main.ts - my entry file
|- dist
    |- intermediate files go here
|- web
    |- app.js - final bundle
    |- app.js.map - final bundle map
|- gulpfile.js

gulpfile.js:

var gulp = require('gulp'),
    tsc = require('gulp-typescript'),
    browserify = require('browserify'),
    uglify = require('gulp-uglify'),
    sourcemaps = require('gulp-sourcemaps'),
    source = require('vinyl-source-stream'),
    buffer = require('vinyl-buffer');

gulp.task('tsc', [], () => {
            return gulp.src(['src/**/*.ts'])
                    .pipe(sourcemaps.init())
                    .pipe(tsc({
                            "target": "es5",
                            "module": "commonjs",
                            "moduleResolution": "node",
                            "sourceMap": true,
                            "emitDecoratorMetadata": true,
                            "experimentalDecorators": true,
                            "lib": [ "es2015", "dom" ],
                            "noImplicitAny": true,
                            "suppressImplicitAnyIndexErrors": true
                    }))
                    .pipe(sourcemaps.write(null, {
                            "sourceRoot": function(file) { 
                                    let parts = file.relative.split('\\');
                                    let root = Array(parts.length + 1).join('../') + 'src';
                                    return root;
                            }
                    }))
                    .pipe(gulp.dest('dist/'));
    }); 
gulp.task('bundle', ['tsc'], () => {
    let b = browserify({
        entries: 'dist/main.js',
        debug: true,
    });

    return b.bundle()
        .pipe(source('app.js'))
        .pipe(buffer())
        .pipe(sourcemaps.init({loadMaps: true}))
        .pipe(sourcemaps.write('./', {
            "sourceRoot": "../",
        }))
        .pipe(gulp.dest('web/'));
})

gulp.task('default', ['bundle']);

Explanation/reasoning:

For some reason browserify doesn't read and parse .js.map files linked in .js file (via special comment at the end) but it does when the source map is embedded in js file. So, by passing null instead of path to sourcemaps it will embed it at the end of generated .js file.

Next issue I noticed was that sourcemaps doesn't automatically follow directory structure (add '../' to sourceRoot when it goes to next directory level), so I made a quick function to complement this. Keep in mind that it only works on Windows - on Linux you'd have to change split character.

function(file) { 
    let parts = file.relative.split('\\'); // put '/' here on Linux
    let root = Array(parts.length + 1).join('../') + 'src';
    return root;
}

Certainly there is a way to detect correct path separator, I'm debugging only on Windows thus it's not important for my purposes.

I hope it helps someone, cause I've spent whole Sunday morning tracking down this problem.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top