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
@@ -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
- /** Local names the hoisted imports introduce. */
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 = declaration.importClause
212
+ const hoisted = clause
146
213
  ? rewriteSpecifierText(htmlPath, scriptPath, source, statement, declaration.moduleSpecifier)
147
- : `import {} from ${JSON.stringify(rewriteSpecifier(htmlPath, scriptPath, declaration.moduleSpecifier.value))}`;
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
- imports.push(rewriteSpecifierText(htmlPath, scriptPath, source, statement, declaration.moduleSpecifier));
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
- sourceRanges.push({ start: offset + header.length, end: offset + header.length + body.length, file });
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, and the shader rewriter already defines
8
- * `UNROLLED_LOOP_INDEX` and keeps the numeric `for` loop. Disabling the pattern
9
- * therefore leaves valid GLSL instead of a half-expanded loop.
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";