@nimbus-sh/core 0.6.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/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/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/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/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/sqlite-vfs.d.ts +19 -0
- package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
- package/dist/vfs/sqlite-vfs.js +106 -23
- 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 +2 -2
- package/src/_shared/byte-stream.ts +5 -4
- package/src/_shared/real-node-imports.ts +8 -0
- package/src/_shared/tarball.ts +56 -5
- package/src/_shared/vfs-write-ledger.ts +22 -16
- 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/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/sqlite-vfs.ts +104 -23
- package/src/workspace/nimbus-workspace.ts +26 -2
- package/src/workspace/supervisor-op.ts +117 -0
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
* this is acceptable for Phase 3. Phase 4+ can move it to a dedicated
|
|
17
17
|
* facet once wasm module passing to dynamic workers is stable.
|
|
18
18
|
*/
|
|
19
|
-
import { CRED_KERNEL } from './os-contracts.js';
|
|
20
19
|
import { resolvePackageEntry, resolveExports } from '../_shared/exports-resolver.js';
|
|
21
20
|
import { normalizeVfsPath, stripLeadingSlashes } from '../vfs/path.js';
|
|
22
21
|
import { errorText } from '../_shared/error-text.js';
|
|
22
|
+
import { tokenizer, tokTypes } from 'acorn';
|
|
23
|
+
import { literalStringValue, nodeList, nodeName, nodeProp, parseJavaScriptModule, } from './javascript-ast.js';
|
|
23
24
|
/**
|
|
24
25
|
* Bundler version tag. BUMP THIS whenever bundling semantics change —
|
|
25
26
|
* the esbuild plugin's resolver logic, the shared-externals rules, the
|
|
@@ -52,7 +53,7 @@ import { errorText } from '../_shared/error-text.js';
|
|
|
52
53
|
* rows hold post-rewrite text and must be re-bundled. user_module_
|
|
53
54
|
* transforms is likewise re-keyed by mount base.
|
|
54
55
|
*/
|
|
55
|
-
export const BUNDLER_VERSION = '
|
|
56
|
+
export const BUNDLER_VERSION = 'v9';
|
|
56
57
|
// ── Shared-runtime externals ────────────────────────────────────────────
|
|
57
58
|
/**
|
|
58
59
|
* Returns the list of specifiers that must be marked `external` when bundling
|
|
@@ -139,95 +140,6 @@ export function getSharedRuntimeExternals(specifier) {
|
|
|
139
140
|
return true;
|
|
140
141
|
});
|
|
141
142
|
}
|
|
142
|
-
/**
|
|
143
|
-
* Detect top-level await in source. Used by `EsbuildService.transform`
|
|
144
|
-
* to decide whether to async-IIFE-wrap CJS-target sources (see
|
|
145
|
-
* transform()'s header comment).
|
|
146
|
-
*
|
|
147
|
-
* Token scan over a comment-and-string-stripped source. Tracks paren
|
|
148
|
-
* depth AND function-body depth so a `{` inside a parameter list (a
|
|
149
|
-
* default-value object literal — `async function f(opts = {})`) is NOT
|
|
150
|
-
* mistaken for the function body. The earlier heuristic lacked the paren
|
|
151
|
-
* guard: the `= {}` braces consumed the body-tracking slot, leaving the
|
|
152
|
-
* real body untracked so an internal `await` read as top-level. That
|
|
153
|
-
* wrongly routed such files (e.g. @bluwy/giget-core's
|
|
154
|
-
* download-template.js, reached by create-astro) into the two-pass
|
|
155
|
-
* ESM→require rewrite, whose assembled output still carried export/import
|
|
156
|
-
* statements → "Cannot use import statement outside a module" at startup.
|
|
157
|
-
*
|
|
158
|
-
* Arrow bodies are distinguished from arrow expression bodies (the
|
|
159
|
-
* `arrowPending` flag): only a `{` immediately following `=>` opens a
|
|
160
|
-
* function scope. The idiomatic object-returning arrow `x => ({ ... })`
|
|
161
|
-
* has its `{` at parenDepth 1, so it opens no body slot — otherwise the
|
|
162
|
-
* leaked slot would be consumed by a later top-level `{ ... }` block,
|
|
163
|
-
* making a real top-level `await` inside it read as non-TLA (false
|
|
164
|
-
* negative) and routing genuine ESM-with-TLA into the wrong transform.
|
|
165
|
-
*
|
|
166
|
-
* A regex/token scan (not acorn) is kept here deliberately: pulling
|
|
167
|
-
* acorn into the esbuild-service bundle chunk duplicates its ~15 KiB
|
|
168
|
-
* Unicode identifier tables.
|
|
169
|
-
*/
|
|
170
|
-
export function hasTopLevelAwait(src) {
|
|
171
|
-
if (!src || src.indexOf('await') === -1)
|
|
172
|
-
return false;
|
|
173
|
-
const stripped = stripCommentsAndStrings(src);
|
|
174
|
-
// Tokens: keywords, arrow, and each bracket kind. `parenDepth` rises
|
|
175
|
-
// inside `(...)`; a `{` only opens a function body when a `function`/
|
|
176
|
-
// `=>` is pending AND we're at parenDepth 0 (past the param list).
|
|
177
|
-
const re = /\b(await|function|class)\b|=>|\{|\}|\(|\)/g;
|
|
178
|
-
const fnEntryDepths = [];
|
|
179
|
-
let depth = 0;
|
|
180
|
-
let parenDepth = 0;
|
|
181
|
-
let pendingFn = 0;
|
|
182
|
-
// An arrow can have either a block body (`=> { ... }`) — which opens a
|
|
183
|
-
// function scope — or an expression body (`=> expr`), which does not.
|
|
184
|
-
// We can only tell which by looking at the token right after `=>`: a
|
|
185
|
-
// block body has `{` as its very next token. `arrowPending` carries
|
|
186
|
-
// that "the next `{` (and only an immediately-following `{`) is this
|
|
187
|
-
// arrow's body" intent, separately from `pendingFn` so the idiomatic
|
|
188
|
-
// `x => ({ ... })` (object-returning expression body, `{` at parenDepth
|
|
189
|
-
// 1) does not leak a body slot onto a later top-level block.
|
|
190
|
-
let arrowPending = false;
|
|
191
|
-
for (let m = re.exec(stripped); m !== null; m = re.exec(stripped)) {
|
|
192
|
-
const tok = m[0];
|
|
193
|
-
if (arrowPending && tok !== '{')
|
|
194
|
-
arrowPending = false;
|
|
195
|
-
if (tok === '(') {
|
|
196
|
-
parenDepth++;
|
|
197
|
-
}
|
|
198
|
-
else if (tok === ')') {
|
|
199
|
-
if (parenDepth > 0)
|
|
200
|
-
parenDepth--;
|
|
201
|
-
}
|
|
202
|
-
else if (tok === '{') {
|
|
203
|
-
depth++;
|
|
204
|
-
if (arrowPending) {
|
|
205
|
-
fnEntryDepths.push(depth);
|
|
206
|
-
arrowPending = false;
|
|
207
|
-
}
|
|
208
|
-
else if (pendingFn > 0 && parenDepth === 0) {
|
|
209
|
-
fnEntryDepths.push(depth);
|
|
210
|
-
pendingFn--;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
else if (tok === '}') {
|
|
214
|
-
if (fnEntryDepths.length > 0 && fnEntryDepths[fnEntryDepths.length - 1] === depth)
|
|
215
|
-
fnEntryDepths.pop();
|
|
216
|
-
depth--;
|
|
217
|
-
}
|
|
218
|
-
else if (tok === 'function' || tok === 'class') {
|
|
219
|
-
pendingFn++;
|
|
220
|
-
}
|
|
221
|
-
else if (tok === '=>') {
|
|
222
|
-
arrowPending = true;
|
|
223
|
-
}
|
|
224
|
-
else if (tok === 'await') {
|
|
225
|
-
if (fnEntryDepths.length === 0)
|
|
226
|
-
return true;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
return false;
|
|
230
|
-
}
|
|
231
143
|
/**
|
|
232
144
|
* Cheap heuristic: does the source contain a top-level ESM `import`
|
|
233
145
|
* statement? Used by `EsbuildService.transform` to detect sources that
|
|
@@ -261,10 +173,8 @@ export function hasTopLevelAwait(src) {
|
|
|
261
173
|
* EXCLUDED — those are expressions, legal anywhere including IIFE
|
|
262
174
|
* bodies, and need no rewrite.
|
|
263
175
|
*
|
|
264
|
-
* Operates on
|
|
265
|
-
*
|
|
266
|
-
* We reuse the comment/string stripper from `hasTopLevelAwait`'s pass
|
|
267
|
-
* — see `stripCommentsAndStrings` below.
|
|
176
|
+
* Operates on comment-and-string-stripped source so commented-out imports
|
|
177
|
+
* and "import" inside string literals do not trigger.
|
|
268
178
|
*/
|
|
269
179
|
function hasEsmImports(src) {
|
|
270
180
|
if (!src || src.indexOf('import') === -1)
|
|
@@ -289,10 +199,8 @@ function hasEsmExports(src) {
|
|
|
289
199
|
* source, replacing each with a single space. The result is byte-aligned
|
|
290
200
|
* with the input on a per-line basis (newlines are preserved), so error
|
|
291
201
|
* line numbers from downstream parsers still align with the original.
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
* `hasEsmImports`. Pure function — no caching needed for the small
|
|
295
|
-
* inputs we see (typical .mjs entry-point: <2KiB).
|
|
202
|
+
* Shared by the import/export classifiers. Pure function — no caching needed
|
|
203
|
+
* for the small entry points that reach the true-TLA fallback.
|
|
296
204
|
*/
|
|
297
205
|
function stripCommentsAndStrings(src) {
|
|
298
206
|
let stripped = '';
|
|
@@ -304,9 +212,8 @@ function stripCommentsAndStrings(src) {
|
|
|
304
212
|
// start-of-file). Pre-fix the stripper had no regex awareness, so
|
|
305
213
|
// patterns like `var X = /^(?:'…)/` contained an unmatched `'` that
|
|
306
214
|
// bit it as a string opener that didn't close until many lines
|
|
307
|
-
// later — corrupting
|
|
308
|
-
//
|
|
309
|
-
// index.mjs has dozens of these regex literals.
|
|
215
|
+
// later — corrupting the import/export classifiers. Real-world bite:
|
|
216
|
+
// sv-utils@0.0.3 index.mjs has dozens of these regex literals.
|
|
310
217
|
let lastNonWsChar = '';
|
|
311
218
|
const recordOut = (ch) => {
|
|
312
219
|
if (ch !== ' ' && ch !== '\t' && ch !== '\n' && ch !== '\r') {
|
|
@@ -559,7 +466,7 @@ function convertEsmImportsToRequire(src) {
|
|
|
559
466
|
continue;
|
|
560
467
|
}
|
|
561
468
|
// Side-effect import: `import "m";` / `import 'm';`
|
|
562
|
-
let m = line.match(/^[ \t]*import\s
|
|
469
|
+
let m = line.match(/^[ \t]*import\s*["']([^"']+)["']\s*;?\s*$/);
|
|
563
470
|
if (m) {
|
|
564
471
|
requires.push(`require(${JSON.stringify(m[1])});`);
|
|
565
472
|
continue;
|
|
@@ -572,14 +479,14 @@ function convertEsmImportsToRequire(src) {
|
|
|
572
479
|
// statement survived into the async-IIFE wrap → SyntaxError
|
|
573
480
|
// "import statement outside module" at facet pre-compile.
|
|
574
481
|
// Default + namespace: `import x, * as ns from "m";`
|
|
575
|
-
m = line.match(/^[ \t]*import\s+([\w$]+)\s*,\s*\*\s
|
|
482
|
+
m = line.match(/^[ \t]*import\s+([\w$]+)\s*,\s*\*\s*as\s+([\w$]+)\s+from\s*["']([^"']+)["']\s*;?\s*$/);
|
|
576
483
|
if (m) {
|
|
577
484
|
const def = m[1], ns = m[2], mod = m[3];
|
|
578
485
|
requires.push(`const ${ns} = require(${JSON.stringify(mod)}); const ${def} = ${ns}.__esModule ? ${ns}.default : ${ns};`);
|
|
579
486
|
continue;
|
|
580
487
|
}
|
|
581
488
|
// Default + named: `import x, { a, b as c } from "m";`
|
|
582
|
-
m = line.match(/^[ \t]*import\s+([\w$]+)\s*,\s*\{([^}]+)\}\s+from\s
|
|
489
|
+
m = line.match(/^[ \t]*import\s+([\w$]+)\s*,\s*\{([^}]+)\}\s+from\s*["']([^"']+)["']\s*;?\s*$/);
|
|
583
490
|
if (m) {
|
|
584
491
|
const def = m[1], bindings = m[2], mod = m[3];
|
|
585
492
|
const tmp = `_nimbus_m_${counter++}`;
|
|
@@ -593,13 +500,13 @@ function convertEsmImportsToRequire(src) {
|
|
|
593
500
|
continue;
|
|
594
501
|
}
|
|
595
502
|
// Namespace: `import * as ns from "m";`
|
|
596
|
-
m = line.match(/^[ \t]*import\s
|
|
503
|
+
m = line.match(/^[ \t]*import\s*\*\s*as\s+([\w$]+)\s+from\s*["']([^"']+)["']\s*;?\s*$/);
|
|
597
504
|
if (m) {
|
|
598
505
|
requires.push(`const ${m[1]} = require(${JSON.stringify(m[2])});`);
|
|
599
506
|
continue;
|
|
600
507
|
}
|
|
601
508
|
// Named only: `import { a, b as c } from "m";`
|
|
602
|
-
m = line.match(/^[ \t]*import\s
|
|
509
|
+
m = line.match(/^[ \t]*import\s*\{([^}]+)\}\s*from\s*["']([^"']+)["']\s*;?\s*$/);
|
|
603
510
|
if (m) {
|
|
604
511
|
const bindings = m[1], mod = m[2];
|
|
605
512
|
const named = bindings.split(',').map((b) => {
|
|
@@ -612,7 +519,7 @@ function convertEsmImportsToRequire(src) {
|
|
|
612
519
|
continue;
|
|
613
520
|
}
|
|
614
521
|
// Default only: `import x from "m";`
|
|
615
|
-
m = line.match(/^[ \t]*import\s+([\w$]+)\s+from\s
|
|
522
|
+
m = line.match(/^[ \t]*import\s+([\w$]+)\s+from\s*["']([^"']+)["']\s*;?\s*$/);
|
|
616
523
|
if (m) {
|
|
617
524
|
const def = m[1], mod = m[2];
|
|
618
525
|
requires.push(`const ${def} = (() => { const _m = require(${JSON.stringify(mod)}); return _m && _m.__esModule ? _m.default : _m; })();`);
|
|
@@ -801,29 +708,399 @@ function convertEsmImportsToRequire(src) {
|
|
|
801
708
|
}
|
|
802
709
|
return { requires: requires.join('\n'), body: out.join('\n') };
|
|
803
710
|
}
|
|
711
|
+
function topLevelModuleDeclarationRanges(source) {
|
|
712
|
+
try {
|
|
713
|
+
const tokens = tokenizer(source, {
|
|
714
|
+
ecmaVersion: 'latest',
|
|
715
|
+
sourceType: 'module',
|
|
716
|
+
allowHashBang: true,
|
|
717
|
+
});
|
|
718
|
+
const ranges = [];
|
|
719
|
+
let active = null;
|
|
720
|
+
let braces = 0;
|
|
721
|
+
let parens = 0;
|
|
722
|
+
let brackets = 0;
|
|
723
|
+
const updateDepth = (type) => {
|
|
724
|
+
if (type === tokTypes.braceL || type === tokTypes.dollarBraceL)
|
|
725
|
+
braces++;
|
|
726
|
+
else if (type === tokTypes.braceR)
|
|
727
|
+
braces = Math.max(0, braces - 1);
|
|
728
|
+
else if (type === tokTypes.parenL)
|
|
729
|
+
parens++;
|
|
730
|
+
else if (type === tokTypes.parenR)
|
|
731
|
+
parens = Math.max(0, parens - 1);
|
|
732
|
+
else if (type === tokTypes.bracketL)
|
|
733
|
+
brackets++;
|
|
734
|
+
else if (type === tokTypes.bracketR)
|
|
735
|
+
brackets = Math.max(0, brackets - 1);
|
|
736
|
+
};
|
|
737
|
+
while (true) {
|
|
738
|
+
const token = tokens.getToken();
|
|
739
|
+
const type = token.type;
|
|
740
|
+
if (type === tokTypes.eof)
|
|
741
|
+
return active ? null : ranges;
|
|
742
|
+
if (active) {
|
|
743
|
+
updateDepth(type);
|
|
744
|
+
if (type === tokTypes.semi && braces === 0 && parens === 0 && brackets === 0) {
|
|
745
|
+
ranges.push({ ...active, end: token.end });
|
|
746
|
+
active = null;
|
|
747
|
+
}
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
const topLevel = braces === 0 && parens === 0 && brackets === 0;
|
|
751
|
+
if (topLevel && type === tokTypes._import) {
|
|
752
|
+
const next = tokens.getToken();
|
|
753
|
+
if (next.type !== tokTypes.parenL && next.type !== tokTypes.dot) {
|
|
754
|
+
active = { start: token.start, kind: 'import' };
|
|
755
|
+
}
|
|
756
|
+
updateDepth(next.type);
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (topLevel && type === tokTypes._export) {
|
|
760
|
+
active = { start: token.start, kind: 'export' };
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
updateDepth(type);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
catch {
|
|
767
|
+
return null;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
function hasUnscopedAwait(source) {
|
|
771
|
+
try {
|
|
772
|
+
const tokens = tokenizer(source, {
|
|
773
|
+
ecmaVersion: 'latest',
|
|
774
|
+
sourceType: 'module',
|
|
775
|
+
allowHashBang: true,
|
|
776
|
+
});
|
|
777
|
+
const functionBraces = [];
|
|
778
|
+
const functionParenDepths = [];
|
|
779
|
+
const methodParenCandidates = [];
|
|
780
|
+
const arrowExpressions = [];
|
|
781
|
+
let bracketDepth = 0;
|
|
782
|
+
let pendingMethodBody = false;
|
|
783
|
+
let pendingArrowBody = false;
|
|
784
|
+
let pendingFunctionKeyword = false;
|
|
785
|
+
let previous = tokTypes.eof;
|
|
786
|
+
let previousEnd = 0;
|
|
787
|
+
while (true) {
|
|
788
|
+
const token = tokens.getToken();
|
|
789
|
+
const type = token.type;
|
|
790
|
+
if (type === tokTypes.eof)
|
|
791
|
+
return false;
|
|
792
|
+
if (pendingMethodBody && type !== tokTypes.braceL)
|
|
793
|
+
pendingMethodBody = false;
|
|
794
|
+
if (pendingArrowBody && type !== tokTypes.braceL) {
|
|
795
|
+
arrowExpressions.push({
|
|
796
|
+
parens: methodParenCandidates.length,
|
|
797
|
+
braces: functionBraces.length,
|
|
798
|
+
brackets: bracketDepth,
|
|
799
|
+
});
|
|
800
|
+
pendingArrowBody = false;
|
|
801
|
+
}
|
|
802
|
+
if (pendingFunctionKeyword) {
|
|
803
|
+
if (type === tokTypes.colon || type === tokTypes.comma || type === tokTypes.braceR
|
|
804
|
+
|| type === tokTypes.parenR || type === tokTypes.bracketR || type === tokTypes.eq)
|
|
805
|
+
functionParenDepths.pop();
|
|
806
|
+
pendingFunctionKeyword = false;
|
|
807
|
+
}
|
|
808
|
+
if (source.slice(previousEnd, token.start).includes('\n')) {
|
|
809
|
+
while (arrowExpressions.length > 0) {
|
|
810
|
+
const arrow = arrowExpressions[arrowExpressions.length - 1];
|
|
811
|
+
if (methodParenCandidates.length !== arrow.parens
|
|
812
|
+
|| functionBraces.length !== arrow.braces
|
|
813
|
+
|| bracketDepth !== arrow.brackets)
|
|
814
|
+
break;
|
|
815
|
+
arrowExpressions.pop();
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
while (arrowExpressions.length > 0) {
|
|
819
|
+
const arrow = arrowExpressions[arrowExpressions.length - 1];
|
|
820
|
+
const delimited = (type === tokTypes.semi || type === tokTypes.comma)
|
|
821
|
+
&& methodParenCandidates.length === arrow.parens
|
|
822
|
+
&& functionBraces.length === arrow.braces
|
|
823
|
+
&& bracketDepth === arrow.brackets;
|
|
824
|
+
const closed = (type === tokTypes.parenR && methodParenCandidates.length === arrow.parens)
|
|
825
|
+
|| (type === tokTypes.bracketR && bracketDepth === arrow.brackets)
|
|
826
|
+
|| (type === tokTypes.braceR && functionBraces.length === arrow.braces);
|
|
827
|
+
if (!delimited && !closed)
|
|
828
|
+
break;
|
|
829
|
+
arrowExpressions.pop();
|
|
830
|
+
}
|
|
831
|
+
if (type === tokTypes.name
|
|
832
|
+
&& source.slice(token.start, token.end) === 'await'
|
|
833
|
+
&& !functionBraces.includes(true)
|
|
834
|
+
&& arrowExpressions.length === 0)
|
|
835
|
+
return true;
|
|
836
|
+
if (type === tokTypes._function || type === tokTypes._class) {
|
|
837
|
+
if (previous !== tokTypes.dot && previous !== tokTypes.questionDot) {
|
|
838
|
+
functionParenDepths.push(methodParenCandidates.length);
|
|
839
|
+
pendingFunctionKeyword = true;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
else if (type === tokTypes.arrow) {
|
|
843
|
+
pendingArrowBody = true;
|
|
844
|
+
}
|
|
845
|
+
else if (type === tokTypes.parenL) {
|
|
846
|
+
methodParenCandidates.push(functionBraces.length > 0
|
|
847
|
+
&& (previous === tokTypes.name || previous === tokTypes.string
|
|
848
|
+
|| previous === tokTypes.num || previous === tokTypes.bracketR));
|
|
849
|
+
}
|
|
850
|
+
else if (type === tokTypes.parenR) {
|
|
851
|
+
pendingMethodBody = methodParenCandidates.pop() === true;
|
|
852
|
+
}
|
|
853
|
+
else if (type === tokTypes.bracketL) {
|
|
854
|
+
bracketDepth++;
|
|
855
|
+
}
|
|
856
|
+
else if (type === tokTypes.bracketR) {
|
|
857
|
+
bracketDepth = Math.max(0, bracketDepth - 1);
|
|
858
|
+
}
|
|
859
|
+
else if (type === tokTypes.dollarBraceL) {
|
|
860
|
+
functionBraces.push(false);
|
|
861
|
+
}
|
|
862
|
+
else if (type === tokTypes.braceL) {
|
|
863
|
+
const functionBody = pendingArrowBody
|
|
864
|
+
|| pendingMethodBody
|
|
865
|
+
|| functionParenDepths[functionParenDepths.length - 1] === methodParenCandidates.length;
|
|
866
|
+
if (functionParenDepths[functionParenDepths.length - 1] === methodParenCandidates.length) {
|
|
867
|
+
functionParenDepths.pop();
|
|
868
|
+
}
|
|
869
|
+
functionBraces.push(functionBody);
|
|
870
|
+
pendingArrowBody = false;
|
|
871
|
+
pendingMethodBody = false;
|
|
872
|
+
}
|
|
873
|
+
else if (type === tokTypes.braceR) {
|
|
874
|
+
functionBraces.pop();
|
|
875
|
+
}
|
|
876
|
+
previousEnd = token.end;
|
|
877
|
+
previous = type;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
catch {
|
|
881
|
+
return true;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
function convertBundledModuleDeclarations(snippets) {
|
|
885
|
+
const imports = [];
|
|
886
|
+
const exports = [];
|
|
887
|
+
let importIndex = 0;
|
|
888
|
+
let markedEsm = false;
|
|
889
|
+
for (const snippet of snippets) {
|
|
890
|
+
const bindingList = snippet.match(/^[ \t]*export\s*\{([\s\S]*)\}\s*;?\s*$/);
|
|
891
|
+
if (bindingList && !/\}\s*from\b/.test(snippet)) {
|
|
892
|
+
if (!markedEsm) {
|
|
893
|
+
exports.push('Object.defineProperty(module.exports, "__esModule", { value: true });');
|
|
894
|
+
markedEsm = true;
|
|
895
|
+
}
|
|
896
|
+
for (const binding of bindingList[1].split(',')) {
|
|
897
|
+
const match = binding.trim().match(/^([\w$]+)(?:\s+as\s+([\w$]+))?$/);
|
|
898
|
+
if (!match)
|
|
899
|
+
return null;
|
|
900
|
+
const local = match[1];
|
|
901
|
+
const exported = match[2] || local;
|
|
902
|
+
exports.push(`Object.defineProperty(module.exports, ${JSON.stringify(exported)}, { enumerable: true, get: () => ${local} });`);
|
|
903
|
+
}
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
let ast;
|
|
907
|
+
try {
|
|
908
|
+
ast = parseJavaScriptModule(snippet);
|
|
909
|
+
}
|
|
910
|
+
catch {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
const body = nodeList(ast, 'body');
|
|
914
|
+
if (body.length !== 1)
|
|
915
|
+
return null;
|
|
916
|
+
const declaration = body[0];
|
|
917
|
+
if (declaration.type === 'ImportDeclaration') {
|
|
918
|
+
const source = literalStringValue(nodeProp(declaration, 'source'));
|
|
919
|
+
if (!source)
|
|
920
|
+
return null;
|
|
921
|
+
const specifiers = nodeList(declaration, 'specifiers');
|
|
922
|
+
if (specifiers.length === 0) {
|
|
923
|
+
imports.push(`module.require(${JSON.stringify(source)});`);
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
const moduleName = `__nimbus_import_${importIndex++}`;
|
|
927
|
+
imports.push(`const ${moduleName} = module.require(${JSON.stringify(source)});`);
|
|
928
|
+
for (const specifier of specifiers) {
|
|
929
|
+
const local = nodeName(nodeProp(specifier, 'local'));
|
|
930
|
+
if (!local)
|
|
931
|
+
return null;
|
|
932
|
+
if (specifier.type === 'ImportDefaultSpecifier') {
|
|
933
|
+
imports.push(`const ${local} = ${moduleName} && ${moduleName}.__esModule ? ${moduleName}.default : ${moduleName};`);
|
|
934
|
+
}
|
|
935
|
+
else if (specifier.type === 'ImportNamespaceSpecifier') {
|
|
936
|
+
imports.push(`const ${local} = ${moduleName};`);
|
|
937
|
+
}
|
|
938
|
+
else if (specifier.type === 'ImportSpecifier') {
|
|
939
|
+
const imported = nodeName(nodeProp(specifier, 'imported'));
|
|
940
|
+
if (!imported)
|
|
941
|
+
return null;
|
|
942
|
+
imports.push(`const ${local} = ${moduleName}[${JSON.stringify(imported)}];`);
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
if (declaration.type === 'ExportNamedDeclaration') {
|
|
951
|
+
if (nodeProp(declaration, 'source') || nodeProp(declaration, 'declaration'))
|
|
952
|
+
return null;
|
|
953
|
+
if (!markedEsm) {
|
|
954
|
+
exports.push('Object.defineProperty(module.exports, "__esModule", { value: true });');
|
|
955
|
+
markedEsm = true;
|
|
956
|
+
}
|
|
957
|
+
for (const specifier of nodeList(declaration, 'specifiers')) {
|
|
958
|
+
const local = nodeName(nodeProp(specifier, 'local'));
|
|
959
|
+
const exported = nodeName(nodeProp(specifier, 'exported'));
|
|
960
|
+
if (!local || !exported)
|
|
961
|
+
return null;
|
|
962
|
+
exports.push(`Object.defineProperty(module.exports, ${JSON.stringify(exported)}, { enumerable: true, get: () => ${local} });`);
|
|
963
|
+
}
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
if (declaration.type === 'ExportDefaultDeclaration') {
|
|
967
|
+
const value = nodeProp(declaration, 'declaration');
|
|
968
|
+
if (!value || typeof value.start !== 'number' || typeof value.end !== 'number')
|
|
969
|
+
return null;
|
|
970
|
+
if (value.type === 'FunctionDeclaration' || value.type === 'ClassDeclaration')
|
|
971
|
+
return null;
|
|
972
|
+
if (!markedEsm) {
|
|
973
|
+
exports.push('Object.defineProperty(module.exports, "__esModule", { value: true });');
|
|
974
|
+
markedEsm = true;
|
|
975
|
+
}
|
|
976
|
+
exports.push(`Object.defineProperty(module.exports, "default", { enumerable: true, value: (${snippet.slice(value.start, value.end)}) });`);
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
return { imports: imports.join('\n'), exports: exports.join('\n') };
|
|
982
|
+
}
|
|
983
|
+
function importMetaEdits(source, absoluteUrl) {
|
|
984
|
+
const edits = [];
|
|
985
|
+
try {
|
|
986
|
+
const tokens = tokenizer(source, {
|
|
987
|
+
ecmaVersion: 'latest',
|
|
988
|
+
sourceType: 'module',
|
|
989
|
+
allowHashBang: true,
|
|
990
|
+
});
|
|
991
|
+
while (true) {
|
|
992
|
+
const start = tokens.getToken();
|
|
993
|
+
if (start.type === tokTypes.eof)
|
|
994
|
+
return edits;
|
|
995
|
+
if (start.type !== tokTypes._import)
|
|
996
|
+
continue;
|
|
997
|
+
const dot1 = tokens.getToken();
|
|
998
|
+
if (dot1.type !== tokTypes.dot)
|
|
999
|
+
continue;
|
|
1000
|
+
const meta = tokens.getToken();
|
|
1001
|
+
if (meta.type !== tokTypes.name || source.slice(meta.start, meta.end) !== 'meta')
|
|
1002
|
+
return null;
|
|
1003
|
+
const dot2 = tokens.getToken();
|
|
1004
|
+
if (dot2.type !== tokTypes.dot)
|
|
1005
|
+
return null;
|
|
1006
|
+
const property = tokens.getToken();
|
|
1007
|
+
if (property.type !== tokTypes.name)
|
|
1008
|
+
return null;
|
|
1009
|
+
const propertyName = source.slice(property.start, property.end);
|
|
1010
|
+
if (propertyName === 'url') {
|
|
1011
|
+
edits.push({ start: start.start, end: property.end, text: JSON.stringify(absoluteUrl) });
|
|
1012
|
+
}
|
|
1013
|
+
else if (propertyName === 'resolve') {
|
|
1014
|
+
edits.push({
|
|
1015
|
+
start: start.start,
|
|
1016
|
+
end: property.end,
|
|
1017
|
+
text: `(specifier => globalThis.__nimbusImportMetaResolve(specifier, ${JSON.stringify(absoluteUrl)}))`,
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
catch {
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Converts bundler-emitted ESM without constructing an AST or loading
|
|
1031
|
+
* esbuild-wasm. Returns null for module declarations that are not the compact,
|
|
1032
|
+
* semicolon-terminated shapes emitted by current JS bundlers.
|
|
1033
|
+
*/
|
|
1034
|
+
export function rewriteBundledEsmToCjs(source, absoluteUrl) {
|
|
1035
|
+
if (hasUnscopedAwait(source))
|
|
1036
|
+
return null;
|
|
1037
|
+
const declarations = topLevelModuleDeclarationRanges(source);
|
|
1038
|
+
if (!declarations || declarations.length === 0)
|
|
1039
|
+
return null;
|
|
1040
|
+
const declarationSnippets = declarations.map(({ start, end }) => source.slice(start, end));
|
|
1041
|
+
for (let i = 0; i < declarations.length; i++) {
|
|
1042
|
+
if (/^[ \t]*export\s+default\b/.test(declarationSnippets[i])
|
|
1043
|
+
&& source.slice(declarations[i].end).trim() !== '')
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
const converted = convertBundledModuleDeclarations(declarationSnippets);
|
|
1047
|
+
if (!converted)
|
|
1048
|
+
return null;
|
|
1049
|
+
const metaEdits = importMetaEdits(source, absoluteUrl);
|
|
1050
|
+
if (!metaEdits)
|
|
1051
|
+
return null;
|
|
1052
|
+
const edits = [
|
|
1053
|
+
...declarations.map(({ start, end }) => ({ start, end, text: '' })),
|
|
1054
|
+
...metaEdits.filter((edit) => !declarations.some(({ start, end }) => edit.start >= start && edit.end <= end)),
|
|
1055
|
+
].sort((a, b) => a.start - b.start);
|
|
1056
|
+
const bodyParts = [];
|
|
1057
|
+
let cursor = 0;
|
|
1058
|
+
for (const edit of edits) {
|
|
1059
|
+
if (edit.start < cursor)
|
|
1060
|
+
return null;
|
|
1061
|
+
bodyParts.push(source.slice(cursor, edit.start), edit.text);
|
|
1062
|
+
cursor = edit.end;
|
|
1063
|
+
}
|
|
1064
|
+
bodyParts.push(source.slice(cursor));
|
|
1065
|
+
const body = bodyParts.join('');
|
|
1066
|
+
return {
|
|
1067
|
+
code: converted.imports + '\n' + body + '\n' + converted.exports,
|
|
1068
|
+
map: '',
|
|
1069
|
+
warnings: [],
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
804
1072
|
import esbuildWasmUrl from 'esbuild-wasm/esbuild.wasm';
|
|
805
1073
|
/**
|
|
806
1074
|
* Cached reference to the esbuild namespace. Populated on first
|
|
807
1075
|
* `loadEsbuild()` call; nullable until then so module-load code paths
|
|
808
1076
|
* that never touch bundling can complete without ever evaluating
|
|
809
|
-
*
|
|
1077
|
+
* esbuild-wasm's JS at all.
|
|
810
1078
|
*/
|
|
811
1079
|
let _esbuildMod = null;
|
|
812
1080
|
let _esbuildLoadPromise = null;
|
|
813
1081
|
/**
|
|
814
|
-
*
|
|
815
|
-
*
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
1082
|
+
* Load the esbuild-wasm namespace. Safe to call many times; concurrent
|
|
1083
|
+
* callers share a single in-flight Promise, and a rejection clears the
|
|
1084
|
+
* cache so a later call can retry.
|
|
1085
|
+
*
|
|
1086
|
+
* Exported so `tests/unit/esbuild-wasm-entrypoint.mjs` can drive the real
|
|
1087
|
+
* specifier under a Node-style resolver. A test that restated the specifier
|
|
1088
|
+
* would grade its own copy of it, and this defect reached production
|
|
1089
|
+
* precisely because nothing graded the resolution.
|
|
1090
|
+
*
|
|
1091
|
+
* The specifier stays a literal: a computed one would defeat the host
|
|
1092
|
+
* bundler's static analysis and leave the module out of the deployed worker.
|
|
819
1093
|
*/
|
|
820
|
-
async function loadEsbuild() {
|
|
1094
|
+
export async function loadEsbuild() {
|
|
821
1095
|
if (_esbuildMod)
|
|
822
1096
|
return _esbuildMod;
|
|
823
1097
|
if (_esbuildLoadPromise)
|
|
824
1098
|
return _esbuildLoadPromise;
|
|
825
1099
|
_esbuildLoadPromise = (async () => {
|
|
826
|
-
|
|
1100
|
+
// Deliberately dynamic: a static import would evaluate esbuild-wasm in
|
|
1101
|
+
// every session, including the ones that only serve a shell and never
|
|
1102
|
+
// bundle. The specifier is still a literal so the host bundler sees it.
|
|
1103
|
+
const mod = await import('esbuild-wasm/esm/browser.js');
|
|
827
1104
|
_esbuildMod = mod;
|
|
828
1105
|
return _esbuildMod;
|
|
829
1106
|
})();
|
|
@@ -831,12 +1108,101 @@ async function loadEsbuild() {
|
|
|
831
1108
|
return await _esbuildLoadPromise;
|
|
832
1109
|
}
|
|
833
1110
|
catch (e) {
|
|
834
|
-
// Reset so a future call can retry (e.g., after the caller has done
|
|
835
|
-
// environment setup we didn't anticipate).
|
|
836
1111
|
_esbuildLoadPromise = null;
|
|
837
1112
|
throw e;
|
|
838
1113
|
}
|
|
839
1114
|
}
|
|
1115
|
+
async function transformWithEsbuild(esbuildApi, source, options) {
|
|
1116
|
+
let code = source;
|
|
1117
|
+
const format = options?.format || 'esm';
|
|
1118
|
+
const loader = options?.loader || 'ts';
|
|
1119
|
+
if (format === 'cjs') {
|
|
1120
|
+
try {
|
|
1121
|
+
const direct = await esbuildApi.transform(code, {
|
|
1122
|
+
loader,
|
|
1123
|
+
format,
|
|
1124
|
+
target: options?.target || 'esnext',
|
|
1125
|
+
sourcemap: options?.sourcemap ?? false,
|
|
1126
|
+
minify: options?.minify ?? false,
|
|
1127
|
+
jsx: options?.jsx,
|
|
1128
|
+
jsxFactory: options?.jsxFactory,
|
|
1129
|
+
jsxFragment: options?.jsxFragment,
|
|
1130
|
+
tsconfigRaw: options?.tsconfigRaw,
|
|
1131
|
+
define: options?.define,
|
|
1132
|
+
supported: { 'dynamic-import': false },
|
|
1133
|
+
});
|
|
1134
|
+
return {
|
|
1135
|
+
code: direct.code,
|
|
1136
|
+
map: direct.map || '',
|
|
1137
|
+
warnings: direct.warnings?.map((warning) => ({
|
|
1138
|
+
text: warning.text,
|
|
1139
|
+
location: warning.location,
|
|
1140
|
+
})) || [],
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
catch (error) {
|
|
1144
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1145
|
+
if (!/top-level await.*not supported.*cjs/i.test(message))
|
|
1146
|
+
throw error;
|
|
1147
|
+
}
|
|
1148
|
+
if (hasEsmImports(code) || hasEsmExports(code)) {
|
|
1149
|
+
const pass1 = await esbuildApi.transform(code, {
|
|
1150
|
+
loader,
|
|
1151
|
+
format: 'esm',
|
|
1152
|
+
target: options?.target || 'esnext',
|
|
1153
|
+
sourcemap: false,
|
|
1154
|
+
minify: false,
|
|
1155
|
+
jsx: options?.jsx,
|
|
1156
|
+
jsxFactory: options?.jsxFactory,
|
|
1157
|
+
jsxFragment: options?.jsxFragment,
|
|
1158
|
+
tsconfigRaw: options?.tsconfigRaw,
|
|
1159
|
+
define: options?.define,
|
|
1160
|
+
supported: { 'dynamic-import': false },
|
|
1161
|
+
});
|
|
1162
|
+
const { requires, body } = convertEsmImportsToRequire(pass1.code);
|
|
1163
|
+
return {
|
|
1164
|
+
code: requires + '\nreturn (async () => {\n' + body + '\n})();\n',
|
|
1165
|
+
map: '',
|
|
1166
|
+
warnings: pass1.warnings?.map((warning) => ({
|
|
1167
|
+
text: warning.text,
|
|
1168
|
+
location: warning.location,
|
|
1169
|
+
})) || [],
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
code = 'return (async () => {\n' + code + '\n})();\n';
|
|
1173
|
+
}
|
|
1174
|
+
const result = await esbuildApi.transform(code, {
|
|
1175
|
+
loader,
|
|
1176
|
+
format,
|
|
1177
|
+
target: options?.target || 'esnext',
|
|
1178
|
+
sourcemap: options?.sourcemap ?? false,
|
|
1179
|
+
minify: options?.minify ?? false,
|
|
1180
|
+
jsx: options?.jsx,
|
|
1181
|
+
jsxFactory: options?.jsxFactory,
|
|
1182
|
+
jsxFragment: options?.jsxFragment,
|
|
1183
|
+
tsconfigRaw: options?.tsconfigRaw,
|
|
1184
|
+
define: options?.define,
|
|
1185
|
+
supported: { 'dynamic-import': false },
|
|
1186
|
+
});
|
|
1187
|
+
return {
|
|
1188
|
+
code: result.code,
|
|
1189
|
+
map: result.map || '',
|
|
1190
|
+
warnings: result.warnings?.map((warning) => ({
|
|
1191
|
+
text: warning.text,
|
|
1192
|
+
location: warning.location,
|
|
1193
|
+
})) || [],
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
/** Source needed by the slim Worker Loader transform isolate. */
|
|
1197
|
+
export function generateEsbuildTransformRuntimeSource() {
|
|
1198
|
+
return [
|
|
1199
|
+
stripCommentsAndStrings.toString(),
|
|
1200
|
+
hasEsmImports.toString(),
|
|
1201
|
+
hasEsmExports.toString(),
|
|
1202
|
+
convertEsmImportsToRequire.toString(),
|
|
1203
|
+
transformWithEsbuild.toString(),
|
|
1204
|
+
].join('\n');
|
|
1205
|
+
}
|
|
840
1206
|
// ── EsbuildService ──────────────────────────────────────────────────────
|
|
841
1207
|
export class EsbuildService {
|
|
842
1208
|
vfs;
|
|
@@ -844,8 +1210,9 @@ export class EsbuildService {
|
|
|
844
1210
|
initPromise = null;
|
|
845
1211
|
/** Resolved esbuild namespace — populated by ensureInit() after loadEsbuild(). */
|
|
846
1212
|
_esbuild = null;
|
|
1213
|
+
/** Build reads use only the caller-supplied view; omit it for transform-only use. */
|
|
847
1214
|
constructor(vfs) {
|
|
848
|
-
this.vfs = vfs
|
|
1215
|
+
this.vfs = vfs ?? null;
|
|
849
1216
|
}
|
|
850
1217
|
/**
|
|
851
1218
|
* Initialize esbuild-wasm (lazy, on first use). Loads the namespace
|
|
@@ -987,82 +1354,7 @@ export class EsbuildService {
|
|
|
987
1354
|
*/
|
|
988
1355
|
async transform(code, options) {
|
|
989
1356
|
await this.ensureInit();
|
|
990
|
-
|
|
991
|
-
const loader = options?.loader || 'ts';
|
|
992
|
-
// ── CJS + TLA + ESM module syntax: two-pass with module rewrite ──
|
|
993
|
-
// This precedes the simple TLA-only IIFE wrap. We test ESM module
|
|
994
|
-
// syntax first because the IIFE wrap is incompatible with import
|
|
995
|
-
// and export statements; if both conditions hold we MUST go through
|
|
996
|
-
// the rewrite path.
|
|
997
|
-
if (format === 'cjs' && hasTopLevelAwait(code) && (hasEsmImports(code) || hasEsmExports(code))) {
|
|
998
|
-
// Pass 1: emit ESM so esbuild accepts TLA + imports without complaint.
|
|
999
|
-
// We use the same loader/target so TS/JSX is handled here too.
|
|
1000
|
-
const pass1 = await this._esbuild.transform(code, {
|
|
1001
|
-
loader,
|
|
1002
|
-
format: 'esm',
|
|
1003
|
-
target: options?.target || 'esnext',
|
|
1004
|
-
sourcemap: false, // pass-1 source map is discarded; pass-2 doesn't run esbuild
|
|
1005
|
-
minify: false, // minify only on the final output if requested
|
|
1006
|
-
jsx: options?.jsx,
|
|
1007
|
-
jsxFactory: options?.jsxFactory,
|
|
1008
|
-
jsxFragment: options?.jsxFragment,
|
|
1009
|
-
tsconfigRaw: options?.tsconfigRaw,
|
|
1010
|
-
define: options?.define,
|
|
1011
|
-
// Dynamic-import lowering: rewrites `import(x)` to
|
|
1012
|
-
// `Promise.resolve().then(() => __toESM(require(x)))` so the
|
|
1013
|
-
// call routes through Nimbus's scopedRequire → VFS lookup
|
|
1014
|
-
// instead of workerd's worker-module-map resolver (which only
|
|
1015
|
-
// knows {'runner.js': workerCode}). Without this, every user
|
|
1016
|
-
// dynamic import() rejects with "No such module ..." — and if
|
|
1017
|
-
// user code has no .catch() (e.g. create-astro.mjs's
|
|
1018
|
-
// `import('./dist/index.js').then(({main}) => main())`), the
|
|
1019
|
-
// rejection is unhandled and the facet exits silently
|
|
1020
|
-
supported: { 'dynamic-import': false },
|
|
1021
|
-
});
|
|
1022
|
-
const { requires, body } = convertEsmImportsToRequire(pass1.code);
|
|
1023
|
-
const assembled = requires + '\n' +
|
|
1024
|
-
'return (async () => {\n' +
|
|
1025
|
-
body +
|
|
1026
|
-
'\n})();\n';
|
|
1027
|
-
return {
|
|
1028
|
-
code: assembled,
|
|
1029
|
-
map: '',
|
|
1030
|
-
warnings: pass1.warnings?.map((w) => ({
|
|
1031
|
-
text: w.text,
|
|
1032
|
-
location: w.location,
|
|
1033
|
-
})) || [],
|
|
1034
|
-
};
|
|
1035
|
-
}
|
|
1036
|
-
// ── CJS + TLA (no ESM imports): single-pass IIFE wrap (P2 fix) ──
|
|
1037
|
-
let sourceToTransform = code;
|
|
1038
|
-
if (format === 'cjs' && hasTopLevelAwait(code)) {
|
|
1039
|
-
sourceToTransform =
|
|
1040
|
-
'return (async () => {\n' +
|
|
1041
|
-
code +
|
|
1042
|
-
'\n})();\n';
|
|
1043
|
-
}
|
|
1044
|
-
const result = await this._esbuild.transform(sourceToTransform, {
|
|
1045
|
-
loader,
|
|
1046
|
-
format,
|
|
1047
|
-
target: options?.target || 'esnext',
|
|
1048
|
-
sourcemap: options?.sourcemap ?? false,
|
|
1049
|
-
minify: options?.minify ?? false,
|
|
1050
|
-
jsx: options?.jsx,
|
|
1051
|
-
jsxFactory: options?.jsxFactory,
|
|
1052
|
-
jsxFragment: options?.jsxFragment,
|
|
1053
|
-
tsconfigRaw: options?.tsconfigRaw,
|
|
1054
|
-
define: options?.define,
|
|
1055
|
-
// Dynamic-import lowering — see two-pass branch above for rationale.
|
|
1056
|
-
supported: { 'dynamic-import': false },
|
|
1057
|
-
});
|
|
1058
|
-
return {
|
|
1059
|
-
code: result.code,
|
|
1060
|
-
map: result.map || '',
|
|
1061
|
-
warnings: result.warnings?.map(w => ({
|
|
1062
|
-
text: w.text,
|
|
1063
|
-
location: w.location,
|
|
1064
|
-
})) || [],
|
|
1065
|
-
};
|
|
1357
|
+
return transformWithEsbuild(this._esbuild, code, options);
|
|
1066
1358
|
}
|
|
1067
1359
|
/**
|
|
1068
1360
|
* Bundle entry points from the VFS.
|
|
@@ -1105,13 +1397,18 @@ export class EsbuildService {
|
|
|
1105
1397
|
warnings: result.warnings?.map(w => ({ text: w.text, location: w.location })) || [],
|
|
1106
1398
|
};
|
|
1107
1399
|
}
|
|
1400
|
+
requireVfs() {
|
|
1401
|
+
if (!this.vfs)
|
|
1402
|
+
throw new Error('EsbuildService build requires a VFS');
|
|
1403
|
+
return this.vfs;
|
|
1404
|
+
}
|
|
1108
1405
|
/**
|
|
1109
1406
|
* VFS resolver plugin for esbuild.
|
|
1110
|
-
* Reads
|
|
1407
|
+
* Reads through the caller's credentialed view (synchronous, no snapshot needed).
|
|
1111
1408
|
* Handles: absolute paths, relative paths, bare specifiers (node_modules).
|
|
1112
1409
|
*/
|
|
1113
1410
|
makeVfsPlugin() {
|
|
1114
|
-
const vfs = this.
|
|
1411
|
+
const vfs = this.requireVfs();
|
|
1115
1412
|
const EXTS = ['', '.ts', '.tsx', '.js', '.jsx', '.mts', '.mjs', '.cjs', '.json', '.css'];
|
|
1116
1413
|
const INDEX_FILES = ['index.ts', 'index.tsx', 'index.js', 'index.jsx', 'index.mjs'];
|
|
1117
1414
|
// Path helpers shared with git-commands via ./vfs-path.ts.
|