Skip to content

[wrangler] Fix named-only module Worker format detection - #15518

Open
taylorlee wants to merge 1 commit into
mainfrom
tlee/fix-accidentally-detecting-sw-without-default-export
Open

[wrangler] Fix named-only module Worker format detection#15518
taylorlee wants to merge 1 commit into
mainfrom
tlee/fix-accidentally-detecting-sw-without-default-export

Conversation

@taylorlee

@taylorlee taylorlee commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #15309.

The legacy heuristic of assuming anything without a default export is
going more harm than good. It also seems like it must have regressed at
some point, because it used to be possible to have DO-class-only
Workers a few years ago,
even though trying that now would fail by detecting SW syntax.

After looking at a bunch of options and various forms of required
back-compat, I think the best path forward is to resolve the ambigious
case in favor of modules workers.

Current heuristic:

  • default export -> module worker
  • otherwise -> service worker

New heuristic:

  • no exports -> service worker
  • default export -> module worker
  • ambigious exports:
    • recognized global addEventListener reference -> service worker
    • otherwise -> module worker

This does that the most tricky dynamic constructions of SW can be
falsely interpreted as module workers if they have some named exports. I
think that's acceptable for a 2 reasons:

  1. it is less harmful than misclassifying MW as SW
  2. many of the most dynamic constructions (of either MW or SW) can't
    actually be deployed in practice (due to preexisting validator limitations),
    so the real breakage is minimal.

Alternatives considered: export-only heuristics, source regexes,
entrypoint-specific AST matching, an additional parser, and full dependency
bundling. These were avoided due to compatibility risks, false positives,
added complexity, or resolution overhead.


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation

A picture of a cute animal (not mandatory, but encouraged)


Devin Review

@changeset-bot

changeset-bot Bot commented Sep 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 84f7e87

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

This PR includes changesets to release 3 packages
Name Type
wrangler Patch
@cloudflare/vite-plugin Patch
@cloudflare/vitest-plugin 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

@github-project-automation github-project-automation Bot moved this to Untriaged in workers-sdk Sep 5, 2026
@workers-devprod
workers-devprod requested review from a team and jamesopstad and removed request for a team September 5, 2026 01:52
@workers-devprod

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler
Show detailed file reviewers
  • .changeset/bright-workers-listen.md: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/guess-worker-format.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/deployment-bundle/guess-worker-format.ts: [@cloudflare/wrangler]

bundle: false,
write: false,
...(tsconfig && { tsconfig }),
define: { addEventListener: SERVICE_WORKER_EVENT_LISTENER },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

define only rewrites the bare identifier. self.addEventListener(...) is a standard legacy Service Worker handler form, so a Worker with named exports using that form now has no marker and is incorrectly uploaded as a modules Worker. Mark the global property forms as well.

Suggested change
define: { addEventListener: SERVICE_WORKER_EVENT_LISTENER },
define: {
addEventListener: SERVICE_WORKER_EVENT_LISTENER,
"globalThis.addEventListener": SERVICE_WORKER_EVENT_LISTENER,
"self.addEventListener": SERVICE_WORKER_EVENT_LISTENER,
},

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Also a good opportunity to clarify which types of dynamic registrations we're actually trying to catch here. Fixed.

@ask-bonk

ask-bonk Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

I'm Bonk, and I've done a quick review of your PR.

Updates Worker-format detection for named-only module entrypoints.

  1. P1 guess-worker-format.ts:38 misses self.addEventListener, causing named-export legacy Workers using this standard form to be misclassified as modules. Posted an inline suggestion.

github run

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

Devin Review

Comment thread packages/wrangler/src/deployment-bundle/guess-worker-format.ts Outdated
@@ -31,28 +35,33 @@ export async function guessWorkerFormat(
bundle: false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Imported listeners select module format

A named-export Worker can import its addEventListener registration from another file. The unbundled detection output omits that listener and selects module format.

Prompt for agents
guessWorkerFormat builds only the entry file with bundle:false, then searches its emitted text for the listener marker. Wrangler otherwise supports bundling legacy Service Workers whose event registration lives in an imported module. For a named-export entrypoint importing such a module, the marker never appears and the new fallback selects module format. Detect listener registration across the dependency graph without changing normal module resolution behavior, and cover a named-export entrypoint with an imported listener.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is an intentional and accepted limitation.

Comment on lines +46 to +48
const usesServiceWorkerEventListener = result.outputFiles.some(({ text }) =>
text.includes(SERVICE_WORKER_EVENT_LISTENER)
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Marker collisions select legacy format

A named-only module containing __WRANGLER_SERVICE_WORKER_EVENT_LISTENER__ anywhere in emitted text passes the marker check. Wrangler builds it as a legacy Worker.

Prompt for agents
The listener check uses text.includes() with a fixed valid identifier. User source can independently emit the same identifier or string, so named-only module Workers can become false positives. Replace textual sentinel detection with collision-resistant structural metadata or another mechanism that distinguishes esbuild's replacement from user-authored output, and add a collision test.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is fine. This isn't a security measure, it's a heuristic to attempt to reduce misclassifications.

@pkg-pr-new

pkg-pr-new Bot commented Sep 5, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@15518

@cloudflare/build-output-utils

npm i https://pkg.pr.new/@cloudflare/build-output-utils@15518

@cloudflare/codemods

npm i https://pkg.pr.new/@cloudflare/codemods@15518

@cloudflare/config

npm i https://pkg.pr.new/@cloudflare/config@15518

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@15518

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@15518

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@15518

miniflare

npm i https://pkg.pr.new/miniflare@15518

@cloudflare/pages-functions

npm i https://pkg.pr.new/@cloudflare/pages-functions@15518

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@15518

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@15518

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@15518

@cloudflare/vitest-plugin

npm i https://pkg.pr.new/@cloudflare/vitest-plugin@15518

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@15518

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@15518

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@15518

wrangler

npm i https://pkg.pr.new/wrangler@15518

commit: 84f7e87

Fixes #15309.

The legacy heuristic of assuming anything without a default export is
going more harm than good. It also seems like it must have regressed at
some point, because it used to be possible to have DO-class-only
Workers a [few years ago](cloudflare/cloudflare-docs#5185),
even though trying that now would fail by detecting SW syntax.

After looking at a bunch of options and various forms of required
back-compat, I think the best path forward is to resolve the ambigious
case in favor of modules workers.

Current heuristic:
* default export -> module worker
* otherwise -> service worker

New heuristic:
* no exports -> service worker
* default export -> module worker
* ambigious exports:
    - recognized global addEventListener reference -> service worker
    - otherwise -> module worker

This does that the most tricky dynamic constructions of SW can be
falsely interpreted as module workers if they have some named exports. I
think that's acceptable for a 2 reasons:
1. it is less harmful than misclassifying MW as SW
2. many of the most dynamic constructions (of either MW or SW) can't
   actually be deployed in practice (due to preexisting validator limitations),
   so the real breakage is minimal.

Alternatives considered: export-only heuristics, source regexes,
entrypoint-specific AST matching, an additional parser, and full dependency
bundling. These were avoided due to compatibility risks, false positives,
added complexity, or resolution overhead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

Wrangler bundles code that imports WorkerEntrypoint as service worker if no default export is provided

2 participants