serviceworker-webpack-plugin
serviceworker-webpack-plugin copied to clipboard
Serious issues with sourcemaps and multiple webpack configs.
I tried playing around with this plugin over the weekend and ran into a few issues. One was just getting a better understanding of service workers :), but it was really hard to do because sourcemaps are completely borked because of the way this plugin operates.
So I did a bit of tinkering around, and I found out a few things.
- Don't make your service worker an entrypoint in your main webpack config.
It's way better to eliminate a bunch of the noise from other plugins that aren't intended to mess
around with service worker code. Also, from what I read, you really don't want the sw.js filename
changing with a hash every build, and you're probably using those (hashes) in your prod config.
// webpack.config.js module.exports = [ { // your main code for the page and all your plugins }, { // your service worker code, and its plugins. } ]
- You shouldn't be rewriting any source before it goes through babel, or the minifier. The best place to
put that is in a loader. (just like babel!)
// in the service worker webpack config module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: [ { loader: "babel-loader" }, { loader: "asset-loader" }, // <-- This is the loader I made to do the asset injection. ], }, ], },
- Multiple configs totally makes it harder to get the assets you want to cache! So I made a new plugin:
Cool... so I've squirreled away, in a really hackey way, the assets for the main layer! But the service worker config finishes waaaaaaay before the main one... so here's another small hack, and my loader.// in the main webpack config plugins: [ { apply: compiler => { compiler.plugin("done", ({ compilation }) => { global.assets = Object.keys(compilation.assets); }); }, }, ],
// asset-laoder.js module.exports = function(source) { const callback = this.async(); const interval = setInterval(() => { if (!global.assets) return; // just wait till the main config is done compiling. clearInterval(interval); const assets = global.assets.map(asset => `/${asset}`); callback(null, source.replace(/'\{assets\}'/ig, JSON.stringify(assets))); }, 100); };
// worker.js const filesToCache = '{assets}' .filter(asset => !asset.endsWith('map') && !asset.endsWith('manifest')); // I don't want to cache those.
Feel free to take this approach for your plugin. I find that I have way more control over how my serviceworker gets packed this way. The sourcemaps also work perfectly now... well... as well as they do for everything else ;)
Ah ha! I almost forgot. You have to do a bit more work when using hot module replace for your main code. (I'm not sure I want to think about doing this with the service worker.... I guess it can work?)
// my express server
if (config.get('dev:devmode')) {
const webpack = require('webpack');
const wdm = require('webpack-dev-middleware');
const whm = require('webpack-hot-middleware');
const wpconf = require('./webpack.config');
const compiler = webpack(wpconf[0]);
app.use(wdm(compiler, { publicPath: '/' }));
app.use(whm(compiler));
app.use(wdm(webpack(wpconf[1]), { publicPath: '/' }));
}
Without this, wdm/whm gets very upset about changes in your service worker, if you passed it the whole array of webpack confs as the compiler.