goldhand / sw-precache-webpack-plugin

Webpack plugin that generates a service worker using sw-precache that will cache webpack's bundles' emitted assets. You can optionally pass sw-precache configuration options to webpack through this plugin.
MIT License
1.44k stars 105 forks source link

Service Worker not registering in Chrome normal (non-Incognito mode) and delivering old cached UI #139

Closed dwalintukan closed 6 years ago

dwalintukan commented 6 years ago

BEFORE YOU SUBMIT please read the following:

webpack version: "webpack": "^3.5.5", "webpack-dev-server": "2.7.1",

sw-precache-webpack-plugin version: "sw-precache-webpack-plugin": "0.11.4"

Please tell us about your environment: OSX 10.13.3

Browser: [Phantom XX | Chrome XX | Firefox XX | IE XX | Safari XX | Other XX] Chrome Version 64.0.3282.140 (Official Build) (64-bit)

Current behavior: Service Worker is not going through registering behavior in normal (non-incognito) Chrome mode. In incognito mode, it executes the register() logic.

My main issue is that I am running a react-webpack app on localhost, but when I make changes to the UI it still shows the old cached changes in Chrome normal mode. In incognito it works fine.

I have to do a hard refresh to be able to see the new version. I don't want to have to inform our users to do a hard refresh in order to see the new changes.

registerServiceWorker.js

export default function register() {
  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
    window.addEventListener('load', () => {
      console.log('REGISTERING SW');

      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
      navigator.serviceWorker
        .register(swUrl)
        .then((registration) => {
          console.log('SW REGISTERED', registration);

          const reg = registration;
          reg.onupdatefound = () => {
            const installingWorker = registration.installing;
            installingWorker.onstatechange = () => {
              console.log('SW ONSTATECHANGE', installingWorker.state);

              if (installingWorker.state === 'installed') {
                if (navigator.serviceWorker.controller) {
                  // At this point, the old content will have been purged and
                  // the fresh content will have been added to the cache.
                  // It's the perfect time to display a "New content is
                  // available; please refresh." message in your web app.
                  console.log('NEW CONTENT FOUND');
                } else {
                  // At this point, everything has been precached.
                  // It's the perfect time to display a
                  // "Content is cached for offline use." message.
                  console.log('USING CACHE');
                }
              }
            };
          };
        })
        .catch((error) => {
          console.error('Error during service worker registration:', error);
        });
    });
  }
}

export function unregister() {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.ready.then((registration) => {
      registration.unregister();
    });
  }
}

index.js

const currentAppLocale = AppLocale.en;

ReactDOM.render(
  <LocaleProvider locale={currentAppLocale.antd}>
    <IntlProvider
      locale={currentAppLocale.locale}
      messages={currentAppLocale.messages}
    >
      <ThemeProvider theme={themes[themeConfig.theme]}>
        <DashAppHolder>
          <DashApp />
        </DashAppHolder>
      </ThemeProvider>
    </IntlProvider>
  </LocaleProvider>,
  document.getElementById('root')
);

// Hot Module Replacement API
if (module.hot) {
  module.hot.accept('./dashApp.js', () => {
    const NextApp = DashApp.default;
    ReactDOM.render(<NextApp />, document.getElementById('root'));
  });
}

registerServiceWorker();

export { AppLocale };

Expected/desired behavior: Service Worker should be doing register behavior in Chrome normal mode and deliver new UI changes.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem along with your:

const autoprefixer = require('autoprefixer'); const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ManifestPlugin = require('webpack-manifest-plugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); const eslintFormatter = require('react-dev-utils/eslintFormatter'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');

const paths = require('./paths'); const getClientEnvironment = require('./env');

// Webpack uses publicPath to determine where the app is being served from. // It requires a trailing slash, or the file assets will get an incorrect path. const publicPath = paths.servedPath; // Some apps do not use client-side routing with pushState. // For these, "homepage" can be set to "." to enable relative asset paths. const shouldUseRelativeAssetPaths = publicPath === './'; // publicUrl is just like publicPath, but we will provide it to our app // as %PUBLIC_URL% in index.html and process.env.PUBLIC_URL in JavaScript. // Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz. const publicUrl = publicPath.slice(0, -1); // Get environment variables to inject into our app. const env = getClientEnvironment(publicUrl);

