Sourcemaps not working - webpack build of webextension using @sentry/webpack-plugin

Hello all,

I have been toiling with this issue for a day now and thought that the time has come to reach out to the community. I am hoping that @benvinegar or @mitsuhiko might be able to shed some light on this problem as they seem to be giving some great support to others with sourcemap problems. Forgive me if I have missed, but I have done quite a bit of searching and can’t find a question that encompasses my issue entirely. I will try to give as much detail as possible.

Description of issue:
The actual problem is that sourcemap artefacts are seemingly not being “linked” correctly in Sentry to the js files that are throwing the exception/error. The message I see on the issue’s page is:
image
(Source code was not found Url popup.js.map). The JSON errors object is:
[{“url”:“popup.js.map”,“type”:“js_no_source”}]

My artefacts seem to be uploaded correctly and are visible in the “Artefacts” tab for the particular release. (My Issues are also being correctly pinned to the release).

Summary of my thinking so far:
My suspicions about the root cause(s) of my issue are the following:

  • Sentry documentation states that “~” replaces the need to state protocol and host in uploaded artefacts, however web extensions run on client browsers and have unique url-like structures - e.g. moz-extension://32183b6a-bed4-494f-9d0b-9c2dc5ff7d48/popup/popup.html. Every Installation of a web extension is given a UUID and this is used throughout its life (therefore we cannot use fully qualified urls like other web apps because every instance has a different “url”). Perhaps Sentry is not deeming “moz-extension://” as a valid protocol, or “32183b6a-bed4-494f-9d0b-9c2dc5ff7d48” as a valid host? If this were the case, the Sentry backend would potentially not be getting relative paths correct (i.e. “~/popup/popup.js.map” would not be correctly found for an exception that originated from moz-extension://32183b6a-bed4-494f-9d0b-9c2dc5ff7d48/popup/popup.js) even if correctly referenced in the js file as “//# sourceMappingURL=popup.js.map”.
  • There may be a bug in the way that sourcemaps are created (or re-written) by the minifier or the sentry webpack-plugin which makes the “file” property of the .map file relative to the webpack.config.js, rather than relative to the directory that the js file resides in. For example, my “popup” map file contains: “file”:“popup/popup.js” rather than just “file”:“popup.js”. I don’t know how important the “file” property is for Sentry to link the map to the js? No matter what I try, I can’t seem to get this property to be relative to the js file that it sits next to.

Detail that got me to this point:
So I am building a web extension (for Firefox, although this is irrelevant). I’m using npm and webpack to build. The actual build output happens in ./dist/firefox/production/1.0.0/src
inside the build (i.e. the …1.0.0/src folder), the structure is broadly a bunch of directories, and a manifest file:

$ ls
assets      content_scripts     manifest.json  popup
background  options

each of these directories then contains js files and sub-directories. I will use the “popup” as my example through this post.

$ ls
popup.html  popup.js  popup.js.map

As you can see, the .map file is being generated as expected alongside the .js file. The .js file is minified and uglified as expected by the terser plugin (I have set sourceMap:true as explained in the Sentry docs). The .js file seems to have the correct sourceMappingURL comment at the end of the file:

//# sourceMappingURL=popup.js.map

The map file does contain the following properties (I won’t paste all content since it is too large):

"version":3,
"sources":["webpack:///webpack/bootstrap","webpack:///./node_modules/tslib/tslib.es6.js","webpack:///./src/libs/emptyLog.js" (I have shortened this),
"file":"popup/popup.js",
"sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require"]...(I have shortened this),
"sourceRoot":""

My main observations about this file are that the file property seems to be a file path relative to my build script, not relative to where the map file is? Also the sourceRoot is not set (although I have tried setting this to “/” but still get the same issues)

The relevant sections of my webpack.config.js to build these maps are:

const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const GitRevisionPlugin = require('git-revision-webpack-plugin');
const gitRevisionPlugin = new GitRevisionPlugin();
var entryPoints = {
   ...
    "popup" : { outputPath: "popup", src: "./src/popup/popup.js" },
   ...
};
...
entry: {
     ...
      "popup": "./src/popup/popup.js",
     ...
},
devtool: false, //see the webpack.SourceMapDevToolPlugin below
...
optimization: {
    minimize: true,
    minimizer: [
        new TerserPlugin({
            sourceMap: true
        }),
        new OptimizeCssAssetsPlugin({})
    ]
  },
