fix(qwik-city): support non-root `basePathname` for service-worker.js
What is it?
- [ ] Feature / enhancement
- [x] Bug
- [ ] Docs / tests
Description
Related to this issue: https://github.com/BuilderIO/qwik/issues/1855
tl;dr:
- service worker register code refers to
/base/base/service-worker.js - service worker output to
dist/base/service-worker.jswould be served underhttp://localhost:5173/base/base/service-worker.jswhich would give the service worker a default scope of/base/base/and be effectively useless - route patterns generated for the base route would match
/base/and/base//
There are a few issues I noticed with the service worker when provided a non-root basePathname (e.g. /base/).
The original issue focused around the first issue where @qwik-city-sw-register was:
((s,a,i,r)=>{i=(e,t)=>{t=document.querySelector("[q\\:base]"),t&&a.active&&a.active.postMessage({type:"qprefetch",base:t.getAttribute("q:base"),...e})},document.addEventListener("qprefetch",e=>{const t=e.detail;a?i(t):t.bundles&&s.push(...t.bundles)}),navigator.serviceWorker.register("/base/base/service-worker.js").then(e=>{r=()=>{a=e,i({bundles:s})},e.installing?e.installing.addEventListener("statechange",t=>{t.target.state=="activated"&&r()}):e.active&&r()}).catch(e=>console.error(e))})([])
which includes the basePathname twice: /base/base/service-worker.js.
The second related issue is that the service worker chunk is emitted to dist/base/service-worker.js which gets served on http://localhost:5173/base/base/service-worker.js. The other qwik chunks are in dist/build.
I did update entry.express.tsx to be:
app.use(`/base/build`, express.static(buildDir, { immutable: true, maxAge: '1y' }));
app.use(`/base`, express.static(distDir, { redirect: false }));
These two issues together though does make the service-worker.js registration work correctly which led me to originally believe this wasn't that big of an issue. However, with the http://localhost:5173/base/base/service-worker.js path, the service worker's scope is /base/base/ rather than just /base/. This makes it so only pages underneath /base/base/ can interact with the service worker and ultimately the service worker does nothing. (I confirmed this via the Chrome DevTools service worker network.)
A third issue I noticed was the service worker would generate a link bundle that includes a pattern that would match /base/ and /base//:
const linkBundles=[[/^\/base\/flower\/?$/,[3,4,14,5]],[/^\/base\/\/?$/,[3,4,17,1]]];
Note the pattern here is: /^\/base\/\/?$/.
Side-note: If I put a grouped layout at the root of routes like /src/routes/(group)/index.tsx, this will generate the proper pattern of /^\/base\/?$/.
Tracing through the code, the solve I found for the first two issues was to remove the entire basePathname from the chunkFilename for service worker entries in the qwik city build time plugin. And the solve for the last issue was to filter out empty segments when generating the route patterns since the route here would be /base/ which would produce segments of ['base', ''] (which no other routes would ever produce this empty string).
Use cases and why
Service worker should be emitted to dist/service-worker.js and served under http://localhost:5173/base/service-worker.js with route patterns that would match /base and /base/.
Side-note: in order to support the non-trailing slash /base path, I needed to add a Service-Worker-Allowed header to the service-worker.js file in entry.express.tsx:
app.use(/.*\/service-worker\.js$/, (_req, res, next) => {
res.setHeader('Service-Worker-Allowed', '/base');
next();
});
Checklist:
- [x] My code follows the developer guidelines of this project
- [x] I have performed a self-review of my own code
- [ ] I have made corresponding changes to the documentation
- [x] Added new tests to cover the fix / functionality
Run & review this pull request in StackBlitz Codeflow.
This is an amazing piece of work! thank you so much for your PR!, hope to see more :) Thanks for your time!