@nimbus-sh/core 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -26
- package/dist/_shared/byte-stream.d.ts +5 -4
- package/dist/_shared/byte-stream.d.ts.map +1 -1
- package/dist/_shared/cache-stats.d.ts +23 -1
- package/dist/_shared/cache-stats.d.ts.map +1 -1
- package/dist/_shared/cache-stats.js +13 -1
- package/dist/_shared/exports-resolver.d.ts +1 -1
- package/dist/_shared/exports-resolver.js +1 -1
- package/dist/_shared/real-node-imports.d.ts +7 -0
- package/dist/_shared/real-node-imports.d.ts.map +1 -1
- package/dist/_shared/real-node-imports.js +8 -0
- package/dist/_shared/retry.js +1 -1
- package/dist/_shared/tarball.d.ts +23 -5
- package/dist/_shared/tarball.d.ts.map +1 -1
- package/dist/_shared/tarball.js +38 -5
- package/dist/_shared/vfs-write-ledger.d.ts.map +1 -1
- package/dist/_shared/vfs-write-ledger.js +22 -16
- package/dist/constants.d.ts +5 -11
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +16 -56
- package/dist/runtime/assets-loader.js +1 -1
- package/dist/runtime/comment-strip.d.ts +0 -35
- package/dist/runtime/comment-strip.d.ts.map +1 -1
- package/dist/runtime/comment-strip.js +48 -20
- package/dist/runtime/esbuild-service.d.ts +38 -42
- package/dist/runtime/esbuild-service.d.ts.map +1 -1
- package/dist/runtime/esbuild-service.js +494 -197
- package/dist/runtime/javascript-ast.d.ts.map +1 -1
- package/dist/runtime/javascript-ast.js +45 -7
- package/dist/runtime/opentui-wasm-backend.d.ts.map +1 -1
- package/dist/runtime/opentui-wasm-backend.js +18 -16
- package/dist/runtime/port-registry.d.ts +4 -2
- package/dist/runtime/port-registry.d.ts.map +1 -1
- package/dist/runtime/port-registry.js +60 -8
- package/dist/runtime/virtual-socket-kernel.generated.d.ts +1 -1
- package/dist/runtime/virtual-socket-kernel.generated.js +1 -1
- package/dist/shell/stdin-adapter.d.ts +11 -0
- package/dist/shell/stdin-adapter.d.ts.map +1 -0
- package/dist/shell/stdin-adapter.js +59 -0
- package/dist/shell/unix-commands.d.ts +2 -1
- package/dist/shell/unix-commands.d.ts.map +1 -1
- package/dist/shell/unix-commands.js +1078 -50
- package/dist/substrate/lifo/commands/io/dd.js +1 -1
- package/dist/substrate/lifo/commands/net/curl.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/net/curl.js +270 -54
- package/dist/substrate/lifo/commands/system/npm.d.ts +18 -1
- package/dist/substrate/lifo/commands/system/npm.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/system/npm.js +26 -43
- package/dist/substrate/lifo/commands/text/head.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/text/head.js +2 -1
- package/dist/substrate/lifo/commands/text/sed.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/text/sed.js +499 -117
- package/dist/substrate/lifo/commands/types.d.ts +8 -4
- package/dist/substrate/lifo/commands/types.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/zlib.d.ts +37 -18
- package/dist/substrate/lifo/node-compat/zlib.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/zlib.js +122 -23
- package/dist/substrate/lifo/shell/interpreter.d.ts +9 -1
- package/dist/substrate/lifo/shell/interpreter.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/interpreter.js +79 -29
- package/dist/substrate/lifo/shell/pipe.d.ts +16 -0
- package/dist/substrate/lifo/shell/pipe.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/pipe.js +62 -18
- package/dist/substrate/lifo/shell/terminal-stdin.d.ts +20 -3
- package/dist/substrate/lifo/shell/terminal-stdin.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/terminal-stdin.js +60 -24
- package/dist/vfs/seed-project.d.ts.map +1 -1
- package/dist/vfs/seed-project.js +1 -1
- package/dist/vfs/sqlite-vfs.d.ts +21 -30
- package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
- package/dist/vfs/sqlite-vfs.js +113 -29
- package/dist/workspace/nimbus-workspace.d.ts +15 -0
- package/dist/workspace/nimbus-workspace.d.ts.map +1 -1
- package/dist/workspace/nimbus-workspace.js +20 -3
- package/dist/workspace/supervisor-op.d.ts +23 -0
- package/dist/workspace/supervisor-op.d.ts.map +1 -0
- package/dist/workspace/supervisor-op.js +92 -0
- package/package.json +3 -2
- package/src/_shared/byte-stream.ts +5 -4
- package/src/_shared/cache-stats.ts +27 -1
- package/src/_shared/exports-resolver.ts +1 -1
- package/src/_shared/real-node-imports.ts +8 -0
- package/src/_shared/retry.ts +1 -1
- package/src/_shared/tarball.ts +56 -5
- package/src/_shared/vfs-write-ledger.ts +22 -16
- package/src/constants.ts +30 -60
- package/src/runtime/assets-loader.ts +1 -1
- package/src/runtime/comment-strip.ts +42 -23
- package/src/runtime/esbuild-service.ts +566 -217
- package/src/runtime/javascript-ast.ts +40 -9
- package/src/runtime/opentui-wasm-backend.ts +19 -17
- package/src/runtime/port-registry.ts +66 -8
- package/src/runtime/virtual-socket-kernel.generated.ts +1 -1
- package/src/shell/stdin-adapter.ts +58 -0
- package/src/shell/unix-commands.ts +1054 -50
- package/src/substrate/lifo/commands/io/dd.ts +2 -2
- package/src/substrate/lifo/commands/net/curl.ts +310 -58
- package/src/substrate/lifo/commands/system/npm.ts +58 -38
- package/src/substrate/lifo/commands/text/head.ts +2 -1
- package/src/substrate/lifo/commands/text/sed.ts +519 -109
- package/src/substrate/lifo/commands/types.ts +8 -5
- package/src/substrate/lifo/node-compat/zlib.ts +155 -26
- package/src/substrate/lifo/shell/interpreter.ts +74 -27
- package/src/substrate/lifo/shell/pipe.ts +65 -24
- package/src/substrate/lifo/shell/terminal-stdin.ts +66 -30
- package/src/vfs/seed-project.ts +3 -3
- package/src/vfs/sqlite-vfs.ts +128 -64
- package/src/workspace/nimbus-workspace.ts +26 -2
- package/src/workspace/supervisor-op.ts +117 -0
- package/dist/_shared/install-phase.d.ts +0 -47
- package/dist/_shared/install-phase.d.ts.map +0 -1
- package/dist/_shared/install-phase.js +0 -1
- package/dist/_shared/rpc-dispose.d.ts +0 -4
- package/dist/_shared/rpc-dispose.d.ts.map +0 -1
- package/dist/_shared/rpc-dispose.js +0 -30
- package/dist/_shared/w7-frame.d.ts +0 -79
- package/dist/_shared/w7-frame.d.ts.map +0 -1
- package/dist/_shared/w7-frame.js +0 -840
- package/dist/_shared/weighted-credit-pool.d.ts +0 -61
- package/dist/_shared/weighted-credit-pool.d.ts.map +0 -1
- package/dist/_shared/weighted-credit-pool.js +0 -196
- package/dist/observability/diag-counters.d.ts +0 -236
- package/dist/observability/diag-counters.d.ts.map +0 -1
- package/dist/observability/diag-counters.js +0 -195
- package/dist/observability/heap-estimate.d.ts +0 -225
- package/dist/observability/heap-estimate.d.ts.map +0 -1
- package/dist/observability/heap-estimate.js +0 -185
- package/dist/observability/heavy-alloc-coord.d.ts +0 -93
- package/dist/observability/heavy-alloc-coord.d.ts.map +0 -1
- package/dist/observability/heavy-alloc-coord.js +0 -181
- package/dist/observability/oom-classify.d.ts +0 -138
- package/dist/observability/oom-classify.d.ts.map +0 -1
- package/dist/observability/oom-classify.js +0 -268
- package/dist/observability/oom-discriminator.d.ts +0 -176
- package/dist/observability/oom-discriminator.d.ts.map +0 -1
- package/dist/observability/oom-discriminator.js +0 -272
- package/src/_shared/install-phase.ts +0 -56
- package/src/_shared/rpc-dispose.ts +0 -31
- package/src/_shared/w7-frame.ts +0 -1013
- package/src/_shared/weighted-credit-pool.ts +0 -244
- package/src/observability/diag-counters.ts +0 -343
- package/src/observability/heap-estimate.ts +0 -331
- package/src/observability/heavy-alloc-coord.ts +0 -241
- package/src/observability/oom-classify.ts +0 -260
- package/src/observability/oom-discriminator.ts +0 -405
|
@@ -17,11 +17,18 @@
|
|
|
17
17
|
* facet once wasm module passing to dynamic workers is stable.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import type { CredentialedVfs
|
|
21
|
-
import { CRED_KERNEL } from './os-contracts.js';
|
|
20
|
+
import type { CredentialedVfs } from '../vfs/sqlite-vfs.js';
|
|
22
21
|
import { resolvePackageEntry, resolveExports, type ResolvablePackageJson } from '../_shared/exports-resolver.js';
|
|
23
22
|
import { normalizeVfsPath, stripLeadingSlashes } from '../vfs/path.js';
|
|
24
23
|
import { errorText } from '../_shared/error-text.js';
|
|
24
|
+
import { tokenizer, tokTypes } from 'acorn';
|
|
25
|
+
import {
|
|
26
|
+
literalStringValue,
|
|
27
|
+
nodeList,
|
|
28
|
+
nodeName,
|
|
29
|
+
nodeProp,
|
|
30
|
+
parseJavaScriptModule,
|
|
31
|
+
} from './javascript-ast.js';
|
|
25
32
|
|
|
26
33
|
/**
|
|
27
34
|
* Bundler version tag. BUMP THIS whenever bundling semantics change —
|
|
@@ -55,7 +62,7 @@ import { errorText } from '../_shared/error-text.js';
|
|
|
55
62
|
* rows hold post-rewrite text and must be re-bundled. user_module_
|
|
56
63
|
* transforms is likewise re-keyed by mount base.
|
|
57
64
|
*/
|
|
58
|
-
export const BUNDLER_VERSION = '
|
|
65
|
+
export const BUNDLER_VERSION = 'v9';
|
|
59
66
|
|
|
60
67
|
// ── Shared-runtime externals ────────────────────────────────────────────
|
|
61
68
|
|
|
@@ -141,77 +148,6 @@ export function getSharedRuntimeExternals(specifier: string): string[] {
|
|
|
141
148
|
});
|
|
142
149
|
}
|
|
143
150
|
|
|
144
|
-
/**
|
|
145
|
-
* Detect top-level await in source. Used by `EsbuildService.transform`
|
|
146
|
-
* to decide whether to async-IIFE-wrap CJS-target sources (see
|
|
147
|
-
* transform()'s header comment).
|
|
148
|
-
*
|
|
149
|
-
* Token scan over a comment-and-string-stripped source. Tracks paren
|
|
150
|
-
* depth AND function-body depth so a `{` inside a parameter list (a
|
|
151
|
-
* default-value object literal — `async function f(opts = {})`) is NOT
|
|
152
|
-
* mistaken for the function body. The earlier heuristic lacked the paren
|
|
153
|
-
* guard: the `= {}` braces consumed the body-tracking slot, leaving the
|
|
154
|
-
* real body untracked so an internal `await` read as top-level. That
|
|
155
|
-
* wrongly routed such files (e.g. @bluwy/giget-core's
|
|
156
|
-
* download-template.js, reached by create-astro) into the two-pass
|
|
157
|
-
* ESM→require rewrite, whose assembled output still carried export/import
|
|
158
|
-
* statements → "Cannot use import statement outside a module" at startup.
|
|
159
|
-
*
|
|
160
|
-
* Arrow bodies are distinguished from arrow expression bodies (the
|
|
161
|
-
* `arrowPending` flag): only a `{` immediately following `=>` opens a
|
|
162
|
-
* function scope. The idiomatic object-returning arrow `x => ({ ... })`
|
|
163
|
-
* has its `{` at parenDepth 1, so it opens no body slot — otherwise the
|
|
164
|
-
* leaked slot would be consumed by a later top-level `{ ... }` block,
|
|
165
|
-
* making a real top-level `await` inside it read as non-TLA (false
|
|
166
|
-
* negative) and routing genuine ESM-with-TLA into the wrong transform.
|
|
167
|
-
*
|
|
168
|
-
* A regex/token scan (not acorn) is kept here deliberately: pulling
|
|
169
|
-
* acorn into the esbuild-service bundle chunk duplicates its ~15 KiB
|
|
170
|
-
* Unicode identifier tables.
|
|
171
|
-
*/
|
|
172
|
-
export function hasTopLevelAwait(src: string): boolean {
|
|
173
|
-
if (!src || src.indexOf('await') === -1) return false;
|
|
174
|
-
const stripped = stripCommentsAndStrings(src);
|
|
175
|
-
// Tokens: keywords, arrow, and each bracket kind. `parenDepth` rises
|
|
176
|
-
// inside `(...)`; a `{` only opens a function body when a `function`/
|
|
177
|
-
// `=>` is pending AND we're at parenDepth 0 (past the param list).
|
|
178
|
-
const re = /\b(await|function|class)\b|=>|\{|\}|\(|\)/g;
|
|
179
|
-
const fnEntryDepths: number[] = [];
|
|
180
|
-
let depth = 0;
|
|
181
|
-
let parenDepth = 0;
|
|
182
|
-
let pendingFn = 0;
|
|
183
|
-
// An arrow can have either a block body (`=> { ... }`) — which opens a
|
|
184
|
-
// function scope — or an expression body (`=> expr`), which does not.
|
|
185
|
-
// We can only tell which by looking at the token right after `=>`: a
|
|
186
|
-
// block body has `{` as its very next token. `arrowPending` carries
|
|
187
|
-
// that "the next `{` (and only an immediately-following `{`) is this
|
|
188
|
-
// arrow's body" intent, separately from `pendingFn` so the idiomatic
|
|
189
|
-
// `x => ({ ... })` (object-returning expression body, `{` at parenDepth
|
|
190
|
-
// 1) does not leak a body slot onto a later top-level block.
|
|
191
|
-
let arrowPending = false;
|
|
192
|
-
for (let m = re.exec(stripped); m !== null; m = re.exec(stripped)) {
|
|
193
|
-
const tok = m[0];
|
|
194
|
-
if (arrowPending && tok !== '{') arrowPending = false;
|
|
195
|
-
if (tok === '(') { parenDepth++; }
|
|
196
|
-
else if (tok === ')') { if (parenDepth > 0) parenDepth--; }
|
|
197
|
-
else if (tok === '{') {
|
|
198
|
-
depth++;
|
|
199
|
-
if (arrowPending) { fnEntryDepths.push(depth); arrowPending = false; }
|
|
200
|
-
else if (pendingFn > 0 && parenDepth === 0) { fnEntryDepths.push(depth); pendingFn--; }
|
|
201
|
-
} else if (tok === '}') {
|
|
202
|
-
if (fnEntryDepths.length > 0 && fnEntryDepths[fnEntryDepths.length - 1] === depth) fnEntryDepths.pop();
|
|
203
|
-
depth--;
|
|
204
|
-
} else if (tok === 'function' || tok === 'class') {
|
|
205
|
-
pendingFn++;
|
|
206
|
-
} else if (tok === '=>') {
|
|
207
|
-
arrowPending = true;
|
|
208
|
-
} else if (tok === 'await') {
|
|
209
|
-
if (fnEntryDepths.length === 0) return true;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
151
|
/**
|
|
216
152
|
* Cheap heuristic: does the source contain a top-level ESM `import`
|
|
217
153
|
* statement? Used by `EsbuildService.transform` to detect sources that
|
|
@@ -245,10 +181,8 @@ export function hasTopLevelAwait(src: string): boolean {
|
|
|
245
181
|
* EXCLUDED — those are expressions, legal anywhere including IIFE
|
|
246
182
|
* bodies, and need no rewrite.
|
|
247
183
|
*
|
|
248
|
-
* Operates on
|
|
249
|
-
*
|
|
250
|
-
* We reuse the comment/string stripper from `hasTopLevelAwait`'s pass
|
|
251
|
-
* — see `stripCommentsAndStrings` below.
|
|
184
|
+
* Operates on comment-and-string-stripped source so commented-out imports
|
|
185
|
+
* and "import" inside string literals do not trigger.
|
|
252
186
|
*/
|
|
253
187
|
function hasEsmImports(src: string): boolean {
|
|
254
188
|
if (!src || src.indexOf('import') === -1) return false;
|
|
@@ -273,10 +207,8 @@ function hasEsmExports(src: string): boolean {
|
|
|
273
207
|
* source, replacing each with a single space. The result is byte-aligned
|
|
274
208
|
* with the input on a per-line basis (newlines are preserved), so error
|
|
275
209
|
* line numbers from downstream parsers still align with the original.
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* `hasEsmImports`. Pure function — no caching needed for the small
|
|
279
|
-
* inputs we see (typical .mjs entry-point: <2KiB).
|
|
210
|
+
* Shared by the import/export classifiers. Pure function — no caching needed
|
|
211
|
+
* for the small entry points that reach the true-TLA fallback.
|
|
280
212
|
*/
|
|
281
213
|
function stripCommentsAndStrings(src: string): string {
|
|
282
214
|
let stripped = '';
|
|
@@ -288,9 +220,8 @@ function stripCommentsAndStrings(src: string): string {
|
|
|
288
220
|
// start-of-file). Pre-fix the stripper had no regex awareness, so
|
|
289
221
|
// patterns like `var X = /^(?:'…)/` contained an unmatched `'` that
|
|
290
222
|
// bit it as a string opener that didn't close until many lines
|
|
291
|
-
// later — corrupting
|
|
292
|
-
//
|
|
293
|
-
// index.mjs has dozens of these regex literals.
|
|
223
|
+
// later — corrupting the import/export classifiers. Real-world bite:
|
|
224
|
+
// sv-utils@0.0.3 index.mjs has dozens of these regex literals.
|
|
294
225
|
let lastNonWsChar = '';
|
|
295
226
|
const recordOut = (ch: string) => {
|
|
296
227
|
if (ch !== ' ' && ch !== '\t' && ch !== '\n' && ch !== '\r') {
|
|
@@ -508,7 +439,7 @@ function convertEsmImportsToRequire(src: string): { requires: string; body: stri
|
|
|
508
439
|
continue;
|
|
509
440
|
}
|
|
510
441
|
// Side-effect import: `import "m";` / `import 'm';`
|
|
511
|
-
let m = line.match(/^[ \t]*import\s
|
|
442
|
+
let m = line.match(/^[ \t]*import\s*["']([^"']+)["']\s*;?\s*$/);
|
|
512
443
|
if (m) { requires.push(`require(${JSON.stringify(m[1])});`); continue; }
|
|
513
444
|
// Identifier class: JS spec allows `$` and `_` in addition to `\w`
|
|
514
445
|
// (letters/digits/underscore). esbuild's ESM-pass-1 emits `process$1`
|
|
@@ -518,14 +449,14 @@ function convertEsmImportsToRequire(src: string): { requires: string; body: stri
|
|
|
518
449
|
// statement survived into the async-IIFE wrap → SyntaxError
|
|
519
450
|
// "import statement outside module" at facet pre-compile.
|
|
520
451
|
// Default + namespace: `import x, * as ns from "m";`
|
|
521
|
-
m = line.match(/^[ \t]*import\s+([\w$]+)\s*,\s*\*\s
|
|
452
|
+
m = line.match(/^[ \t]*import\s+([\w$]+)\s*,\s*\*\s*as\s+([\w$]+)\s+from\s*["']([^"']+)["']\s*;?\s*$/);
|
|
522
453
|
if (m) {
|
|
523
454
|
const def = m[1], ns = m[2], mod = m[3];
|
|
524
455
|
requires.push(`const ${ns} = require(${JSON.stringify(mod)}); const ${def} = ${ns}.__esModule ? ${ns}.default : ${ns};`);
|
|
525
456
|
continue;
|
|
526
457
|
}
|
|
527
458
|
// Default + named: `import x, { a, b as c } from "m";`
|
|
528
|
-
m = line.match(/^[ \t]*import\s+([\w$]+)\s*,\s*\{([^}]+)\}\s+from\s
|
|
459
|
+
m = line.match(/^[ \t]*import\s+([\w$]+)\s*,\s*\{([^}]+)\}\s+from\s*["']([^"']+)["']\s*;?\s*$/);
|
|
529
460
|
if (m) {
|
|
530
461
|
const def = m[1], bindings = m[2], mod = m[3];
|
|
531
462
|
const tmp = `_nimbus_m_${counter++}`;
|
|
@@ -538,10 +469,10 @@ function convertEsmImportsToRequire(src: string): { requires: string; body: stri
|
|
|
538
469
|
continue;
|
|
539
470
|
}
|
|
540
471
|
// Namespace: `import * as ns from "m";`
|
|
541
|
-
m = line.match(/^[ \t]*import\s
|
|
472
|
+
m = line.match(/^[ \t]*import\s*\*\s*as\s+([\w$]+)\s+from\s*["']([^"']+)["']\s*;?\s*$/);
|
|
542
473
|
if (m) { requires.push(`const ${m[1]} = require(${JSON.stringify(m[2])});`); continue; }
|
|
543
474
|
// Named only: `import { a, b as c } from "m";`
|
|
544
|
-
m = line.match(/^[ \t]*import\s
|
|
475
|
+
m = line.match(/^[ \t]*import\s*\{([^}]+)\}\s*from\s*["']([^"']+)["']\s*;?\s*$/);
|
|
545
476
|
if (m) {
|
|
546
477
|
const bindings = m[1], mod = m[2];
|
|
547
478
|
const named = bindings.split(',').map((b) => {
|
|
@@ -553,7 +484,7 @@ function convertEsmImportsToRequire(src: string): { requires: string; body: stri
|
|
|
553
484
|
continue;
|
|
554
485
|
}
|
|
555
486
|
// Default only: `import x from "m";`
|
|
556
|
-
m = line.match(/^[ \t]*import\s+([\w$]+)\s+from\s
|
|
487
|
+
m = line.match(/^[ \t]*import\s+([\w$]+)\s+from\s*["']([^"']+)["']\s*;?\s*$/);
|
|
557
488
|
if (m) {
|
|
558
489
|
const def = m[1], mod = m[2];
|
|
559
490
|
requires.push(`const ${def} = (() => { const _m = require(${JSON.stringify(mod)}); return _m && _m.__esModule ? _m.default : _m; })();`);
|
|
@@ -742,57 +673,447 @@ function convertEsmImportsToRequire(src: string): { requires: string; body: stri
|
|
|
742
673
|
return { requires: requires.join('\n'), body: out.join('\n') };
|
|
743
674
|
}
|
|
744
675
|
|
|
676
|
+
interface ModuleDeclarationRange {
|
|
677
|
+
start: number;
|
|
678
|
+
end: number;
|
|
679
|
+
kind: 'import' | 'export';
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function topLevelModuleDeclarationRanges(source: string): ModuleDeclarationRange[] | null {
|
|
683
|
+
try {
|
|
684
|
+
const tokens = tokenizer(source, {
|
|
685
|
+
ecmaVersion: 'latest',
|
|
686
|
+
sourceType: 'module',
|
|
687
|
+
allowHashBang: true,
|
|
688
|
+
});
|
|
689
|
+
const ranges: ModuleDeclarationRange[] = [];
|
|
690
|
+
let active: Omit<ModuleDeclarationRange, 'end'> | null = null;
|
|
691
|
+
let braces = 0;
|
|
692
|
+
let parens = 0;
|
|
693
|
+
let brackets = 0;
|
|
694
|
+
|
|
695
|
+
const updateDepth = (type: typeof tokTypes.eof): void => {
|
|
696
|
+
if (type === tokTypes.braceL || type === tokTypes.dollarBraceL) braces++;
|
|
697
|
+
else if (type === tokTypes.braceR) braces = Math.max(0, braces - 1);
|
|
698
|
+
else if (type === tokTypes.parenL) parens++;
|
|
699
|
+
else if (type === tokTypes.parenR) parens = Math.max(0, parens - 1);
|
|
700
|
+
else if (type === tokTypes.bracketL) brackets++;
|
|
701
|
+
else if (type === tokTypes.bracketR) brackets = Math.max(0, brackets - 1);
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
while (true) {
|
|
705
|
+
const token = tokens.getToken();
|
|
706
|
+
const type = token.type;
|
|
707
|
+
if (type === tokTypes.eof) return active ? null : ranges;
|
|
708
|
+
|
|
709
|
+
if (active) {
|
|
710
|
+
updateDepth(type);
|
|
711
|
+
if (type === tokTypes.semi && braces === 0 && parens === 0 && brackets === 0) {
|
|
712
|
+
ranges.push({ ...active, end: token.end });
|
|
713
|
+
active = null;
|
|
714
|
+
}
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const topLevel = braces === 0 && parens === 0 && brackets === 0;
|
|
719
|
+
if (topLevel && type === tokTypes._import) {
|
|
720
|
+
const next = tokens.getToken();
|
|
721
|
+
if (next.type !== tokTypes.parenL && next.type !== tokTypes.dot) {
|
|
722
|
+
active = { start: token.start, kind: 'import' };
|
|
723
|
+
}
|
|
724
|
+
updateDepth(next.type);
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
if (topLevel && type === tokTypes._export) {
|
|
728
|
+
active = { start: token.start, kind: 'export' };
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
updateDepth(type);
|
|
732
|
+
}
|
|
733
|
+
} catch {
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
function hasUnscopedAwait(source: string): boolean {
|
|
740
|
+
try {
|
|
741
|
+
const tokens = tokenizer(source, {
|
|
742
|
+
ecmaVersion: 'latest',
|
|
743
|
+
sourceType: 'module',
|
|
744
|
+
allowHashBang: true,
|
|
745
|
+
});
|
|
746
|
+
const functionBraces: boolean[] = [];
|
|
747
|
+
const functionParenDepths: number[] = [];
|
|
748
|
+
const methodParenCandidates: boolean[] = [];
|
|
749
|
+
const arrowExpressions: Array<{ parens: number; braces: number; brackets: number }> = [];
|
|
750
|
+
let bracketDepth = 0;
|
|
751
|
+
let pendingMethodBody = false;
|
|
752
|
+
let pendingArrowBody = false;
|
|
753
|
+
let pendingFunctionKeyword = false;
|
|
754
|
+
let previous = tokTypes.eof;
|
|
755
|
+
let previousEnd = 0;
|
|
756
|
+
|
|
757
|
+
while (true) {
|
|
758
|
+
const token = tokens.getToken();
|
|
759
|
+
const type = token.type;
|
|
760
|
+
if (type === tokTypes.eof) return false;
|
|
761
|
+
|
|
762
|
+
if (pendingMethodBody && type !== tokTypes.braceL) pendingMethodBody = false;
|
|
763
|
+
if (pendingArrowBody && type !== tokTypes.braceL) {
|
|
764
|
+
arrowExpressions.push({
|
|
765
|
+
parens: methodParenCandidates.length,
|
|
766
|
+
braces: functionBraces.length,
|
|
767
|
+
brackets: bracketDepth,
|
|
768
|
+
});
|
|
769
|
+
pendingArrowBody = false;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (pendingFunctionKeyword) {
|
|
773
|
+
if (
|
|
774
|
+
type === tokTypes.colon || type === tokTypes.comma || type === tokTypes.braceR
|
|
775
|
+
|| type === tokTypes.parenR || type === tokTypes.bracketR || type === tokTypes.eq
|
|
776
|
+
) functionParenDepths.pop();
|
|
777
|
+
pendingFunctionKeyword = false;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
if (source.slice(previousEnd, token.start).includes('\n')) {
|
|
781
|
+
while (arrowExpressions.length > 0) {
|
|
782
|
+
const arrow = arrowExpressions[arrowExpressions.length - 1];
|
|
783
|
+
if (
|
|
784
|
+
methodParenCandidates.length !== arrow.parens
|
|
785
|
+
|| functionBraces.length !== arrow.braces
|
|
786
|
+
|| bracketDepth !== arrow.brackets
|
|
787
|
+
) break;
|
|
788
|
+
arrowExpressions.pop();
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
while (arrowExpressions.length > 0) {
|
|
793
|
+
const arrow = arrowExpressions[arrowExpressions.length - 1];
|
|
794
|
+
const delimited = (type === tokTypes.semi || type === tokTypes.comma)
|
|
795
|
+
&& methodParenCandidates.length === arrow.parens
|
|
796
|
+
&& functionBraces.length === arrow.braces
|
|
797
|
+
&& bracketDepth === arrow.brackets;
|
|
798
|
+
const closed = (type === tokTypes.parenR && methodParenCandidates.length === arrow.parens)
|
|
799
|
+
|| (type === tokTypes.bracketR && bracketDepth === arrow.brackets)
|
|
800
|
+
|| (type === tokTypes.braceR && functionBraces.length === arrow.braces);
|
|
801
|
+
if (!delimited && !closed) break;
|
|
802
|
+
arrowExpressions.pop();
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if (
|
|
806
|
+
type === tokTypes.name
|
|
807
|
+
&& source.slice(token.start, token.end) === 'await'
|
|
808
|
+
&& !functionBraces.includes(true)
|
|
809
|
+
&& arrowExpressions.length === 0
|
|
810
|
+
) return true;
|
|
811
|
+
|
|
812
|
+
if (type === tokTypes._function || type === tokTypes._class) {
|
|
813
|
+
if (previous !== tokTypes.dot && previous !== tokTypes.questionDot) {
|
|
814
|
+
functionParenDepths.push(methodParenCandidates.length);
|
|
815
|
+
pendingFunctionKeyword = true;
|
|
816
|
+
}
|
|
817
|
+
} else if (type === tokTypes.arrow) {
|
|
818
|
+
pendingArrowBody = true;
|
|
819
|
+
} else if (type === tokTypes.parenL) {
|
|
820
|
+
methodParenCandidates.push(
|
|
821
|
+
functionBraces.length > 0
|
|
822
|
+
&& (previous === tokTypes.name || previous === tokTypes.string
|
|
823
|
+
|| previous === tokTypes.num || previous === tokTypes.bracketR),
|
|
824
|
+
);
|
|
825
|
+
} else if (type === tokTypes.parenR) {
|
|
826
|
+
pendingMethodBody = methodParenCandidates.pop() === true;
|
|
827
|
+
} else if (type === tokTypes.bracketL) {
|
|
828
|
+
bracketDepth++;
|
|
829
|
+
} else if (type === tokTypes.bracketR) {
|
|
830
|
+
bracketDepth = Math.max(0, bracketDepth - 1);
|
|
831
|
+
} else if (type === tokTypes.dollarBraceL) {
|
|
832
|
+
functionBraces.push(false);
|
|
833
|
+
} else if (type === tokTypes.braceL) {
|
|
834
|
+
const functionBody = pendingArrowBody
|
|
835
|
+
|| pendingMethodBody
|
|
836
|
+
|| functionParenDepths[functionParenDepths.length - 1] === methodParenCandidates.length;
|
|
837
|
+
if (functionParenDepths[functionParenDepths.length - 1] === methodParenCandidates.length) {
|
|
838
|
+
functionParenDepths.pop();
|
|
839
|
+
}
|
|
840
|
+
functionBraces.push(functionBody);
|
|
841
|
+
pendingArrowBody = false;
|
|
842
|
+
pendingMethodBody = false;
|
|
843
|
+
} else if (type === tokTypes.braceR) {
|
|
844
|
+
functionBraces.pop();
|
|
845
|
+
}
|
|
846
|
+
previousEnd = token.end;
|
|
847
|
+
previous = type;
|
|
848
|
+
}
|
|
849
|
+
} catch {
|
|
850
|
+
return true;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
interface ConvertedModuleDeclarations {
|
|
855
|
+
imports: string;
|
|
856
|
+
exports: string;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function convertBundledModuleDeclarations(snippets: string[]): ConvertedModuleDeclarations | null {
|
|
860
|
+
const imports: string[] = [];
|
|
861
|
+
const exports: string[] = [];
|
|
862
|
+
let importIndex = 0;
|
|
863
|
+
let markedEsm = false;
|
|
864
|
+
|
|
865
|
+
for (const snippet of snippets) {
|
|
866
|
+
const bindingList = snippet.match(/^[ \t]*export\s*\{([\s\S]*)\}\s*;?\s*$/);
|
|
867
|
+
if (bindingList && !/\}\s*from\b/.test(snippet)) {
|
|
868
|
+
if (!markedEsm) {
|
|
869
|
+
exports.push('Object.defineProperty(module.exports, "__esModule", { value: true });');
|
|
870
|
+
markedEsm = true;
|
|
871
|
+
}
|
|
872
|
+
for (const binding of bindingList[1].split(',')) {
|
|
873
|
+
const match = binding.trim().match(/^([\w$]+)(?:\s+as\s+([\w$]+))?$/);
|
|
874
|
+
if (!match) return null;
|
|
875
|
+
const local = match[1];
|
|
876
|
+
const exported = match[2] || local;
|
|
877
|
+
exports.push(
|
|
878
|
+
`Object.defineProperty(module.exports, ${JSON.stringify(exported)}, { enumerable: true, get: () => ${local} });`,
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
let ast: ReturnType<typeof parseJavaScriptModule>;
|
|
884
|
+
try {
|
|
885
|
+
ast = parseJavaScriptModule(snippet);
|
|
886
|
+
} catch {
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
const body = nodeList(ast, 'body');
|
|
890
|
+
if (body.length !== 1) return null;
|
|
891
|
+
const declaration = body[0];
|
|
892
|
+
|
|
893
|
+
if (declaration.type === 'ImportDeclaration') {
|
|
894
|
+
const source = literalStringValue(nodeProp(declaration, 'source'));
|
|
895
|
+
if (!source) return null;
|
|
896
|
+
const specifiers = nodeList(declaration, 'specifiers');
|
|
897
|
+
if (specifiers.length === 0) {
|
|
898
|
+
imports.push(`module.require(${JSON.stringify(source)});`);
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
const moduleName = `__nimbus_import_${importIndex++}`;
|
|
902
|
+
imports.push(`const ${moduleName} = module.require(${JSON.stringify(source)});`);
|
|
903
|
+
for (const specifier of specifiers) {
|
|
904
|
+
const local = nodeName(nodeProp(specifier, 'local'));
|
|
905
|
+
if (!local) return null;
|
|
906
|
+
if (specifier.type === 'ImportDefaultSpecifier') {
|
|
907
|
+
imports.push(`const ${local} = ${moduleName} && ${moduleName}.__esModule ? ${moduleName}.default : ${moduleName};`);
|
|
908
|
+
} else if (specifier.type === 'ImportNamespaceSpecifier') {
|
|
909
|
+
imports.push(`const ${local} = ${moduleName};`);
|
|
910
|
+
} else if (specifier.type === 'ImportSpecifier') {
|
|
911
|
+
const imported = nodeName(nodeProp(specifier, 'imported'));
|
|
912
|
+
if (!imported) return null;
|
|
913
|
+
imports.push(`const ${local} = ${moduleName}[${JSON.stringify(imported)}];`);
|
|
914
|
+
} else {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (declaration.type === 'ExportNamedDeclaration') {
|
|
922
|
+
if (nodeProp(declaration, 'source') || nodeProp(declaration, 'declaration')) return null;
|
|
923
|
+
if (!markedEsm) {
|
|
924
|
+
exports.push('Object.defineProperty(module.exports, "__esModule", { value: true });');
|
|
925
|
+
markedEsm = true;
|
|
926
|
+
}
|
|
927
|
+
for (const specifier of nodeList(declaration, 'specifiers')) {
|
|
928
|
+
const local = nodeName(nodeProp(specifier, 'local'));
|
|
929
|
+
const exported = nodeName(nodeProp(specifier, 'exported'));
|
|
930
|
+
if (!local || !exported) return null;
|
|
931
|
+
exports.push(
|
|
932
|
+
`Object.defineProperty(module.exports, ${JSON.stringify(exported)}, { enumerable: true, get: () => ${local} });`,
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (declaration.type === 'ExportDefaultDeclaration') {
|
|
939
|
+
const value = nodeProp(declaration, 'declaration');
|
|
940
|
+
if (!value || typeof value.start !== 'number' || typeof value.end !== 'number') return null;
|
|
941
|
+
if (value.type === 'FunctionDeclaration' || value.type === 'ClassDeclaration') return null;
|
|
942
|
+
if (!markedEsm) {
|
|
943
|
+
exports.push('Object.defineProperty(module.exports, "__esModule", { value: true });');
|
|
944
|
+
markedEsm = true;
|
|
945
|
+
}
|
|
946
|
+
exports.push(
|
|
947
|
+
`Object.defineProperty(module.exports, "default", { enumerable: true, value: (${snippet.slice(value.start, value.end)}) });`,
|
|
948
|
+
);
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
return { imports: imports.join('\n'), exports: exports.join('\n') };
|
|
956
|
+
}
|
|
957
|
+
interface SourceEdit {
|
|
958
|
+
start: number;
|
|
959
|
+
end: number;
|
|
960
|
+
text: string;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function importMetaEdits(source: string, absoluteUrl: string): SourceEdit[] | null {
|
|
964
|
+
const edits: SourceEdit[] = [];
|
|
965
|
+
try {
|
|
966
|
+
const tokens = tokenizer(source, {
|
|
967
|
+
ecmaVersion: 'latest',
|
|
968
|
+
sourceType: 'module',
|
|
969
|
+
allowHashBang: true,
|
|
970
|
+
});
|
|
971
|
+
while (true) {
|
|
972
|
+
const start = tokens.getToken();
|
|
973
|
+
if (start.type === tokTypes.eof) return edits;
|
|
974
|
+
if (start.type !== tokTypes._import) continue;
|
|
975
|
+
const dot1 = tokens.getToken();
|
|
976
|
+
if (dot1.type !== tokTypes.dot) continue;
|
|
977
|
+
const meta = tokens.getToken();
|
|
978
|
+
if (meta.type !== tokTypes.name || source.slice(meta.start, meta.end) !== 'meta') return null;
|
|
979
|
+
const dot2 = tokens.getToken();
|
|
980
|
+
if (dot2.type !== tokTypes.dot) return null;
|
|
981
|
+
const property = tokens.getToken();
|
|
982
|
+
if (property.type !== tokTypes.name) return null;
|
|
983
|
+
const propertyName = source.slice(property.start, property.end);
|
|
984
|
+
if (propertyName === 'url') {
|
|
985
|
+
edits.push({ start: start.start, end: property.end, text: JSON.stringify(absoluteUrl) });
|
|
986
|
+
} else if (propertyName === 'resolve') {
|
|
987
|
+
edits.push({
|
|
988
|
+
start: start.start,
|
|
989
|
+
end: property.end,
|
|
990
|
+
text: `(specifier => globalThis.__nimbusImportMetaResolve(specifier, ${JSON.stringify(absoluteUrl)}))`,
|
|
991
|
+
});
|
|
992
|
+
} else {
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
} catch {
|
|
997
|
+
return null;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* Converts bundler-emitted ESM without constructing an AST or loading
|
|
1003
|
+
* esbuild-wasm. Returns null for module declarations that are not the compact,
|
|
1004
|
+
* semicolon-terminated shapes emitted by current JS bundlers.
|
|
1005
|
+
*/
|
|
1006
|
+
export function rewriteBundledEsmToCjs(
|
|
1007
|
+
source: string,
|
|
1008
|
+
absoluteUrl: string,
|
|
1009
|
+
): TransformResult | null {
|
|
1010
|
+
if (hasUnscopedAwait(source)) return null;
|
|
1011
|
+
const declarations = topLevelModuleDeclarationRanges(source);
|
|
1012
|
+
if (!declarations || declarations.length === 0) return null;
|
|
1013
|
+
const declarationSnippets = declarations.map(({ start, end }) => source.slice(start, end));
|
|
1014
|
+
for (let i = 0; i < declarations.length; i++) {
|
|
1015
|
+
if (/^[ \t]*export\s+default\b/.test(declarationSnippets[i])
|
|
1016
|
+
&& source.slice(declarations[i].end).trim() !== '') return null;
|
|
1017
|
+
}
|
|
1018
|
+
const converted = convertBundledModuleDeclarations(declarationSnippets);
|
|
1019
|
+
if (!converted) return null;
|
|
1020
|
+
const metaEdits = importMetaEdits(source, absoluteUrl);
|
|
1021
|
+
if (!metaEdits) return null;
|
|
1022
|
+
|
|
1023
|
+
const edits: SourceEdit[] = [
|
|
1024
|
+
...declarations.map(({ start, end }) => ({ start, end, text: '' })),
|
|
1025
|
+
...metaEdits.filter((edit) =>
|
|
1026
|
+
!declarations.some(({ start, end }) => edit.start >= start && edit.end <= end)
|
|
1027
|
+
),
|
|
1028
|
+
].sort((a, b) => a.start - b.start);
|
|
1029
|
+
|
|
1030
|
+
const bodyParts: string[] = [];
|
|
1031
|
+
let cursor = 0;
|
|
1032
|
+
for (const edit of edits) {
|
|
1033
|
+
if (edit.start < cursor) return null;
|
|
1034
|
+
bodyParts.push(source.slice(cursor, edit.start), edit.text);
|
|
1035
|
+
cursor = edit.end;
|
|
1036
|
+
}
|
|
1037
|
+
bodyParts.push(source.slice(cursor));
|
|
1038
|
+
const body = bodyParts.join('');
|
|
1039
|
+
|
|
1040
|
+
return {
|
|
1041
|
+
code: converted.imports + '\n' + body + '\n' + converted.exports,
|
|
1042
|
+
map: '',
|
|
1043
|
+
warnings: [],
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
|
|
745
1047
|
// ── esbuild-wasm imports ────────────────────────────────────────────────
|
|
746
1048
|
//
|
|
747
|
-
//
|
|
748
|
-
//
|
|
749
|
-
//
|
|
750
|
-
//
|
|
751
|
-
//
|
|
752
|
-
// `
|
|
1049
|
+
// `esbuild-wasm` ships no `exports` map. Its `main` is the Node CJS build
|
|
1050
|
+
// `lib/main.js`, and only the LEGACY `browser` field points at a build that
|
|
1051
|
+
// can run here. Every resolver that ignores that legacy field resolves the
|
|
1052
|
+
// bare specifier to `lib/main.js`: Node, Bun, and — the case that matters in
|
|
1053
|
+
// production — the worker environment of a host bundler, because Vite leaves
|
|
1054
|
+
// `browser` out of `resolve.mainFields` for every non-client environment.
|
|
1055
|
+
//
|
|
1056
|
+
// `lib/main.js` cannot serve this file, for two independent reasons:
|
|
1057
|
+
// 1. It runs `createRequire(import.meta.url)('fs')` at module init, which
|
|
1058
|
+
// workerd rejects with `Dynamic require of "fs" is not supported`
|
|
1059
|
+
// (`nodejs_compat` satisfies static `import 'node:fs'`, not runtime
|
|
1060
|
+
// CJS requires).
|
|
1061
|
+
// 2. Its `initialize()` refuses the `wasmModule` option outright:
|
|
1062
|
+
// `The "wasmModule" option only works in the browser`.
|
|
1063
|
+
//
|
|
1064
|
+
// `wasmModule` is the only initialization form available to us. There is no
|
|
1065
|
+
// filesystem for the Node build to read, and `wasmURL` would fetch a third
|
|
1066
|
+
// party mid-request, which the 100% edge contract forbids. So the entrypoint
|
|
1067
|
+
// is named rather than inferred: `esm/browser.js` is the one build whose
|
|
1068
|
+
// `initialize()` accepts a precompiled module. It is also the exact file
|
|
1069
|
+
// `packages/worker/scripts/bundle-esbuild-wasm.mjs` stages for facets, and
|
|
1070
|
+
// that script gates it against `createRequire` / `require('fs')` on every
|
|
1071
|
+
// install — so both consumers of esbuild-wasm load the same vetted build.
|
|
753
1072
|
//
|
|
754
|
-
//
|
|
755
|
-
// `esbuild.Loader` without emitting a runtime require. The actual
|
|
756
|
-
// namespace is loaded lazily by `loadEsbuild()` below, triggered on
|
|
757
|
-
// the first transform/build/initialize call. If no caller ever runs
|
|
758
|
-
// esbuild (e.g. an inner Nimbus that only serves its shell), the
|
|
759
|
-
// module never loads and `__require2('fs')` is never hit.
|
|
1073
|
+
// The load stays lazy: a session that never bundles never evaluates it.
|
|
760
1074
|
//
|
|
761
|
-
// The .wasm import is a compile-time asset binding (
|
|
762
|
-
// it to a WebAssembly.Module)
|
|
763
|
-
//
|
|
764
|
-
import type * as esbuild from 'esbuild-wasm';
|
|
1075
|
+
// The .wasm import is a compile-time asset binding (the host bundler resolves
|
|
1076
|
+
// it to a WebAssembly.Module) and executes no esbuild-wasm JS, so it is safe
|
|
1077
|
+
// at the top level.
|
|
1078
|
+
import type * as esbuild from 'esbuild-wasm/esm/browser.js';
|
|
765
1079
|
import esbuildWasmUrl from 'esbuild-wasm/esbuild.wasm';
|
|
766
1080
|
|
|
767
1081
|
/**
|
|
768
1082
|
* Cached reference to the esbuild namespace. Populated on first
|
|
769
1083
|
* `loadEsbuild()` call; nullable until then so module-load code paths
|
|
770
1084
|
* that never touch bundling can complete without ever evaluating
|
|
771
|
-
*
|
|
1085
|
+
* esbuild-wasm's JS at all.
|
|
772
1086
|
*/
|
|
773
1087
|
let _esbuildMod: typeof esbuild | null = null;
|
|
774
1088
|
let _esbuildLoadPromise: Promise<typeof esbuild> | null = null;
|
|
775
1089
|
|
|
776
1090
|
/**
|
|
777
|
-
*
|
|
778
|
-
*
|
|
779
|
-
*
|
|
780
|
-
*
|
|
781
|
-
*
|
|
1091
|
+
* Load the esbuild-wasm namespace. Safe to call many times; concurrent
|
|
1092
|
+
* callers share a single in-flight Promise, and a rejection clears the
|
|
1093
|
+
* cache so a later call can retry.
|
|
1094
|
+
*
|
|
1095
|
+
* Exported so `tests/unit/esbuild-wasm-entrypoint.mjs` can drive the real
|
|
1096
|
+
* specifier under a Node-style resolver. A test that restated the specifier
|
|
1097
|
+
* would grade its own copy of it, and this defect reached production
|
|
1098
|
+
* precisely because nothing graded the resolution.
|
|
1099
|
+
*
|
|
1100
|
+
* The specifier stays a literal: a computed one would defeat the host
|
|
1101
|
+
* bundler's static analysis and leave the module out of the deployed worker.
|
|
782
1102
|
*/
|
|
783
|
-
async function loadEsbuild(): Promise<typeof esbuild> {
|
|
1103
|
+
export async function loadEsbuild(): Promise<typeof esbuild> {
|
|
784
1104
|
if (_esbuildMod) return _esbuildMod;
|
|
785
1105
|
if (_esbuildLoadPromise) return _esbuildLoadPromise;
|
|
786
1106
|
_esbuildLoadPromise = (async () => {
|
|
787
|
-
|
|
1107
|
+
// Deliberately dynamic: a static import would evaluate esbuild-wasm in
|
|
1108
|
+
// every session, including the ones that only serve a shell and never
|
|
1109
|
+
// bundle. The specifier is still a literal so the host bundler sees it.
|
|
1110
|
+
const mod = await import('esbuild-wasm/esm/browser.js');
|
|
788
1111
|
_esbuildMod = mod as unknown as typeof esbuild;
|
|
789
1112
|
return _esbuildMod;
|
|
790
1113
|
})();
|
|
791
1114
|
try {
|
|
792
1115
|
return await _esbuildLoadPromise;
|
|
793
1116
|
} catch (e) {
|
|
794
|
-
// Reset so a future call can retry (e.g., after the caller has done
|
|
795
|
-
// environment setup we didn't anticipate).
|
|
796
1117
|
_esbuildLoadPromise = null;
|
|
797
1118
|
throw e;
|
|
798
1119
|
}
|
|
@@ -800,6 +1121,19 @@ async function loadEsbuild(): Promise<typeof esbuild> {
|
|
|
800
1121
|
|
|
801
1122
|
// ── Types ───────────────────────────────────────────────────────────────
|
|
802
1123
|
|
|
1124
|
+
export interface EsbuildTransformOptions {
|
|
1125
|
+
loader?: 'ts' | 'tsx' | 'jsx' | 'js' | 'css' | 'json';
|
|
1126
|
+
format?: 'esm' | 'cjs' | 'iife';
|
|
1127
|
+
target?: string;
|
|
1128
|
+
sourcemap?: boolean | 'inline' | 'external';
|
|
1129
|
+
minify?: boolean;
|
|
1130
|
+
jsx?: 'transform' | 'preserve' | 'automatic';
|
|
1131
|
+
jsxFactory?: string;
|
|
1132
|
+
jsxFragment?: string;
|
|
1133
|
+
tsconfigRaw?: string;
|
|
1134
|
+
define?: Record<string, string>;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
803
1137
|
export interface TransformResult {
|
|
804
1138
|
code: string;
|
|
805
1139
|
map: string;
|
|
@@ -817,17 +1151,119 @@ export interface BuildResult {
|
|
|
817
1151
|
warnings: { text: string; location?: esbuild.Location | null }[];
|
|
818
1152
|
}
|
|
819
1153
|
|
|
820
|
-
|
|
1154
|
+
type EsbuildTransformApi = Pick<typeof esbuild, 'transform'>;
|
|
1155
|
+
|
|
1156
|
+
async function transformWithEsbuild(
|
|
1157
|
+
esbuildApi: EsbuildTransformApi,
|
|
1158
|
+
source: string,
|
|
1159
|
+
options?: EsbuildTransformOptions,
|
|
1160
|
+
): Promise<TransformResult> {
|
|
1161
|
+
let code = source;
|
|
1162
|
+
const format = options?.format || 'esm';
|
|
1163
|
+
const loader = options?.loader || 'ts';
|
|
1164
|
+
|
|
1165
|
+
if (format === 'cjs') {
|
|
1166
|
+
try {
|
|
1167
|
+
const direct = await esbuildApi.transform(code, {
|
|
1168
|
+
loader,
|
|
1169
|
+
format,
|
|
1170
|
+
target: options?.target || 'esnext',
|
|
1171
|
+
sourcemap: options?.sourcemap ?? false,
|
|
1172
|
+
minify: options?.minify ?? false,
|
|
1173
|
+
jsx: options?.jsx,
|
|
1174
|
+
jsxFactory: options?.jsxFactory,
|
|
1175
|
+
jsxFragment: options?.jsxFragment,
|
|
1176
|
+
tsconfigRaw: options?.tsconfigRaw,
|
|
1177
|
+
define: options?.define,
|
|
1178
|
+
supported: { 'dynamic-import': false },
|
|
1179
|
+
});
|
|
1180
|
+
return {
|
|
1181
|
+
code: direct.code,
|
|
1182
|
+
map: direct.map || '',
|
|
1183
|
+
warnings: direct.warnings?.map((warning) => ({
|
|
1184
|
+
text: warning.text,
|
|
1185
|
+
location: warning.location,
|
|
1186
|
+
})) || [],
|
|
1187
|
+
};
|
|
1188
|
+
} catch (error) {
|
|
1189
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1190
|
+
if (!/top-level await.*not supported.*cjs/i.test(message)) throw error;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
if (hasEsmImports(code) || hasEsmExports(code)) {
|
|
1194
|
+
const pass1 = await esbuildApi.transform(code, {
|
|
1195
|
+
loader,
|
|
1196
|
+
format: 'esm',
|
|
1197
|
+
target: options?.target || 'esnext',
|
|
1198
|
+
sourcemap: false,
|
|
1199
|
+
minify: false,
|
|
1200
|
+
jsx: options?.jsx,
|
|
1201
|
+
jsxFactory: options?.jsxFactory,
|
|
1202
|
+
jsxFragment: options?.jsxFragment,
|
|
1203
|
+
tsconfigRaw: options?.tsconfigRaw,
|
|
1204
|
+
define: options?.define,
|
|
1205
|
+
supported: { 'dynamic-import': false },
|
|
1206
|
+
});
|
|
1207
|
+
const { requires, body } = convertEsmImportsToRequire(pass1.code);
|
|
1208
|
+
return {
|
|
1209
|
+
code: requires + '\nreturn (async () => {\n' + body + '\n})();\n',
|
|
1210
|
+
map: '',
|
|
1211
|
+
warnings: pass1.warnings?.map((warning) => ({
|
|
1212
|
+
text: warning.text,
|
|
1213
|
+
location: warning.location,
|
|
1214
|
+
})) || [],
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
code = 'return (async () => {\n' + code + '\n})();\n';
|
|
1219
|
+
}
|
|
821
1220
|
|
|
1221
|
+
const result = await esbuildApi.transform(code, {
|
|
1222
|
+
loader,
|
|
1223
|
+
format,
|
|
1224
|
+
target: options?.target || 'esnext',
|
|
1225
|
+
sourcemap: options?.sourcemap ?? false,
|
|
1226
|
+
minify: options?.minify ?? false,
|
|
1227
|
+
jsx: options?.jsx,
|
|
1228
|
+
jsxFactory: options?.jsxFactory,
|
|
1229
|
+
jsxFragment: options?.jsxFragment,
|
|
1230
|
+
tsconfigRaw: options?.tsconfigRaw,
|
|
1231
|
+
define: options?.define,
|
|
1232
|
+
supported: { 'dynamic-import': false },
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
return {
|
|
1236
|
+
code: result.code,
|
|
1237
|
+
map: result.map || '',
|
|
1238
|
+
warnings: result.warnings?.map((warning) => ({
|
|
1239
|
+
text: warning.text,
|
|
1240
|
+
location: warning.location,
|
|
1241
|
+
})) || [],
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/** Source needed by the slim Worker Loader transform isolate. */
|
|
1246
|
+
export function generateEsbuildTransformRuntimeSource(): string {
|
|
1247
|
+
return [
|
|
1248
|
+
stripCommentsAndStrings.toString(),
|
|
1249
|
+
hasEsmImports.toString(),
|
|
1250
|
+
hasEsmExports.toString(),
|
|
1251
|
+
convertEsmImportsToRequire.toString(),
|
|
1252
|
+
transformWithEsbuild.toString(),
|
|
1253
|
+
].join('\n');
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// ── EsbuildService ──────────────────────────────────────────────────────
|
|
822
1257
|
export class EsbuildService {
|
|
823
|
-
private vfs: CredentialedVfs;
|
|
1258
|
+
private vfs: CredentialedVfs | null;
|
|
824
1259
|
private initialized = false;
|
|
825
1260
|
private initPromise: Promise<void> | null = null;
|
|
826
1261
|
/** Resolved esbuild namespace — populated by ensureInit() after loadEsbuild(). */
|
|
827
1262
|
private _esbuild: typeof esbuild | null = null;
|
|
828
1263
|
|
|
829
|
-
|
|
830
|
-
|
|
1264
|
+
/** Build reads use only the caller-supplied view; omit it for transform-only use. */
|
|
1265
|
+
constructor(vfs?: CredentialedVfs) {
|
|
1266
|
+
this.vfs = vfs ?? null;
|
|
831
1267
|
}
|
|
832
1268
|
|
|
833
1269
|
/**
|
|
@@ -973,102 +1409,10 @@ export class EsbuildService {
|
|
|
973
1409
|
*/
|
|
974
1410
|
async transform(
|
|
975
1411
|
code: string,
|
|
976
|
-
options?:
|
|
977
|
-
loader?: 'ts' | 'tsx' | 'jsx' | 'js' | 'css' | 'json';
|
|
978
|
-
format?: 'esm' | 'cjs' | 'iife';
|
|
979
|
-
target?: string;
|
|
980
|
-
sourcemap?: boolean | 'inline' | 'external';
|
|
981
|
-
minify?: boolean;
|
|
982
|
-
jsx?: 'transform' | 'preserve' | 'automatic';
|
|
983
|
-
jsxFactory?: string;
|
|
984
|
-
jsxFragment?: string;
|
|
985
|
-
tsconfigRaw?: string;
|
|
986
|
-
define?: Record<string, string>;
|
|
987
|
-
},
|
|
1412
|
+
options?: EsbuildTransformOptions,
|
|
988
1413
|
): Promise<TransformResult> {
|
|
989
1414
|
await this.ensureInit();
|
|
990
|
-
|
|
991
|
-
const format = options?.format || 'esm';
|
|
992
|
-
const loader = options?.loader || 'ts';
|
|
993
|
-
|
|
994
|
-
// ── CJS + TLA + ESM module syntax: two-pass with module rewrite ──
|
|
995
|
-
// This precedes the simple TLA-only IIFE wrap. We test ESM module
|
|
996
|
-
// syntax first because the IIFE wrap is incompatible with import
|
|
997
|
-
// and export statements; if both conditions hold we MUST go through
|
|
998
|
-
// the rewrite path.
|
|
999
|
-
if (format === 'cjs' && hasTopLevelAwait(code) && (hasEsmImports(code) || hasEsmExports(code))) {
|
|
1000
|
-
// Pass 1: emit ESM so esbuild accepts TLA + imports without complaint.
|
|
1001
|
-
// We use the same loader/target so TS/JSX is handled here too.
|
|
1002
|
-
const pass1 = await this._esbuild!.transform(code, {
|
|
1003
|
-
loader,
|
|
1004
|
-
format: 'esm',
|
|
1005
|
-
target: options?.target || 'esnext',
|
|
1006
|
-
sourcemap: false, // pass-1 source map is discarded; pass-2 doesn't run esbuild
|
|
1007
|
-
minify: false, // minify only on the final output if requested
|
|
1008
|
-
jsx: options?.jsx,
|
|
1009
|
-
jsxFactory: options?.jsxFactory,
|
|
1010
|
-
jsxFragment: options?.jsxFragment,
|
|
1011
|
-
tsconfigRaw: options?.tsconfigRaw,
|
|
1012
|
-
define: options?.define,
|
|
1013
|
-
// Dynamic-import lowering: rewrites `import(x)` to
|
|
1014
|
-
// `Promise.resolve().then(() => __toESM(require(x)))` so the
|
|
1015
|
-
// call routes through Nimbus's scopedRequire → VFS lookup
|
|
1016
|
-
// instead of workerd's worker-module-map resolver (which only
|
|
1017
|
-
// knows {'runner.js': workerCode}). Without this, every user
|
|
1018
|
-
// dynamic import() rejects with "No such module ..." — and if
|
|
1019
|
-
// user code has no .catch() (e.g. create-astro.mjs's
|
|
1020
|
-
// `import('./dist/index.js').then(({main}) => main())`), the
|
|
1021
|
-
// rejection is unhandled and the facet exits silently
|
|
1022
|
-
supported: { 'dynamic-import': false },
|
|
1023
|
-
});
|
|
1024
|
-
const { requires, body } = convertEsmImportsToRequire(pass1.code);
|
|
1025
|
-
const assembled =
|
|
1026
|
-
requires + '\n' +
|
|
1027
|
-
'return (async () => {\n' +
|
|
1028
|
-
body +
|
|
1029
|
-
'\n})();\n';
|
|
1030
|
-
return {
|
|
1031
|
-
code: assembled,
|
|
1032
|
-
map: '',
|
|
1033
|
-
warnings: pass1.warnings?.map((w) => ({
|
|
1034
|
-
text: w.text,
|
|
1035
|
-
location: w.location,
|
|
1036
|
-
})) || [],
|
|
1037
|
-
};
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
// ── CJS + TLA (no ESM imports): single-pass IIFE wrap (P2 fix) ──
|
|
1041
|
-
let sourceToTransform = code;
|
|
1042
|
-
if (format === 'cjs' && hasTopLevelAwait(code)) {
|
|
1043
|
-
sourceToTransform =
|
|
1044
|
-
'return (async () => {\n' +
|
|
1045
|
-
code +
|
|
1046
|
-
'\n})();\n';
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
const result = await this._esbuild!.transform(sourceToTransform, {
|
|
1050
|
-
loader,
|
|
1051
|
-
format,
|
|
1052
|
-
target: options?.target || 'esnext',
|
|
1053
|
-
sourcemap: options?.sourcemap ?? false,
|
|
1054
|
-
minify: options?.minify ?? false,
|
|
1055
|
-
jsx: options?.jsx,
|
|
1056
|
-
jsxFactory: options?.jsxFactory,
|
|
1057
|
-
jsxFragment: options?.jsxFragment,
|
|
1058
|
-
tsconfigRaw: options?.tsconfigRaw,
|
|
1059
|
-
define: options?.define,
|
|
1060
|
-
// Dynamic-import lowering — see two-pass branch above for rationale.
|
|
1061
|
-
supported: { 'dynamic-import': false },
|
|
1062
|
-
});
|
|
1063
|
-
|
|
1064
|
-
return {
|
|
1065
|
-
code: result.code,
|
|
1066
|
-
map: result.map || '',
|
|
1067
|
-
warnings: result.warnings?.map(w => ({
|
|
1068
|
-
text: w.text,
|
|
1069
|
-
location: w.location,
|
|
1070
|
-
})) || [],
|
|
1071
|
-
};
|
|
1415
|
+
return transformWithEsbuild(this._esbuild!, code, options);
|
|
1072
1416
|
}
|
|
1073
1417
|
|
|
1074
1418
|
/**
|
|
@@ -1134,13 +1478,18 @@ export class EsbuildService {
|
|
|
1134
1478
|
};
|
|
1135
1479
|
}
|
|
1136
1480
|
|
|
1481
|
+
private requireVfs(): CredentialedVfs {
|
|
1482
|
+
if (!this.vfs) throw new Error('EsbuildService build requires a VFS');
|
|
1483
|
+
return this.vfs;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1137
1486
|
/**
|
|
1138
1487
|
* VFS resolver plugin for esbuild.
|
|
1139
|
-
* Reads
|
|
1488
|
+
* Reads through the caller's credentialed view (synchronous, no snapshot needed).
|
|
1140
1489
|
* Handles: absolute paths, relative paths, bare specifiers (node_modules).
|
|
1141
1490
|
*/
|
|
1142
1491
|
private makeVfsPlugin(): esbuild.Plugin {
|
|
1143
|
-
const vfs = this.
|
|
1492
|
+
const vfs = this.requireVfs();
|
|
1144
1493
|
const EXTS = ['', '.ts', '.tsx', '.js', '.jsx', '.mts', '.mjs', '.cjs', '.json', '.css'];
|
|
1145
1494
|
const INDEX_FILES = ['index.ts', 'index.tsx', 'index.js', 'index.jsx', 'index.mjs'];
|
|
1146
1495
|
|