xbintsc 0.3.18 → 0.3.35
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 +33 -0
- package/dist/src/cli/main.js +124 -30
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/calls/access.js +8 -1
- package/dist/src/codegen/generator/calls/access.js.map +1 -1
- package/dist/src/codegen/generator/state.js +20 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +6 -0
- package/dist/src/codegen/generator/tables.js +38 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/commonjs.d.ts +52 -0
- package/dist/src/driver/bundler/commonjs.js +493 -0
- package/dist/src/driver/bundler/commonjs.js.map +1 -0
- package/dist/src/driver/bundler/graph.d.ts +2 -1
- package/dist/src/driver/bundler/graph.js +32 -3
- package/dist/src/driver/bundler/graph.js.map +1 -1
- package/dist/src/driver/bundler/merge.d.ts +2 -1
- package/dist/src/driver/bundler/merge.js +16 -2
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/bundler/types.d.ts +8 -0
- package/dist/src/driver/compiler.d.ts +8 -1
- package/dist/src/driver/compiler.js +75 -7
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/config.d.ts +53 -0
- package/dist/src/driver/config.js +141 -0
- package/dist/src/driver/config.js.map +1 -0
- package/dist/src/driver/icon.d.ts +62 -0
- package/dist/src/driver/icon.js +151 -0
- package/dist/src/driver/icon.js.map +1 -0
- package/dist/src/driver/index.d.ts +4 -0
- package/dist/src/driver/index.js +4 -0
- package/dist/src/driver/index.js.map +1 -1
- package/dist/src/driver/mac-bundle.d.ts +41 -0
- package/dist/src/driver/mac-bundle.js +123 -0
- package/dist/src/driver/mac-bundle.js.map +1 -0
- package/dist/src/driver/win-icon.d.ts +27 -0
- package/dist/src/driver/win-icon.js +80 -0
- package/dist/src/driver/win-icon.js.map +1 -0
- package/dist/src/extensions/gui/html.d.ts +25 -0
- package/dist/src/extensions/gui/html.js +203 -0
- package/dist/src/extensions/gui/html.js.map +1 -0
- package/dist/src/extensions/gui/index.js +11 -0
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/registry.d.ts +29 -0
- package/dist/src/extensions/registry.js +14 -0
- package/dist/src/extensions/registry.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/lexer/scanner/identifiers.js +4 -12
- package/dist/src/lexer/scanner/identifiers.js.map +1 -1
- package/dist/src/parser/statements.js +17 -4
- package/dist/src/parser/statements.js.map +1 -1
- package/dist/tests/cli/main.test.js +46 -1
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/driver/bundler-resolve.test.d.ts +1 -0
- package/dist/tests/driver/bundler-resolve.test.js +230 -0
- package/dist/tests/driver/bundler-resolve.test.js.map +1 -0
- package/dist/tests/driver/config.test.d.ts +1 -0
- package/dist/tests/driver/config.test.js +124 -0
- package/dist/tests/driver/config.test.js.map +1 -0
- package/dist/tests/driver/icon.test.d.ts +1 -0
- package/dist/tests/driver/icon.test.js +421 -0
- package/dist/tests/driver/icon.test.js.map +1 -0
- package/dist/tests/driver/modules.test.js +177 -0
- package/dist/tests/driver/modules.test.js.map +1 -1
- package/dist/tests/e2e/fetch.test.d.ts +12 -0
- package/dist/tests/e2e/fetch.test.js +272 -0
- package/dist/tests/e2e/fetch.test.js.map +1 -0
- package/dist/tests/e2e/gui-dom.test.d.ts +5 -0
- package/dist/tests/e2e/gui-dom.test.js +102 -0
- package/dist/tests/e2e/gui-dom.test.js.map +1 -0
- package/dist/tests/e2e/gui-events.test.d.ts +5 -0
- package/dist/tests/e2e/gui-events.test.js +60 -0
- package/dist/tests/e2e/gui-events.test.js.map +1 -0
- package/dist/tests/e2e/gui-helpers.d.ts +31 -0
- package/dist/tests/e2e/gui-helpers.js +74 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -0
- package/dist/tests/e2e/gui-layout.test.d.ts +4 -0
- package/dist/tests/e2e/gui-layout.test.js +88 -0
- package/dist/tests/e2e/gui-layout.test.js.map +1 -0
- package/dist/tests/e2e/gui-lifecycle.test.d.ts +5 -0
- package/dist/tests/e2e/gui-lifecycle.test.js +58 -0
- package/dist/tests/e2e/gui-lifecycle.test.js.map +1 -0
- package/dist/tests/e2e/gui-paint.test.d.ts +5 -0
- package/dist/tests/e2e/gui-paint.test.js +111 -0
- package/dist/tests/e2e/gui-paint.test.js.map +1 -0
- package/dist/tests/e2e/gui-script.test.d.ts +5 -0
- package/dist/tests/e2e/gui-script.test.js +78 -0
- package/dist/tests/e2e/gui-script.test.js.map +1 -0
- package/dist/tests/e2e/gui-style.test.d.ts +5 -0
- package/dist/tests/e2e/gui-style.test.js +175 -0
- package/dist/tests/e2e/gui-style.test.js.map +1 -0
- package/dist/tests/e2e/icon.test.d.ts +7 -0
- package/dist/tests/e2e/icon.test.js +80 -0
- package/dist/tests/e2e/icon.test.js.map +1 -0
- package/dist/tests/e2e/npm-modules.test.d.ts +4 -1
- package/dist/tests/e2e/npm-modules.test.js +47 -1
- package/dist/tests/e2e/npm-modules.test.js.map +1 -1
- package/dist/tests/e2e/runtime-coverage-typed-array.test.d.ts +9 -0
- package/dist/tests/e2e/runtime-coverage-typed-array.test.js +140 -0
- package/dist/tests/e2e/runtime-coverage-typed-array.test.js.map +1 -0
- package/dist/tests/extensions/gui-index.test.d.ts +1 -0
- package/dist/tests/extensions/gui-index.test.js +49 -0
- package/dist/tests/extensions/gui-index.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.d.ts +1 -0
- package/dist/tests/extensions/gui.test.js +177 -0
- package/dist/tests/extensions/gui.test.js.map +1 -0
- package/dist/tests/extensions/registry.test.js +36 -0
- package/dist/tests/extensions/registry.test.js.map +1 -1
- package/dist/tests/parser/statements.test.js +11 -0
- package/dist/tests/parser/statements.test.js.map +1 -1
- package/dist/tests/public-api.test.d.ts +1 -0
- package/dist/tests/public-api.test.js +31 -0
- package/dist/tests/public-api.test.js.map +1 -0
- package/package.json +1 -1
- package/runtime/ext_gui/css.cpp +4 -517
- package/runtime/ext_gui/css_internal.h +27 -0
- package/runtime/ext_gui/css_match.cpp +195 -0
- package/runtime/ext_gui/css_selector.cpp +207 -0
- package/runtime/ext_gui/css_sheet.cpp +129 -0
- package/runtime/ext_gui/document.cpp +90 -0
- package/runtime/ext_gui/document.h +23 -0
- package/runtime/ext_gui/dom.cpp +43 -403
- package/runtime/ext_gui/dom.h +7 -0
- package/runtime/ext_gui/dom_api.cpp +164 -0
- package/runtime/ext_gui/dom_api.h +12 -0
- package/runtime/ext_gui/dom_api_document.cpp +96 -0
- package/runtime/ext_gui/dom_api_events.cpp +123 -0
- package/runtime/ext_gui/dom_api_internal.h +135 -0
- package/runtime/ext_gui/dom_api_node.cpp +329 -0
- package/runtime/ext_gui/dom_api_node_read.cpp +294 -0
- package/runtime/ext_gui/dom_api_proto.cpp +130 -0
- package/runtime/ext_gui/dom_api_style.cpp +192 -0
- package/runtime/ext_gui/dom_internal.h +39 -0
- package/runtime/ext_gui/dom_parse.cpp +348 -0
- package/runtime/ext_gui/dom_serialize.cpp +54 -0
- package/runtime/ext_gui/gui.cpp +61 -245
- package/runtime/ext_gui/gui_engine.h +54 -0
- package/runtime/ext_gui/gui_events.cpp +294 -0
- package/runtime/ext_gui/gui_internal.h +29 -0
- package/runtime/ext_gui/layout.cpp +6 -597
- package/runtime/ext_gui/layout_flex.cpp +200 -0
- package/runtime/ext_gui/layout_flow.cpp +191 -0
- package/runtime/ext_gui/layout_flow_internal.h +149 -0
- package/runtime/ext_gui/layout_inline.cpp +181 -0
- package/runtime/ext_gui/layout_internal.h +20 -0
- package/runtime/ext_gui/renderer.cpp +15 -679
- package/runtime/ext_gui/renderer_image.cpp +233 -0
- package/runtime/ext_gui/renderer_internal.h +112 -0
- package/runtime/ext_gui/renderer_shaders.h +108 -0
- package/runtime/ext_gui/renderer_text.cpp +342 -0
- package/runtime/ext_gui/script.cpp +63 -0
- package/runtime/ext_gui/style.cpp +12 -660
- package/runtime/ext_gui/style_internal.h +33 -0
- package/runtime/ext_gui/style_print.cpp +113 -0
- package/runtime/ext_gui/style_ua.cpp +98 -0
- package/runtime/ext_gui/style_values.cpp +468 -0
- package/runtime/ext_gui/window.cpp +60 -0
- package/runtime/rt.h +6 -0
- package/runtime/rt_internal.h +4 -0
- package/runtime/xt_builtins.c +1 -0
- package/runtime/xt_containers/access.inc +32 -1
- package/runtime/xt_fetch/buffer.inc +64 -0
- package/runtime/xt_fetch/headers.inc +312 -0
- package/runtime/xt_fetch/request.inc +386 -0
- package/runtime/xt_fetch/response.inc +107 -0
- package/runtime/xt_fetch/socket.inc +72 -0
- package/runtime/xt_fetch/url.inc +108 -0
- package/runtime/xt_fetch.c +39 -0
- package/runtime/xt_stdlib2/set.inc +1 -1
- package/runtime/xt_typed_array.c +614 -0
- package/src/cli/main.ts +137 -29
- package/src/codegen/generator/calls/access.ts +9 -1
- package/src/codegen/generator/state.ts +20 -0
- package/src/codegen/generator/tables.ts +40 -0
- package/src/driver/bundler/commonjs.ts +553 -0
- package/src/driver/bundler/graph.ts +35 -2
- package/src/driver/bundler/merge.ts +18 -1
- package/src/driver/bundler/types.ts +8 -0
- package/src/driver/compiler.ts +85 -7
- package/src/driver/config.ts +173 -0
- package/src/driver/icon.ts +184 -0
- package/src/driver/index.ts +30 -0
- package/src/driver/mac-bundle.ts +156 -0
- package/src/driver/win-icon.ts +96 -0
- package/src/extensions/gui/html.ts +236 -0
- package/src/extensions/gui/index.ts +11 -0
- package/src/extensions/registry.ts +39 -0
- package/src/index.ts +1 -1
- package/src/lexer/scanner/identifiers.ts +4 -11
- package/src/parser/statements.ts +17 -4
- package/dist/tests/e2e/gui.test.d.ts +0 -12
- package/dist/tests/e2e/gui.test.js +0 -493
- package/dist/tests/e2e/gui.test.js.map +0 -1
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CommonJS compatibility for `node_modules`.
|
|
3
|
+
*
|
|
4
|
+
* xbintsc is ESM-first: relative `.ts` files and ESM packages are bundled into
|
|
5
|
+
* one module and `require()` in user code is rejected. npm, however, still
|
|
6
|
+
* ships a lot of CommonJS. This pass lets a CommonJS file *inside
|
|
7
|
+
* `node_modules`* keep working by lowering its free `module` / `exports` /
|
|
8
|
+
* `require` references to per-module synthetic bindings:
|
|
9
|
+
*
|
|
10
|
+
* const <prefix>$cjs_module = { exports: {} };
|
|
11
|
+
* const <prefix>$cjs_exports = <prefix>$cjs_module.exports;
|
|
12
|
+
*
|
|
13
|
+
* `module.exports = x` and `exports.x = y` then behave as they do in Node, and
|
|
14
|
+
* `require("x")` is rewritten to the already-bundled dependency's exports
|
|
15
|
+
* object. User code (anything outside `node_modules`) is left untouched, so
|
|
16
|
+
* the code generator still reports its `require` use.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { dirname } from "node:path";
|
|
20
|
+
import {
|
|
21
|
+
SyntaxKind,
|
|
22
|
+
type BindingElement,
|
|
23
|
+
type CallExpression,
|
|
24
|
+
type Expression,
|
|
25
|
+
type Identifier,
|
|
26
|
+
type ImportDeclaration,
|
|
27
|
+
type ImportSpecifier,
|
|
28
|
+
type NamedImports,
|
|
29
|
+
type NamespaceImport,
|
|
30
|
+
type Node,
|
|
31
|
+
type ObjectBindingPattern,
|
|
32
|
+
type ObjectLiteralExpression,
|
|
33
|
+
type ObjectLiteralElementLike,
|
|
34
|
+
type PropertyAccessExpression,
|
|
35
|
+
type SourceFileNode,
|
|
36
|
+
type Statement,
|
|
37
|
+
type StringLiteral,
|
|
38
|
+
type VariableDeclaration,
|
|
39
|
+
type VariableDeclarationList,
|
|
40
|
+
type VariableStatement,
|
|
41
|
+
} from "../../ast/nodes.js";
|
|
42
|
+
import type { BindResult } from "../../binder/binder.js";
|
|
43
|
+
import type { DiagnosticBag } from "../../diagnostics/diagnostic.js";
|
|
44
|
+
import { DiagnosticCode } from "../../diagnostics/diagnostic.js";
|
|
45
|
+
import { moduleScope, renameReferences } from "./symbols.js";
|
|
46
|
+
import type { DependencyResolution, ModuleRecord } from "./types.js";
|
|
47
|
+
|
|
48
|
+
/** The specifier of every `require("...")` call in a module. */
|
|
49
|
+
export interface RequireSite {
|
|
50
|
+
/** The call node, for the diagnostic location. */
|
|
51
|
+
readonly node: CallExpression;
|
|
52
|
+
/** The string literal argument's value. */
|
|
53
|
+
readonly specifier: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** True when a path lives under a `node_modules` directory. */
|
|
57
|
+
export function isNodeModulesPath(path: string): boolean {
|
|
58
|
+
return path.split(/[\\/]+/).includes("node_modules");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** True when a module references `require`, `module` or `exports` freely. */
|
|
62
|
+
export function usesCommonJS(bind: BindResult): boolean {
|
|
63
|
+
return bind.unresolved.some(
|
|
64
|
+
(identifier) =>
|
|
65
|
+
identifier.text === "require" || identifier.text === "module" || identifier.text === "exports",
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A `require` identifier is CommonJS only when it has no binding; a local
|
|
71
|
+
* `function require() {}` or parameter must not be treated as the CJS hook.
|
|
72
|
+
*/
|
|
73
|
+
function isFreeRequire(identifier: Identifier, bind: BindResult): boolean {
|
|
74
|
+
return identifier.text === "require" && !bind.symbolOfIdentifier.get(identifier);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Every `require("<literal>")` call in a module. */
|
|
78
|
+
export function collectRequireSites(sourceFile: SourceFileNode, bind: BindResult): RequireSite[] {
|
|
79
|
+
const sites: RequireSite[] = [];
|
|
80
|
+
forEachNode(sourceFile, (node) => {
|
|
81
|
+
if (node.kind !== SyntaxKind.CallExpression) return;
|
|
82
|
+
const call = node as CallExpression;
|
|
83
|
+
const callee = call.expression;
|
|
84
|
+
if (callee.kind !== SyntaxKind.Identifier) return;
|
|
85
|
+
if (!isFreeRequire(callee as Identifier, bind)) return;
|
|
86
|
+
const first = call.arguments[0];
|
|
87
|
+
if (first && first.kind === SyntaxKind.StringLiteral) {
|
|
88
|
+
sites.push({ node: call, specifier: (first as StringLiteral).value });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return sites;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface CommonJSLoweringContext {
|
|
95
|
+
readonly resolve: (fromDir: string, specifier: string) => DependencyResolution;
|
|
96
|
+
readonly recordFor: (path: string) => ModuleRecord | undefined;
|
|
97
|
+
readonly diagnostics: DiagnosticBag;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Phase A: give a CommonJS module its `module` / `exports` state, rewrite the
|
|
102
|
+
* free references, and record its export surface (whole-object `default` plus
|
|
103
|
+
* every syntactically detectable named export). Runs before the ESM export
|
|
104
|
+
* collection so re-exports of a CommonJS module resolve.
|
|
105
|
+
*/
|
|
106
|
+
export function prepareCommonJS(record: ModuleRecord): void {
|
|
107
|
+
if (!record.commonjs) return;
|
|
108
|
+
const statements = record.sourceFile.statements as Statement[];
|
|
109
|
+
const anchor = statements[0]?.start ?? record.sourceFile.start;
|
|
110
|
+
|
|
111
|
+
const moduleName = `${record.prefix}$cjs_module`;
|
|
112
|
+
const exportsName = `${record.prefix}$cjs_exports`;
|
|
113
|
+
record.cjsModule = moduleName;
|
|
114
|
+
|
|
115
|
+
const hasExports = record.bind.unresolved.some((identifier) => identifier.text === "exports");
|
|
116
|
+
const hasModule = record.bind.unresolved.some((identifier) => identifier.text === "module");
|
|
117
|
+
// Detect exports before rewriting `module`/`exports`, since the detection
|
|
118
|
+
// matches on their original names.
|
|
119
|
+
const exportNames = detectExportNames(record.sourceFile, record.bind);
|
|
120
|
+
for (const identifier of record.bind.unresolved) {
|
|
121
|
+
if (identifier.text === "module" && hasModule) (identifier as { text: string }).text = moduleName;
|
|
122
|
+
else if (identifier.text === "exports" && hasExports) (identifier as { text: string }).text = exportsName;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const prefixStatements: Statement[] = [
|
|
126
|
+
variableStatement(moduleName, objectLiteral([propertyAssignment("exports", objectLiteral([]))]), anchor),
|
|
127
|
+
];
|
|
128
|
+
prefixStatements.push(variableStatement(exportsName, member(moduleName, "exports", anchor), anchor));
|
|
129
|
+
|
|
130
|
+
const suffixStatements: Statement[] = [];
|
|
131
|
+
const defaultName = `${record.prefix}$cjs_default`;
|
|
132
|
+
suffixStatements.push(variableStatement(defaultName, member(moduleName, "exports", anchor), anchor));
|
|
133
|
+
record.exports.set("default", defaultName);
|
|
134
|
+
for (const name of exportNames) {
|
|
135
|
+
const local = `${record.prefix}$cjs_${name}`;
|
|
136
|
+
suffixStatements.push(variableStatement(local, member(member(moduleName, "exports", anchor), name, anchor), anchor));
|
|
137
|
+
record.exports.set(name, local);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
statements.unshift(...prefixStatements);
|
|
141
|
+
statements.push(...suffixStatements);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Phase B: rewrite every `require("x")` call to the bundled dependency's
|
|
146
|
+
* exports. Runs after the ESM export collection because a required ESM module
|
|
147
|
+
* is exposed as a synthetic namespace object.
|
|
148
|
+
*/
|
|
149
|
+
export function lowerCommonJSRequires(record: ModuleRecord, context: CommonJSLoweringContext): void {
|
|
150
|
+
if (!record.commonjs) return;
|
|
151
|
+
const statements = record.sourceFile.statements as Statement[];
|
|
152
|
+
const anchor = statements[0]?.start ?? record.sourceFile.start;
|
|
153
|
+
const imports: Statement[] = [];
|
|
154
|
+
const namespaces: Statement[] = [];
|
|
155
|
+
let counter = 0;
|
|
156
|
+
|
|
157
|
+
// Before rewriting requires, turn `var x = require("node:fs")` into an ESM
|
|
158
|
+
// namespace import. Extension/built-in modules are not first-class values in
|
|
159
|
+
// the code generator, so binding one to a local name has to make the local
|
|
160
|
+
// name an import binding rather than an assignment of the module object.
|
|
161
|
+
hoistExternalNamespaceRequires(record, context, statements, anchor);
|
|
162
|
+
|
|
163
|
+
const replace = (node: Node): Node | undefined => {
|
|
164
|
+
if (node.kind !== SyntaxKind.CallExpression) return undefined;
|
|
165
|
+
const call = node as CallExpression;
|
|
166
|
+
const callee = call.expression;
|
|
167
|
+
if (callee.kind !== SyntaxKind.Identifier) return undefined;
|
|
168
|
+
if (!isFreeRequire(callee as Identifier, record.bind)) return undefined;
|
|
169
|
+
const first = call.arguments[0];
|
|
170
|
+
if (!first || first.kind !== SyntaxKind.StringLiteral) {
|
|
171
|
+
context.diagnostics.error(
|
|
172
|
+
DiagnosticCode.UnsupportedFeature,
|
|
173
|
+
"CommonJS `require()` with a non-literal argument is not supported",
|
|
174
|
+
node,
|
|
175
|
+
record.path,
|
|
176
|
+
);
|
|
177
|
+
return undefinedLiteral(anchor);
|
|
178
|
+
}
|
|
179
|
+
const specifier = (first as StringLiteral).value;
|
|
180
|
+
const resolution = context.resolve(dirname(record.path), specifier);
|
|
181
|
+
if (resolution.kind === "missing") {
|
|
182
|
+
context.diagnostics.error(
|
|
183
|
+
DiagnosticCode.CodegenError,
|
|
184
|
+
`Cannot resolve module '${specifier}' required from '${record.path}'`,
|
|
185
|
+
node,
|
|
186
|
+
record.path,
|
|
187
|
+
);
|
|
188
|
+
return undefinedLiteral(anchor);
|
|
189
|
+
}
|
|
190
|
+
if (resolution.kind === "external") {
|
|
191
|
+
const name = `${record.prefix}$ext_${counter++}`;
|
|
192
|
+
imports.push(defaultImport(name, specifier, anchor));
|
|
193
|
+
return identifier(name, anchor);
|
|
194
|
+
}
|
|
195
|
+
const dependency = context.recordFor(resolution.path);
|
|
196
|
+
if (!dependency) {
|
|
197
|
+
context.diagnostics.error(
|
|
198
|
+
DiagnosticCode.CodegenError,
|
|
199
|
+
`Cannot resolve module '${specifier}' required from '${record.path}'`,
|
|
200
|
+
node,
|
|
201
|
+
record.path,
|
|
202
|
+
);
|
|
203
|
+
return undefinedLiteral(anchor);
|
|
204
|
+
}
|
|
205
|
+
if (dependency.commonjs && dependency.cjsModule) {
|
|
206
|
+
return member(dependency.cjsModule, "exports", anchor);
|
|
207
|
+
}
|
|
208
|
+
// An ESM dependency is exposed to `require` as a namespace object.
|
|
209
|
+
const name = `${record.prefix}$req_${counter++}`;
|
|
210
|
+
namespaces.push(variableStatement(name, namespaceObject(dependency, anchor), anchor));
|
|
211
|
+
return identifier(name, anchor);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
for (const statement of statements) replaceInTree(statement, replace);
|
|
215
|
+
if (imports.length > 0 || namespaces.length > 0) statements.unshift(...imports, ...namespaces);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Statically detect a CommonJS module's named exports so ESM consumers can
|
|
220
|
+
* import them: `exports.x = ...`, `module.exports.x = ...`, an object-literal
|
|
221
|
+
* `module.exports = { ... }`, and the `Object.defineProperty`/`Object.assign`
|
|
222
|
+
* forms.
|
|
223
|
+
*/
|
|
224
|
+
function detectExportNames(sourceFile: SourceFileNode, bind: BindResult): string[] {
|
|
225
|
+
const names = new Set<string>();
|
|
226
|
+
forEachNode(sourceFile, (node) => {
|
|
227
|
+
if (node.kind === SyntaxKind.BinaryExpression) {
|
|
228
|
+
const assignment = node as unknown as { left: Expression; operator: string; right: Expression };
|
|
229
|
+
if (assignment.operator !== "=") return;
|
|
230
|
+
const name = namedExportTarget(assignment.left, bind);
|
|
231
|
+
if (name) names.add(name);
|
|
232
|
+
if (isModuleExports(assignment.left, bind) && assignment.right.kind === SyntaxKind.ObjectLiteralExpression) {
|
|
233
|
+
for (const property of (assignment.right as ObjectLiteralExpression).properties) {
|
|
234
|
+
const key = literalPropertyName(property);
|
|
235
|
+
if (key) names.add(key);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
} else if (node.kind === SyntaxKind.CallExpression) {
|
|
239
|
+
const call = node as CallExpression;
|
|
240
|
+
const callee = call.expression;
|
|
241
|
+
if (callee.kind !== SyntaxKind.PropertyAccessExpression) return;
|
|
242
|
+
const access = callee as PropertyAccessExpression;
|
|
243
|
+
if (access.expression.kind !== SyntaxKind.Identifier) return;
|
|
244
|
+
const method = access.name.text;
|
|
245
|
+
const target = call.arguments[0];
|
|
246
|
+
const value = call.arguments[1];
|
|
247
|
+
if (access.expression.kind === SyntaxKind.Identifier && access.expression.text === "Object") {
|
|
248
|
+
if (method === "defineProperty" && target && value && value.kind === SyntaxKind.StringLiteral) {
|
|
249
|
+
if (isModuleExportsOrExports(target, bind)) names.add((value as StringLiteral).value);
|
|
250
|
+
} else if (method === "assign" && target && isModuleExportsOrExports(target, bind) && value) {
|
|
251
|
+
if (value.kind === SyntaxKind.ObjectLiteralExpression) {
|
|
252
|
+
for (const property of (value as ObjectLiteralExpression).properties) {
|
|
253
|
+
const key = literalPropertyName(property);
|
|
254
|
+
if (key) names.add(key);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
// Keep only names that are safe to snapshot via `.name` access.
|
|
262
|
+
return [...names].filter((name) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && name !== "default");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** The property name assigned through `exports.x` / `module.exports.x`. */
|
|
266
|
+
function namedExportTarget(left: Expression, bind: BindResult): string | undefined {
|
|
267
|
+
if (left.kind !== SyntaxKind.PropertyAccessExpression) return undefined;
|
|
268
|
+
const access = left as PropertyAccessExpression;
|
|
269
|
+
const owner = access.expression;
|
|
270
|
+
if (isModuleExportsOrExports(owner, bind)) return access.name.text;
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function isModuleExportsOrExports(expression: Expression, bind: BindResult): boolean {
|
|
275
|
+
if (expression.kind !== SyntaxKind.Identifier) {
|
|
276
|
+
if (expression.kind !== SyntaxKind.PropertyAccessExpression) return false;
|
|
277
|
+
return isModuleExports(expression, bind);
|
|
278
|
+
}
|
|
279
|
+
const identifier = expression as Identifier;
|
|
280
|
+
return identifier.text === "exports" && !bind.symbolOfIdentifier.get(identifier);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function isModuleExports(expression: Expression, bind: BindResult): boolean {
|
|
284
|
+
if (expression.kind !== SyntaxKind.PropertyAccessExpression) return false;
|
|
285
|
+
const access = expression as PropertyAccessExpression;
|
|
286
|
+
if (access.name.text !== "exports") return false;
|
|
287
|
+
const owner = access.expression;
|
|
288
|
+
if (owner.kind !== SyntaxKind.Identifier) return false;
|
|
289
|
+
const identifier = owner as Identifier;
|
|
290
|
+
return identifier.text === "module" && !bind.symbolOfIdentifier.get(identifier);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function literalPropertyName(property: ObjectLiteralElementLike): string | undefined {
|
|
294
|
+
if (property.kind === SyntaxKind.ShorthandPropertyAssignment) {
|
|
295
|
+
return (property as unknown as { name: Identifier }).name.text;
|
|
296
|
+
}
|
|
297
|
+
if (property.kind === SyntaxKind.PropertyAssignment) {
|
|
298
|
+
const name = (property as unknown as { name: Node }).name;
|
|
299
|
+
if (name.kind === SyntaxKind.Identifier || name.kind === SyntaxKind.StringLiteral) {
|
|
300
|
+
return (name as unknown as { text?: string; value?: string }).text ?? (name as StringLiteral).value;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Build `{ default: <dep default>, ...named }` for a required ESM module. */
|
|
307
|
+
function namespaceObject(dependency: ModuleRecord, anchor: number): ObjectLiteralExpression {
|
|
308
|
+
const properties: ObjectLiteralElementLike[] = [];
|
|
309
|
+
const names = [...dependency.exports.keys()].filter((name) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name));
|
|
310
|
+
for (const name of ["default", ...names.filter((name) => name !== "default")]) {
|
|
311
|
+
const target = dependency.exports.get(name);
|
|
312
|
+
if (target) properties.push(propertyAssignment(name, identifier(target, anchor)));
|
|
313
|
+
}
|
|
314
|
+
return objectLiteral(properties);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// Generic, allocation-light AST helpers (constructed nodes carry no trivia).
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
function isNode(value: unknown): value is Node {
|
|
322
|
+
return !!value && typeof value === "object" && "kind" in (value as object);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Pre-order walk over every child node. */
|
|
326
|
+
function forEachNode(root: Node, visit: (node: Node) => void): void {
|
|
327
|
+
visit(root);
|
|
328
|
+
const record = root as unknown as Record<string, unknown>;
|
|
329
|
+
for (const key of Object.keys(record)) {
|
|
330
|
+
if (key === "kind" || key === "start" || key === "end" || key === "text" || key === "value" || key === "raw") continue;
|
|
331
|
+
const value = record[key];
|
|
332
|
+
if (Array.isArray(value)) {
|
|
333
|
+
for (const child of value) if (isNode(child)) forEachNode(child, visit);
|
|
334
|
+
} else if (isNode(value)) {
|
|
335
|
+
forEachNode(value, visit);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Children-first walk that lets `replace` swap a node for a new expression. */
|
|
341
|
+
function replaceInTree(node: Node, replace: (node: Node) => Node | undefined): Node {
|
|
342
|
+
const record = node as unknown as Record<string, unknown>;
|
|
343
|
+
for (const key of Object.keys(record)) {
|
|
344
|
+
if (key === "kind" || key === "start" || key === "end" || key === "text" || key === "value" || key === "raw") continue;
|
|
345
|
+
const value = record[key];
|
|
346
|
+
if (Array.isArray(value)) {
|
|
347
|
+
for (let index = 0; index < value.length; index++) {
|
|
348
|
+
if (isNode(value[index])) value[index] = replaceInTree(value[index] as Node, replace);
|
|
349
|
+
}
|
|
350
|
+
} else if (isNode(value)) {
|
|
351
|
+
record[key] = replaceInTree(value, replace);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return replace(node) ?? node;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function identifier(text: string, anchor: number): Identifier {
|
|
358
|
+
return { kind: SyntaxKind.Identifier, text, start: anchor, end: anchor };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function member(object: string | Expression, name: string, anchor: number): PropertyAccessExpression {
|
|
362
|
+
const expression: Expression = typeof object === "string" ? identifier(object, anchor) : object;
|
|
363
|
+
return {
|
|
364
|
+
kind: SyntaxKind.PropertyAccessExpression,
|
|
365
|
+
expression,
|
|
366
|
+
name: identifier(name, anchor),
|
|
367
|
+
optional: false,
|
|
368
|
+
start: anchor,
|
|
369
|
+
end: anchor,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function objectLiteral(properties: ObjectLiteralElementLike[]): ObjectLiteralExpression {
|
|
374
|
+
return { kind: SyntaxKind.ObjectLiteralExpression, properties, start: 0, end: 0 };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function propertyAssignment(name: string, initializer: Expression): ObjectLiteralElementLike {
|
|
378
|
+
return {
|
|
379
|
+
kind: SyntaxKind.PropertyAssignment,
|
|
380
|
+
name: identifier(name, 0),
|
|
381
|
+
initializer,
|
|
382
|
+
start: 0,
|
|
383
|
+
end: 0,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function undefinedLiteral(anchor: number): Expression {
|
|
388
|
+
return { kind: SyntaxKind.UndefinedKeyword, start: anchor, end: anchor };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function variableStatement(name: string, initializer: Expression, anchor: number): VariableStatement {
|
|
392
|
+
const declaration: VariableDeclaration = {
|
|
393
|
+
kind: SyntaxKind.VariableDeclaration,
|
|
394
|
+
name: identifier(name, anchor),
|
|
395
|
+
exclamation: false,
|
|
396
|
+
initializer,
|
|
397
|
+
start: anchor,
|
|
398
|
+
end: anchor,
|
|
399
|
+
};
|
|
400
|
+
const list: VariableDeclarationList = {
|
|
401
|
+
kind: SyntaxKind.VariableDeclarationList,
|
|
402
|
+
declarationKind: "const",
|
|
403
|
+
declarations: [declaration],
|
|
404
|
+
start: anchor,
|
|
405
|
+
end: anchor,
|
|
406
|
+
};
|
|
407
|
+
return { kind: SyntaxKind.VariableStatement, declarationList: list, modifiers: [], start: anchor, end: anchor };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function defaultImport(name: string, specifier: string, anchor: number): ImportDeclaration {
|
|
411
|
+
const moduleSpecifier: StringLiteral = {
|
|
412
|
+
kind: SyntaxKind.StringLiteral,
|
|
413
|
+
text: JSON.stringify(specifier),
|
|
414
|
+
value: specifier,
|
|
415
|
+
raw: JSON.stringify(specifier),
|
|
416
|
+
start: anchor,
|
|
417
|
+
end: anchor,
|
|
418
|
+
};
|
|
419
|
+
return {
|
|
420
|
+
kind: SyntaxKind.ImportDeclaration,
|
|
421
|
+
importClause: { kind: SyntaxKind.ImportClause, name: identifier(name, anchor), isTypeOnly: false, start: anchor, end: anchor },
|
|
422
|
+
moduleSpecifier,
|
|
423
|
+
attributes: [],
|
|
424
|
+
start: anchor,
|
|
425
|
+
end: anchor,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Rewrite `var x = require("<external>")` into an ESM import so that the
|
|
431
|
+
* extension / built-in module can be used as a first-class value:
|
|
432
|
+
*
|
|
433
|
+
* const os = require("node:os") -> import * as os from "node:os"
|
|
434
|
+
* const { join } = require("node:path") -> import { join } from "node:path"
|
|
435
|
+
*
|
|
436
|
+
* Only simple identifier / object-literal bindings at the top level are
|
|
437
|
+
* handled; a require nested inside an expression falls back to the default
|
|
438
|
+
* import path, which supports direct member access but not value use.
|
|
439
|
+
*/
|
|
440
|
+
function hoistExternalNamespaceRequires(
|
|
441
|
+
record: ModuleRecord,
|
|
442
|
+
context: CommonJSLoweringContext,
|
|
443
|
+
statements: Statement[],
|
|
444
|
+
anchor: number,
|
|
445
|
+
): void {
|
|
446
|
+
const scope = moduleScope(record);
|
|
447
|
+
if (!scope) return;
|
|
448
|
+
for (let index = 0; index < statements.length; index++) {
|
|
449
|
+
const statement = statements[index];
|
|
450
|
+
if (!statement || statement.kind !== SyntaxKind.VariableStatement) continue;
|
|
451
|
+
const list = (statement as VariableStatement).declarationList;
|
|
452
|
+
if (list.declarations.length !== 1) continue;
|
|
453
|
+
const declaration = list.declarations[0];
|
|
454
|
+
if (!declaration) continue;
|
|
455
|
+
const initializer = declaration.initializer;
|
|
456
|
+
if (!initializer || initializer.kind !== SyntaxKind.CallExpression) continue;
|
|
457
|
+
const call = initializer as CallExpression;
|
|
458
|
+
const callee = call.expression;
|
|
459
|
+
if (callee.kind !== SyntaxKind.Identifier || !isFreeRequire(callee as Identifier, record.bind)) continue;
|
|
460
|
+
const first = call.arguments[0];
|
|
461
|
+
if (!first || first.kind !== SyntaxKind.StringLiteral) continue;
|
|
462
|
+
const specifier = (first as StringLiteral).value;
|
|
463
|
+
if (context.resolve(dirname(record.path), specifier).kind !== "external") continue;
|
|
464
|
+
|
|
465
|
+
if (declaration.name.kind === SyntaxKind.Identifier) {
|
|
466
|
+
const symbol = scope.symbols.get(declaration.name.text);
|
|
467
|
+
const finalName = symbol ? record.finalNames.get(symbol.id) : undefined;
|
|
468
|
+
if (!symbol || !finalName) continue;
|
|
469
|
+
renameReferences(symbol, finalName);
|
|
470
|
+
statements[index] = namespaceImport(finalName, specifier, anchor);
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (declaration.name.kind === SyntaxKind.ObjectBindingPattern) {
|
|
475
|
+
const hoisted = hoistObjectBinding(record, scope, declaration.name as ObjectBindingPattern, specifier, anchor);
|
|
476
|
+
if (hoisted) statements[index] = hoisted;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** Build `import { a, b as c } from "spec"` for a `const { ... } = require` binding. */
|
|
482
|
+
function hoistObjectBinding(
|
|
483
|
+
record: ModuleRecord,
|
|
484
|
+
scope: NonNullable<ReturnType<typeof moduleScope>>,
|
|
485
|
+
pattern: ObjectBindingPattern,
|
|
486
|
+
specifier: string,
|
|
487
|
+
anchor: number,
|
|
488
|
+
): ImportDeclaration | undefined {
|
|
489
|
+
const specifiers: ImportSpecifier[] = [];
|
|
490
|
+
for (const element of pattern.elements as BindingElement[]) {
|
|
491
|
+
if (element.dotDotDotToken || element.initializer || element.name.kind !== SyntaxKind.Identifier) return undefined;
|
|
492
|
+
const localText = (element.name as Identifier).text;
|
|
493
|
+
const importedName = element.propertyName
|
|
494
|
+
? element.propertyName.kind === SyntaxKind.Identifier
|
|
495
|
+
? (element.propertyName as Identifier).text
|
|
496
|
+
: (element.propertyName as StringLiteral).value
|
|
497
|
+
: localText;
|
|
498
|
+
const symbol = scope.symbols.get(localText);
|
|
499
|
+
const finalName = symbol ? record.finalNames.get(symbol.id) : undefined;
|
|
500
|
+
if (!symbol || !finalName) return undefined;
|
|
501
|
+
renameReferences(symbol, finalName);
|
|
502
|
+
specifiers.push({
|
|
503
|
+
kind: SyntaxKind.ImportSpecifier,
|
|
504
|
+
propertyName: importedName === finalName ? undefined : identifier(importedName, anchor),
|
|
505
|
+
name: identifier(finalName, anchor),
|
|
506
|
+
isTypeOnly: false,
|
|
507
|
+
start: anchor,
|
|
508
|
+
end: anchor,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
if (specifiers.length === 0) return undefined;
|
|
512
|
+
const namedBindings: NamedImports = { kind: SyntaxKind.NamedImports, elements: specifiers, start: anchor, end: anchor };
|
|
513
|
+
return importDeclaration(namedBindings, specifier, anchor);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function namespaceImport(name: string, specifier: string, anchor: number): ImportDeclaration {
|
|
517
|
+
const namedBindings = {
|
|
518
|
+
kind: SyntaxKind.NamespaceImport as const,
|
|
519
|
+
name: identifier(name, anchor),
|
|
520
|
+
start: anchor,
|
|
521
|
+
end: anchor,
|
|
522
|
+
};
|
|
523
|
+
return importDeclaration(namedBindings, specifier, anchor);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function importDeclaration(
|
|
527
|
+
namedBindings: NamedImports | NamespaceImport,
|
|
528
|
+
specifier: string,
|
|
529
|
+
anchor: number,
|
|
530
|
+
): ImportDeclaration {
|
|
531
|
+
const moduleSpecifier: StringLiteral = {
|
|
532
|
+
kind: SyntaxKind.StringLiteral,
|
|
533
|
+
text: JSON.stringify(specifier),
|
|
534
|
+
value: specifier,
|
|
535
|
+
raw: JSON.stringify(specifier),
|
|
536
|
+
start: anchor,
|
|
537
|
+
end: anchor,
|
|
538
|
+
};
|
|
539
|
+
return {
|
|
540
|
+
kind: SyntaxKind.ImportDeclaration,
|
|
541
|
+
importClause: {
|
|
542
|
+
kind: SyntaxKind.ImportClause,
|
|
543
|
+
namedBindings,
|
|
544
|
+
isTypeOnly: false,
|
|
545
|
+
start: anchor,
|
|
546
|
+
end: anchor,
|
|
547
|
+
},
|
|
548
|
+
moduleSpecifier,
|
|
549
|
+
attributes: [],
|
|
550
|
+
start: anchor,
|
|
551
|
+
end: anchor,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
-
import { dirname } from "node:path";
|
|
7
|
+
import { dirname, extname } from "node:path";
|
|
8
8
|
import { SyntaxKind, type ExportDeclaration, type ImportDeclaration, type Statement } from "../../ast/nodes.js";
|
|
9
9
|
import { bind } from "../../binder/binder.js";
|
|
10
10
|
import { DiagnosticBag, DiagnosticCode } from "../../diagnostics/diagnostic.js";
|
|
11
11
|
import { SourceFile } from "../../diagnostics/source.js";
|
|
12
12
|
import { Parser } from "../../parser/parser.js";
|
|
13
|
+
import type { AssetLoader } from "../../extensions/registry.js";
|
|
13
14
|
import { classifyDependency } from "./resolve.js";
|
|
15
|
+
import { collectRequireSites, isNodeModulesPath, usesCommonJS } from "./commonjs.js";
|
|
14
16
|
import type { ModuleRecord } from "./types.js";
|
|
15
17
|
|
|
16
18
|
function moduleSpecifierOf(statement: Statement): string | undefined {
|
|
@@ -29,6 +31,7 @@ export function loadGraph(
|
|
|
29
31
|
entryPath: string,
|
|
30
32
|
diagnostics: DiagnosticBag,
|
|
31
33
|
externalSpecifiers: ReadonlySet<string>,
|
|
34
|
+
assetLoaders: Readonly<Record<string, AssetLoader>> = {},
|
|
32
35
|
): ModuleRecord[] | undefined {
|
|
33
36
|
const records = new Map<string, ModuleRecord>();
|
|
34
37
|
const order: ModuleRecord[] = [];
|
|
@@ -50,7 +53,24 @@ export function loadGraph(
|
|
|
50
53
|
return undefined;
|
|
51
54
|
}
|
|
52
55
|
visiting.add(path);
|
|
53
|
-
const
|
|
56
|
+
const raw = readFileSync(path, "utf8");
|
|
57
|
+
/* An extension may rewrite an imported asset (e.g. `.html`) into a TS
|
|
58
|
+
* module before it is parsed. The core only sees the transformed text. */
|
|
59
|
+
const loader = assetLoaders[extname(path).toLowerCase()];
|
|
60
|
+
let text = raw;
|
|
61
|
+
if (loader) {
|
|
62
|
+
try {
|
|
63
|
+
text = loader(path, raw).moduleSource;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
diagnostics.error(
|
|
66
|
+
DiagnosticCode.CodegenError,
|
|
67
|
+
error instanceof Error ? error.message : String(error),
|
|
68
|
+
);
|
|
69
|
+
failed = true;
|
|
70
|
+
visiting.delete(path);
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
54
74
|
const file = new SourceFile(path, text);
|
|
55
75
|
const parser = new Parser(file, diagnostics);
|
|
56
76
|
const sourceFile = parser.parseSourceFile();
|
|
@@ -64,7 +84,11 @@ export function loadGraph(
|
|
|
64
84
|
exports: new Map(),
|
|
65
85
|
originalNames: new Map(),
|
|
66
86
|
finalNames: new Map(),
|
|
87
|
+
commonjs: false,
|
|
67
88
|
};
|
|
89
|
+
// `require` / `module` / `exports` are only honoured inside `node_modules`,
|
|
90
|
+
// so user code keeps its ESM-only diagnostic.
|
|
91
|
+
record.commonjs = isNodeModulesPath(path) && usesCommonJS(bindResult);
|
|
68
92
|
records.set(path, record);
|
|
69
93
|
|
|
70
94
|
for (const statement of sourceFile.statements) {
|
|
@@ -84,6 +108,15 @@ export function loadGraph(
|
|
|
84
108
|
}
|
|
85
109
|
load(dependency.path);
|
|
86
110
|
}
|
|
111
|
+
|
|
112
|
+
// A CommonJS module's `require("...")` calls are dynamic import edges: the
|
|
113
|
+
// target must be bundled (and evaluated) before this module runs.
|
|
114
|
+
if (record.commonjs) {
|
|
115
|
+
for (const site of collectRequireSites(sourceFile, bindResult)) {
|
|
116
|
+
const dependency = classifyDependency(dirname(path), site.specifier, externalSpecifiers);
|
|
117
|
+
if (dependency.kind === "file") load(dependency.path);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
87
120
|
visiting.delete(path);
|
|
88
121
|
order.push(record);
|
|
89
122
|
return record;
|
|
@@ -27,7 +27,9 @@ import {
|
|
|
27
27
|
import type { BindingName } from "../../ast/declarations.js";
|
|
28
28
|
import { SymbolKind } from "../../binder/binder.js";
|
|
29
29
|
import type { DiagnosticBag } from "../../diagnostics/diagnostic.js";
|
|
30
|
+
import type { AssetLoader } from "../../extensions/registry.js";
|
|
30
31
|
import { loadGraph } from "./graph.js";
|
|
32
|
+
import { lowerCommonJSRequires, prepareCommonJS } from "./commonjs.js";
|
|
31
33
|
import { classifyDependency } from "./resolve.js";
|
|
32
34
|
import {
|
|
33
35
|
declarationName,
|
|
@@ -43,8 +45,9 @@ export function bundleModules(
|
|
|
43
45
|
entryPath: string,
|
|
44
46
|
diagnostics: DiagnosticBag,
|
|
45
47
|
externalSpecifiers: ReadonlySet<string> = new Set(),
|
|
48
|
+
assetLoaders: Readonly<Record<string, AssetLoader>> = {},
|
|
46
49
|
): BundleResult | undefined {
|
|
47
|
-
const records = loadGraph(entryPath, diagnostics, externalSpecifiers);
|
|
50
|
+
const records = loadGraph(entryPath, diagnostics, externalSpecifiers, assetLoaders);
|
|
48
51
|
if (!records) return undefined;
|
|
49
52
|
|
|
50
53
|
const byPath = new Map(records.map((record) => [record.path, record]));
|
|
@@ -62,6 +65,10 @@ export function bundleModules(
|
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
// Phase 1b: give every CommonJS module its `module`/`exports` state and
|
|
69
|
+
// record its export surface, so later phases can re-export it.
|
|
70
|
+
for (const record of records) prepareCommonJS(record);
|
|
71
|
+
|
|
65
72
|
// Phase 2: collect each module's exported names.
|
|
66
73
|
for (const record of records) {
|
|
67
74
|
const scope = moduleScope(record);
|
|
@@ -115,6 +122,16 @@ export function bundleModules(
|
|
|
115
122
|
}
|
|
116
123
|
}
|
|
117
124
|
|
|
125
|
+
// Phase 2b: resolve `require("...")` calls now that every module's export
|
|
126
|
+
// surface is known (a required ESM module becomes a namespace object).
|
|
127
|
+
for (const record of records) {
|
|
128
|
+
lowerCommonJSRequires(record, {
|
|
129
|
+
resolve: (fromDir, specifier) => classifyDependency(fromDir, specifier, externalSpecifiers),
|
|
130
|
+
recordFor: (path) => byPath.get(path),
|
|
131
|
+
diagnostics,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
118
135
|
// Phase 3: rewrite imported names and record re-exports. Namespace imports
|
|
119
136
|
// are lowered to a synthetic object literal holding every export, built
|
|
120
137
|
// from the dependency's (already renamed) final names.
|
|
@@ -17,6 +17,14 @@ export interface ModuleRecord {
|
|
|
17
17
|
readonly originalNames: Map<number, string>;
|
|
18
18
|
/** final name of every top-level symbol */
|
|
19
19
|
readonly finalNames: Map<number, string>;
|
|
20
|
+
/**
|
|
21
|
+
* True when the module is CommonJS (it references `require`, `module` or
|
|
22
|
+
* `exports` as a free identifier). Its `exports` map points at synthetic
|
|
23
|
+
* per-module bindings lowered by the bundler.
|
|
24
|
+
*/
|
|
25
|
+
commonjs: boolean;
|
|
26
|
+
/** Synthetic `module` object for a CommonJS module (`<prefix>$cjs_module`). */
|
|
27
|
+
cjsModule?: string;
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
export interface BundleResult {
|