DEV Community

Cover image for The easiest way to extend or customize Create React App service worker without ejecting
Michał Gacka for 3dayweek.dev

Posted on • Updated on • Originally published at 3dayweek.dev

The easiest way to extend or customize Create React App service worker without ejecting

Create React App (version before 4.0.0) by default includes a hidden Service Worker that will do some background magic for you in order for your app to be recognized as a Progressive Web Application. But if there's one pitfall of CRA, it's definitely how closed the configuration is and how difficult it is to modify / extend / customize it without ejecting (taking full control of the configuration) the application. Here, I discuss and present what I found to be the simplest way to extend the out-of-the-box service worker functionality.

EDIT: On the 23rd of October 2020 Create React App 4.0.0 has been released which simplifies the process of extending the default service worker immensely, so it might be a good idea to consider upgrading your project instead of using the workarounds I describe below. You can now just initialize the project by using the PWA template and it will generate the service worker file for you in the src folder which you can just add your custom code to. Check out the official instructions.

In my case, for the purpose of adding a background process to take care of firebase messaging for push notifications but this trick should work in any other case as well.

There's a multitude of articles trying to solve the problem but for some reason, most of them are overly complicated and are a pain to make them work in practice. That's because most of them overlook this wonderful utility: cra-append-sw. It lets you easily append the code you need to the existing service worker when building a production-ready app and also place a separate worker file in the public folder so you can register it yourself when running the development server.


It's as simple as installing the package, creating the service worker file in your main folder ('firebase-messaging-sw.js' in my case), and modifying your package.json file like this:

...
"start": "cra-append-sw --mode dev --env ./.env ./firebase-messaging-sw.js && react-scripts start",
"build": "cra-append-sw --env ./.env ./firebase-messaging-sw.js && react-scripts build",
...
Enter fullscreen mode Exit fullscreen mode

Also you need to remember to take care of registering the service worker when your application runs via a development server (CRA will only register its own service worker so in development, the separate file created in the public directory has to be registered separately). Here's a snippet out of my index.tsx file which is the entry point for my react application:

This is to make sure that when cra-append-sw runs in dev mode (and thus generate the worker in the public folder instead of appending it to the react service worker) you register it manually.

That should be all you need. Neat and simple in contrast to other hacky tools trying to accomplish the same.

One of the hardest issues to solve with these pipelines I've found was how to use environment variables within the service worker in order to configure the firebase access key and other secret variables I had in my .env file. This solution solves it because the code is ran through the webpack pipeline before it's outputted both in the normal mode and the dev mode. Meaning you can access the process.env object in your custom service worker code.

I hope this saves you some pain and you live happily ever after with your new service worker functionality included in your Create React App.


Additional notes & edits:

  • As pointed out by @sjbuysse in the comments when cra-append-sw runs in the production mode there is no reason to register the service worker manually. It is necessary though in the dev mode, thus the gist I appended from my index.tsx file. I updated it now with a check for the environment to only run it in dev. Here's the relevant part from cra-append-sw documentation:
dev creates public/<file> instead of appending the code to build/service-worker.js
build creates build/<file> instead of appending the code to build/service-worker.js
Enter fullscreen mode Exit fullscreen mode
  • A little bit beyond the scope of the post but requested in the comments: the firebase-messaging-sw.js that I ended up using. It's a Frankenstein of some solutions I found online if I remember correctly but I can't find the sources now to credit them. Let me know in the comments if you recognize the gist ☀️
  • As pointed out by @sjbuysse it might be necessary to pass your service worker registration object to the firebase messaging getToken() options in production (when you append the firebase service worker to your CRA service worker) but for some reason it worked for me without it.

Top comments (14)

Collapse
 
sjbuysse profile image
sjbuysse

Hi Michal, thanks for this!
Quick question: Why do you still register the firebase-messaging-sw.js ?
shouldn't you use the CRA serviceworker.register() function to register the CRA sw, which is now appended with the firebase sw?

Collapse
 
m3h0w profile image
Michał Gacka • Edited

Hi! That's a great question that. I should explain that better.

It's because cra-append-sw on npm start is ran in a dev mode meaning that as per their documentation:

dev creates public/<file> instead of appending the code to build/service-worker.js
Enter fullscreen mode Exit fullscreen mode

So if you want it running in development you need to register it. I actually modified the if statement later on and didn't update it in the post. Now it looks like this:

if ('serviceWorker' in navigator && process.env.NODE_ENV !== 'production')
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sjbuysse profile image
sjbuysse

awesome! thanks for this clarification

Thread Thread
 
m3h0w profile image
Michał Gacka

@sjbuysse apparently we missed that there is a simpler way now for. CRA 4.0.0 has the service worker file available for extension: dev.to/m3h0w/don-t-struggle-with-e...

Collapse
 
sjbuysse profile image
sjbuysse

Thanks for the last answer, one more comment that I needed to make it work for me in production as well.
Firebase is expecting a firebase-messaging-sw.js file to function as a service worker, but you are actually appending your firebase sw to the CRA serviceworker.js in your production build.
So you will have to let firebase messaging know that the serviceworker was registered else where by passing your serviceworker registration to the getToken() method when you call it.

messaging.getToken({serviceWorkerRegistration})
Enter fullscreen mode Exit fullscreen mode
Collapse
 
m3h0w profile image
Michał Gacka

Interesting. I wonder how I had it working then without passing that in (some kind of version discrepancy maybe?). Thanks for pointing it out! I'll include it in the post.

Collapse
 
avatar19710 profile image
Daniel Dopiriak

Wow, thanks a lot. I spent countless hours finding the right solution, and this one actually worked perfectly. :)

Collapse
 
m3h0w profile image
Michał Gacka

Hey @daniel Dopiriak there's a better solution I found today. CRA 4.0.0 has the service worker file available in the src folder: dev.to/m3h0w/don-t-struggle-with-e...

Collapse
 
avatar19710 profile image
Daniel Dopiriak

Thanks, Michal. We do use some libraries which might not be supported by the new version of react. I will have to look at it more closely. But I really appreciate your comment.

Thread Thread
 
m3h0w profile image
Michał Gacka

Based on this discussion I think there is a minor version upgrade in React 16 that is compatible with CRA 4 so maybe you don't have to upgrade to React 17 if that's what you meant :)

Collapse
 
m3h0w profile image
Michał Gacka

Me too! ☀️

Collapse
 
ericyoung profile image
Eric Young • Edited

for those of us newer to service workers, it would be cool to show an example of your "firebase-messaging-sw.js" file.
Also, how you're handling requesting the user permissions would be nice to see.

Collapse
 
m3h0w profile image
Michał Gacka

Sure. I don't remember where I got the code from to glue it together unfortunately but I appended it to the post so you can take a look if you'd like.

The gist.

Collapse
 
m3h0w profile image
Michał Gacka

Happy to help! ☀️