GoogleChrome / workbox

📦 Workbox: JavaScript libraries for Progressive Web Apps
https://developers.google.com/web/tools/workbox/
MIT License
12.34k stars 814 forks source link

Error registering service worker script with production build #2276

Closed deeDude closed 4 years ago

deeDude commented 4 years ago

I'm using Workbox Webpack plugin (v4.3.1) to generate a service worker script and Workbox-Window (v4.3.1) plugin to register it.

It all works fine on dev environment (I use webpack dev server) but with the production build I'm getting the error below on the Chrome (v78) dev console:

Uncaught (in promise) TypeError: Failed to register a ServiceWorker for scope ('http://localhost:4321/') with script ('http://localhost:4321/undefined'): A bad HTTP response code (404) was received when fetching the script.

(Note: Im using a local Nginx server to test my production build)

Im using Workbox Webpack GenerateSW like this:

new WorkboxPlugin.GenerateSW({
  clientsClaim: true,
  skipWaiting: false
})

And I confirmed that, after the build, the service worker script (service-worker.js) is generated and its in the dist folder.

I am using Workbox-Window plugin to register the aforementioned service worker script:

 import { Workbox } from 'workbox-window'

 if ('serviceWorker' in navigator) {
      const wb = new Workbox('/service-worker.js') // Note: I also tried without the bar and with path './service-worker.js' and didnt work
      wb.register()
  }

I'm guessing the problem is not related to the fact Im using Nginx to test the prod build, nor related with the url or path of the service worker script, provided to the Workbox constructor because, using ServiceWorker Web API instead of Workbox Window API, it works fine:

if ('serviceWorker' in navigator) {
   navigator.serviceWorker.register('/service-worker.js') // THIS WORKS!!!
}

Why, under the exact same conditions, the service worker registration fails with Workbox Window API? Where does that undefined, that you can see in the error logs, come from???

//******* Webpack Prod Config *******
'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const WorkboxPlugin = require('workbox-webpack-plugin')

const env = process.env.NODE_ENV === 'testing'
  ? require('../config/test.env')
  : require('../config/prod.env')

const webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true,
      usePostCSS: true
    })
  },
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      },
      sourceMap: config.build.productionSourceMap,
      parallel: true
    }),
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      allChunks: false,
    }),
    new OptimizeCSSPlugin({
      cssProcessorOptions: config.build.productionSourceMap
      ? { safe: true, map: { inline: false } }
      : { safe: true }
    }),
    new HtmlWebpackPlugin({
      filename: process.env.NODE_ENV === 'testing'
        ? 'index.html'
        : config.build.index,
      template: 'index.html',
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
      },
      chunksSortMode: 'dependency'
    }),
    new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: function (module) {
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(
            path.join(__dirname, '../node_modules')
          ) === 0
        )
      }
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      minChunks: Infinity
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'app',
      async: 'vendor-async',
      children: true,
      minChunks: 3
    }),
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

webpackConfig.plugins.push(new WorkboxPlugin.GenerateSW({
  clientsClaim: true,
  skipWaiting: false
}))

module.exports = webpackConfig
jeffposnick commented 4 years ago

Sorry for the delay in getting back to you.

What does your baseWebpackConfig look like? I'm assuming the code that calls new Workbox('/service-worker.js') is included in one of the entrys defined in that configuration, and I'm curious if there's anything there that would end up doing some substitutions or other manipulation of that '/service-worker.js' string.

Also, can you try upgrading to Workbox v5 and seeing if that helps?

alexistbell commented 4 years ago

I may be seeing the same issue, but on version 5.1.3 of workbox and workbox-window.

Before adding in workbox-window this worked just fine:

  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/serviceWorker.js')
  })
}

I added in workbox-window and switched my code to:

import { Workbox } from 'workbox-window'

  if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    const wb = new Workbox('./serviceWorker.js')
    wb.register()
  })
}

And now I get this error:

Uncaught (in promise) TypeError: Failed to register a ServiceWorker for scope ('http://localhost:3000/') with script ('http://localhost:3000/serviceWorker.js'): ServiceWorker script evaluation failed

I didn't change our webpack configuration for this, the Workbox section is still:

      new WorkboxWebpackPlugin.InjectManifest({
        swSrc: './src/src-serviceWorker.js',
        swDest: 'serviceWorker.js',
        exclude: [/\.map$/, /asset-manifest\.json$/]
      }),

I can post the rest of the webpack config file if you think it would help, but it's bascially the ejected version from create-react-app

jeffposnick commented 4 years ago

@alexistbell, I'm not sure if that's the same issue or not. I noticed that in the first example, you were registering /serviceWorker.js and in the second (using workbox-window) you were registering ./serviceWorker.js (with a leading . character). If your current web page isn't served from the / path prefix, then those two URLs aren't equivalent.

Can you try using the same service worker URL with workbox-window as you were previously using?

alexistbell commented 4 years ago

You ended up being right, my issue was not related. That ./ was a step I had tried troubleshooting. I rolled back most of my changes and discovered the issue came up when I upgraded workbox-webpack-plugin to match the version of workbox-window. That meant the way I was calling the cache first strategy:

new workbox.strategies.CacheFirst()

no longer worked and I needed to import CacheFirst from workbox-strategies.

jeffposnick commented 4 years ago

I'm closing this issue, as we never heard back from the person experiencing the original issue, and the other issue ended up be solved.

JerryC8080 commented 2 years ago

I ran into the same problem, and then I found my cause. Since Workbox v5 is a compressed file for workbox-window which export with it's package.json. Then it was compressed twice by webpack UglifyJsPlugin(Just like you(@deeDude) and mine webpack configuration).

  1. The core code in build/workbox-window.prod.umd.js

    w.bn = function () {
    try {
    var n = this;
    return function (n, t) {
      try {
        var r = n()
      } catch (n) {
        return t(n)
      }
      if (r && r.then)
        return r.then(void 0, t);
      return r
    }((function () {
      // core called
      return a(navigator.serviceWorker.register(n.sn, n.nn), (function (t) {
        return n.un = performance.now(),
          t
      }
      ))
    }
    ), (function (n) {
      throw n
    }
    ))
    } catch (n) {
    return Promise.reject(n)
    }
    }
  2. The core code uglify the above

    h.bn = function () {
    try {
    var t = this;
    return function (t, e) {
      try {
        var n = function () {
          // core called
          return u(navigator.serviceWorker.register(t.sn, t.nn), function (e) {
            return t.un = performance.now(),
              e
          })
        }()
      } catch (t) {
        return e(t)
      }
      return n && n.then ? n.then(void 0, e) : n
    }(0, function (t) {
      throw t
    })
    } catch (t) {
    return Promise.reject(t)
    }
    }

    This will cause a runtime error with t.sn(named this._scriptURL in source code) was undefined

image