browserify: Insertion of globals breaks sourcemaps

When a source file that already has a sourcemap (from a prior transform) is transformed to insert globals, the sourcemap is not updated, which breaks all positions in the file thereafter.

A trivial reproducing test case using coffeeify (this bug has nothing to do with coffeeify specifically; any transform that uses sourcemaps is affected):

x.coffee

global.foo = 3
console.log('hello')

command line

browserify -t coffeeify -d x.coffee

This link shows the resulting sourcemap. Note how the addition of the (function (global){ line causes the mappings to be off by exactly one line at that point. This offset will persist through the entire bundle, as the mappings are calculated relative to the original output rather than the transformed output.

About this issue

  • Original URL
  • State: open
  • Created 10 years ago
  • Comments: 16 (2 by maintainers)

Most upvoted comments

A problem people still might be seeing (with source maps when global detection is not disabled and variables such as global are found (and with debug: true), as well as the presence of a simple transform like uglifyify) is the Windows-specific issue at https://github.com/thlorenz/combine-source-map/pull/23

I’m adding further details below as a record (since it was little fun to debug!), but the PR already addresses the concern succinctly.

If the PR is still not accepted after my reminder, I think browserify ought to depend on the PR’s fork of combine-source-map (or browserify’s own copy of it) to resolve this (via updates or forks of insert-module-globals and browser-pack, the dependencies which browserify directly includes and which both (problematically) utilize the unfixed combine-source-map).


I am attempting to use node-browserify with uglifyify to handle minification while preserving info about source files to source maps.

(I’m also adding my own build of mapstraction to save the map to an external file (while being able to deal with base and relative paths) though this latter plugin only does anything of substance after its stream ends and that only occurs after the problem mentioned below.)

browserify({
    entries: 'index.js',
    standalone: 'Typeson',
    debug: true // Needed by mapstraction to extract source map
}).transform({global: true}, 'uglifyify').plugin(
    mapstraction, {_: ['dist/all.js.map']}
).bundle().pipe(
    fs.createWriteStream('dist/all.js')
);

Since I did not add detectGlobals: false, insert-module-globals will be invoked. This creates a through stream whose end calls a function closeOver which combines this stream’s chunks and invokes combine-source-map’s create (which just builds a Combiner) and then invokes the Combiner’s addFile with these source contents, and in so doing gets its source converted into a map as such: var existingMap = resolveMap(opts.source);.

The map then gets passed to Combiner.prototype._addExistingMap which copies the map var mappings = mappingsFromMap(existingMap); where mappingsFromMap refers to lib/mappings-from-map.

Later in Combiner.prototype._addExistingMap the following is called (though the code preceding it invoking this.generator.addSourceContent is also affected because of its use of rebaseRelativePath):

this.generator.addMappings(
      rebaseRelativePath(sourceFile, null, mapping.source), [mapping], offset);

In my case, sourceFile is types/errors.js while mapping.source is errors.js and when it is resolved, it becomes types\errors.js, so that is the mapping that is added (with Windows backslashes).

Later, when browserify continues its normal work unrelated to inserting globals, apparently since we have debug enabled, Browserify.prototype._debug gets called, including with this line:

row.sourceFile = relativePath(basedir, row.file)
                .replace(/\\/g, '/');

Although a Windows-style path is present in row.file (C:\<path to my repos>\typeson-registry\types\errors.js), the conversion to a relative path with the backslashes escaped leads row.sourceFile to getting a non-Windows path types/errors.js. The row is added to the stream and somehow browser-pack (which had been invoked earlier by browserify) gets its write method invoked with this row and it then invokes addFile (on the earlier created instance of combine-source-map) with an object including the non-Windows row.sourceFile path.

The above-mentioned _addExistingMap method from combine-source-map thus gets called again, calling addSourceContent (and addMappings) on an earlier created inline-source-map generator and to determine which path to supply (and determine what file if any ends up getting added).

Before supplying the arguments, however, _addExistingMap will invoke rebaseRelativePath to check whether the currently supplied file (the non-Windows one in this case) matches any of the previous existing ones (which it should since it has been added already albeit in Windows form).

Since the code within rebaseRelativePath of combine-source-map does a mere check without normalization of if (sourceFile === relativeRootedPath ||, there is a failure to match due to the difference in slashes. This causes the problem that the following line, return path.join(path.dirname(sourceFile), relativeRootedPath); is run which effectively adds another directory onto the path (e.g., so my file, types/errors.js mistakenly becomes types/types/errors.js in the source map).

The PR details another issue whereby non-recognition can also occur because a file (e.g., from CoffeeScript or TypeScript) is not recognized with its converted extension.