xbintsc 0.5.37 → 0.5.38

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 (68) hide show
  1. package/dist/src/cli/help.d.ts +1 -1
  2. package/dist/src/codegen/generator/state.js +1 -0
  3. package/dist/src/codegen/generator/state.js.map +1 -1
  4. package/dist/src/codegen/generator/tables.js +1 -0
  5. package/dist/src/codegen/generator/tables.js.map +1 -1
  6. package/dist/src/driver/bundler/deferred.d.ts +63 -0
  7. package/dist/src/driver/bundler/deferred.js +279 -0
  8. package/dist/src/driver/bundler/deferred.js.map +1 -0
  9. package/dist/src/driver/bundler/merge.js +214 -2
  10. package/dist/src/driver/bundler/merge.js.map +1 -1
  11. package/dist/src/driver/runtime-objects.d.ts +1 -1
  12. package/dist/src/driver/runtime-objects.js +1 -0
  13. package/dist/src/driver/runtime-objects.js.map +1 -1
  14. package/dist/src/driver/version.d.ts +1 -1
  15. package/dist/src/driver/version.js +1 -1
  16. package/dist/src/extensions/gui/html.js +89 -13
  17. package/dist/src/extensions/gui/html.js.map +1 -1
  18. package/dist/src/extensions/gui/index.js +1 -0
  19. package/dist/src/extensions/gui/index.js.map +1 -1
  20. package/dist/src/extensions/gui/three.d.ts +4 -3
  21. package/dist/src/extensions/gui/three.js +4 -3
  22. package/dist/src/extensions/gui/three.js.map +1 -1
  23. package/dist/tests/e2e/gui-script-import-order.test.d.ts +10 -0
  24. package/dist/tests/e2e/gui-script-import-order.test.js +161 -0
  25. package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -0
  26. package/dist/tests/e2e/gui-webgl2-shadow.test.d.ts +10 -0
  27. package/dist/tests/e2e/gui-webgl2-shadow.test.js +108 -0
  28. package/dist/tests/e2e/gui-webgl2-shadow.test.js.map +1 -0
  29. package/dist/tests/e2e/url.test.d.ts +6 -0
  30. package/dist/tests/e2e/url.test.js +114 -0
  31. package/dist/tests/e2e/url.test.js.map +1 -0
  32. package/dist/tests/extensions/gui.test.js +22 -10
  33. package/dist/tests/extensions/gui.test.js.map +1 -1
  34. package/doc/gui/webgl.md +20 -10
  35. package/doc/gui-scripts.md +19 -19
  36. package/doc/gui.md +10 -8
  37. package/doc/implemented/fetch.md +5 -0
  38. package/doc/implemented/modules.md +13 -0
  39. package/doc/implemented/stdlib.md +17 -0
  40. package/doc/unimplemented/deviations.md +1 -0
  41. package/doc/zh-CN/gui/webgl.md +3 -3
  42. package/doc/zh-CN/gui-scripts.md +14 -13
  43. package/doc/zh-CN/gui.md +7 -6
  44. package/doc/zh-CN/implemented/fetch.md +4 -0
  45. package/doc/zh-CN/implemented/modules.md +10 -0
  46. package/doc/zh-CN/implemented/stdlib.md +15 -0
  47. package/doc/zh-CN/unimplemented/deviations.md +1 -0
  48. package/package.json +7 -7
  49. package/runtime/ext_gui/dom_api_webgl.cpp +10 -0
  50. package/runtime/ext_gui/dom_api_webgl2.cpp +33 -1
  51. package/runtime/ext_gui/gui_engine.h +2 -0
  52. package/runtime/ext_gui/script.cpp +31 -0
  53. package/runtime/ext_gui/webgl_glsl.h +3 -0
  54. package/runtime/ext_gui/webgl_glsl_unroll.cpp +283 -0
  55. package/runtime/ext_gui/webgl_msl.cpp +4 -0
  56. package/runtime/ext_gui/webgl_shader.cpp +12 -6
  57. package/runtime/rt.h +3 -0
  58. package/runtime/xt_fetch/async.inc +60 -0
  59. package/runtime/xt_url_object.c +463 -0
  60. package/src/codegen/generator/state.ts +1 -0
  61. package/src/codegen/generator/tables.ts +1 -0
  62. package/src/driver/bundler/deferred.ts +351 -0
  63. package/src/driver/bundler/merge.ts +219 -2
  64. package/src/driver/runtime-objects.ts +1 -0
  65. package/src/driver/version.ts +1 -1
  66. package/src/extensions/gui/html.ts +91 -15
  67. package/src/extensions/gui/index.ts +1 -0
  68. package/src/extensions/gui/three.ts +4 -3
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Deferred module evaluation for `<script src>` imports.
3
+ *
4
+ * A page's external script is AOT-compiled and registered at module
5
+ * initialisation, but its imports are ordinary ES imports: the bundler merges
6
+ * them ahead of the page's entry and they run before `loadHTML`. A browser
7
+ * evaluates a module graph after the document exists, so a module that touches
8
+ * the DOM from its top level sees an empty document here.
9
+ *
10
+ * The `.html` loader marks each relative import of a `<script src>` with a
11
+ * synthetic `__xt_deferred_module` binding. The bundler turns the target module
12
+ * (and its local dependency graph) into a function that returns its exports,
13
+ * memoised by the runtime's `__xt_defer`; the generated script function calls
14
+ * that wrapper after `loadHTML`. Modules that are also imported eagerly stay
15
+ * eager and get a thin wrapper over their already-initialised exports.
16
+ */
17
+
18
+ import { dirname } from "node:path";
19
+ import {
20
+ NodeFlags,
21
+ SyntaxKind,
22
+ type Block,
23
+ type CallExpression,
24
+ type Expression,
25
+ type FunctionDeclaration,
26
+ type Identifier,
27
+ type ImportDeclaration,
28
+ type ObjectLiteralElementLike,
29
+ type PropertyAssignment,
30
+ type ReturnStatement,
31
+ type Statement,
32
+ type StringLiteral,
33
+ type VariableDeclaration,
34
+ type VariableDeclarationList,
35
+ type VariableStatement,
36
+ } from "../../ast/nodes.js";
37
+ import { SymbolKind } from "../../binder/types.js";
38
+ import { classifyDependency } from "./resolve.js";
39
+ import { topLevelSymbols } from "./symbols.js";
40
+ import type { ModuleRecord } from "./types.js";
41
+
42
+ /** The imported name the `.html` loader uses to mark a deferred import. */
43
+ export const DEFERRED_MARKER = "__xt_deferred_module";
44
+
45
+ /** Whether `statement` is a synthetic deferred-import marker. */
46
+ export function isDeferredMarkerImport(statement: Statement): boolean {
47
+ if (statement.kind !== SyntaxKind.ImportDeclaration) return false;
48
+ const declaration = statement as ImportDeclaration;
49
+ const bindings = declaration.importClause?.namedBindings;
50
+ if (!bindings || bindings.kind !== SyntaxKind.NamedImports) return false;
51
+ return bindings.elements.some(
52
+ (element) => (element.propertyName?.text ?? element.name.text) === DEFERRED_MARKER,
53
+ );
54
+ }
55
+
56
+ /** The final name of a module's wrapper function (`() => exports`). */
57
+ export function deferredWrapperName(record: ModuleRecord): string {
58
+ return `__xt_deferred_${record.prefix.replace(/_$/, "")}`;
59
+ }
60
+
61
+ function deferredImplName(record: ModuleRecord): string {
62
+ return `${deferredWrapperName(record)}_impl`;
63
+ }
64
+
65
+ /** A unique local name for an import inside a deferred module's wrapper. */
66
+ export function deferredAliasName(record: ModuleRecord, index: number, hint: string): string {
67
+ const clean = hint.replace(/[^A-Za-z0-9_$]/g, "_");
68
+ return `${record.prefix}imp_${index}_${clean}`;
69
+ }
70
+
71
+ function identifier(text: string, start: number, end: number): Identifier {
72
+ return { kind: SyntaxKind.Identifier, text, start, end };
73
+ }
74
+
75
+ function stringLiteral(value: string, start: number, end: number): StringLiteral {
76
+ const raw = JSON.stringify(value);
77
+ return { kind: SyntaxKind.StringLiteral, text: raw, value, raw, start, end };
78
+ }
79
+
80
+ function call(callee: Expression, args: Expression[], start: number, end: number): CallExpression {
81
+ return {
82
+ kind: SyntaxKind.CallExpression,
83
+ expression: callee,
84
+ typeArguments: [],
85
+ arguments: args,
86
+ optional: false,
87
+ start,
88
+ end,
89
+ };
90
+ }
91
+
92
+ function property(object: Expression, name: string, start: number, end: number): Expression {
93
+ return {
94
+ kind: SyntaxKind.PropertyAccessExpression,
95
+ expression: object,
96
+ name: identifier(name, start, end),
97
+ optional: false,
98
+ start,
99
+ end,
100
+ };
101
+ }
102
+
103
+ function constDeclaration(
104
+ name: string,
105
+ initializer: Expression | undefined,
106
+ start: number,
107
+ end: number,
108
+ ): VariableStatement {
109
+ const declaration: VariableDeclaration = {
110
+ kind: SyntaxKind.VariableDeclaration,
111
+ name: identifier(name, start, end),
112
+ exclamation: false,
113
+ initializer,
114
+ start,
115
+ end,
116
+ };
117
+ const list: VariableDeclarationList = {
118
+ kind: SyntaxKind.VariableDeclarationList,
119
+ declarations: [declaration],
120
+ declarationKind: "const",
121
+ start,
122
+ end,
123
+ };
124
+ return { kind: SyntaxKind.VariableStatement, declarationList: list, modifiers: [], start, end };
125
+ }
126
+
127
+ /** `const <alias> = <wrapper>()[.<importedName>];` */
128
+ function aliasStatement(alias: DeferredAlias): VariableStatement {
129
+ const callee = identifier(alias.wrapper, alias.start, alias.end);
130
+ const namespace = call(callee, [], alias.start, alias.end);
131
+ const value =
132
+ alias.importedName === undefined || alias.namespace
133
+ ? namespace
134
+ : property(namespace, alias.importedName, alias.start, alias.end);
135
+ return constDeclaration(alias.name, value, alias.start, alias.end);
136
+ }
137
+
138
+ /** A wrapper call used for a side-effect-only import. */
139
+ function sideEffectStatement(alias: DeferredAlias): Statement {
140
+ return {
141
+ kind: SyntaxKind.ExpressionStatement,
142
+ expression: call(identifier(alias.wrapper, alias.start, alias.end), [], alias.start, alias.end),
143
+ start: alias.start,
144
+ end: alias.end,
145
+ };
146
+ }
147
+
148
+ function isIdentifierText(text: string): boolean {
149
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(text);
150
+ }
151
+
152
+ function returnObject(entries: readonly DeferredExport[], start: number, end: number): ReturnStatement {
153
+ const properties: ObjectLiteralElementLike[] = entries.map((entry) => {
154
+ const name = isIdentifierText(entry.name)
155
+ ? identifier(entry.name, start, end)
156
+ : stringLiteral(entry.name, start, end);
157
+ const assignment: PropertyAssignment = {
158
+ kind: SyntaxKind.PropertyAssignment,
159
+ name,
160
+ initializer: identifier(entry.local, start, end),
161
+ start,
162
+ end,
163
+ };
164
+ return assignment;
165
+ });
166
+ return {
167
+ kind: SyntaxKind.ReturnStatement,
168
+ expression: { kind: SyntaxKind.ObjectLiteralExpression, properties, start, end },
169
+ start,
170
+ end,
171
+ };
172
+ }
173
+
174
+ function functionDeclaration(
175
+ name: string,
176
+ statements: Statement[],
177
+ start: number,
178
+ end: number,
179
+ ): FunctionDeclaration {
180
+ const body: Block = { kind: SyntaxKind.Block, statements, start, end };
181
+ return {
182
+ kind: SyntaxKind.FunctionDeclaration,
183
+ name: identifier(name, start, end),
184
+ modifiers: [],
185
+ typeParameters: [],
186
+ parameters: [],
187
+ body,
188
+ flags: NodeFlags.None,
189
+ start,
190
+ end,
191
+ };
192
+ }
193
+
194
+ export interface DeferredAlias {
195
+ readonly name: string;
196
+ readonly wrapper: string;
197
+ readonly importedName?: string;
198
+ /** The import's local binding, used to fix up `export { name }` re-exports. */
199
+ readonly local?: string;
200
+ readonly namespace?: boolean;
201
+ readonly sideEffect?: boolean;
202
+ readonly start: number;
203
+ readonly end: number;
204
+ }
205
+
206
+ export interface DeferredExport {
207
+ readonly name: string;
208
+ readonly local: string;
209
+ }
210
+
211
+ /** Every runtime export of `record` (type-only declarations are skipped). */
212
+ export function valueExports(record: ModuleRecord): DeferredExport[] {
213
+ const typeNames = new Set<string>();
214
+ for (const symbol of topLevelSymbols(record)) {
215
+ if (symbol.kind !== SymbolKind.Interface && symbol.kind !== SymbolKind.TypeAlias) continue;
216
+ const finalName = record.finalNames.get(symbol.id);
217
+ if (finalName !== undefined) typeNames.add(finalName);
218
+ }
219
+ const out: DeferredExport[] = [];
220
+ for (const [name, local] of record.exports) {
221
+ if (typeNames.has(local)) continue;
222
+ out.push({ name, local });
223
+ }
224
+ return out;
225
+ }
226
+
227
+ /**
228
+ * Build the body of a deferred module:
229
+ *
230
+ * function wrapper() { return __xt_defer(id, impl); }
231
+ * function impl() { <aliases> <body> return { exports }; }
232
+ *
233
+ * `body` must already have imports and export wrappers removed.
234
+ */
235
+ export function deferredModuleFunctions(
236
+ record: ModuleRecord,
237
+ aliases: readonly DeferredAlias[],
238
+ body: readonly Statement[],
239
+ ): Statement[] {
240
+ const start = record.sourceFile.start;
241
+ const end = record.sourceFile.end;
242
+ const statements: Statement[] = [];
243
+ for (const alias of aliases) {
244
+ statements.push(alias.sideEffect ? sideEffectStatement(alias) : aliasStatement(alias));
245
+ }
246
+ for (const statement of body) statements.push(statement);
247
+ statements.push(returnObject(valueExports(record), start, end));
248
+
249
+ const impl = functionDeclaration(deferredImplName(record), statements, start, end);
250
+ const wrapperBody: Statement[] = [
251
+ {
252
+ kind: SyntaxKind.ReturnStatement,
253
+ expression: call(
254
+ identifier("__xt_defer", start, end),
255
+ [
256
+ stringLiteral(record.prefix, start, end),
257
+ identifier(deferredImplName(record), start, end),
258
+ ],
259
+ start,
260
+ end,
261
+ ),
262
+ start,
263
+ end,
264
+ },
265
+ ];
266
+ const wrapper = functionDeclaration(deferredWrapperName(record), wrapperBody, start, end);
267
+ return [wrapper, impl];
268
+ }
269
+
270
+ /**
271
+ * A wrapper for a module that stayed eager: it only exposes the exports whose
272
+ * values other modules already initialised, so the marker call shape
273
+ * (`imports()` → namespace object) still works.
274
+ */
275
+ export function eagerModuleFunctions(record: ModuleRecord): Statement[] {
276
+ const start = record.sourceFile.start;
277
+ const end = record.sourceFile.end;
278
+ return [
279
+ functionDeclaration(
280
+ deferredWrapperName(record),
281
+ [returnObject(valueExports(record), start, end)],
282
+ start,
283
+ end,
284
+ ),
285
+ ];
286
+ }
287
+
288
+ /**
289
+ * Which modules must be evaluated lazily: everything reachable from a marker
290
+ * import, except modules the entry also reaches through a normal import.
291
+ */
292
+ export function computeDeferredModules(
293
+ records: readonly ModuleRecord[],
294
+ externalSpecifiers: ReadonlySet<string>,
295
+ ): Set<string> {
296
+ const byPath = new Map(records.map((record) => [record.path, record]));
297
+ const dependencyOf = (record: ModuleRecord, specifier: string): ModuleRecord | undefined => {
298
+ const resolution = classifyDependency(dirname(record.path), specifier, externalSpecifiers);
299
+ return resolution.kind === "file" ? byPath.get(resolution.path) : undefined;
300
+ };
301
+ const specifierOf = (statement: Statement): string | undefined => {
302
+ if (statement.kind === SyntaxKind.ImportDeclaration) {
303
+ const declaration = statement as ImportDeclaration;
304
+ if (declaration.importClause?.isTypeOnly) return undefined;
305
+ return declaration.moduleSpecifier.value;
306
+ }
307
+ if (statement.kind === SyntaxKind.ExportDeclaration) {
308
+ const declaration = statement as { moduleSpecifier?: { value: string } };
309
+ return declaration.moduleSpecifier?.value;
310
+ }
311
+ return undefined;
312
+ };
313
+
314
+ /* Modules reachable from the entry without crossing a marker are evaluated
315
+ * eagerly; a module in this set must not be wrapped, or an eager importer
316
+ * would reference a variable its wrapper owns. */
317
+ const eager = new Set<string>();
318
+ const entry = records[records.length - 1];
319
+ const visitEager = (record: ModuleRecord | undefined): void => {
320
+ if (!record || eager.has(record.path)) return;
321
+ eager.add(record.path);
322
+ for (const statement of record.sourceFile.statements) {
323
+ if (isDeferredMarkerImport(statement)) continue;
324
+ const specifier = specifierOf(statement);
325
+ if (specifier !== undefined) visitEager(dependencyOf(record, specifier));
326
+ }
327
+ };
328
+ visitEager(entry);
329
+
330
+ const deferred = new Set<string>();
331
+ const queue: ModuleRecord[] = [];
332
+ for (const record of records) {
333
+ for (const statement of record.sourceFile.statements) {
334
+ if (!isDeferredMarkerImport(statement)) continue;
335
+ const target = dependencyOf(record, (statement as ImportDeclaration).moduleSpecifier.value);
336
+ if (target && !eager.has(target.path)) queue.push(target);
337
+ }
338
+ }
339
+ while (queue.length > 0) {
340
+ const record = queue.pop()!;
341
+ if (eager.has(record.path) || deferred.has(record.path)) continue;
342
+ deferred.add(record.path);
343
+ for (const statement of record.sourceFile.statements) {
344
+ const specifier = specifierOf(statement);
345
+ if (specifier === undefined) continue;
346
+ const target = dependencyOf(record, specifier);
347
+ if (target && !eager.has(target.path)) queue.push(target);
348
+ }
349
+ }
350
+ return deferred;
351
+ }
@@ -15,6 +15,7 @@ import {
15
15
  type ExportDeclaration,
16
16
  type Identifier,
17
17
  type ImportDeclaration,
18
+ type Modifier,
18
19
  type Node,
19
20
  type ObjectLiteralExpression,
20
21
  type PropertyAssignment,
@@ -31,6 +32,15 @@ import type { AssetLoader } from "../../extensions/registry.js";
31
32
  import { loadGraph } from "./graph.js";
32
33
  import { lowerCommonJSRequires, prepareCommonJS } from "./commonjs.js";
33
34
  import { classifyDependency } from "./resolve.js";
35
+ import {
36
+ computeDeferredModules,
37
+ deferredAliasName,
38
+ deferredModuleFunctions,
39
+ deferredWrapperName,
40
+ eagerModuleFunctions,
41
+ isDeferredMarkerImport,
42
+ type DeferredAlias,
43
+ } from "./deferred.js";
34
44
  import {
35
45
  declarationName,
36
46
  hasModifier,
@@ -137,16 +147,147 @@ export function bundleModules(
137
147
  // Phase 3: rewrite imported names and record re-exports. Namespace imports
138
148
  // are lowered to a synthetic object literal holding every export, built
139
149
  // from the dependency's (already renamed) final names.
150
+ const deferredModules = computeDeferredModules(records, externalSpecifiers);
151
+ const deferredAliases = new Map<string, DeferredAlias[]>();
152
+ const markerTargets = new Set<string>();
140
153
  const namespaceStatements = new Map<Statement, Statement>();
141
154
  for (const record of records) {
142
155
  const scope = moduleScope(record);
143
156
  if (!scope) continue;
157
+ const deferred = deferredModules.has(record.path);
158
+ const aliases: DeferredAlias[] = [];
144
159
  for (const statement of record.sourceFile.statements) {
160
+ if (statement.kind === SyntaxKind.ExportDeclaration) {
161
+ if (!deferred) continue;
162
+ const declaration = statement as ExportDeclaration;
163
+ if (!declaration.moduleSpecifier) continue;
164
+ const dependency = dependencyOf(record, declaration.moduleSpecifier.value);
165
+ if (!dependency || !deferredModules.has(dependency.path)) continue;
166
+ const wrapper = deferredWrapperName(dependency);
167
+ const clause = declaration.exportClause;
168
+ if (clause && clause.kind === SyntaxKind.NamedExports) {
169
+ for (const specifier of clause.elements) {
170
+ if (specifier.isTypeOnly) continue;
171
+ const importedName = specifier.propertyName?.text ?? specifier.name.text;
172
+ const alias = deferredAliasName(record, aliases.length, `re_${specifier.name.text}`);
173
+ aliases.push({
174
+ name: alias,
175
+ wrapper,
176
+ importedName,
177
+ start: declaration.start,
178
+ end: declaration.end,
179
+ });
180
+ record.exports.set(specifier.name.text, alias);
181
+ }
182
+ } else if (!clause) {
183
+ /* `export * from "./dep"`: alias every runtime export the deferred
184
+ * dependency contributes, so our own wrapper can return them. */
185
+ for (const [name, target] of dependency.exports) {
186
+ if (!target) continue;
187
+ const alias = deferredAliasName(record, aliases.length, `re_${name}`);
188
+ aliases.push({
189
+ name: alias,
190
+ wrapper,
191
+ importedName: name,
192
+ start: declaration.start,
193
+ end: declaration.end,
194
+ });
195
+ record.exports.set(name, alias);
196
+ }
197
+ }
198
+ continue;
199
+ }
145
200
  if (statement.kind !== SyntaxKind.ImportDeclaration) continue;
146
201
  const declaration = statement as ImportDeclaration;
147
- const dependency = dependencyOf(record, declaration.moduleSpecifier.value);
148
202
  const clause = declaration.importClause;
149
- if (!clause || clause.isTypeOnly || !dependency) continue;
203
+ if (clause?.isTypeOnly) continue;
204
+ const dependency = dependencyOf(record, declaration.moduleSpecifier.value);
205
+ if (!dependency) continue;
206
+ const wrapper = deferredWrapperName(dependency);
207
+
208
+ /* `import "./side-effect"`: a deferred module still has to run the
209
+ * dependency's top-level code when it initialises. */
210
+ if (!clause) {
211
+ if (deferred && deferredModules.has(dependency.path)) {
212
+ aliases.push({
213
+ name: "",
214
+ wrapper,
215
+ sideEffect: true,
216
+ start: declaration.start,
217
+ end: declaration.end,
218
+ });
219
+ }
220
+ continue;
221
+ }
222
+
223
+ if (isDeferredMarkerImport(declaration)) {
224
+ /* `<script src>`'s deferred imports: the loader calls the binding as a
225
+ * function returning the dependency's exports. */
226
+ markerTargets.add(dependency.path);
227
+ const bindings = clause.namedBindings;
228
+ if (bindings && bindings.kind === SyntaxKind.NamedImports) {
229
+ for (const specifier of bindings.elements) {
230
+ const local = scope.symbols.get(specifier.name.text);
231
+ if (local) renameReferences(local, wrapper);
232
+ }
233
+ }
234
+ continue;
235
+ }
236
+
237
+ if (deferred && deferredModules.has(dependency.path)) {
238
+ /* Imports inside a deferred module become local aliases read from the
239
+ * dependency's wrapper when it initialises, not global names. */
240
+ const start = declaration.start;
241
+ const end = declaration.end;
242
+ if (clause.name) {
243
+ const local = scope.symbols.get(clause.name.text);
244
+ const alias = deferredAliasName(record, aliases.length, "default");
245
+ if (local) renameReferences(local, alias);
246
+ aliases.push({
247
+ name: alias,
248
+ wrapper,
249
+ importedName: "default",
250
+ local: clause.name.text,
251
+ start,
252
+ end,
253
+ });
254
+ }
255
+ const bindings = clause.namedBindings;
256
+ if (bindings && bindings.kind === SyntaxKind.NamedImports) {
257
+ if (bindings.elements.length === 0) {
258
+ aliases.push({ name: "", wrapper, sideEffect: true, start, end });
259
+ }
260
+ for (const specifier of bindings.elements) {
261
+ if (specifier.isTypeOnly) continue;
262
+ const importedName = specifier.propertyName?.text ?? specifier.name.text;
263
+ const local = scope.symbols.get(specifier.name.text);
264
+ const alias = deferredAliasName(record, aliases.length, importedName);
265
+ if (local) renameReferences(local, alias);
266
+ aliases.push({
267
+ name: alias,
268
+ wrapper,
269
+ importedName,
270
+ local: specifier.name.text,
271
+ start,
272
+ end,
273
+ });
274
+ }
275
+ } else if (bindings && bindings.kind === SyntaxKind.NamespaceImport) {
276
+ const local = scope.symbols.get(bindings.name.text);
277
+ const alias = deferredAliasName(record, aliases.length, "namespace");
278
+ if (local) renameReferences(local, alias);
279
+ aliases.push({
280
+ name: alias,
281
+ wrapper,
282
+ namespace: true,
283
+ local: bindings.name.text,
284
+ start,
285
+ end,
286
+ });
287
+ }
288
+ continue;
289
+ }
290
+
150
291
  if (clause.name) {
151
292
  const target = dependency.exports.get("default") ?? dependency.exports.get("default");
152
293
  const local = scope.symbols.get(clause.name.text);
@@ -168,6 +309,23 @@ export function bundleModules(
168
309
  namespaceStatements.set(statement, namespaceImportToStatement(statement, finalName, dependency));
169
310
  }
170
311
  }
312
+ if (aliases.length > 0) {
313
+ deferredAliases.set(record.path, aliases);
314
+ /* `import { foo } from "./x"; export { foo };` maps the export to the
315
+ * import's local name in Phase 2; the wrapper's binding is the alias. */
316
+ const localAliases = new Map<string, string>();
317
+ for (const alias of aliases) {
318
+ if (alias.local !== undefined && alias.name.length > 0) {
319
+ localAliases.set(alias.local, alias.name);
320
+ }
321
+ }
322
+ if (localAliases.size > 0) {
323
+ for (const [name, local] of record.exports) {
324
+ const alias = localAliases.get(local);
325
+ if (alias !== undefined) record.exports.set(name, alias);
326
+ }
327
+ }
328
+ }
171
329
  }
172
330
 
173
331
  // Phase 4: concatenate statements, dropping imports and pure export wrappers.
@@ -213,6 +371,48 @@ export function bundleModules(
213
371
  };
214
372
  const first = records[records.length - 1]!;
215
373
  for (const record of records) {
374
+ if (deferredModules.has(record.path)) {
375
+ /* A deferred module is evaluated by the wrapper when a script runs. Its
376
+ * external imports stay top-level (codegen resolves them there); eager
377
+ * namespace objects likewise. */
378
+ const topLevel: Statement[] = [];
379
+ const body: Statement[] = [];
380
+ for (const statement of record.sourceFile.statements) {
381
+ if (statement.kind === SyntaxKind.ImportDeclaration) {
382
+ const declaration = statement as ImportDeclaration;
383
+ const resolution = classifyDependency(
384
+ dirname(record.path),
385
+ declaration.moduleSpecifier.value,
386
+ externalSpecifiers,
387
+ );
388
+ if (resolution.kind === "external") topLevel.push(statement);
389
+ const synthetic = namespaceStatements.get(statement);
390
+ if (synthetic) topLevel.push(synthetic);
391
+ continue;
392
+ }
393
+ if (statement.kind === SyntaxKind.ExportDeclaration) {
394
+ const declaration = statement as ExportDeclaration;
395
+ const inner = (declaration as unknown as { declaration?: Statement }).declaration;
396
+ if (inner) body.push(inner);
397
+ continue;
398
+ }
399
+ if (statement.kind === SyntaxKind.ExportAssignment) {
400
+ body.push(exportAssignmentToStatement(record, statement as ExportAssignment));
401
+ continue;
402
+ }
403
+ stripExportModifiers(statement);
404
+ body.push(statement);
405
+ }
406
+ for (const statement of topLevel) push(record, statement);
407
+ for (const statement of deferredModuleFunctions(
408
+ record,
409
+ deferredAliases.get(record.path) ?? [],
410
+ body,
411
+ )) {
412
+ push(record, statement);
413
+ }
414
+ continue;
415
+ }
216
416
  for (const statement of record.sourceFile.statements) {
217
417
  if (statement.kind === SyntaxKind.ImportDeclaration) {
218
418
  // External (extension / built-in) imports stay in place for codegen.
@@ -245,6 +445,13 @@ export function bundleModules(
245
445
  }
246
446
  }
247
447
 
448
+ /* A marker target that stayed eager still needs a wrapper: its exports are
449
+ * already initialised, so the wrapper only builds the namespace object. */
450
+ for (const record of records) {
451
+ if (deferredModules.has(record.path) || !markerTargets.has(record.path)) continue;
452
+ for (const statement of eagerModuleFunctions(record)) push(record, statement);
453
+ }
454
+
248
455
  // Phase 5: rename every top-level binding (declaration + references) to its
249
456
  // unique final name, now that import references have been rewritten.
250
457
  for (const record of records) {
@@ -293,6 +500,16 @@ function collectBindingIdentifiers(name: BindingName, out: string[]): void {
293
500
  }
294
501
  }
295
502
 
503
+
504
+ /** Drop `export` / `export default` from a statement moved into a wrapper. */
505
+ function stripExportModifiers(statement: Statement): void {
506
+ const modifiers = (statement as { modifiers?: Modifier[] }).modifiers;
507
+ if (!modifiers) return;
508
+ (statement as { modifiers: Modifier[] }).modifiers = modifiers.filter(
509
+ (modifier) => modifier.modifierKind !== ModifierKind.Export && modifier.modifierKind !== ModifierKind.Default,
510
+ );
511
+ }
512
+
296
513
  /** True for anything shaped like an AST node (constructed nodes carry no trivia). */
297
514
  function isNode(value: unknown): value is Node {
298
515
  return !!value && typeof value === "object" && "kind" in (value as object);
@@ -38,6 +38,7 @@ export const RUNTIME_SOURCES = [
38
38
  "xt_performance.c",
39
39
  "xt_text_codec.c",
40
40
  "xt_url.c",
41
+ "xt_url_object.c",
41
42
  "xt_array_buffer.c",
42
43
  "xt_io.c",
43
44
  ] as const;
@@ -9,4 +9,4 @@
9
9
  * `main`/`master` only (see `.githooks/pre-commit`), so feature branches never
10
10
  * change this line and merging two of them cannot conflict here.
11
11
  */
12
- export const COMPILER_VERSION = "0.5.37";
12
+ export const COMPILER_VERSION = "0.5.38";