Since service workers are a big part of the "spa web" and especially PWA:s I think that parcel should support it out of the box. As mentioned in https://github.com/parcel-bundler/parcel/issues/301 and according to the MDN docs:
The
register()method of theServiceWorkerContainerinterface creates or updates aServiceWorkerRegistrationfor the givenscriptURL.
The key point to take away being that a service worker has to be provided to the browser via a URL.
And as it's shown in this SO answer, it can't be provided as a data URI. Witch only leaves one option, to compile and bundle a second bundle with the service worker as its entry point.
_Example of registration a service worker:_
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./service-worker.js')
.then(function(registration) {
console.log('Registration successful, scope is:', registration.scope);
})
.catch(function(error) {
console.log('Service worker registration failed, error:', error);
});
}
The service worker should be built and bundled on its own, and its name should remain the same as it was prior to the bundling.
Currently the service worker is excluded from the build process, unless explicitly imported, in which case the service worker ends up as part of the main bundle, which is not what we want.
IMO this could be implemented in one of three ways:
I'm am writing a template for typescript and react based web applications in which I use parcel for building and bundling. And I'm now extending the template to include the fundamentals of a PWA, including a service worker.
Can this be accomplished via code-splitting?
@davidnagli As it says in the parcel docs:
Code splitting is controlled by use of the dynamic import() function syntax proposal, which works like the normal import statement or require function, but returns a Promise.
The dynamic import function functions just the same as an import statement in this respect. Aka, the service worker would end up being part of the main bundle. Or am I misunderstanding the docs?
Also the service worker has to be provided to the browser via a __URL__ and not as an imported module.
As I see it, workers (both service and web) should just be babelized and moved to the dist folder.
navigator.serviceWorker.register('./worker.js'). It does not preserve the name of the worker script. The register call will be re-written with the name of the worker bundle ([...hash...].js)@jdanyow will this work for both JS and TS?
Aka, will it detect the registration in both JS and TS. And will it transpile both JS and TS workers?
@Olian04 yes 馃憤
Merged #398 which adds support for detecting calls to navigator.serviceWorker.register. Closing this issue.
Would someone like to do a PR to detect new Worker('somescript.js') calls as well? importScripts doesn't seem terribly useful since you could just use require/import in parcel, but willing to be proven wrong there. Thoughts?
Agreed- importScripts wouldn't be useful.
Here's the PR for new Worker(...): #441
Yeah indeed, importScripts isn't very useful, but it's standard ...
And there is also SharedWorker to support : https://developer.mozilla.org/fr/docs/Web/API/SharedWorker
@BrieucP I intentionally left SharedWorker out the PR because it's not well supported. Should be pretty easy to add if necessary.
Regarding importScripts(...), I'm guessing some folks would want it to work like System.import(...) (separate bundle) while others would want it to work like import ... from ... (same bundle) and still others might want parcel to ignore those expressions entirely. Maybe a separate RFC should be opened for this so that feedback can be gathered.
I also took an approach for this.
My attempt was to use an offline.html in which I linked my entry files (index.js, etc) and also included blank hyperlinks pointing to my service-worker.js which then gets put into dist.
The filenames stay the same, except if parcel renames them based on hashing, etc
basically saying if you want your service-worker bundled but not in your main module,
use a html entry file using
<a href="./relativeFile"></a>
<!--optionally style it invisible (they dont get displayed anyway) -->
<a href="./relativeFile" style="display:none"></a>
My approach can be found here: https://github.com/peanutbother/parcel-serviceworker-example
Example on heroku: https://parcel-serviceworker-example.herokuapp.com/
@jdanyow Hi jeremy, Is there any plan to add SharedWorker support to parcel?
Why are there downvotes for @peanutbother's example?
I guess my approach was not fitting for the purpose of those people. Reactions are useful in either way, but a little explanation might be more helpful.
It might be nicer to use some sort of link tag with a custom parcel-specific rel in the head section, rather than an a tag
Unless there's a link[rel] that is supposed to be used for ServiceWorkers...
the traditional usage would go as follows: https://github.com/parcel-bundler/parcel/issues/1957
I'm actually exporting a script that takes a config object and runs the navigator.... Is there a way to "manually" trigger waking up Parcel to the service worker instead of relying on code exposed to the scope of an import?
Here's a snippet of my code:
export const registerBackup = (config = defaultConfig) =>
navigator.serviceWorker.register(config.SW_file).then(async worker => {
...
I'm not sure if it's the best way to hack this, but if I add a redundant inclusion of the SW file above the function that actually uses the worker, parcel works 馃
+ navigator.serviceWorker.register('sw.js')
export const registerBackup = (config = defaultConfig) =>
navigator.serviceWorker.register(config.SW_file).then(async worker => {
...
Parcel [1.12.4] is bundling my application to a nested folder inside the dist one, something like this: /dist/__/client/container/App/serviceWorker.js. I did this to solve my problem:
navigator.serviceWorker.register('./serviceWorker.js').then(() => {
console.log('Parcel will not work here, service worker will be called by http!')
}).catch(() => {
navigator.serviceWorker.register('http://localhost:3000/__/src/client/containers/App/serviceWorker.js')
.then(() => {
console.log('Service worker registered by http!')
}).catch(() => {
console.error('Failed to register service worker!')
})
})
Better then putting the sw.js inside the dist folder. Hope it helps!
I was trying to register a service worker as officially recommended above (https://github.com/parcel-bundler/parcel/issues/331#issuecomment-354314502) but my problem was that Parcel (or Babel behind it) adds a bunch of _invalid_ JS-code into my serviceWorker.js e.g. window, Websockets (for HMR), etc. This causes errors as window and such do not exist within the workers' scope.
Here is how I solved this problem:
1) Install concurrently and copy-and-watch:
npm i concurrently --save-dev
npm i copy-and-watch --save-dev
2) In package.json add:
"scripts": {
"dev": "concurrently \"parcel public/index.html --port 5000\" \"copy-and-watch --watch src/workers/serviceWorker.js dist\"",
"build": "parcel build public/index.html && copy-and-watch src/workers/serviceWorker.js dist"
}
How it works:
copy-and-watch simply copies your serviceWorker.js into dist w/o any changes. And concurrently allows running copy-and-watch and parcel simultaneously on npm run dev
Most helpful comment
I also took an approach for this.
My attempt was to use an
offline.htmlin which I linked my entry files (index.js, etc) and also included blank hyperlinks pointing to myservice-worker.jswhich then gets put intodist.The filenames stay the same, except if parcel renames them based on hashing, etc
basically saying if you want your service-worker bundled but not in your main module,
use a html entry file using
My approach can be found here: https://github.com/peanutbother/parcel-serviceworker-example
Example on heroku: https://parcel-serviceworker-example.herokuapp.com/