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.
Files changed (194) hide show
  1. package/README.md +33 -0
  2. package/dist/src/cli/main.js +124 -30
  3. package/dist/src/cli/main.js.map +1 -1
  4. package/dist/src/codegen/generator/calls/access.js +8 -1
  5. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  6. package/dist/src/codegen/generator/state.js +20 -0
  7. package/dist/src/codegen/generator/state.js.map +1 -1
  8. package/dist/src/codegen/generator/tables.d.ts +6 -0
  9. package/dist/src/codegen/generator/tables.js +38 -0
  10. package/dist/src/codegen/generator/tables.js.map +1 -1
  11. package/dist/src/driver/bundler/commonjs.d.ts +52 -0
  12. package/dist/src/driver/bundler/commonjs.js +493 -0
  13. package/dist/src/driver/bundler/commonjs.js.map +1 -0
  14. package/dist/src/driver/bundler/graph.d.ts +2 -1
  15. package/dist/src/driver/bundler/graph.js +32 -3
  16. package/dist/src/driver/bundler/graph.js.map +1 -1
  17. package/dist/src/driver/bundler/merge.d.ts +2 -1
  18. package/dist/src/driver/bundler/merge.js +16 -2
  19. package/dist/src/driver/bundler/merge.js.map +1 -1
  20. package/dist/src/driver/bundler/types.d.ts +8 -0
  21. package/dist/src/driver/compiler.d.ts +8 -1
  22. package/dist/src/driver/compiler.js +75 -7
  23. package/dist/src/driver/compiler.js.map +1 -1
  24. package/dist/src/driver/config.d.ts +53 -0
  25. package/dist/src/driver/config.js +141 -0
  26. package/dist/src/driver/config.js.map +1 -0
  27. package/dist/src/driver/icon.d.ts +62 -0
  28. package/dist/src/driver/icon.js +151 -0
  29. package/dist/src/driver/icon.js.map +1 -0
  30. package/dist/src/driver/index.d.ts +4 -0
  31. package/dist/src/driver/index.js +4 -0
  32. package/dist/src/driver/index.js.map +1 -1
  33. package/dist/src/driver/mac-bundle.d.ts +41 -0
  34. package/dist/src/driver/mac-bundle.js +123 -0
  35. package/dist/src/driver/mac-bundle.js.map +1 -0
  36. package/dist/src/driver/win-icon.d.ts +27 -0
  37. package/dist/src/driver/win-icon.js +80 -0
  38. package/dist/src/driver/win-icon.js.map +1 -0
  39. package/dist/src/extensions/gui/html.d.ts +25 -0
  40. package/dist/src/extensions/gui/html.js +203 -0
  41. package/dist/src/extensions/gui/html.js.map +1 -0
  42. package/dist/src/extensions/gui/index.js +11 -0
  43. package/dist/src/extensions/gui/index.js.map +1 -1
  44. package/dist/src/extensions/registry.d.ts +29 -0
  45. package/dist/src/extensions/registry.js +14 -0
  46. package/dist/src/extensions/registry.js.map +1 -1
  47. package/dist/src/index.d.ts +1 -1
  48. package/dist/src/index.js.map +1 -1
  49. package/dist/src/lexer/scanner/identifiers.js +4 -12
  50. package/dist/src/lexer/scanner/identifiers.js.map +1 -1
  51. package/dist/src/parser/statements.js +17 -4
  52. package/dist/src/parser/statements.js.map +1 -1
  53. package/dist/tests/cli/main.test.js +46 -1
  54. package/dist/tests/cli/main.test.js.map +1 -1
  55. package/dist/tests/driver/bundler-resolve.test.d.ts +1 -0
  56. package/dist/tests/driver/bundler-resolve.test.js +230 -0
  57. package/dist/tests/driver/bundler-resolve.test.js.map +1 -0
  58. package/dist/tests/driver/config.test.d.ts +1 -0
  59. package/dist/tests/driver/config.test.js +124 -0
  60. package/dist/tests/driver/config.test.js.map +1 -0
  61. package/dist/tests/driver/icon.test.d.ts +1 -0
  62. package/dist/tests/driver/icon.test.js +421 -0
  63. package/dist/tests/driver/icon.test.js.map +1 -0
  64. package/dist/tests/driver/modules.test.js +177 -0
  65. package/dist/tests/driver/modules.test.js.map +1 -1
  66. package/dist/tests/e2e/fetch.test.d.ts +12 -0
  67. package/dist/tests/e2e/fetch.test.js +272 -0
  68. package/dist/tests/e2e/fetch.test.js.map +1 -0
  69. package/dist/tests/e2e/gui-dom.test.d.ts +5 -0
  70. package/dist/tests/e2e/gui-dom.test.js +102 -0
  71. package/dist/tests/e2e/gui-dom.test.js.map +1 -0
  72. package/dist/tests/e2e/gui-events.test.d.ts +5 -0
  73. package/dist/tests/e2e/gui-events.test.js +60 -0
  74. package/dist/tests/e2e/gui-events.test.js.map +1 -0
  75. package/dist/tests/e2e/gui-helpers.d.ts +31 -0
  76. package/dist/tests/e2e/gui-helpers.js +74 -0
  77. package/dist/tests/e2e/gui-helpers.js.map +1 -0
  78. package/dist/tests/e2e/gui-layout.test.d.ts +4 -0
  79. package/dist/tests/e2e/gui-layout.test.js +88 -0
  80. package/dist/tests/e2e/gui-layout.test.js.map +1 -0
  81. package/dist/tests/e2e/gui-lifecycle.test.d.ts +5 -0
  82. package/dist/tests/e2e/gui-lifecycle.test.js +58 -0
  83. package/dist/tests/e2e/gui-lifecycle.test.js.map +1 -0
  84. package/dist/tests/e2e/gui-paint.test.d.ts +5 -0
  85. package/dist/tests/e2e/gui-paint.test.js +111 -0
  86. package/dist/tests/e2e/gui-paint.test.js.map +1 -0
  87. package/dist/tests/e2e/gui-script.test.d.ts +5 -0
  88. package/dist/tests/e2e/gui-script.test.js +78 -0
  89. package/dist/tests/e2e/gui-script.test.js.map +1 -0
  90. package/dist/tests/e2e/gui-style.test.d.ts +5 -0
  91. package/dist/tests/e2e/gui-style.test.js +175 -0
  92. package/dist/tests/e2e/gui-style.test.js.map +1 -0
  93. package/dist/tests/e2e/icon.test.d.ts +7 -0
  94. package/dist/tests/e2e/icon.test.js +80 -0
  95. package/dist/tests/e2e/icon.test.js.map +1 -0
  96. package/dist/tests/e2e/npm-modules.test.d.ts +4 -1
  97. package/dist/tests/e2e/npm-modules.test.js +47 -1
  98. package/dist/tests/e2e/npm-modules.test.js.map +1 -1
  99. package/dist/tests/e2e/runtime-coverage-typed-array.test.d.ts +9 -0
  100. package/dist/tests/e2e/runtime-coverage-typed-array.test.js +140 -0
  101. package/dist/tests/e2e/runtime-coverage-typed-array.test.js.map +1 -0
  102. package/dist/tests/extensions/gui-index.test.d.ts +1 -0
  103. package/dist/tests/extensions/gui-index.test.js +49 -0
  104. package/dist/tests/extensions/gui-index.test.js.map +1 -0
  105. package/dist/tests/extensions/gui.test.d.ts +1 -0
  106. package/dist/tests/extensions/gui.test.js +177 -0
  107. package/dist/tests/extensions/gui.test.js.map +1 -0
  108. package/dist/tests/extensions/registry.test.js +36 -0
  109. package/dist/tests/extensions/registry.test.js.map +1 -1
  110. package/dist/tests/parser/statements.test.js +11 -0
  111. package/dist/tests/parser/statements.test.js.map +1 -1
  112. package/dist/tests/public-api.test.d.ts +1 -0
  113. package/dist/tests/public-api.test.js +31 -0
  114. package/dist/tests/public-api.test.js.map +1 -0
  115. package/package.json +1 -1
  116. package/runtime/ext_gui/css.cpp +4 -517
  117. package/runtime/ext_gui/css_internal.h +27 -0
  118. package/runtime/ext_gui/css_match.cpp +195 -0
  119. package/runtime/ext_gui/css_selector.cpp +207 -0
  120. package/runtime/ext_gui/css_sheet.cpp +129 -0
  121. package/runtime/ext_gui/document.cpp +90 -0
  122. package/runtime/ext_gui/document.h +23 -0
  123. package/runtime/ext_gui/dom.cpp +43 -403
  124. package/runtime/ext_gui/dom.h +7 -0
  125. package/runtime/ext_gui/dom_api.cpp +164 -0
  126. package/runtime/ext_gui/dom_api.h +12 -0
  127. package/runtime/ext_gui/dom_api_document.cpp +96 -0
  128. package/runtime/ext_gui/dom_api_events.cpp +123 -0
  129. package/runtime/ext_gui/dom_api_internal.h +135 -0
  130. package/runtime/ext_gui/dom_api_node.cpp +329 -0
  131. package/runtime/ext_gui/dom_api_node_read.cpp +294 -0
  132. package/runtime/ext_gui/dom_api_proto.cpp +130 -0
  133. package/runtime/ext_gui/dom_api_style.cpp +192 -0
  134. package/runtime/ext_gui/dom_internal.h +39 -0
  135. package/runtime/ext_gui/dom_parse.cpp +348 -0
  136. package/runtime/ext_gui/dom_serialize.cpp +54 -0
  137. package/runtime/ext_gui/gui.cpp +61 -245
  138. package/runtime/ext_gui/gui_engine.h +54 -0
  139. package/runtime/ext_gui/gui_events.cpp +294 -0
  140. package/runtime/ext_gui/gui_internal.h +29 -0
  141. package/runtime/ext_gui/layout.cpp +6 -597
  142. package/runtime/ext_gui/layout_flex.cpp +200 -0
  143. package/runtime/ext_gui/layout_flow.cpp +191 -0
  144. package/runtime/ext_gui/layout_flow_internal.h +149 -0
  145. package/runtime/ext_gui/layout_inline.cpp +181 -0
  146. package/runtime/ext_gui/layout_internal.h +20 -0
  147. package/runtime/ext_gui/renderer.cpp +15 -679
  148. package/runtime/ext_gui/renderer_image.cpp +233 -0
  149. package/runtime/ext_gui/renderer_internal.h +112 -0
  150. package/runtime/ext_gui/renderer_shaders.h +108 -0
  151. package/runtime/ext_gui/renderer_text.cpp +342 -0
  152. package/runtime/ext_gui/script.cpp +63 -0
  153. package/runtime/ext_gui/style.cpp +12 -660
  154. package/runtime/ext_gui/style_internal.h +33 -0
  155. package/runtime/ext_gui/style_print.cpp +113 -0
  156. package/runtime/ext_gui/style_ua.cpp +98 -0
  157. package/runtime/ext_gui/style_values.cpp +468 -0
  158. package/runtime/ext_gui/window.cpp +60 -0
  159. package/runtime/rt.h +6 -0
  160. package/runtime/rt_internal.h +4 -0
  161. package/runtime/xt_builtins.c +1 -0
  162. package/runtime/xt_containers/access.inc +32 -1
  163. package/runtime/xt_fetch/buffer.inc +64 -0
  164. package/runtime/xt_fetch/headers.inc +312 -0
  165. package/runtime/xt_fetch/request.inc +386 -0
  166. package/runtime/xt_fetch/response.inc +107 -0
  167. package/runtime/xt_fetch/socket.inc +72 -0
  168. package/runtime/xt_fetch/url.inc +108 -0
  169. package/runtime/xt_fetch.c +39 -0
  170. package/runtime/xt_stdlib2/set.inc +1 -1
  171. package/runtime/xt_typed_array.c +614 -0
  172. package/src/cli/main.ts +137 -29
  173. package/src/codegen/generator/calls/access.ts +9 -1
  174. package/src/codegen/generator/state.ts +20 -0
  175. package/src/codegen/generator/tables.ts +40 -0
  176. package/src/driver/bundler/commonjs.ts +553 -0
  177. package/src/driver/bundler/graph.ts +35 -2
  178. package/src/driver/bundler/merge.ts +18 -1
  179. package/src/driver/bundler/types.ts +8 -0
  180. package/src/driver/compiler.ts +85 -7
  181. package/src/driver/config.ts +173 -0
  182. package/src/driver/icon.ts +184 -0
  183. package/src/driver/index.ts +30 -0
  184. package/src/driver/mac-bundle.ts +156 -0
  185. package/src/driver/win-icon.ts +96 -0
  186. package/src/extensions/gui/html.ts +236 -0
  187. package/src/extensions/gui/index.ts +11 -0
  188. package/src/extensions/registry.ts +39 -0
  189. package/src/index.ts +1 -1
  190. package/src/lexer/scanner/identifiers.ts +4 -11
  191. package/src/parser/statements.ts +17 -4
  192. package/dist/tests/e2e/gui.test.d.ts +0 -12
  193. package/dist/tests/e2e/gui.test.js +0 -493
  194. 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 text = readFileSync(path, "utf8");
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 {