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.
- package/dist/src/cli/help.d.ts +1 -1
- package/dist/src/codegen/generator/state.js +1 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/tables.js +1 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/deferred.d.ts +63 -0
- package/dist/src/driver/bundler/deferred.js +279 -0
- package/dist/src/driver/bundler/deferred.js.map +1 -0
- package/dist/src/driver/bundler/merge.js +214 -2
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/runtime-objects.d.ts +1 -1
- package/dist/src/driver/runtime-objects.js +1 -0
- package/dist/src/driver/runtime-objects.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/gui/html.js +89 -13
- package/dist/src/extensions/gui/html.js.map +1 -1
- package/dist/src/extensions/gui/index.js +1 -0
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/gui/three.d.ts +4 -3
- package/dist/src/extensions/gui/three.js +4 -3
- package/dist/src/extensions/gui/three.js.map +1 -1
- package/dist/tests/e2e/gui-script-import-order.test.d.ts +10 -0
- package/dist/tests/e2e/gui-script-import-order.test.js +161 -0
- package/dist/tests/e2e/gui-script-import-order.test.js.map +1 -0
- package/dist/tests/e2e/gui-webgl2-shadow.test.d.ts +10 -0
- package/dist/tests/e2e/gui-webgl2-shadow.test.js +108 -0
- package/dist/tests/e2e/gui-webgl2-shadow.test.js.map +1 -0
- package/dist/tests/e2e/url.test.d.ts +6 -0
- package/dist/tests/e2e/url.test.js +114 -0
- package/dist/tests/e2e/url.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.js +22 -10
- package/dist/tests/extensions/gui.test.js.map +1 -1
- package/doc/gui/webgl.md +20 -10
- package/doc/gui-scripts.md +19 -19
- package/doc/gui.md +10 -8
- package/doc/implemented/fetch.md +5 -0
- package/doc/implemented/modules.md +13 -0
- package/doc/implemented/stdlib.md +17 -0
- package/doc/unimplemented/deviations.md +1 -0
- package/doc/zh-CN/gui/webgl.md +3 -3
- package/doc/zh-CN/gui-scripts.md +14 -13
- package/doc/zh-CN/gui.md +7 -6
- package/doc/zh-CN/implemented/fetch.md +4 -0
- package/doc/zh-CN/implemented/modules.md +10 -0
- package/doc/zh-CN/implemented/stdlib.md +15 -0
- package/doc/zh-CN/unimplemented/deviations.md +1 -0
- package/package.json +7 -7
- package/runtime/ext_gui/dom_api_webgl.cpp +10 -0
- package/runtime/ext_gui/dom_api_webgl2.cpp +33 -1
- package/runtime/ext_gui/gui_engine.h +2 -0
- package/runtime/ext_gui/script.cpp +31 -0
- package/runtime/ext_gui/webgl_glsl.h +3 -0
- package/runtime/ext_gui/webgl_glsl_unroll.cpp +283 -0
- package/runtime/ext_gui/webgl_msl.cpp +4 -0
- package/runtime/ext_gui/webgl_shader.cpp +12 -6
- package/runtime/rt.h +3 -0
- package/runtime/xt_fetch/async.inc +60 -0
- package/runtime/xt_url_object.c +463 -0
- package/src/codegen/generator/state.ts +1 -0
- package/src/codegen/generator/tables.ts +1 -0
- package/src/driver/bundler/deferred.ts +351 -0
- package/src/driver/bundler/merge.ts +219 -2
- package/src/driver/runtime-objects.ts +1 -0
- package/src/driver/version.ts +1 -1
- package/src/extensions/gui/html.ts +91 -15
- package/src/extensions/gui/index.ts +1 -0
- 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 (
|
|
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);
|
package/src/driver/version.ts
CHANGED