// Assert this just to be safe. // Development builds of React are slow and not intended for production. if (env.stringified['process.env'].NODE_ENV !== '"production"') { throw new Error('Production builds must have NODE_ENV=production.'); }

// Note: defined here because it will be used more than once. const cssFilename = 'static/css/[name].[contenthash:8].css';

// ExtractTextPlugin expects the build output to be flat. // (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27) // However, our output is structured with css, js and media folders. // To have this structure working with relative paths, we have to use custom options. const extractTextPluginOptions = shouldUseRelativeAssetPaths ? // Making sure that the publicPath goes back to to build folder. { publicPath: Array(cssFilename.split('/').length).join('../') } : {};

const extractLess = new ExtractTextPlugin({ filename: '[name].[contenthash:8].css', disable: process.env.NODE_ENV === 'development', });

// This is the production configuration. // It compiles slowly and is focused on producing a fast and minimal bundle. // The development configuration is different and lives in a separate file. module.exports = { // Don't attempt to continue if there are any errors. bail: true, // We generate sourcemaps in production. This is slow but gives good results. // You can exclude the *.map files from the build during deployment. devtool: false, // In production, we only want to load the polyfills and the app code. entry: [require.resolve('./polyfills'), paths.appIndexJs], output: { // The build folder. path: paths.appBuild, // Generated JS file names (with nested folders). // There will be one main bundle, and one file per asynchronous chunk. // We don't currently advertise code splitting but Webpack supports it. filename: 'static/js/[name].[chunkhash:8].js', chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js', // We inferred the "public path" (such as / or /my-project) from homepage. publicPath: publicPath, // Point sourcemap entries to original disk location (format as URL on Windows) devtoolModuleFilenameTemplate: info => path .relative(paths.appSrc, info.absoluteResourcePath) .replace(/\/g, '/'), }, resolve: { // This allows you to set a fallback for where Webpack should look for modules. // We placed these paths second because we want node_modules to "win" // if there are any conflicts. This matches Node resolution mechanism. // https://github.com/facebookincubator/create-react-app/issues/253 modules: ['node_modules', paths.appNodeModules].concat( // It is guaranteed to exist because we tweak it in env.js process.env.NODE_PATH.split(path.delimiter).filter(Boolean) ), // These are the reasonable defaults supported by the Node ecosystem. // We also include JSX as a common component filename extension to support // some tools, although we do not recommend using it, see: // https://github.com/facebookincubator/create-react-app/issues/290 // web extension prefixes have been added for better support // for React Native Web. extensions: ['.web.js', '.js', '.json', '.web.jsx', '.jsx'], alias: { // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 'react-native': 'react-native-web', }, plugins: [ // Prevents users from importing files from outside of src/ (or node_modules/). // This often causes confusion because we only process files within src/ with babel. // To fix this, we prevent you from importing files out of src/ -- if you'd like to, // please link the files into your node_modules/ and let module-resolution kick in. // Make sure your source files are compiled, as they will not be processed in any way. new ModuleScopePlugin(paths.appSrc), ], }, module: { strictExportPresence: true, rules: [ // TODO: Disable require.ensure as it's not a standard language feature. // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176. // { parser: { requireEnsure: false } },

  // First, run the linter.
  // It's important to do this before Babel processes the JS.
  {
    test: /\.(js|jsx)$/,
    enforce: 'pre',
    use: [
      {
        options: {
          formatter: eslintFormatter,
        },
        loader: require.resolve('eslint-loader'),
      },
    ],
    include: paths.appSrc,
    exclude: paths.appSrc + '/modules'
  },
  // ** ADDING/UPDATING LOADERS **
  // The "file" loader handles all assets unless explicitly excluded.
  // The `exclude` list *must* be updated with every change to loader extensions.
  // When adding a new loader, you must add its `test`
  // as a new entry in the `exclude` list in the "file" loader.

  // "file" loader makes sure those assets end up in the `build` folder.
  // When you `import` an asset, you get its filename.
  {
    exclude: [
      /\.html$/,
      /\.(js|jsx)$/,
      /\.less$/,
      /\.css$/,
      /\.json$/,
      /\.bmp$/,
      /\.gif$/,
      /\.jpe?g$/,
      /\.png$/,
    ],
    loader: require.resolve('file-loader'),
    options: {
      name: 'static/media/[name].[hash:8].[ext]',
    },
  },
  // "url" loader works just like "file" loader but it also embeds
  // assets smaller than specified size as data URLs to avoid requests.
  {
    test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
    loader: require.resolve('url-loader'),
    options: {
      limit: 10000,
      name: 'static/media/[name].[hash:8].[ext]',
    },
  },
  // Process JS with Babel.
  {
    test: /\.(js|jsx)$/,
    include: paths.appSrc,
    exclude: paths.appSrc + '/modules',
    loader: require.resolve('babel-loader'),
    options: {
      plugins: [['import', { libraryName: 'antd', style: true }]],
      compact: true,
    },
  },
  // The notation here is somewhat confusing.
  // "postcss" loader applies autoprefixer to our CSS.
  // "css" loader resolves paths in CSS and adds assets as dependencies.
  // "style" loader normally turns CSS into JS modules injecting <style>,
  // but unlike in development configuration, we do something different.
  // `ExtractTextPlugin` first applies the "postcss" and "css" loaders
  // (second argument), then grabs the result CSS and puts it into a
  // separate file in our build process. This way we actually ship
  // a single CSS file in production instead of JS code injecting <style>
  // tags. If you use code splitting, however, any async bundles will still
  // use the "style" loader inside the async code so CSS from them won't be
  // in the main CSS file.
  {
    test: /\.css$/,
    loader: ExtractTextPlugin.extract(
      Object.assign(
        {
          fallback: require.resolve('style-loader'),
          use: [
            {
              loader: require.resolve('css-loader'),
              options: {
                importLoaders: 1,
                minimize: true,
                sourceMap: true,
              },
            },
            {
              loader: require.resolve('postcss-loader'),
              options: {
                // Necessary for external CSS imports to work
                // https://github.com/facebookincubator/create-react-app/issues/2677
                ident: 'postcss',
                plugins: () => [
                  require('postcss-flexbugs-fixes'),
                  autoprefixer({
                    browsers: [
                      '>1%',
                      'last 4 versions',
                      'Firefox ESR',
                      'not ie < 9', // React doesn't support IE8 anyway
                    ],
                    flexbox: 'no-2009',
                  }),
                ],
              },
            },
          ],
        },
        extractTextPluginOptions
      )
    ),
    // Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
  },
  {
    test: /\.less$/,
    use: extractLess.extract({
      fallback: require.resolve('style-loader'),
      use: [
        require.resolve('css-loader'),
        {
          loader: require.resolve('postcss-loader'),
          options: {
            ident: 'postcss',
            plugins: () => [
              require('postcss-flexbugs-fixes'),
              autoprefixer({
                browsers: [
                  '>1%',
                  'last 4 versions',
                  'Firefox ESR',
                  'not ie < 9', // React doesn't support IE8 anyway
                ],
                flexbox: 'no-2009',
              }),
            ],
          },
        },
        {
          loader: require.resolve('less-loader'),
        },
      ],
    }),
  },
  // ** STOP ** Are you adding a new loader?
  // Remember to add the new extension(s) to the "file" loader exclusion list.
],

}, plugins: [ // Makes some environment variables available in index.html. // The public URL is available as %PUBLIC_URL% in index.html, e.g.: // // In production, it will be an empty string unless you specify "homepage" // in package.json, in which case it will be the pathname of that URL. new InterpolateHtmlPlugin(env.raw), // Generates an index.html file with the Githubissues.

  • Githubissues is a development platform for aggregating issues.