Search code examples
javascripttypescriptangulargulpsource-maps

What is adding this wrapper around my code?


Summary

I'm writing an Angular 2 app in TypeScript, and using SystemJS and Gulp to deploy it. I'm facing a curious problem when trying to use sourcemaps. If I include the sourcemap inline, everything works. If I use an external map file, SystemJS seems to add a wrapper around my code that I don't understand, breaking the browser's ability to find the sourcemap. I would like to figure out what's actually happening and how I can fix it.

Inline maps work

gulpfile.js Here is the task that compiles TypeScript to JavaScript

gulp.task('build-ts', function () {
    return gulp.src(appDev + '**/*.ts')
        .pipe(sourcemaps.init()) //gulp-sourcemaps
        .pipe(typescript(tsProject)) //gulp-typescript
        .pipe(sourcemaps.write()) //map will be inline
        .pipe(gulp.dest(appProd));
});

The resulting JS file (login.component.js) on disk and in the browser ends like this:

165| exports.LoginComponent = LoginComponent;
166| 
167| //# sourceMappingURL=data:application/json;base64,eyJ2Z...

The browser is able to use the inline map to let me see the source TypeScript file. No problem.

External maps are broken

gulpfile.js to generate external maps

gulp.task('build-ts', function () {
    return gulp.src(appDev + '**/*.ts')
        .pipe(sourcemaps.init())
        .pipe(typescript(tsProject))
        .pipe(sourcemaps.write('.')) //map will be external
        .pipe(gulp.dest(appProd));
});

The resulting JS file (login.component.js) on disk is the same as above except line 167:

165| exports.LoginComponent = LoginComponent;
166| 
167| //# sourceMappingURL=login.component.js.map

The same file, when I view the source in the browser ends like this:

165| exports.LoginComponent = LoginComponent;
166| 
167| //# sourceMappingURL=login.component.js.map
168| 
169| }).apply(__cjsWrapper.exports, __cjsWrapper.args);
170| })(System, System);
171| //# sourceURL=http://example.com/path/to/login.component.js

The map file login.component.js.map is properly generated in the same directory but Firefox never fetches it. This wrapping breaks Firefox's ability to load the sourcemap. Why would the file generated by the gulp task be different from the same file once the browser loads it?

Update

@robisim74 below has helped me narrow the problem: it seems that the wrapping has nothing to do with why Firefox can't load the external maps. Rather, Firefox is unable to resolve relative paths in sourceMappingURL. In my last excerpt above, if I change line 167 from:

//# sourceMappingURL=login.component.js.map

to:

//# sourceMappingURL=http://example.com/path/to/login.component.js.map

Then Firefox will load the map! So I used the SourceMappingURL parameter of gulp-sourcemaps write() function to use full URLs when linking to the maps. Thus I changed my gulpfile.js typescript build task from what I posted above (under External maps are broken) to:

gulp.task('build-ts', function () {
    return gulp.src(appDev + '**/*.ts')
        .pipe(sourcemaps.init())
        .pipe(typescript(tsProject))
        .pipe(sourcemaps.write('.',{
            sourceMappingURL: function(file) {
                return '//example.com/app/' + file.relative + '.map'; //full URL
            }
        }))
        .pipe(gulp.dest(appProd));
});

This put the sourcemaps in separate files same as before, but it transformed line 167 (from the file excerpts above) to:

167| //# sourceMappingURL=//example.com/path\to\login.component.js.map

Now Firefox can use the map to show me the Typescript source.

PS: This is cross-posted on Github


Solution

  • It should be a Firefox problem: In-browser transpilation sourcemaps do not work outside of Chrome. See also Bug 1224078. Sourcemap inline works anyway because it is in the same js file.

    Edit. When you publish your js and js.map files, js files contain:

    // # sourceMappingURL = [file name].js.map.
    

    This is a relative path, that Chrome is able to recognize, Firefox not. In fact, if you manually correct the path with the absolute path, you'll see that Firefox finds them. The transpilation is not from TypeScript to Javascript, but vice versa: the sourcemaps files serve to the browser to rebuild the source files in TypeScript so that you can debug them (if you open the Chrome console, you'll see the TypeScript files even if you have no published them).