oliviertassinari / serviceworker-webpack-plugin

Simplifies creation of a service worker to serve your webpack bundles. :recycle:
https://oliviertassinari.github.io/serviceworker-webpack-plugin/
MIT License
458 stars 76 forks source link

Serious issues with sourcemaps and multiple webpack configs. #75

Open ddumont opened 6 years ago

ddumont commented 6 years ago

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.

  1. 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.
      }
    ]
  2. 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.
          ],
        },
      ],
    },      
  3. Multiple configs totally makes it harder to get the assets you want to cache! So I made a new plugin:
    // in the main webpack config
    plugins: [
      {
        apply: compiler => {
          compiler.plugin("done", ({ compilation }) => {
            global.assets = Object.keys(compilation.assets);
          });
        },
      },
    ],

    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.

    // 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.
ddumont commented 6 years ago

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 ;)

ddumont commented 6 years ago

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.