@scriptc/compiler 0.0.7 → 0.0.9
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/ambient/scriptc-node-fallback.d.ts +13 -0
- package/dist/backend/cc.js +14 -0
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +155 -6
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-island.js +9 -1
- package/dist/backend/emission/emit-island.js.map +1 -1
- package/dist/backend/emission/emit-stmts.js +3 -1
- package/dist/backend/emission/emit-stmts.js.map +1 -1
- package/dist/backend/emission/emit-types.d.ts +3 -0
- package/dist/backend/emission/emit-types.js +12 -0
- package/dist/backend/emission/emit-types.js.map +1 -1
- package/dist/backend/emission/emit-walkers.d.ts +7 -5
- package/dist/backend/emission/emit-walkers.js +45 -18
- package/dist/backend/emission/emit-walkers.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +1 -1
- package/dist/backend/emission/emitter.js +35 -10
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/emission/may-throw.js +4 -3
- package/dist/backend/emission/may-throw.js.map +1 -1
- package/dist/backend/llvm/emitter.js +300 -18
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +98 -11
- package/dist/diagnostics/diagnostic.js +185 -25
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lib-contract.d.ts +24 -6
- package/dist/frontend/lib-contract.js +125 -44
- package/dist/frontend/lib-contract.js.map +1 -1
- package/dist/frontend/lowering/lower-assert.js +16 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +11 -0
- package/dist/frontend/lowering/lower-builtins.js +127 -4
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +77 -18
- package/dist/frontend/lowering/lower-calls.js +978 -44
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +14 -1
- package/dist/frontend/lowering/lower-classes.js +112 -16
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +4 -2
- package/dist/frontend/lowering/lower-containers.js +236 -44
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-emitter.js +55 -1
- package/dist/frontend/lowering/lower-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +26 -3
- package/dist/frontend/lowering/lower-exprs.js +507 -47
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +7 -0
- package/dist/frontend/lowering/lower-island.js +71 -10
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-mixins.d.ts +1 -1
- package/dist/frontend/lowering/lower-mixins.js +1 -1
- package/dist/frontend/lowering/lower-modules.js +95 -4
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +866 -56
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +28 -2
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +85 -17
- package/dist/frontend/lowering/lowerer.js +459 -37
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.js +20 -3
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/npm-static-rewrite.d.ts +7 -2
- package/dist/frontend/npm-static-rewrite.js +566 -138
- package/dist/frontend/npm-static-rewrite.js.map +1 -1
- package/dist/frontend/npm-static.js +11 -2
- package/dist/frontend/npm-static.js.map +1 -1
- package/dist/frontend/program.js +5 -5
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/shared.d.ts +6 -1
- package/dist/frontend/shared.js +46 -1
- package/dist/frontend/shared.js.map +1 -1
- package/dist/frontend/types.d.ts +28 -1
- package/dist/frontend/types.js +304 -10
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +181 -26
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +159 -9
- package/dist/ir/nodes.js +76 -6
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +93 -14
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/profile.d.ts +17 -3
- package/dist/library/profile.js +38 -9
- package/dist/library/profile.js.map +1 -1
- package/dist/library/sidecar.js +162 -43
- package/dist/library/sidecar.js.map +1 -1
- package/dist/library/trap-teaching.d.ts +11 -0
- package/dist/library/trap-teaching.js +49 -0
- package/dist/library/trap-teaching.js.map +1 -0
- package/package.json +2 -2
- package/surface-manifest.json +46 -4
|
@@ -55,6 +55,17 @@
|
|
|
55
55
|
* statement positions (visible only to load-order-sensitive side
|
|
56
56
|
* effects). Node's __esModule marker survives on the rewritten object.
|
|
57
57
|
*
|
|
58
|
+
* THE IMPORT-SIDE TWIN: the same pass erases esbuild's __toESM interop
|
|
59
|
+
* wrapper around EXTERNAL dependencies (`var import_x =
|
|
60
|
+
* __toESM(require("x"))`, the `, 1` node-mode variant, inline
|
|
61
|
+
* member-accessed forms) — see the section header at planToEsmInterop.
|
|
62
|
+
* The wrapper pads down to the bare require it wraps and module-valued
|
|
63
|
+
* `.default` accesses pad down to their binding, so member accesses model
|
|
64
|
+
* on the required package's canonical table through the ordinary require
|
|
65
|
+
* machinery. Interop the recognizers cannot finish answers `{ degrade }`:
|
|
66
|
+
* the caller reports the package as an offender (naming the construct)
|
|
67
|
+
* and it serves from the island — never a failed build.
|
|
68
|
+
*
|
|
58
69
|
* Everything here is SYNTACTIC (typescript5 world, the cjs-lexer's
|
|
59
70
|
* sanctioned island): the chase decides only what TEXT to emit — the
|
|
60
71
|
* checker then infers the types from the emitted spellings. A file whose
|
|
@@ -62,9 +73,10 @@
|
|
|
62
73
|
* untouched (null): its import-site errors stay, and the frontend's
|
|
63
74
|
* offender attribution degrades the PACKAGE to the island with a note. */
|
|
64
75
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
65
|
-
import { dirname, resolve as resolvePath } from "node:path";
|
|
76
|
+
import { dirname, join, resolve as resolvePath } from "node:path";
|
|
66
77
|
import ts from "typescript5";
|
|
67
78
|
import { cjsLexedExportsOf, cjsLexerVisibleNames } from "./cjs-lexer.js";
|
|
79
|
+
import { resolveExports } from "./npm.js";
|
|
68
80
|
/** True when `e` is exactly the `exports` identifier. */
|
|
69
81
|
function isExportsIdent(e) {
|
|
70
82
|
return ts.isIdentifier(e) && e.text === "exports";
|
|
@@ -211,14 +223,10 @@ function definePropertyExportOf(call) {
|
|
|
211
223
|
const esModuleStamp = name === "__esModule" && value !== null && value.kind === ts.SyntaxKind.TrueKeyword;
|
|
212
224
|
return { name, value, esModuleStamp };
|
|
213
225
|
}
|
|
214
|
-
/**
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
|
|
218
|
-
function resolveRelativeCjs(fromFile, spec) {
|
|
219
|
-
if (!spec.startsWith("./") && !spec.startsWith("../"))
|
|
220
|
-
return null;
|
|
221
|
-
const base = resolvePath(dirname(fromFile), spec);
|
|
226
|
+
/** Node's file-first CJS probes over an ABSOLUTE base (the dist-tree
|
|
227
|
+
* subset: exact, .js/.cjs, /index.js, package.json "main" for
|
|
228
|
+
* directories). */
|
|
229
|
+
function resolveCjsBase(base) {
|
|
222
230
|
const candidates = [base, `${base}.js`, `${base}.cjs`];
|
|
223
231
|
for (const c of candidates) {
|
|
224
232
|
try {
|
|
@@ -252,6 +260,118 @@ function resolveRelativeCjs(fromFile, spec) {
|
|
|
252
260
|
}
|
|
253
261
|
return null;
|
|
254
262
|
}
|
|
263
|
+
/** Resolves a RELATIVE CJS require target (resolveCjsBase over the
|
|
264
|
+
* importer-anchored path). Bare specifiers answer null — their names ride
|
|
265
|
+
* the spread entry and the program resolver. */
|
|
266
|
+
function resolveRelativeCjs(fromFile, spec) {
|
|
267
|
+
if (!spec.startsWith("./") && !spec.startsWith("../"))
|
|
268
|
+
return null;
|
|
269
|
+
return resolveCjsBase(resolvePath(dirname(fromFile), spec));
|
|
270
|
+
}
|
|
271
|
+
/** Resolves a BARE require target the way Node's CJS resolution would (the
|
|
272
|
+
* __toESM default-shape probe's subset): node_modules/<name> walked up from
|
|
273
|
+
* the requiring file, "exports" honored with the require condition, then
|
|
274
|
+
* main/index. Best-effort — null keeps the caller conservative. */
|
|
275
|
+
function resolveBareRequireCjs(fromFile, spec) {
|
|
276
|
+
if (spec.startsWith("#") || spec.startsWith("node:"))
|
|
277
|
+
return null;
|
|
278
|
+
const parts = spec.split("/");
|
|
279
|
+
const name = spec.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
|
|
280
|
+
const subparts = spec.startsWith("@") ? parts.slice(2) : parts.slice(1);
|
|
281
|
+
const subpath = subparts.length > 0 ? `./${subparts.join("/")}` : ".";
|
|
282
|
+
for (let dir = dirname(fromFile);;) {
|
|
283
|
+
const pkgDir = join(dir, "node_modules", name);
|
|
284
|
+
try {
|
|
285
|
+
if (existsSync(pkgDir) && statSync(pkgDir).isDirectory()) {
|
|
286
|
+
const pkgPath = join(pkgDir, "package.json");
|
|
287
|
+
let exports;
|
|
288
|
+
try {
|
|
289
|
+
exports = existsSync(pkgPath)
|
|
290
|
+
? JSON.parse(readFileSync(pkgPath, "utf8")).exports
|
|
291
|
+
: undefined;
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
if (exports !== undefined) {
|
|
297
|
+
const target = resolveExports(exports, subpath, "require");
|
|
298
|
+
return target === null ? null : resolveCjsBase(join(pkgDir, target));
|
|
299
|
+
}
|
|
300
|
+
return resolveCjsBase(subpath === "." ? pkgDir : join(pkgDir, subpath));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
const parent = dirname(dir);
|
|
307
|
+
if (parent === dir)
|
|
308
|
+
return null;
|
|
309
|
+
dir = parent;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/** Whether `require(spec)` from `fromFile` answers an object whose
|
|
313
|
+
* `__esModule` is TRUTHY at runtime under Node — the branch esbuild's
|
|
314
|
+
* __toESM takes to decide what `default` binds. True for the transpiled-ESM
|
|
315
|
+
* stamps (the lexer-visible `exports.__esModule =` / defineProperty forms,
|
|
316
|
+
* esbuild's own `module.exports = __toCommonJS(…)` where the stamp hides
|
|
317
|
+
* from the lexer inside the helper) and for real ES-module targets
|
|
318
|
+
* (require(esm) marks the namespace for interop); `module.exports =
|
|
319
|
+
* require(…)` forwarding chains follow. False (the CJS answer: `default`
|
|
320
|
+
* IS module.exports) everywhere else, unresolvable targets included —
|
|
321
|
+
* their require throws before `default` matters. */
|
|
322
|
+
function requireTargetEsModuleStamped(fromFile, spec, depth = 0) {
|
|
323
|
+
if (depth > 8)
|
|
324
|
+
return false;
|
|
325
|
+
const file = spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/")
|
|
326
|
+
? resolveCjsBase(spec.startsWith("/") ? spec : resolvePath(dirname(fromFile), spec))
|
|
327
|
+
: resolveBareRequireCjs(fromFile, spec);
|
|
328
|
+
if (file === null)
|
|
329
|
+
return false;
|
|
330
|
+
if (file.endsWith(".mjs"))
|
|
331
|
+
return true;
|
|
332
|
+
if (file.endsWith(".json"))
|
|
333
|
+
return false;
|
|
334
|
+
if (file.endsWith(".js")) {
|
|
335
|
+
// the nearest package.json "type" decides the .js format
|
|
336
|
+
for (let dir = dirname(file);;) {
|
|
337
|
+
const pkgPath = join(dir, "package.json");
|
|
338
|
+
if (existsSync(pkgPath)) {
|
|
339
|
+
try {
|
|
340
|
+
if (JSON.parse(readFileSync(pkgPath, "utf8")).type === "module")
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
/* unreadable — treat as CJS */
|
|
345
|
+
}
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
const parent = dirname(dir);
|
|
349
|
+
if (parent === dir)
|
|
350
|
+
break;
|
|
351
|
+
dir = parent;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
let src;
|
|
355
|
+
try {
|
|
356
|
+
src = readFileSync(file, "utf8");
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
if (cjsLexedExportsOf(src, file).exports.has("__esModule"))
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
/* unlexable — keep probing */
|
|
367
|
+
}
|
|
368
|
+
if (src.includes("__toCommonJS("))
|
|
369
|
+
return true;
|
|
370
|
+
const fwd = /module\.exports\s*=\s*require\(\s*["']([^"']+)["']\s*\)/.exec(src);
|
|
371
|
+
if (fwd !== null)
|
|
372
|
+
return requireTargetEsModuleStamped(file, fwd[1], depth + 1);
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
255
375
|
/** The Node-visible named-export set of a star-re-export TARGET file:
|
|
256
376
|
* its recursive lexer-visible names (relative edges only — exactly what
|
|
257
377
|
* the emitted member entries must enumerate). */
|
|
@@ -275,13 +395,272 @@ function padSpan(text, start, end) {
|
|
|
275
395
|
function isPlainName(name) {
|
|
276
396
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
277
397
|
}
|
|
398
|
+
/** Structural recognition of esbuild's vendored __toESM helper:
|
|
399
|
+
* `var __toESM = (mod, isNodeMode, target) => (target = …, __copyProps(
|
|
400
|
+
* isNodeMode || !mod || !mod.__esModule
|
|
401
|
+
* ? __defProp(target, "default", { value: mod, enumerable: true })
|
|
402
|
+
* : target,
|
|
403
|
+
* mod))`
|
|
404
|
+
* — an arrow over (mod, isNodeMode[, target]) whose result is a
|
|
405
|
+
* __copyProps(…, mod) call copying onto a conditional that tests
|
|
406
|
+
* isNodeMode/__esModule and defines "default" with value mod. Comments and
|
|
407
|
+
* formatting are free; the SHAPE is the contract. */
|
|
408
|
+
function recognizedToEsmDecl(stmt) {
|
|
409
|
+
if (!ts.isVariableStatement(stmt))
|
|
410
|
+
return false;
|
|
411
|
+
const decls = stmt.declarationList.declarations;
|
|
412
|
+
if (decls.length !== 1)
|
|
413
|
+
return false;
|
|
414
|
+
const d = decls[0];
|
|
415
|
+
if (!ts.isIdentifier(d.name) || d.name.text !== "__toESM" || d.initializer === undefined)
|
|
416
|
+
return false;
|
|
417
|
+
let init = d.initializer;
|
|
418
|
+
while (ts.isParenthesizedExpression(init))
|
|
419
|
+
init = init.expression;
|
|
420
|
+
if (!ts.isArrowFunction(init))
|
|
421
|
+
return false;
|
|
422
|
+
if (init.parameters.length < 2 || init.parameters.length > 3)
|
|
423
|
+
return false;
|
|
424
|
+
const p0 = init.parameters[0];
|
|
425
|
+
const p1 = init.parameters[1];
|
|
426
|
+
if (!ts.isIdentifier(p0.name) || !ts.isIdentifier(p1.name))
|
|
427
|
+
return false;
|
|
428
|
+
const mod = p0.name.text;
|
|
429
|
+
const isNodeMode = p1.name.text;
|
|
430
|
+
if (ts.isBlock(init.body))
|
|
431
|
+
return false;
|
|
432
|
+
let expr = init.body;
|
|
433
|
+
while (ts.isParenthesizedExpression(expr))
|
|
434
|
+
expr = expr.expression;
|
|
435
|
+
// the rightmost comma operand carries the result
|
|
436
|
+
while (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.CommaToken) {
|
|
437
|
+
expr = expr.right;
|
|
438
|
+
}
|
|
439
|
+
while (ts.isParenthesizedExpression(expr))
|
|
440
|
+
expr = expr.expression;
|
|
441
|
+
if (!ts.isCallExpression(expr) || !ts.isIdentifier(expr.expression) || expr.expression.text !== "__copyProps")
|
|
442
|
+
return false;
|
|
443
|
+
if (expr.arguments.length < 2)
|
|
444
|
+
return false;
|
|
445
|
+
const from = expr.arguments[1];
|
|
446
|
+
if (!ts.isIdentifier(from) || from.text !== mod)
|
|
447
|
+
return false;
|
|
448
|
+
let to = expr.arguments[0];
|
|
449
|
+
while (ts.isParenthesizedExpression(to))
|
|
450
|
+
to = to.expression;
|
|
451
|
+
if (!ts.isConditionalExpression(to))
|
|
452
|
+
return false;
|
|
453
|
+
const condText = to.condition.getText();
|
|
454
|
+
if (!condText.includes(isNodeMode) || !condText.includes("__esModule"))
|
|
455
|
+
return false;
|
|
456
|
+
let stamp = to.whenTrue;
|
|
457
|
+
while (ts.isParenthesizedExpression(stamp))
|
|
458
|
+
stamp = stamp.expression;
|
|
459
|
+
if (!ts.isCallExpression(stamp) || !ts.isIdentifier(stamp.expression) || stamp.expression.text !== "__defProp")
|
|
460
|
+
return false;
|
|
461
|
+
if (stamp.arguments.length !== 3)
|
|
462
|
+
return false;
|
|
463
|
+
const nameArg = stamp.arguments[1];
|
|
464
|
+
if (!ts.isStringLiteral(nameArg) || nameArg.text !== "default")
|
|
465
|
+
return false;
|
|
466
|
+
const desc = stamp.arguments[2];
|
|
467
|
+
if (!ts.isObjectLiteralExpression(desc))
|
|
468
|
+
return false;
|
|
469
|
+
return desc.properties.some((p) => ts.isPropertyAssignment(p) &&
|
|
470
|
+
ts.isIdentifier(p.name) &&
|
|
471
|
+
p.name.text === "value" &&
|
|
472
|
+
ts.isIdentifier(p.initializer) &&
|
|
473
|
+
p.initializer.text === mod);
|
|
474
|
+
}
|
|
475
|
+
const TO_ESM_SHAPE_DEGRADE = "its shipped JS calls the __toESM bundler-interop helper but spells it in a shape the " +
|
|
476
|
+
"recognizer cannot verify — the package serves from the island instead";
|
|
477
|
+
const TO_ESM_ESCAPE_DEGRADE = "a __toESM(require(…)) bundler-interop object escapes into a use the static rewrite cannot " +
|
|
478
|
+
"follow (only module-scope bindings and direct member reads compile) — the package serves " +
|
|
479
|
+
"from the island instead";
|
|
480
|
+
const TO_ESM_ARG_DEGRADE = "it calls the __toESM bundler-interop helper on something other than require(…) of a string " +
|
|
481
|
+
"literal — the package serves from the island instead";
|
|
482
|
+
const TO_ESM_MIXED_DEGRADE = "its bundler-emitted export surface cannot be respelled around its __toESM interop imports " +
|
|
483
|
+
"— the package serves from the island instead";
|
|
484
|
+
/** The interop-erasure plan for one CJS file (see the section header). */
|
|
485
|
+
function planToEsmInterop(sf) {
|
|
486
|
+
const pads = [];
|
|
487
|
+
const moduleBindings = new Set();
|
|
488
|
+
const plan = { pads, moduleBindings, degrade: null };
|
|
489
|
+
const fail = (reason) => ({ pads: [], moduleBindings: new Set(), degrade: reason });
|
|
490
|
+
const declStmts = sf.statements.filter((s) => ts.isVariableStatement(s) &&
|
|
491
|
+
s.declarationList.declarations.some((d) => ts.isIdentifier(d.name) && d.name.text === "__toESM"));
|
|
492
|
+
const refs = [];
|
|
493
|
+
const calls = [];
|
|
494
|
+
const collect = (n) => {
|
|
495
|
+
if (ts.isIdentifier(n) && n.text === "__toESM")
|
|
496
|
+
refs.push(n);
|
|
497
|
+
if (ts.isCallExpression(n) && ts.isIdentifier(n.expression) && n.expression.text === "__toESM")
|
|
498
|
+
calls.push(n);
|
|
499
|
+
ts.forEachChild(n, collect);
|
|
500
|
+
};
|
|
501
|
+
collect(sf);
|
|
502
|
+
if (calls.length === 0 && refs.length === 0)
|
|
503
|
+
return plan;
|
|
504
|
+
// Every reference must be the declarator's own name or a call's callee —
|
|
505
|
+
// a helper that escapes as a VALUE is outside the recognized shape.
|
|
506
|
+
const callees = new Set(calls.map((c) => c.expression));
|
|
507
|
+
const declNames = new Set();
|
|
508
|
+
for (const s of declStmts) {
|
|
509
|
+
for (const d of s.declarationList.declarations) {
|
|
510
|
+
if (ts.isIdentifier(d.name) && d.name.text === "__toESM")
|
|
511
|
+
declNames.add(d.name);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (refs.some((r) => !callees.has(r) && !declNames.has(r)))
|
|
515
|
+
return fail(TO_ESM_ESCAPE_DEGRADE);
|
|
516
|
+
if (calls.length === 0)
|
|
517
|
+
return plan; // declared but never called — dead helper
|
|
518
|
+
if (declStmts.length !== 1 || !recognizedToEsmDecl(declStmts[0]))
|
|
519
|
+
return fail(TO_ESM_SHAPE_DEGRADE);
|
|
520
|
+
/** Module-scope interop bindings: name → whether `.default` IS the
|
|
521
|
+
* module (pad the access) rather than a member read of its `default`. */
|
|
522
|
+
const bindings = new Map();
|
|
523
|
+
for (const call of calls) {
|
|
524
|
+
const arg0 = call.arguments[0];
|
|
525
|
+
const spec = arg0 !== undefined ? bareRequireSpecOf(arg0) : null;
|
|
526
|
+
if (spec === null || call.arguments.length > 2)
|
|
527
|
+
return fail(TO_ESM_ARG_DEGRADE);
|
|
528
|
+
let isNodeMode = false;
|
|
529
|
+
if (call.arguments.length === 2) {
|
|
530
|
+
const modeArg = call.arguments[1];
|
|
531
|
+
if (ts.isNumericLiteral(modeArg))
|
|
532
|
+
isNodeMode = Number(modeArg.text) !== 0;
|
|
533
|
+
else if (modeArg.kind === ts.SyntaxKind.TrueKeyword)
|
|
534
|
+
isNodeMode = true;
|
|
535
|
+
else if (modeArg.kind === ts.SyntaxKind.FalseKeyword)
|
|
536
|
+
isNodeMode = false;
|
|
537
|
+
else
|
|
538
|
+
return fail(TO_ESM_ARG_DEGRADE);
|
|
539
|
+
}
|
|
540
|
+
const defaultIsModule = isNodeMode || !requireTargetEsModuleStamped(sf.fileName, spec);
|
|
541
|
+
// classify the call's use: a variable binding, or an immediate member
|
|
542
|
+
// read; anything else escapes.
|
|
543
|
+
let child = call;
|
|
544
|
+
let parent = call.parent;
|
|
545
|
+
while (ts.isParenthesizedExpression(parent)) {
|
|
546
|
+
child = parent;
|
|
547
|
+
parent = parent.parent;
|
|
548
|
+
}
|
|
549
|
+
if (ts.isVariableDeclaration(parent) && parent.initializer === child && ts.isIdentifier(parent.name)) {
|
|
550
|
+
if (bindings.has(parent.name.text))
|
|
551
|
+
return fail(TO_ESM_ESCAPE_DEGRADE);
|
|
552
|
+
bindings.set(parent.name.text, defaultIsModule);
|
|
553
|
+
if (defaultIsModule)
|
|
554
|
+
moduleBindings.add(parent.name.text);
|
|
555
|
+
}
|
|
556
|
+
else if (ts.isPropertyAccessExpression(parent) && parent.expression === child && ts.isIdentifier(parent.name)) {
|
|
557
|
+
if (parent.name.text === "default" && defaultIsModule) {
|
|
558
|
+
if (!readOnlyAccess(parent))
|
|
559
|
+
return fail(TO_ESM_ESCAPE_DEGRADE);
|
|
560
|
+
pads.push({ start: child.getEnd(), end: parent.getEnd() });
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
return fail(TO_ESM_ESCAPE_DEGRADE);
|
|
565
|
+
}
|
|
566
|
+
// the wrapper itself: callee + '(' down, and everything after the
|
|
567
|
+
// require argument (`, 1` included) — the bare require survives at its
|
|
568
|
+
// original offsets.
|
|
569
|
+
pads.push({ start: call.getStart(sf), end: arg0.getStart(sf) });
|
|
570
|
+
pads.push({ start: arg0.getEnd(), end: call.getEnd() });
|
|
571
|
+
}
|
|
572
|
+
if (bindings.size > 0) {
|
|
573
|
+
// Binding safety: the erased binding must be declared exactly once and
|
|
574
|
+
// never written — the `.default` mapping below matches receivers by
|
|
575
|
+
// NAME (syntactic, like the rest of this file), so a shadow or a
|
|
576
|
+
// reassignment would silently change what a padded access reads.
|
|
577
|
+
const declCount = new Map();
|
|
578
|
+
const bump = (name) => {
|
|
579
|
+
if (bindings.has(name))
|
|
580
|
+
declCount.set(name, (declCount.get(name) ?? 0) + 1);
|
|
581
|
+
};
|
|
582
|
+
let violated = false;
|
|
583
|
+
const scan = (n) => {
|
|
584
|
+
if (violated)
|
|
585
|
+
return;
|
|
586
|
+
if ((ts.isVariableDeclaration(n) || ts.isBindingElement(n) || ts.isParameter(n)) &&
|
|
587
|
+
ts.isIdentifier(n.name)) {
|
|
588
|
+
bump(n.name.text);
|
|
589
|
+
}
|
|
590
|
+
else if ((ts.isFunctionDeclaration(n) || ts.isClassDeclaration(n)) && n.name !== undefined) {
|
|
591
|
+
bump(n.name.text);
|
|
592
|
+
}
|
|
593
|
+
else if (ts.isBinaryExpression(n) &&
|
|
594
|
+
n.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
|
|
595
|
+
n.operatorToken.kind <= ts.SyntaxKind.LastAssignment &&
|
|
596
|
+
ts.isIdentifier(n.left) &&
|
|
597
|
+
bindings.has(n.left.text)) {
|
|
598
|
+
violated = true;
|
|
599
|
+
}
|
|
600
|
+
else if ((ts.isPrefixUnaryExpression(n) || ts.isPostfixUnaryExpression(n)) &&
|
|
601
|
+
(n.operator === ts.SyntaxKind.PlusPlusToken || n.operator === ts.SyntaxKind.MinusMinusToken) &&
|
|
602
|
+
ts.isIdentifier(n.operand) &&
|
|
603
|
+
bindings.has(n.operand.text)) {
|
|
604
|
+
violated = true;
|
|
605
|
+
}
|
|
606
|
+
ts.forEachChild(n, scan);
|
|
607
|
+
};
|
|
608
|
+
scan(sf);
|
|
609
|
+
if (violated || [...bindings.keys()].some((name) => (declCount.get(name) ?? 0) !== 1)) {
|
|
610
|
+
return fail(TO_ESM_ESCAPE_DEGRADE);
|
|
611
|
+
}
|
|
612
|
+
// `.default` reads on a module-valued binding pad down to the binding.
|
|
613
|
+
const padDefaults = (n) => {
|
|
614
|
+
if (ts.isPropertyAccessExpression(n) &&
|
|
615
|
+
ts.isIdentifier(n.expression) &&
|
|
616
|
+
bindings.get(n.expression.text) === true &&
|
|
617
|
+
ts.isIdentifier(n.name) &&
|
|
618
|
+
n.name.text === "default") {
|
|
619
|
+
if (!readOnlyAccess(n)) {
|
|
620
|
+
violated = true;
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
pads.push({ start: n.expression.getEnd(), end: n.getEnd() });
|
|
624
|
+
}
|
|
625
|
+
ts.forEachChild(n, padDefaults);
|
|
626
|
+
};
|
|
627
|
+
padDefaults(sf);
|
|
628
|
+
if (violated)
|
|
629
|
+
return fail(TO_ESM_ESCAPE_DEGRADE);
|
|
630
|
+
}
|
|
631
|
+
return plan;
|
|
632
|
+
}
|
|
633
|
+
/** True when a padded-away `.default` access is a plain READ — an
|
|
634
|
+
* assignment target or delete operand cannot lose its member spelling. */
|
|
635
|
+
function readOnlyAccess(access) {
|
|
636
|
+
const p = access.parent;
|
|
637
|
+
if (p !== undefined && ts.isDeleteExpression(p))
|
|
638
|
+
return false;
|
|
639
|
+
if (p !== undefined &&
|
|
640
|
+
ts.isBinaryExpression(p) &&
|
|
641
|
+
p.left === access &&
|
|
642
|
+
p.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
|
|
643
|
+
p.operatorToken.kind <= ts.SyntaxKind.LastAssignment) {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
if (p !== undefined &&
|
|
647
|
+
(ts.isPrefixUnaryExpression(p) || ts.isPostfixUnaryExpression(p)) &&
|
|
648
|
+
(p.operator === ts.SyntaxKind.PlusPlusToken || p.operator === ts.SyntaxKind.MinusMinusToken)) {
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
return true;
|
|
652
|
+
}
|
|
278
653
|
const RESERVED_KEYS = new Set(["__proto__"]);
|
|
279
654
|
/** The rewrite (see the header). Null = not a recognized bundle shape (or
|
|
280
|
-
* nothing to fix) — serve the file untouched.
|
|
655
|
+
* nothing to fix) — serve the file untouched. A `{ degrade }` answer means
|
|
656
|
+
* the file carries a bundler-interop construct the recognizers cannot
|
|
657
|
+
* finish: the caller reports the PACKAGE as an offender with the reason
|
|
658
|
+
* and the fallback loop islands it — never a failed build. */
|
|
281
659
|
export function rewriteBundlerCjsExports(source, filePath) {
|
|
282
660
|
// Cheap gates before any parse: CJS bundle plumbing leaves textual
|
|
283
661
|
// fingerprints; files without any are never candidates.
|
|
284
662
|
if (!source.includes("__toCommonJS") &&
|
|
663
|
+
!source.includes("__toESM") &&
|
|
285
664
|
!source.includes("__exportStar") &&
|
|
286
665
|
!source.includes("__reExport") &&
|
|
287
666
|
!(source.includes("Object.defineProperty(exports") && source.includes("get"))) {
|
|
@@ -290,11 +669,29 @@ export function rewriteBundlerCjsExports(source, filePath) {
|
|
|
290
669
|
// parents ON: the helper sweep excludes declarator-name self references
|
|
291
670
|
// and the chase reads expression text through the tree
|
|
292
671
|
const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
|
|
293
|
-
// ESM syntax → not CJS; leave alone
|
|
672
|
+
// ESM syntax → not CJS; leave alone — except when the ES module CALLS
|
|
673
|
+
// the __toESM interop helper (esbuild's ESM output around __require of
|
|
674
|
+
// an external): no static story respells that, and served untouched the
|
|
675
|
+
// helper's `var __create = Object.create;` chain fences at MODULE LOAD,
|
|
676
|
+
// so the honest answer is the per-package degrade.
|
|
294
677
|
for (const stmt of sf.statements) {
|
|
295
|
-
if (ts.isImportDeclaration(stmt) || ts.isExportDeclaration(stmt) || ts.isExportAssignment(stmt))
|
|
296
|
-
return
|
|
678
|
+
if (ts.isImportDeclaration(stmt) || ts.isExportDeclaration(stmt) || ts.isExportAssignment(stmt)) {
|
|
679
|
+
return source.includes("__toESM(")
|
|
680
|
+
? {
|
|
681
|
+
degrade: "its ES-module dist routes an external dependency through the __toESM " +
|
|
682
|
+
"bundler-interop helper, which has no static story in ESM output — the " +
|
|
683
|
+
"package serves from the island instead",
|
|
684
|
+
}
|
|
685
|
+
: null;
|
|
686
|
+
}
|
|
297
687
|
}
|
|
688
|
+
// The __toESM interop pass (see the section header): wrapper call sites
|
|
689
|
+
// pad down to the bare require they wrap, module-valued `.default`
|
|
690
|
+
// accesses pad down to their binding, and a file whose interop deviates
|
|
691
|
+
// beyond recognition degrades the package.
|
|
692
|
+
const toEsm = planToEsmInterop(sf);
|
|
693
|
+
if (toEsm.degrade !== null)
|
|
694
|
+
return { degrade: toEsm.degrade };
|
|
298
695
|
const neutralize = [];
|
|
299
696
|
const exportTables = [];
|
|
300
697
|
const definePropEntries = [];
|
|
@@ -428,145 +825,162 @@ export function rewriteBundlerCjsExports(source, filePath) {
|
|
|
428
825
|
continue;
|
|
429
826
|
}
|
|
430
827
|
}
|
|
431
|
-
if (!sawBundleShape)
|
|
432
|
-
return null;
|
|
433
828
|
const lexed = cjsLexedExportsOf(source, filePath);
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
/* Star specs Node actually honors are the lexer's SURVIVING reexports
|
|
437
|
-
* (annotation spreads for esbuild bundles, star-pattern calls for tsc
|
|
438
|
-
* output). The statements walked above contribute load order; specs the
|
|
439
|
-
* lexer dropped (cleared by a later module.exports=) drop here too. */
|
|
440
|
-
const lexerSpecs = new Set(lexed.reexports);
|
|
441
|
-
const starSpecs = starSpecsInOrder.filter((s) => lexerSpecs.has(s));
|
|
442
|
-
for (const s of lexed.reexports) {
|
|
443
|
-
if (!starSpecs.includes(s))
|
|
444
|
-
starSpecs.push(s);
|
|
445
|
-
}
|
|
446
|
-
/* The entry list, first-wins per name: own getter-table entries, then
|
|
447
|
-
* defineProperty entries, then surviving top-level member exports, then
|
|
448
|
-
* (for names still uncovered) star enumerations in spec order, then the
|
|
449
|
-
* any fallback for every remaining lexer-visible name. */
|
|
829
|
+
let emitTable = sawBundleShape && (lexed.exports.size > 0 || lexed.reexports.length > 0);
|
|
830
|
+
const starSpecs = [];
|
|
450
831
|
const entries = new Map();
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
/* Top-level member exports. Node's export-object identity rules decide
|
|
477
|
-
* each statement's fate against the ORIGINAL table (esbuildMainTarget's
|
|
478
|
-
* assignment): a member attached BEFORE it was discarded (the table
|
|
479
|
-
* replaced the object), a bare `exports.N =` AFTER it wrote the stale
|
|
480
|
-
* object — both keep only their lexer-visible NAME (value undefined,
|
|
481
|
-
* exactly Node's link answer: the any fallback below). A surviving
|
|
482
|
-
* member (`module.exports.N =` after the table, or any spelling when no
|
|
483
|
-
* table exists) re-emits its VALUE as a canonical entry — identifier
|
|
484
|
-
* RHS only; any other RHS would need a second evaluation, so the file
|
|
485
|
-
* is left alone (the offender path owns it). Every member statement is
|
|
486
|
-
* then NEUTRALIZED: the canonical tail table replaces the object, and
|
|
487
|
-
* a stray earlier attach would meet the lowering's discard fence. */
|
|
488
|
-
for (const m of memberStmts) {
|
|
489
|
-
let cur = m.finalRhs;
|
|
490
|
-
while (ts.isParenthesizedExpression(cur))
|
|
491
|
-
cur = cur.expression;
|
|
492
|
-
const isVoidInit = cur.kind === ts.SyntaxKind.VoidExpression || (ts.isIdentifier(cur) && cur.text === "undefined");
|
|
493
|
-
const discarded = tablePos !== null && (m.pos < tablePos || m.viaBareExports);
|
|
494
|
-
if (!isVoidInit) {
|
|
495
|
-
// Only side-effect-free RHS shapes may be neutralized (identifiers,
|
|
496
|
-
// scalar literals, ident-rooted member chains — chaseValue's set);
|
|
497
|
-
// anything else keeps the file untouched (the offender path owns
|
|
498
|
-
// it: neutralizing would drop an evaluation, keeping it meets the
|
|
499
|
-
// lowering's discard fence at load).
|
|
500
|
-
const chased = chaseValue(cur);
|
|
832
|
+
const starMembers = [];
|
|
833
|
+
const requireConsts = [];
|
|
834
|
+
if (emitTable) {
|
|
835
|
+
/* Star specs Node actually honors are the lexer's SURVIVING reexports
|
|
836
|
+
* (annotation spreads for esbuild bundles, star-pattern calls for tsc
|
|
837
|
+
* output). The statements walked above contribute load order; specs the
|
|
838
|
+
* lexer dropped (cleared by a later module.exports=) drop here too. */
|
|
839
|
+
const lexerSpecs = new Set(lexed.reexports);
|
|
840
|
+
starSpecs.push(...starSpecsInOrder.filter((s) => lexerSpecs.has(s)));
|
|
841
|
+
for (const s of lexed.reexports) {
|
|
842
|
+
if (!starSpecs.includes(s))
|
|
843
|
+
starSpecs.push(s);
|
|
844
|
+
}
|
|
845
|
+
/* The entry list, first-wins per name: own getter-table entries, then
|
|
846
|
+
* defineProperty entries, then surviving top-level member exports, then
|
|
847
|
+
* (for names still uncovered) star enumerations in spec order, then the
|
|
848
|
+
* any fallback for every remaining lexer-visible name. */
|
|
849
|
+
const claim = (name, spec) => {
|
|
850
|
+
if (name === "__esModule" || RESERVED_KEYS.has(name))
|
|
851
|
+
return;
|
|
852
|
+
if (!entries.has(name))
|
|
853
|
+
entries.set(name, spec);
|
|
854
|
+
};
|
|
855
|
+
const classify = (name, value) => {
|
|
856
|
+
const chased = value !== null ? chaseValue(value) : null;
|
|
501
857
|
if (chased === null)
|
|
502
|
-
return
|
|
503
|
-
if (
|
|
504
|
-
|
|
505
|
-
|
|
858
|
+
return { name, kind: "any" };
|
|
859
|
+
if (chased.kind === "ident")
|
|
860
|
+
return { name, kind: "ident", value: chased.text };
|
|
861
|
+
return { name, kind: "hoist", value: chased.text };
|
|
862
|
+
};
|
|
863
|
+
// Own names: the getter table(s) — the one feeding module.exports first.
|
|
864
|
+
const tablesOrdered = [
|
|
865
|
+
...exportTables.filter((t) => t.target === esbuildMainTarget),
|
|
866
|
+
...exportTables.filter((t) => t.target !== esbuildMainTarget),
|
|
867
|
+
];
|
|
868
|
+
for (const t of tablesOrdered) {
|
|
869
|
+
for (const [name, value] of t.entries)
|
|
870
|
+
claim(name, classify(name, value));
|
|
871
|
+
}
|
|
872
|
+
for (const d of definePropEntries)
|
|
873
|
+
claim(d.name, classify(d.name, d.value));
|
|
874
|
+
/* Top-level member exports. Node's export-object identity rules decide
|
|
875
|
+
* each statement's fate against the ORIGINAL table (esbuildMainTarget's
|
|
876
|
+
* assignment): a member attached BEFORE it was discarded (the table
|
|
877
|
+
* replaced the object), a bare `exports.N =` AFTER it wrote the stale
|
|
878
|
+
* object — both keep only their lexer-visible NAME (value undefined,
|
|
879
|
+
* exactly Node's link answer: the any fallback below). A surviving
|
|
880
|
+
* member (`module.exports.N =` after the table, or any spelling when no
|
|
881
|
+
* table exists) re-emits its VALUE as a canonical entry — identifier
|
|
882
|
+
* RHS only; any other RHS would need a second evaluation, so the file
|
|
883
|
+
* is left alone (the offender path owns it — and when the file also
|
|
884
|
+
* carries erased interop wrappers, "untouched" is not on the menu:
|
|
885
|
+
* the live helper chain would fence at module load, so the package
|
|
886
|
+
* degrades with the note instead). Every member statement is then
|
|
887
|
+
* NEUTRALIZED: the canonical tail table replaces the object, and a
|
|
888
|
+
* stray earlier attach would meet the lowering's discard fence. */
|
|
889
|
+
for (const m of memberStmts) {
|
|
890
|
+
let cur = m.finalRhs;
|
|
891
|
+
while (ts.isParenthesizedExpression(cur))
|
|
892
|
+
cur = cur.expression;
|
|
893
|
+
const isVoidInit = cur.kind === ts.SyntaxKind.VoidExpression || (ts.isIdentifier(cur) && cur.text === "undefined");
|
|
894
|
+
const discarded = tablePos !== null && (m.pos < tablePos || m.viaBareExports);
|
|
895
|
+
if (!isVoidInit) {
|
|
896
|
+
// Only side-effect-free RHS shapes may be neutralized (identifiers,
|
|
897
|
+
// scalar literals, ident-rooted member chains — chaseValue's set);
|
|
898
|
+
// anything else keeps the file untouched (the offender path owns
|
|
899
|
+
// it: neutralizing would drop an evaluation, keeping it meets the
|
|
900
|
+
// lowering's discard fence at load).
|
|
901
|
+
const chased = chaseValue(cur);
|
|
902
|
+
if (chased === null) {
|
|
903
|
+
return toEsm.pads.length > 0 ? { degrade: TO_ESM_MIXED_DEGRADE } : null;
|
|
904
|
+
}
|
|
905
|
+
if (!discarded) {
|
|
906
|
+
for (const name of m.names)
|
|
907
|
+
claim(name, { name, kind: chased.kind, value: chased.text });
|
|
908
|
+
}
|
|
909
|
+
// discarded members keep only their lexer-visible NAME (the any
|
|
910
|
+
// fallback below) — value undefined, exactly Node's link answer
|
|
506
911
|
}
|
|
507
|
-
|
|
508
|
-
// fallback below) — value undefined, exactly Node's link answer
|
|
912
|
+
neutralize.push({ start: m.start, end: m.end });
|
|
509
913
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
immutableTopLevel.add(d.name.text);
|
|
914
|
+
/* Direct-vs-hoist for ident entries: only bindings declared by a
|
|
915
|
+
* top-level const/function/class may be spelled directly (alias
|
|
916
|
+
* plumbing needs an immutable target — a `var`/`let` in the table would
|
|
917
|
+
* fence the whole statement). Everything else snapshots via a tail
|
|
918
|
+
* const. */
|
|
919
|
+
const immutableTopLevel = new Set();
|
|
920
|
+
for (const stmt of sf.statements) {
|
|
921
|
+
if (ts.isFunctionDeclaration(stmt) && stmt.name !== undefined)
|
|
922
|
+
immutableTopLevel.add(stmt.name.text);
|
|
923
|
+
else if (ts.isClassDeclaration(stmt) && stmt.name !== undefined)
|
|
924
|
+
immutableTopLevel.add(stmt.name.text);
|
|
925
|
+
else if (ts.isVariableStatement(stmt) && (stmt.declarationList.flags & ts.NodeFlags.Const) !== 0) {
|
|
926
|
+
for (const d of stmt.declarationList.declarations) {
|
|
927
|
+
if (ts.isIdentifier(d.name))
|
|
928
|
+
immutableTopLevel.add(d.name.text);
|
|
929
|
+
}
|
|
527
930
|
}
|
|
528
931
|
}
|
|
932
|
+
for (const spec of entries.values()) {
|
|
933
|
+
if (spec.kind === "ident" && !immutableTopLevel.has(spec.value))
|
|
934
|
+
spec.kind = "hoist";
|
|
935
|
+
}
|
|
936
|
+
// Star enumerations for names the own entries left uncovered.
|
|
937
|
+
starSpecs.forEach((spec, i) => {
|
|
938
|
+
const target = resolveRelativeCjs(filePath, spec);
|
|
939
|
+
if (target === null)
|
|
940
|
+
return; // bare/unresolved: the spread entry carries it
|
|
941
|
+
const names = starTargetNames(target);
|
|
942
|
+
let refVar = null;
|
|
943
|
+
for (const n of names) {
|
|
944
|
+
if (n === "default" || n === "__esModule" || entries.has(n) || RESERVED_KEYS.has(n))
|
|
945
|
+
continue;
|
|
946
|
+
if (starMembers.some((m) => m.name === n))
|
|
947
|
+
continue; // earlier spec wins
|
|
948
|
+
if (refVar === null) {
|
|
949
|
+
refVar = `__scriptc_r${i}`;
|
|
950
|
+
requireConsts.push(`const ${refVar} = require(${JSON.stringify(spec)});`);
|
|
951
|
+
}
|
|
952
|
+
starMembers.push({ name: n, ref: refVar });
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
// The any fallback: every lexer-visible name nothing above covered.
|
|
956
|
+
for (const n of lexed.exports)
|
|
957
|
+
claim(n, { name: n, kind: "any" });
|
|
958
|
+
emitTable = entries.size > 0 || starSpecs.length > 0;
|
|
529
959
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
960
|
+
/* A file with interop pads but no canonical table still rewrites: the
|
|
961
|
+
* pads erase the __toESM wrappers and the sweep below drops the dead
|
|
962
|
+
* helper chain — but nothing replaces the export plumbing, so none of
|
|
963
|
+
* it may be neutralized (the surviving text keeps exactly its
|
|
964
|
+
* untouched-file behavior). */
|
|
965
|
+
if (!emitTable) {
|
|
966
|
+
if (toEsm.pads.length === 0)
|
|
967
|
+
return null;
|
|
968
|
+
neutralize.length = 0;
|
|
533
969
|
}
|
|
534
|
-
//
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
const target = resolveRelativeCjs(filePath, spec);
|
|
539
|
-
if (target === null)
|
|
540
|
-
return; // bare/unresolved: the spread entry carries it
|
|
541
|
-
const names = starTargetNames(target);
|
|
542
|
-
let refVar = null;
|
|
543
|
-
for (const n of names) {
|
|
544
|
-
if (n === "default" || n === "__esModule" || entries.has(n) || RESERVED_KEYS.has(n))
|
|
545
|
-
continue;
|
|
546
|
-
if (starMembers.some((m) => m.name === n))
|
|
547
|
-
continue; // earlier spec wins
|
|
548
|
-
if (refVar === null) {
|
|
549
|
-
refVar = `__scriptc_r${i}`;
|
|
550
|
-
requireConsts.push(`const ${refVar} = require(${JSON.stringify(spec)});`);
|
|
551
|
-
}
|
|
552
|
-
starMembers.push({ name: n, ref: refVar });
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
// The any fallback: every lexer-visible name nothing above covered.
|
|
556
|
-
for (const n of lexed.exports)
|
|
557
|
-
claim(n, { name: n, kind: "any" });
|
|
558
|
-
if (entries.size === 0 && starSpecs.length === 0)
|
|
559
|
-
return null;
|
|
970
|
+
// Interop pads join the neutralized set BEFORE the sweep: the erased
|
|
971
|
+
// __toESM call sites read as dead references, so the helper declaration
|
|
972
|
+
// sweeps away and its __create/__getProtoOf chain follows.
|
|
973
|
+
neutralize.push(...toEsm.pads);
|
|
560
974
|
/* Bundler HELPPER declarations left unused once the plumbing statements
|
|
561
975
|
* are gone would still fence at module load (`var __defProp =
|
|
562
976
|
* Object.defineProperty;` — a stdlib generic method as a value has no
|
|
563
977
|
* lowering, and a top-level declaration's fence throws when init runs
|
|
564
978
|
* it). Mark-and-sweep the RECOGNIZED helper names: a helper whose every
|
|
565
979
|
* reference lies inside an already-neutralized span (the plumbing
|
|
566
|
-
* calls, or another swept helper's
|
|
567
|
-
* Helpers live code still calls
|
|
568
|
-
* chunk factories) stay — their declarations are plain
|
|
569
|
-
* whose bodies defer per the JS function-poison rule. */
|
|
980
|
+
* calls, the erased __toESM interop wrappers, or another swept helper's
|
|
981
|
+
* declaration) is neutralized too. Helpers live code still calls
|
|
982
|
+
* (__commonJS chunk factories) stay — their declarations are plain
|
|
983
|
+
* function values whose bodies defer per the JS function-poison rule. */
|
|
570
984
|
{
|
|
571
985
|
const HELPER_NAMES = new Set([
|
|
572
986
|
"__create", "__defProp", "__defProps", "__getOwnPropDesc", "__getOwnPropDescs",
|
|
@@ -631,6 +1045,8 @@ export function rewriteBundlerCjsExports(source, filePath) {
|
|
|
631
1045
|
for (const n of [...neutralize].sort((a, b) => b.start - a.start)) {
|
|
632
1046
|
text = padSpan(text, n.start, n.end);
|
|
633
1047
|
}
|
|
1048
|
+
if (!emitTable)
|
|
1049
|
+
return text; // interop pads only — nothing appends
|
|
634
1050
|
const lines = [""];
|
|
635
1051
|
const hasAnyFallback = [...entries.values()].some((s) => s.kind === "any");
|
|
636
1052
|
if (hasAnyFallback) {
|
|
@@ -642,6 +1058,18 @@ export function rewriteBundlerCjsExports(source, filePath) {
|
|
|
642
1058
|
const hoists = [];
|
|
643
1059
|
let hoistN = 0;
|
|
644
1060
|
const keyOf = (name) => (isPlainName(name) ? name : JSON.stringify(name));
|
|
1061
|
+
/* Hoist texts are COPIED off the original source, where a `.default`
|
|
1062
|
+
* access on a module-valued interop binding still carries its member
|
|
1063
|
+
* spelling (the pads erase only source spans) — drop it here exactly
|
|
1064
|
+
* like the pads do, or the tail const would read a member the module
|
|
1065
|
+
* never exports. */
|
|
1066
|
+
const dropInteropDefault = (text) => {
|
|
1067
|
+
let out = text;
|
|
1068
|
+
for (const b of toEsm.moduleBindings) {
|
|
1069
|
+
out = out.replace(new RegExp(`\\b${b}\\s*\\.\\s*default\\b`, "g"), b);
|
|
1070
|
+
}
|
|
1071
|
+
return out;
|
|
1072
|
+
};
|
|
645
1073
|
const plainParts = [];
|
|
646
1074
|
for (const spec of entries.values()) {
|
|
647
1075
|
if (spec.kind === "ident") {
|
|
@@ -649,7 +1077,7 @@ export function rewriteBundlerCjsExports(source, filePath) {
|
|
|
649
1077
|
}
|
|
650
1078
|
else if (spec.kind === "hoist") {
|
|
651
1079
|
const v = `__scriptc_e${hoistN++}`;
|
|
652
|
-
hoists.push(`const ${v} = ${spec.value};`);
|
|
1080
|
+
hoists.push(`const ${v} = ${dropInteropDefault(spec.value)};`);
|
|
653
1081
|
plainParts.push(`${keyOf(spec.name)}: ${v},`);
|
|
654
1082
|
}
|
|
655
1083
|
else {
|