Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Guidlines for Using Workbox #5673

Closed
Nick-t-go opened this issue Nov 1, 2018 · 22 comments
Closed

Guidlines for Using Workbox #5673

Nick-t-go opened this issue Nov 1, 2018 · 22 comments
Labels

Comments

@Nick-t-go
Copy link

I am on React-Scripts 2.0.5. I am eager to use the new Workbox functionality that is mentioned in the release notes for 2.0. I can't find any directions on how to do it though.

There are posts on Medium, Freecodecamp and such that are really just hacks to the 1.x versions of React-scripts.

I had thought since it has just been added as a dependency we could get some assistance on how and where to add configurations. When I include Workbox in my registerServiceWorker.js and attempt to set some strategies on assets it crashes the entire app once built.

Unable to import module 'workbox-routing' from 'WORKBOX_CDN_ROOT_URL/workbox-routing.prod.js'.

I understand serviceworkers are now opt-in.
I understand that in the latest version of create react app the service worker register file is now just called serviceworker.js.

I compared the old registerServiceWorker.js to the new serviceworker.js and they are the same. I also am able to build with the default service worker file and I do see Workbox in the application tab in Chrome dev tools. I know its working out of the box. I just want to be able to edit it. Guidance would be greatly appreciated.

@Rainson12
Copy link

Im stuck in the same boat..

@Rainson12
Copy link

Rainson12 commented Nov 13, 2018

After struggeling with how the magic works i finally figured out what needs to be done to be able to "customize" the service worker routes:

