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
|
@@ -119,32 +119,99 @@ function importLocalNames(declaration: ImportDeclaration): string[] {
|
|
|
119
119
|
interface CompiledModule {
|
|
120
120
|
/** Hoisted `import`/`export … from` statements (specifiers rewritten). */
|
|
121
121
|
imports: string[];
|
|
122
|
-
/**
|
|
122
|
+
/** Synthetic imports that ask the bundler to defer a local module. */
|
|
123
|
+
lazyImports: string[];
|
|
124
|
+
/** Statements that load deferred modules at script-run time. */
|
|
125
|
+
prelude: string[];
|
|
126
|
+
/** Local names the hoisted (eager) imports introduce. */
|
|
123
127
|
localNames: string[];
|
|
124
128
|
/** The module body, wrapped as a script function by the caller. */
|
|
125
129
|
body: string;
|
|
126
130
|
}
|
|
127
131
|
|
|
132
|
+
/**
|
|
133
|
+
* A relative script import is evaluated lazily (after `loadHTML`), like a
|
|
134
|
+
* browser module script. Bare specifiers stay eager: they usually name a
|
|
135
|
+
* compiler extension or a package whose top level does not touch the page.
|
|
136
|
+
*/
|
|
137
|
+
function isDeferredSpecifier(specifier: string): boolean {
|
|
138
|
+
return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/") ||
|
|
139
|
+
/^[A-Za-z]:[\\/]/.test(specifier);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** `const { a, b: c } = load();` for a deferred import's named bindings. */
|
|
143
|
+
function deferredPrelude(alias: string, declaration: ImportDeclaration): string[] {
|
|
144
|
+
const clause = declaration.importClause;
|
|
145
|
+
if (!clause) return [`${alias}();`];
|
|
146
|
+
const lines: string[] = [];
|
|
147
|
+
if (clause.name) lines.push(`const ${clause.name.text} = ${alias}().default;`);
|
|
148
|
+
const bindings = clause.namedBindings;
|
|
149
|
+
if (bindings && bindings.kind === SyntaxKind.NamedImports) {
|
|
150
|
+
const pairs = bindings.elements
|
|
151
|
+
.filter((element) => !element.isTypeOnly)
|
|
152
|
+
.map((element) => {
|
|
153
|
+
const imported = element.propertyName?.text ?? element.name.text;
|
|
154
|
+
return imported === element.name.text ? element.name.text : `${imported}: ${element.name.text}`;
|
|
155
|
+
});
|
|
156
|
+
if (pairs.length > 0) lines.push(`const { ${pairs.join(", ")} } = ${alias}();`);
|
|
157
|
+
} else if (bindings && bindings.kind === SyntaxKind.NamespaceImport) {
|
|
158
|
+
lines.push(`const ${bindings.name.text} = ${alias}();`);
|
|
159
|
+
}
|
|
160
|
+
if (lines.length === 0) lines.push(`${alias}();`);
|
|
161
|
+
return lines;
|
|
162
|
+
}
|
|
163
|
+
|
|
128
164
|
/** Split an external script module into hoisted imports and a body. */
|
|
129
|
-
function compileModuleSource(htmlPath: string, scriptPath: string): CompiledModule {
|
|
165
|
+
function compileModuleSource(htmlPath: string, scriptPath: string, scriptIndex: number): CompiledModule {
|
|
130
166
|
const source = readFileSync(scriptPath, "utf8");
|
|
131
167
|
const bag = new DiagnosticBag();
|
|
132
168
|
const parsed = new Parser(new SourceFile(scriptPath, source), bag).parseSourceFile();
|
|
133
169
|
const chars = source.split("");
|
|
134
170
|
const imports: string[] = [];
|
|
171
|
+
const lazyImports: string[] = [];
|
|
172
|
+
const prelude: string[] = [];
|
|
135
173
|
const localNames: string[] = [];
|
|
174
|
+
let lazyIndex = 0;
|
|
136
175
|
const blank = (start: number, end: number): void => {
|
|
137
176
|
for (let i = start; i < end; i += 1) chars[i] = " ";
|
|
138
177
|
};
|
|
178
|
+
const hasRuntimeBinding = (clause: ImportDeclaration["importClause"]): boolean => {
|
|
179
|
+
if (!clause) return true;
|
|
180
|
+
if (clause.name) return true;
|
|
181
|
+
const bindings = clause.namedBindings;
|
|
182
|
+
if (!bindings) return false;
|
|
183
|
+
if (bindings.kind === SyntaxKind.NamespaceImport) return true;
|
|
184
|
+
return bindings.elements.some((element) => !element.isTypeOnly);
|
|
185
|
+
};
|
|
186
|
+
const marker = (specifier: string): string => {
|
|
187
|
+
const alias = `__xt_deferred_${scriptIndex}_${lazyIndex}`;
|
|
188
|
+
lazyIndex += 1;
|
|
189
|
+
lazyImports.push(
|
|
190
|
+
`import { __xt_deferred_module as ${alias} } from ${JSON.stringify(rewriteSpecifier(htmlPath, scriptPath, specifier))};`,
|
|
191
|
+
);
|
|
192
|
+
return alias;
|
|
193
|
+
};
|
|
139
194
|
|
|
140
195
|
for (const statement of parsed.statements) {
|
|
141
196
|
if (statement.kind === SyntaxKind.ImportDeclaration) {
|
|
142
197
|
const declaration = statement as ImportDeclaration;
|
|
198
|
+
const specifier = declaration.moduleSpecifier.value;
|
|
199
|
+
const clause = declaration.importClause;
|
|
200
|
+
if (
|
|
201
|
+
(clause === undefined || !clause.isTypeOnly) &&
|
|
202
|
+
hasRuntimeBinding(clause) &&
|
|
203
|
+
isDeferredSpecifier(specifier)
|
|
204
|
+
) {
|
|
205
|
+
const alias = marker(specifier);
|
|
206
|
+
prelude.push(...deferredPrelude(alias, declaration));
|
|
207
|
+
blank(statement.start, statement.end);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
143
210
|
/* The front-end has no side-effect `import "x"` form; spell it as an
|
|
144
211
|
* empty named import so the dependency is still loaded. */
|
|
145
|
-
const hoisted =
|
|
212
|
+
const hoisted = clause
|
|
146
213
|
? rewriteSpecifierText(htmlPath, scriptPath, source, statement, declaration.moduleSpecifier)
|
|
147
|
-
: `import {} from ${JSON.stringify(rewriteSpecifier(htmlPath, scriptPath,
|
|
214
|
+
: `import {} from ${JSON.stringify(rewriteSpecifier(htmlPath, scriptPath, specifier))}`;
|
|
148
215
|
imports.push(hoisted);
|
|
149
216
|
localNames.push(...importLocalNames(declaration));
|
|
150
217
|
blank(statement.start, statement.end);
|
|
@@ -153,7 +220,12 @@ function compileModuleSource(htmlPath: string, scriptPath: string): CompiledModu
|
|
|
153
220
|
if (statement.kind === SyntaxKind.ExportDeclaration) {
|
|
154
221
|
const declaration = statement as ExportDeclaration;
|
|
155
222
|
if (declaration.moduleSpecifier) {
|
|
156
|
-
|
|
223
|
+
if (isDeferredSpecifier(declaration.moduleSpecifier.value)) {
|
|
224
|
+
const alias = marker(declaration.moduleSpecifier.value);
|
|
225
|
+
prelude.push(`${alias}();`);
|
|
226
|
+
} else {
|
|
227
|
+
imports.push(rewriteSpecifierText(htmlPath, scriptPath, source, statement, declaration.moduleSpecifier));
|
|
228
|
+
}
|
|
157
229
|
}
|
|
158
230
|
blank(statement.start, statement.end);
|
|
159
231
|
continue;
|
|
@@ -173,7 +245,7 @@ function compileModuleSource(htmlPath: string, scriptPath: string): CompiledModu
|
|
|
173
245
|
}
|
|
174
246
|
}
|
|
175
247
|
|
|
176
|
-
return { imports, localNames, body: chars.join("") };
|
|
248
|
+
return { imports, lazyImports, prelude, localNames, body: chars.join("") };
|
|
177
249
|
}
|
|
178
250
|
|
|
179
251
|
/** Resolve a `<script src>` value to a local file path (ignoring `?`/`#`). */
|
|
@@ -187,8 +259,9 @@ export function loadHtmlAsset(path: string, source: string): AssetLoadResult {
|
|
|
187
259
|
/* `file` is set for an external `<script src>`: its body keeps that file as
|
|
188
260
|
* the origin of every node, so a `.js` script stays untyped and diagnostics
|
|
189
261
|
* name the script, not the HTML page. */
|
|
190
|
-
const scripts: Array<{ id: string; body: string; file?: string }> = [];
|
|
262
|
+
const scripts: Array<{ id: string; body: string; prelude: string[]; file?: string }> = [];
|
|
191
263
|
const imports: string[] = [];
|
|
264
|
+
const lazyImports: string[] = [];
|
|
192
265
|
const importedText = new Set<string>();
|
|
193
266
|
const bound = new Set<string>();
|
|
194
267
|
|
|
@@ -204,13 +277,14 @@ export function loadHtmlAsset(path: string, source: string): AssetLoadResult {
|
|
|
204
277
|
if (!existsSync(scriptPath)) {
|
|
205
278
|
throw new Error(`gui: <script src="${src}"> not found (looked for ${scriptPath})`);
|
|
206
279
|
}
|
|
207
|
-
const compiled = compileModuleSource(path, scriptPath);
|
|
280
|
+
const compiled = compileModuleSource(path, scriptPath, scripts.length);
|
|
208
281
|
for (const text of compiled.imports) {
|
|
209
282
|
if (!importedText.has(text)) {
|
|
210
283
|
importedText.add(text);
|
|
211
284
|
imports.push(text);
|
|
212
285
|
}
|
|
213
286
|
}
|
|
287
|
+
lazyImports.push(...compiled.lazyImports);
|
|
214
288
|
for (const name of compiled.localNames) {
|
|
215
289
|
if (bound.has(name)) {
|
|
216
290
|
throw new Error(
|
|
@@ -219,8 +293,8 @@ export function loadHtmlAsset(path: string, source: string): AssetLoadResult {
|
|
|
219
293
|
}
|
|
220
294
|
bound.add(name);
|
|
221
295
|
}
|
|
222
|
-
const id = hashString(compiled.body);
|
|
223
|
-
scripts.push({ id, body: compiled.body, file: scriptPath });
|
|
296
|
+
const id = hashString([...compiled.prelude, compiled.body].join("\n"));
|
|
297
|
+
scripts.push({ id, body: compiled.body, prelude: compiled.prelude, file: scriptPath });
|
|
224
298
|
return `<script data-xt-id="${id}"></script>`;
|
|
225
299
|
}
|
|
226
300
|
|
|
@@ -228,21 +302,23 @@ export function loadHtmlAsset(path: string, source: string): AssetLoadResult {
|
|
|
228
302
|
if (src !== undefined) return match;
|
|
229
303
|
if (inlineBody.trim() === "") return match;
|
|
230
304
|
const id = hashString(inlineBody);
|
|
231
|
-
scripts.push({ id, body: inlineBody });
|
|
305
|
+
scripts.push({ id, body: inlineBody, prelude: [] });
|
|
232
306
|
return `<script data-xt-id="${id}"></script>`;
|
|
233
307
|
});
|
|
234
308
|
|
|
235
|
-
const parts: string[] = [...imports];
|
|
309
|
+
const parts: string[] = [...imports, ...lazyImports];
|
|
236
310
|
const sourceRanges: AssetSourceRange[] = [];
|
|
237
311
|
/* Offset of the next part once joined with "\n", so a body's span in the
|
|
238
312
|
* generated module can be reported back as the file it was copied from. */
|
|
239
313
|
let offset = parts.reduce((total, part) => total + part.length + 1, 0);
|
|
240
|
-
for (const { id, body, file } of scripts) {
|
|
314
|
+
for (const { id, body, prelude, file } of scripts) {
|
|
241
315
|
const header = `function __xt_script_${id}(window: any, document: any): void {\n`;
|
|
316
|
+
const preludeText = prelude.length > 0 ? `${prelude.join("\n")}\n` : "";
|
|
242
317
|
const part =
|
|
243
|
-
`${header}${body}\n}\n` + `__registerScript(${JSON.stringify(id)}, __xt_script_${id});`;
|
|
318
|
+
`${header}${preludeText}${body}\n}\n` + `__registerScript(${JSON.stringify(id)}, __xt_script_${id});`;
|
|
244
319
|
if (file !== undefined) {
|
|
245
|
-
|
|
320
|
+
const bodyStart = offset + header.length + preludeText.length;
|
|
321
|
+
sourceRanges.push({ start: bodyStart, end: bodyStart + body.length, file });
|
|
246
322
|
}
|
|
247
323
|
parts.push(part);
|
|
248
324
|
offset += part.length + 1;
|
|
@@ -202,6 +202,7 @@ export const guiExtension: Extension = {
|
|
|
202
202
|
*/
|
|
203
203
|
builtins: () => ({
|
|
204
204
|
__registerScript: { symbol: "xt_register_script" },
|
|
205
|
+
__xt_defer: { symbol: "xt_gui_defer_module" },
|
|
205
206
|
requestAnimationFrame: { symbol: "xt_browser_request_animation_frame" },
|
|
206
207
|
cancelAnimationFrame: { symbol: "xt_browser_cancel_animation_frame" },
|
|
207
208
|
matchMedia: { symbol: "xt_browser_match_media" },
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* three.js's `WebGLRenderer` runs unchanged on this engine's real WebGL 2
|
|
5
5
|
* backend; no Canvas2D fallback (and no renderer wrapper) is needed. The one
|
|
6
6
|
* shader-side workaround is its `unrollLoops` pass: its lazy global regex does
|
|
7
|
-
* not behave reliably on this runtime
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
7
|
+
* not behave reliably on this runtime. The shader rewriter expands
|
|
8
|
+
* `#pragma unroll_loop_start` itself (and substitutes `UNROLLED_LOOP_INDEX`),
|
|
9
|
+
* so disabling the JavaScript pass leaves valid GLSL instead of a
|
|
10
|
+
* half-expanded loop.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import type { AssetLoadResult } from "../registry.js";
|