Skip to content

fix(router): bundle service workers into a single classic script - #9227

Merged
maiieul merged 6 commits into
QwikDev:mainfrom
mevichitra:fix/service-worker-bundle
Oct 25, 2026
Merged

maiieul merged 6 commits into
QwikDev:mainfrom
mevichitra:fix/service-worker-bundle

Conversation

@mevichitra

@mevichitra mevichitra commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

What is it?

  • Bug

Description

Fixes #8075.

Service workers are registered as classic scripts (navigator.serviceWorker.register(url), no type: 'module'), but the client build emitted service-worker.js as an ES module chunk. As soon as the service worker imported anything, an npm package or a module shared with the app, the bundler split that code into its own chunk, and registration failed:

import{n as e,t}from"./q-C2VAhNAB.js";self.addEventListener(`install`, ...

A second problem: that import path is relative to the chunk's original location in build/, and the chunk is renamed to /service-worker.js afterwards, so the path would be wrong even with module service workers.

This PR fixes both by inlining a service worker's imported chunks into a single IIFE in the client generateBundle, before the rename. Service workers without imports are left untouched. The re-bundle runs on the already-built chunks with Vite's build() (no new dependency) and uses the client build's minify setting.

It also replaces the nested rename loop with a small findEntryChunk helper, so the service-worker lookup and the rename share it.

Verification

  • New bundle-service-worker.unit.ts: a static import and a dynamic import are each inlined, and the result runs as a classic script; a service worker without imports is unchanged. These tests failed before the implementation.
  • pnpm vitest run packages/qwik-router: 809 tests pass. pnpm tsc.check and eslint pass.
  • Built a throwaway app whose src/routes/service-worker.ts imports an npm package and a module shared with a route, then registered the output in a real browser (Edge via Playwright):
    • on main: ServiceWorker script evaluation failed
    • with this PR: the worker registers and activates, and the inlined code runs (it logs from both imports).

I didn't run the full e2e suites locally.

Checklist

  • My code follows the developer guidelines of this project
  • I performed a self-review of my own code
  • I added a changeset with pnpm change
  • I added new tests to cover the fix / functionality

Summary by CodeRabbit

  • Bug Fixes
    • Fixed an issue that could prevent service workers from registering when they shared code with the app. Shared code is now included in the service-worker script, so workers can run without relying on separately imported modules. This improves compatibility for apps that use the same functionality in both the app and a service worker. End-to-end coverage verifies that shared code works in both the page and the service worker.

Service workers are registered as classic scripts, but the client build
emitted them as ES modules importing shared chunks, so registering one
that imported any module failed. The import paths were also relative to
the chunk's original location in build/, which the rename to
/service-worker.js broke.

Inline the imported chunks into an IIFE before the rename.

Fixes QwikDev#8075
@mevichitra
mevichitra requested a review from a team as a code owner October 18, 2026 08:10
@changeset-bot

changeset-bot Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 93b34c6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 8 packages
Name Type
@qwik.dev/router Patch
eslint-plugin-qwik Patch
@qwik.dev/mcp Patch
create-qwik Patch
@qwik.dev/devtools Patch
@qwik.dev/react Patch
@qwik.dev/utils Patch
@qwik.dev/core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

The nested build wrapped inlined dynamic imports in Vite's __vitePreload again. Its error path uses window, which service workers lack, so a failing import threw a ReferenceError instead of its own error.
The bundle is already keyed by file name, and the e2e test now covers bundling before the rename.
@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 15da5a2c-57b8-4fe5-a9bc-a8c020f0f078
Commits

Reviewing files that changed from the base of the PR and between 377d7ea and 93b34c6.

Files selected for processing: 2
  • packages/qwik-router/src/buildtime/vite/bundle-service-worker.ts
  • packages/qwik-router/src/buildtime/vite/bundle-service-worker.unit.ts
Files with no reviewable changes: 2
  • packages/qwik-router/src/buildtime/vite/bundle-service-worker.unit.ts
  • packages/qwik-router/src/buildtime/vite/bundle-service-worker.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Walkthrough

The router build now bundles imported service-worker chunks into classic IIFE scripts. The Vite plugin integrates this step into client builds. Unit and end-to-end tests cover chunk imports and a service worker that shares a module with its registering app.

Changes: Service-worker bundling

Layer / File(s) Summary
Bundle service-worker chunks
packages/qwik-router/src/buildtime/vite/bundle-service-worker.ts, packages/qwik-router/src/buildtime/vite/bundle-service-worker.unit.ts
The new bundling function rebuilds chunks with static or dynamic imports as non-splitting IIFE scripts, then clears their import lists. Unit tests cover static and dynamic imports, and chunks without imports.
Integrate bundling with Vite output
packages/qwik-router/src/buildtime/vite/plugin.ts
The asynchronous generateBundle hook recovers route-loader hashes and replaces placeholders. For client builds, it bundles service-worker chunks before assigning configured filenames. Entry chunks are matched by normalized, extensionless facade paths.
Exercise shared-module service workers
e2e/adapters-e2e/src/greeting.ts, e2e/adapters-e2e/src/routes/greeting/index.tsx, e2e/adapters-e2e/src/routes/service-worker.ts, e2e/adapters-e2e/tests/service-worker.spec.ts, .changeset/service-worker-classic-script.md
The end-to-end test checks the greeting page and the service-worker message response using shared code. A patch changeset records the fix.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant QwikRouterPlugin
  participant bundleServiceWorkerChunks
  participant OutputBundle
  QwikRouterPlugin->>bundleServiceWorkerChunks: Pass service-worker chunks and output bundle
  bundleServiceWorkerChunks->>OutputBundle: Resolve and load imported chunks
  bundleServiceWorkerChunks-->>QwikRouterPlugin: Return bundled code and clear import lists
Loading

Suggested reviewers: wmertens

Merge Risk: Minimal · up to 93b34

No actionable merge-blocking issue is established by the supplied evidence; the change appears ready for normal merge checks.

Security architecture risk: Low · up to 377d7

The change repairs classic service-worker output without an identified increase in attacker-controlled input or privileges. Remaining uncertainty concerns unusual build-failure recovery and deployed worker update behavior, rather than a demonstrated security flaw.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The production effect reaches applications whose client builds contain service workers with recorded imports. The changed path transforms developer-built executable code; the inspected call chain does not connect browser message data to build inputs or introduce tenant, credential or infrastructure authority.

Trust Boundaries and Controls

  • observed — The custom loader serves code only for synthetic identifiers that map to bundle chunks. Unhandled resolutions return null to Vite, so this mechanism is not an import sandbox or a complete filesystem-isolation boundary.

Resilience and Maintainability Implications

  • inferred — Sequential processing avoids concurrent worker mutations in the production caller. A worker is committed only after successful rebundling, and cleared import lists make subsequent processing skip it. Error propagation provides build-stage containment, but reuse of a partially transformed bundle after a caught failure is not demonstrated.
Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 7 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: bundling service workers into a single classic script.
Linked Issues check Passed Issue [#8075] requires Qwik City to inline imported modules in generated service workers so classic-script registration does not fail on import syntax. The PR adds bundleServiceWorkerChunks, rebun…
Out of Scope Changes check Passed The changes stay within issue [#8075]. The bundling helper and generateBundle integration implement the service-worker fix. The shared findEntryChunk helper supports the required lookup and rename…
Full details: Linked Issues check

Explanation

Issue [#8075] requires Qwik City to inline imported modules in generated service workers so classic-script registration does not fail on import syntax. The PR adds bundleServiceWorkerChunks, rebundles service-worker chunks as non-splitting IIFE output, and clears import metadata. The unit tests cover static imports, dynamic imports, classic-script execution, and unchanged workers without imports.

Full details: Out of Scope Changes check

Explanation

The changes stay within issue [#8075]. The bundling helper and generateBundle integration implement the service-worker fix. The shared findEntryChunk helper supports the required lookup and rename. The unit and adapter end-to-end tests verify the affected shared-module behavior. The changeset documents the router fix. No unrelated change is demonstrated.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

This reverts commit b2012c3. It only changed the error a service worker reports when a module it loads with import() throws, and it relied on Vite's internal plugin names.
@pkg-pr-new

pkg-pr-new Bot commented Oct 24, 2026

Copy link
Copy Markdown

Open in StackBlitz

@qwik.dev/core

npm i https://pkg.pr.new/QwikDev/qwik/@qwik.dev/core@9227

@qwik.dev/router

npm i https://pkg.pr.new/QwikDev/qwik/@qwik.dev/router@9227

@qwik.dev/utils

npm i https://pkg.pr.new/QwikDev/qwik/@qwik.dev/utils@9227

eslint-plugin-qwik

npm i https://pkg.pr.new/QwikDev/qwik/eslint-plugin-qwik@9227

create-qwik

npm i https://pkg.pr.new/QwikDev/qwik/create-qwik@9227

@qwik.dev/optimizer

npm i https://pkg.pr.new/QwikDev/qwik/@qwik.dev/optimizer@9227

@qwik.dev/devtools

npm i https://pkg.pr.new/QwikDev/qwik/@qwik.dev/devtools@9227

@qwik.dev/mcp

npm i https://pkg.pr.new/QwikDev/qwik/@qwik.dev/mcp@9227

commit: 93b34c6

@maiieul maiieul moved this from Backlog to Waiting For Review in Qwik Development Oct 25, 2026
@maiieul
maiieul merged commit 37ae0bf into QwikDev:main Oct 25, 2026
55 checks passed
@github-project-automation github-project-automation Bot moved this from Waiting For Review to Done in Qwik Development Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

[🐞] module import issue in generated service worker

2 participants