Summary
ReplaceGlobalDefines currently treats TypeScript ambient declarations like declare let self: ServiceWorkerGlobalScope as local runtime bindings. Because of that, a define such as self.__WB_MANIFEST is not replaced.
This affects service worker code that follows the Workbox/Vite PWA pattern:
declare let self: ServiceWorkerGlobalScope;
precacheAndRoute(self.__WB_MANIFEST);
When using Rolldown/Oxc define replacement directly, self.__WB_MANIFEST is left unchanged. However, Vite 7/esbuild behavior erases the ambient declare and replaces the define.
Reproduction
Input:
declare let self: ServiceWorkerGlobalScope;
precacheAndRoute(self.__WB_MANIFEST);
Define config:
{
"self.__WB_MANIFEST": "[]"
}
Current output:
precacheAndRoute(self.__WB_MANIFEST);
Expected output:
Why this matters
declare let self: ServiceWorkerGlobalScope is TypeScript-only. It does not create a runtime binding and should not shadow the global self.
Without the declaration, TypeScript complains about service-worker-specific globals and custom injected properties like self.__WB_MANIFEST. With the declaration present, Oxc/Rolldown currently fails to replace the manifest injection point.
Using globalThis.__WB_MANIFEST would avoid the shadowing issue, but changing existing Workbox/Vite PWA-compatible code from self.__WB_MANIFEST to globalThis.__WB_MANIFEST is a breaking change for downstream users.
Behavior in other tools
Tested on June 10, 2026:
vite@7.3.5
esbuild@0.28.0
@swc/core@1.15.41
rollup@4.61.1
@rollup/plugin-replace@6.0.3
esbuild
esbuild replaces the ambient self case:
declare let self: ServiceWorkerGlobalScope;
precacheAndRoute(self.__WB_MANIFEST);
let other;
precacheAndRoute(other.__WB_MANIFEST);
with define:
{
"self.__WB_MANIFEST": "[]",
"other.__WB_MANIFEST": "[]"
}
outputs:
var define_self_WB_MANIFEST_default = [];
precacheAndRoute(define_self_WB_MANIFEST_default);
let other;
precacheAndRoute(other.__WB_MANIFEST);
So declare let self does not block replacement, while a real runtime let other does.
Rollup
Rollup core does not have an esbuild-style define option, so there is no direct Rollup core behavior to compare.
The closest common Rollup equivalent is @rollup/plugin-replace, which is scope-insensitive textual replacement. With input:
declare let self: ServiceWorkerGlobalScope;
precacheAndRoute(self.__WB_MANIFEST);
let other;
precacheAndRoute(other.__WB_MANIFEST);
and replacement values:
{
"self.__WB_MANIFEST": "[]",
"other.__WB_MANIFEST": "[]"
}
it outputs:
precacheAndRoute([]);
precacheAndRoute([]);
It also replaces function-parameter shadows:
export function f(self) {
precacheAndRoute(self.__WB_MANIFEST);
}
outputs:
function f(self) {
precacheAndRoute([]);
}
export { f };
So @rollup/plugin-replace is closer to SWC jsc.transform.optimizer.globals.vars: it is aggressive and does not preserve shadowing semantics.
SWC
SWC behavior depends on the mechanism:
jsc.transform.optimizer.globals.vars is more aggressive and replaced even locally declared roots like let other; other.__WB_MANIFEST.
jsc.minify.compress.global_defs replaced top-level cases, but did not replace a function parameter shadow such as:
function f(self) {
precacheAndRoute(self.__WB_MANIFEST);
}
Proposed behavior
For define replacement, an identifier root should be considered replaceable when it is either:
- unresolved/global, or
- resolved only to an ambient TypeScript declaration (
declare)
Real runtime bindings should still block replacement:
let self;
precacheAndRoute(self.__WB_MANIFEST); // should not replace
function f(self) {
precacheAndRoute(self.__WB_MANIFEST); // should not replace
}
This matches esbuild/Vite behavior while preserving normal shadowing semantics.
Summary
ReplaceGlobalDefinescurrently treats TypeScript ambient declarations likedeclare let self: ServiceWorkerGlobalScopeas local runtime bindings. Because of that, a define such asself.__WB_MANIFESTis not replaced.This affects service worker code that follows the Workbox/Vite PWA pattern:
When using Rolldown/Oxc define replacement directly,
self.__WB_MANIFESTis left unchanged. However, Vite 7/esbuild behavior erases the ambientdeclareand replaces the define.Reproduction
Input:
Define config:
Current output:
Expected output:
Why this matters
declare let self: ServiceWorkerGlobalScopeis TypeScript-only. It does not create a runtime binding and should not shadow the globalself.Without the declaration, TypeScript complains about service-worker-specific globals and custom injected properties like
self.__WB_MANIFEST. With the declaration present, Oxc/Rolldown currently fails to replace the manifest injection point.Using
globalThis.__WB_MANIFESTwould avoid the shadowing issue, but changing existing Workbox/Vite PWA-compatible code fromself.__WB_MANIFESTtoglobalThis.__WB_MANIFESTis a breaking change for downstream users.Behavior in other tools
Tested on June 10, 2026:
vite@7.3.5esbuild@0.28.0@swc/core@1.15.41rollup@4.61.1@rollup/plugin-replace@6.0.3esbuild
esbuild replaces the ambient
selfcase:with define:
outputs:
So
declare let selfdoes not block replacement, while a real runtimelet otherdoes.Rollup
Rollup core does not have an esbuild-style
defineoption, so there is no direct Rollup core behavior to compare.The closest common Rollup equivalent is
@rollup/plugin-replace, which is scope-insensitive textual replacement. With input:and replacement values:
it outputs:
It also replaces function-parameter shadows:
outputs:
So
@rollup/plugin-replaceis closer to SWCjsc.transform.optimizer.globals.vars: it is aggressive and does not preserve shadowing semantics.SWC
SWC behavior depends on the mechanism:
jsc.transform.optimizer.globals.varsis more aggressive and replaced even locally declared roots likelet other; other.__WB_MANIFEST.jsc.minify.compress.global_defsreplaced top-level cases, but did not replace a function parameter shadow such as:Proposed behavior
For define replacement, an identifier root should be considered replaceable when it is either:
declare)Real runtime bindings should still block replacement:
This matches esbuild/Vite behavior while preserving normal shadowing semantics.