plugins: [
...
new webpack.SourceMapDevToolPlugin({
          filename: "[file].map",
      }),
new SentryCliPlugin({
          include: "./dist/firefox/production/1.0.0/src",
          ignoreFile: '.sentrycliignore',
          configFile: ".sentryclirc",
          ignore: ['node_modules', 'webpack.config.js', 'manifest.js', 'manifest.js.map', 'babel.config.js'],
          release: gitRevisionPlugin.commithash(),
          validate: true,
      }),
...
],
output: {
    path: path.resolve(__dirname, "dist/firefox/production/1.0.0/src"),
    filename: (chunkData) => {
        let ret = chunkData.chunk.name + ".js";
        if(chunkData.chunk.name !== "manifest") {
            ret = entryPoints[chunkData.chunk.name]["outputPath"] + "/" + chunkData.chunk.name + ".js";
        }
        return ret;
    },
...

entryPoints is just a variable to hold output paths because I originally thought that having path structures in the chunk/entry point name was causing an issue.
One of my suspicions is that the SourceMapDevToolPlugin property “filename” is using the placeholder “[file]” which is the whole path+filename, meaning that the sourcemap gets written in the right place next to the .js file, however it is perhaps using this same filename property to write the “file” property in the .map file, whereas it should be using the path relative to the .js file?

When I run the build, the SentryCliPlugin does its job and correctly uploads the sourcemaps to the Artefacts section of the release. Since the validate option is on, the terminal also outputs sourcemap validation results (again, i’ve abbreviated a few results):

> Analyzing 23 sources
> Rewriting sources
> Adding source map references
> Validating sources
> Uploading source maps for release a0a0f443e60af88e63247f3b8d290578513e939c

Source Map Upload Report
  Minified Scripts
    ~/assets/js/jquery.min.js (no sourcemap ref)
      - warning: could not determine a source map reference (Could not auto-detect referenced sourcemap for ~/assets/js/jquery.min.js.)
      - error: missing sourcemap!
    ~/background/background.js (sourcemap at background.js.map)
    ...
    ~/options/options.js (sourcemap at options.js.map)
    ~/popup/popup.js (sourcemap at popup.js.map)
  Source Maps
    ~/background/background.js.map
    ...
    ~/options/options.js.map
    ~/popup/popup.js.map

OK - my bad about the jQuery one, but I can fix this easily by setting the exclude properly.
In Sentry.io, under Artefacts for the release:
image
Just to be sure, I have downloaded the popup.js file and confirm that the last line is still:

//# sourceMappingURL=popup.js.map

The ~/popup/popup.js.map file looks like:

{"version":3,"file":"popup/popup.js","sources":["webpack:///webpack/b..."], "sourcesContent":[" \t// The module cache\n \tvar installedMo..."], 

Interestingly sourceRoot property doesn’t exist, and the “file” property has be written directly after the version (I assume this is something that the SentryCliPlugin does since “rewrite” is enabled by default).

So when I get an exception (I have just created a simple one when popup.html/popup.js is loaded, the issue is generated correctly in Sentry.io:


However:
“There was 1 error encountered while processing the event”. If I expand this out:

The relevant JSON message is:

So the error message seems to suggest that maybe Sentry could not find the sourcesContent property within the .map file? Or maybe Sentry has not found popup.js.map? The error message is not very explicit so I am at a loss!

Apologies for the mammoth post - Happy to provide any more detail or files if needed - greatly appreciate any help :slight_smile:

:+1: We’re experiencing exactly the same problem. Hope this gets a response.

I am currently spinning up a vanilla webpack web extension project and seeing if I can replicate since my example above is from a fairly complex project - should have some results by the end of today…

OK so I spun up a fairly vanilla webpack project/browser extension. No fancy folder structures or anything.

This means that the “file” property of my .js.map file was correct if it was being read as a relative path to the .js file. (i.e. “file” property in background.js.map is “background.js”.

background.js has correct sourceMappingURL:

//# sourceMappingURL=background.js.map

sourcemaps uploaded OK as artifacts to a “test-release” in a “test” project:

Same error!!:
image

JSON says:
js_no_source

This makes me think that my first suspicion may be the issue (sentry not being able to interpret the moz-extension://xxx url properly when using the “~” shortcut).

If any from sentry reads this, please let me know if you would like me to upload a zip of the project to aid in replicating this?

I hope he doesn’t mind but I’m also going to reach out to @zeeg to see if he can shed any light?

I have also just found this bug report which looks like the culprit may be a bug in Webpack https://github.com/webpack/webpack/issues/8302

There was an issue in webpack-sources with the level of “granularity” applied to the sourcemaps in webmap. This has now been somewhat fixed, but I’m still experiencing issues.
Any help would be greatly appreciated. Maybe someone from Sentry can shed light on this? Maybe sentry is not able to deal with the odd protocol (web-extension://) or the fact that the domain is not properly formed (the extension is given a uuid) ?
Anyone?