Skip to content

Commit 9be932c

Browse files
committed
fixup! fix(@angular/build): enable oxc WASI fallback in WebContainer environments
1 parent 4d2137e commit 9be932c

4 files changed

Lines changed: 110 additions & 17 deletions

File tree

‎packages/angular/build/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"xxhash-wasm": "1.1.0"
4949
},
5050
"optionalDependencies": {
51+
"@oxc-parser/binding-wasm32-wasi": "0.156.0",
5152
"lmdb": "3.5.9"
5253
},
5354
"devDependencies": {

‎packages/angular/build/src/tools/oxc/oxc-wasi-shim.ts‎

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,30 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9+
import { Module, createRequire } from 'node:module';
10+
911
// Workaround for an upstream incompatibility in `@oxc-parser/binding-wasm32-wasi` when
1012
// falling back to WebAssembly (e.g., inside WebContainers or StackBlitz).
1113
//
1214
// `@oxc-parser/binding-wasm32-wasi` declares a dependency on `@emnapi/core@2.0.0-alpha.6`,
13-
// but depends on `@napi-rs/wasm-runtime@~1.2.6` which expects `@emnapi/core@>=2.0.0-beta.2`.
14-
// In `@napi-rs/wasm-runtime@1.2.6`, the runtime attempts to assign `initWorker` to
15-
// `emnapi_shared.addonModule`, which is undefined in `alpha.6` where the property was named
16-
// `napiModule`. This results in `TypeError: Cannot set properties of undefined (setting 'initWorker')`,
17-
// causing WASI binding initialization to fail and surface as a misleading "WASI binding not found" error
18-
// or an `ERR_REQUIRE_CYCLE_MODULE` failure.
19-
//
20-
// Aliasing `addonModule` to `this.napiModule` on `Object.prototype` allows the WASI runtime to
21-
// initialize successfully.
15+
// but depends on `@napi-rs/wasm-runtime@~1.2.6` which expects `@emnapi/core@^1.7.1 || 2.0.0-beta.2`.
16+
// 1. In `@napi-rs/wasm-runtime@1.2.6`, the runtime attempts to assign `initWorker` to
17+
// `emnapi_shared.addonModule`, which is undefined in `alpha.6` where the property was named
18+
// `napiModule`. Aliasing `addonModule` to `this.napiModule` on `Object.prototype` allows the
19+
// WASI runtime to initialize successfully.
20+
// 2. Under npm, `@napi-rs/wasm-runtime@1.2.6`'s peerDependencies cause npm to hoist `@emnapi/core@1.x`
21+
// to top-level `node_modules` while nesting `2.0.0-alpha.6` under `@oxc-parser/binding-wasm32-wasi`,
22+
// causing an ABI mismatch (`this.bridge.setLastError is not a function`). Under pnpm, `oxc-parser`
23+
// does not declare `@oxc-parser/binding-wasm32-wasi` in its own dependencies. Resolving these
24+
// packages relative to `@angular/build` and `@oxc-parser/binding-wasm32-wasi` ensures consistent
25+
// module resolution across package managers.
2226
//
2327
// See: https://github.com/oxc-project/oxc/issues/28717 and https://github.com/napi-rs/napi-rs/issues/3674
28+
const forceWasi = globalThis.process?.env?.['NAPI_RS_FORCE_WASI'];
2429
if (
2530
(globalThis.process?.versions?.['webcontainer'] ||
26-
globalThis.process?.env?.['NAPI_RS_FORCE_WASI'] === 'true') &&
31+
forceWasi === 'true' ||
32+
forceWasi === 'error') &&
2733
!('addonModule' in Object.prototype)
2834
) {
2935
Object.defineProperty(Object.prototype, 'addonModule', {
@@ -43,4 +49,33 @@ if (
4349
configurable: true,
4450
enumerable: false,
4551
});
52+
53+
const resolvedModules = new Map<string, string>();
54+
try {
55+
const wasiBindingPath = require.resolve('@oxc-parser/binding-wasm32-wasi');
56+
resolvedModules.set('@oxc-parser/binding-wasm32-wasi', wasiBindingPath);
57+
const wasiRequire = createRequire(wasiBindingPath);
58+
resolvedModules.set('@emnapi/core', wasiRequire.resolve('@emnapi/core'));
59+
resolvedModules.set('@emnapi/runtime', wasiRequire.resolve('@emnapi/runtime'));
60+
} catch {}
61+
62+
if (resolvedModules.size > 0) {
63+
const moduleWithResolve = Module as typeof Module & {
64+
_resolveFilename: (
65+
request: string,
66+
parent: unknown,
67+
isMain: boolean,
68+
options: unknown,
69+
) => string;
70+
};
71+
const origResolveFilename = moduleWithResolve._resolveFilename;
72+
moduleWithResolve._resolveFilename = function (request, parent, isMain, options) {
73+
const resolved = resolvedModules.get(request);
74+
if (resolved !== undefined) {
75+
return resolved;
76+
}
77+
78+
return origResolveFilename.call(this, request, parent, isMain, options);
79+
};
80+
}
4681
}

‎pnpm-lock.yaml‎

Lines changed: 61 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tests/e2e/tests/vite/wasi.ts‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import assert from 'node:assert';
22
import { getGlobalVariable } from '../../utils/env';
3-
import { ngServe } from '../../utils/project';
3+
import { ng } from '../../utils/process';
44

55
export default async function () {
66
assert(
@@ -9,14 +9,10 @@ export default async function () {
99
);
1010

1111
const originalForceWasi = process.env['NAPI_RS_FORCE_WASI'];
12-
process.env['NAPI_RS_FORCE_WASI'] = 'true';
12+
process.env['NAPI_RS_FORCE_WASI'] = 'error';
1313

1414
try {
15-
const port = await ngServe();
16-
const response = await fetch(`http://localhost:${port}/`);
17-
assert(response.ok, 'Expected response to be ok');
18-
const text = await response.text();
19-
assert.match(text, /<app-root/);
15+
await ng('build');
2016
} finally {
2117
if (originalForceWasi === undefined) {
2218
delete process.env['NAPI_RS_FORCE_WASI'];

0 commit comments

Comments
 (0)