First run npm run eject
Than go into /config/webpack.config.prod.js
Replace
new WorkboxWebpackPlugin.GenerateSW({ clientsClaim: true, exclude: [/\.map$/, /asset-manifest\.json$/], importWorkboxFrom: 'cdn', navigateFallback: publicUrl + '/index.html', navigateFallbackBlacklist: [ // Exclude URLs starting with /_, as they're likely an API call new RegExp('^/_'), // Exclude URLs containing a dot, as they're likely a resource in // public/ and not a SPA route new RegExp('/[^/]+\\.[^/]+$'), ], }),

with

new WorkboxWebpackPlugin.InjectManifest({ swSrc: './public/service-worker.js', }),

where as swSrc point to your custom service-worker.

My Service-Worker.js looks like this:
if (workbox) {
console.log(Yay! Workbox is loaded 🎉);
} else {
console.log(Boo! Workbox didn't load 😬);
}
workbox.clientsClaim();
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});

workbox.routing.registerRoute(
'http://localhost:3000',
workbox.strategies.networkFirst()
)

workbox.routing.registerNavigationRoute("/index.html", {
blacklist: [/^/_/, //[^\/]+.[^\/]+$/],
});

@Nick-t-go
Copy link
Author

As mentioned, @Rainson12 , there have already been a few articles about ejecting and doing something similar to what you have done. I believe the official way to customize using Workbox w/o ejecting is in the works. I personally don't want to eject. This issue here documents the progress of this new feature(I believe), #5369

@Rainson12
Copy link

Rainson12 commented Nov 14, 2018

@Nick-t-go from what i have seen in the other blog posts they all dont work with the current v2 create react app scripts as they all try to get rid of sw-precache and use workbox instead. Now with v2 you dont have to get rid of sw-precache anymore and because of that, all guides i have seen are obsolete and do not work anymore

@karannagupta
Copy link

karannagupta commented Nov 14, 2018

Here's how I've done it with CRA and without ejecting. It uses the workbox build workflow. (cra 2.x)

TL;DR - This involves not using the SW generated by create-react-app but generating your own SW with injectManifest mode using the Workbox build workflow. Build commands will be added to react-build script in package.json

NOTE:
Keep an eye on #5369 I think it will allow configuring Workbox using the Webpack workflow using a custom configuration file.

Step1 : Make sure you register for a SW in the main index.js file. i.e. change serviceWorker.unregister(); to serviceWorker.register();

Step2: Inside src/serviceWorker.js in window.addEventListener('load', () => { change the swURL to point to a new file (this is what will be created in the build step)
so, change this - const swUrl = ``${process.env.PUBLIC_URL}/service-worker.js``;
to const swUrl = ``${process.env.PUBLIC_URL}/sw.js``;

Step3: Create two files under src/ - sw-build.js and sw.js

Step4: In sw-build.js add the code as provided by Google's injectManifest example here -

const workboxBuild = require('workbox-build');
// NOTE: This should be run *AFTER* all your assets are built
const buildSW = () => {
  // This will return a Promise
  return workboxBuild.injectManifest({
    swSrc: 'src/sw.js', // this is your sw template file
    swDest: 'build/sw.js', // this will be created in the build step
    globDirectory: 'build',
    globPatterns: [
      '**\/*.{js,css,html,png}',
    ]
  }).then(({count, size, warnings}) => {
    // Optionally, log any warnings and details.
    warnings.forEach(console.warn);
    console.log(`${count} files will be precached, totaling ${size} bytes.`);
  });
}
buildSW();

Step 5: Inside your custom service worker template file i.e. src/sw.js add the injection point and your cache rules. e.g - in the file src/sw.js -

if ('function' === typeof importScripts) {
  importScripts(
    'https://storage.googleapis.com/workbox-cdn/releases/3.5.0/workbox-sw.js'
  );
  /* global workbox */
  if (workbox) {
    console.log('Workbox is loaded');

    /* injection point for manifest files.  */
    workbox.precaching.precacheAndRoute([]);

/* custom cache rules*/
workbox.routing.registerNavigationRoute('/index.html', {
      blacklist: [/^\/_/, /\/[^\/]+\.[^\/]+$/],
    });

workbox.routing.registerRoute(
      /\.(?:png|gif|jpg|jpeg)$/,
      workbox.strategies.cacheFirst({
        cacheName: 'images',
        plugins: [
          new workbox.expiration.Plugin({
            maxEntries: 60,
            maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
          }),
        ],
      })
    );

} else {
    console.log('Workbox could not be loaded. No Offline support');
  }
}

step 6: npm install workbox-build --save-dev
I think workbox-build is already included in node_modules, but good idea to make an entry in package.json

step 7: in package.json add the workbox build step
so, in package.json, under scripts add a new entry - "build-sw": "node ./src/sw-build.js"
and then change: "build": "react-scripts build
to "build": "react-scripts build && npm run build-sw

When your run npm run build you will see the the file sw.js inside your build folder. The CRA generated service-worker.js will continue to be there. You can run a build command to clear it, but it won't be used because we change the swURL in step2.

Again, all of this may not be relevant if #5369 provides a way to use the webpack flow by providing a custom configuration file.

@Nick-t-go
Copy link
Author

Nick-t-go commented Nov 15, 2018

What if:

  1. We made our own custom copy of webpack.config.prod.js
    Which is currently hosting the WorkboxWebpackPlugin and is setting the config at line 515:

new WorkboxWebpack.GenerateSW... .

  1. Then in our build script include a script that replaces the default webpack.config.prod.js with our own custom copy of webpack.config.prod.js
  2. In our custom copy we can configure what we would like using the API set here.

Anyone have any ideas on whether or not this will work?

@karannagupta
Copy link

karannagupta commented Nov 16, 2018

Not sure if it will work because, in an un-ejecting CRA, we don't have control over the webpack.cofig. #5369 will address the same problem. I think they will provide a workbox.config that will be read by the CRA's webpack.config to allow customizations ...

Also discussed here - #5359

@Nick-t-go
Copy link
Author

@karannagupta finally circling back to this. going to give your suggestion a whirl. Is it possible for you to edit your post and fix the formatting. If it does work I think others will find is useful but more so if it is actually formatted properly to enhance readability.

@Nick-t-go
Copy link
Author

@karannagupta Hey that worked! Thanks for sharing. Anyone who stumbles upon this looking for a way to edit the config for using Workbox with CRA without ejecting please check out the comment above, looks like the official fix is on the slow burn.

@simka
Copy link

simka commented Dec 12, 2018

another way to get this to work is to use react-app-rewired, this override in config-overrides.js seems to be working:

const WorkboxWebpackPlugin = require('workbox-webpack-plugin');

module.exports = function override(config, env) {
  config.plugins = config.plugins.map(plugin => {
    if (plugin.constructor.name === 'GenerateSW') {
      return new WorkboxWebpackPlugin.InjectManifest({
        swSrc: './src/sw.js',
        swDest: 'service-worker.js',
      });
    }

    return plugin;
  });

  return config;
};

@zwl1619
Copy link

zwl1619 commented Dec 16, 2018

I generated a project with npx create-react-app my-app-ts --typescript,
then I created two files, sw-build.js and sw.js under src/,

There is an error:

Cannot compile namespaces when the '--isolatedModules' flag is provided.

image

What should I do?

@karannagupta
Copy link

I've excluded the SW files in tsconfig.json like this:

{
 "compilerOptions": {
  ...
  },
  "include": [
    "src/*"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts",
    "src/sw-*.js",
    "src/idb.js"
  ]
}

@sandys
Copy link

sandys commented Dec 20, 2018

@karannagupta - could you make a gist or a repo describing your workaround please? It's getting a bit hard following it.

@zhammer
Copy link

zhammer commented Jan 3, 2019

@karannagupta & others: i think #5369 would allow you to pass custom config to the GenerateSW plugin but wouldn’t allow you to use the InjectManifest plugin.

@bscaspar
Copy link

Has anyone else tried @karannagupta's method and run into problems debugging locally? When I deploy with the ootb service worker, it is hooked up and running no problem. When I use the example here, and validate that the correct files are ouput into my /build directory (I've run through the directions multiple times at this point), I start running into errors - specifically it looks like CRA may be looking for "service-worker.js" when it compiles and deploys. When my service worker is named sw.js, it does not register successfully. If I change the name of my generated sw.js file to service-worker.js, a service worker is registered but it is the "no-op" version that CRA inserts when debugging locally.

@karannagupta
Copy link

karannagupta commented Jan 23, 2019

Hi @bscaspar, what errors do you run into? Or is it after you run debug some file?

I've attached screenshots of my console with Workbox messages for localhost ( using serve -s build ) that uses the custom sw.js for reference.

I also clear out the default CRA service-worker.js file like this -

    "build-sw": "node ./src/sw-build.js",
    "clean-cra-sw": "rm -f build/precache-manifest.*.js && rm -f build/service-worker.js",
    "build": "react-scripts build && npm run build-sw && npm run clean-cra-sw",

workbox_sw

workbox

@bscaspar
Copy link

Thanks for getting back to me @karannagupta turns out I was missing the serve step - previously I was testing by building then running the app with npm run start. Things are running smoothly with serve, thanks again for putting this workaround together!

@stale
Copy link

stale bot commented Mar 2, 2019

This issue has been automatically marked as stale because it has not had any recent activity. It will be closed in 5 days if no further activity occurs.

@stale stale bot added the stale label Mar 2, 2019
@stale
Copy link

stale bot commented Mar 7, 2019

This issue has been automatically closed because it has not had any recent activity. If you have a question or comment, please open a new issue.

@stale stale bot closed this as completed Mar 7, 2019
@feugy
Copy link

feugy commented Mar 8, 2019

Another addition to this thread (which was very instructive).
(Source and context on SO)

Append your code to generated service-worker

There's a module for it: cra-append-sw.
You're in charge to provide the appended code.

Pros: easy setup, takes advantage of GenerateSW

Cons: appended code is processed with Babel/Webpack, but not using CRA's config (you could opt-out). Still use GenerateSW which handle network caching for you. Not sure it works when developing locally

Use Workbox in custom service-worker file

  • in src/index.js enable service worker:

    // serviceWorker.unregister()
    serviceWorker.register()
  • in src/serviceWorker.js register your custom file:

    // if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
    if ('serviceWorker' in navigator) {
    // const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
    const swUrl = `${process.env.PUBLIC_URL}/custom-service-worker.js`;

    You have to change the name cause when running dev server, CRA provides a mock for service-worker.js

  • in src/ folder, create custom-service-worker.js file. It will be processed by Webpack, so you can use ES2016/TypeScript syntax and import modules

    /* eslint no-restricted-globals: "off" */
    import * as precaching from 'workbox-precaching'
    // your own imports
    
    if (self.__precacheManifest) {
      precaching.precacheAndRoute(self.__precacheManifest)
    }
    
    // your own code
  • install react-app-rewire:

    • npm add --save-dev react-app-rewired
    • in package.json, in "scripts", replace react-scripts with react-app-rewired
  • tweak webpack configuration: create config-overrides.js in root folder:

    const WebpackBeforeBuildPlugin = require('before-build-webpack')
    const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
    const path = require('path')
    const merge = require('lodash.merge')
    const fs = require('fs')
    
    // from https://www.viget.com/articles/run-multiple-webpack-configs-sequentially/
    class WaitPlugin extends WebpackBeforeBuildPlugin {
      constructor(file, interval = 100, timeout = 60e3) {
        super(function(stats, callback) {
          const start = Date.now()
    
          function poll() {
            if (fs.existsSync(file)) {
              callback()
            } else if (Date.now() - start > timeout) {
              throw Error(`Couldn't access ${file} within ${timeout}s`)
            } else {
              setTimeout(poll, interval)
            }
          }
          poll()
        })
      }
    }
    
    const swOutputName = 'custom-service-worker.js'
    const workerSource = path.resolve(__dirname, 'src', swOutputName)
    
    module.exports = {
      webpack: (config, env) => {
        // we need 2 webpack configurations:
        // 1- for the service worker file.
        //    it needs to be processed by webpack (to include 3rd party modules), and the output must be a
        //    plain, single file, not injected in the HTML page
        const swConfig = merge({}, config, {
          name: 'service worker',
          entry: workerSource,
          output: {
            filename: swOutputName
          },
          optimization: {
            splitChunks: false,
            runtimeChunk: false
          }
        })
        delete swConfig.plugins
    
        // 2- for the main application.
        //    we'll reuse configuration from create-react-app, without a specific Workbox configuration,
        //    so it could inject workbox-precache module and the computed manifest into the BUILT service-worker.js file.
        //    this require to WAIT for the first configuration to be finished
        if (env === 'production') {
          const builtWorkerPath = path.resolve(config.output.path, swOutputName)
          config.name = 'main-application'
          config.plugins.push(
            new WorkboxWebpackPlugin.InjectManifest({
              swSrc: builtWorkerPath,
              swDest: swOutputName
            }),
            new WaitPlugin(builtWorkerPath)
          )
        }
    
        // remove Workbox service-worker.js generator
        const removed = config.plugins.findIndex(
          ({ constructor: { name } }) => name === 'GenerateSW'
        )
        if (removed !== -1) {
          config.plugins.splice(removed, 1)
        } 
    
        const result = [swConfig, config]
        // compatibility hack for CRA's build script to support multiple configurations
        // https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/scripts/build.js#L119
        result.output = { publicPath: config.output.publicPath }
        return result
      }
    }

Pros: you can use ES2016/TypeScript code in service-worker file. You still benefit from Workbox network caching facilities, with total control on it

Cons: complicated and fragile, because of the multiple configuration hack.

I've used the last solution, inspired from @karannagupta and @maciejsimka responses, cause I needed both caching code from Workbox and some import in my service worker file.

react-app-rewire-workbox may help simplifying the Webpack configuration (the one for main app). To be tested.

@ripern
Copy link

ripern commented Mar 8, 2019

@karannagupta Could you elaborate on the difference between your solution (steps 1-7) above, #5673 (comment), and using Craco, https://github.com/sharegate/craco plus https://github.com/kevinsperrine/craco-workbox ? I'm just trying to understand this better. Thanks

@karannagupta
Copy link

karannagupta commented Mar 9, 2019

Hi @ripern

I didn't look into great details of Craco, but in the end, it simply boils down to using Create React App without ejecting and being okay with no control over webpack's config. The difference in my approach is that it uses the Workbox build workflow, but if CRA did allow adding custom config to its webpack config, I would have preferred Workbox Webapck Plugin to achieve the same result like craco-workbox and the one suggested by @maciejsimka

Looks like Craco is similar to react-app-rewired. They are both very cool projects but you have to decide based on your requirements if adding another dependency would make sense. As react-scripts update, you'll have to take care that things don't break ...

@lock lock bot locked and limited conversation to collaborators Mar 15, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

10 participants