xbintsc 0.5.52 → 0.6.8

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 (156) hide show
  1. package/README.md +8 -2
  2. package/README.zh-CN.md +9 -3
  3. package/dist/scripts/fetch-tarball.d.ts +28 -0
  4. package/dist/scripts/fetch-tarball.js +57 -0
  5. package/dist/scripts/fetch-tarball.js.map +1 -0
  6. package/dist/scripts/gui-build-options.d.ts +77 -0
  7. package/dist/scripts/gui-build-options.js +134 -0
  8. package/dist/scripts/gui-build-options.js.map +1 -0
  9. package/dist/src/cli/help.d.ts +1 -1
  10. package/dist/src/cli/hints.d.ts +6 -2
  11. package/dist/src/cli/hints.js +16 -2
  12. package/dist/src/cli/hints.js.map +1 -1
  13. package/dist/src/cli/main.js +9 -0
  14. package/dist/src/cli/main.js.map +1 -1
  15. package/dist/src/driver/app-artifacts.d.ts +2 -0
  16. package/dist/src/driver/app-artifacts.js +3 -3
  17. package/dist/src/driver/app-artifacts.js.map +1 -1
  18. package/dist/src/driver/compiler.js +147 -52
  19. package/dist/src/driver/compiler.js.map +1 -1
  20. package/dist/src/driver/icon.d.ts +1 -1
  21. package/dist/src/driver/icon.js +3 -3
  22. package/dist/src/driver/icon.js.map +1 -1
  23. package/dist/src/driver/macos-sdk.d.ts +32 -0
  24. package/dist/src/driver/macos-sdk.js +50 -0
  25. package/dist/src/driver/macos-sdk.js.map +1 -0
  26. package/dist/src/driver/options.d.ts +6 -0
  27. package/dist/src/driver/platform-support.d.ts +45 -0
  28. package/dist/src/driver/platform-support.js +66 -0
  29. package/dist/src/driver/platform-support.js.map +1 -0
  30. package/dist/src/driver/prebuilt.d.ts +26 -0
  31. package/dist/src/driver/prebuilt.js +58 -0
  32. package/dist/src/driver/prebuilt.js.map +1 -0
  33. package/dist/src/driver/runtime-objects.d.ts +11 -0
  34. package/dist/src/driver/runtime-objects.js +14 -5
  35. package/dist/src/driver/runtime-objects.js.map +1 -1
  36. package/dist/src/driver/toolchain-provider.d.ts +8 -0
  37. package/dist/src/driver/toolchain-provider.js +8 -0
  38. package/dist/src/driver/toolchain-provider.js.map +1 -1
  39. package/dist/src/driver/toolchain.d.ts +20 -1
  40. package/dist/src/driver/toolchain.js +19 -0
  41. package/dist/src/driver/toolchain.js.map +1 -1
  42. package/dist/src/driver/version.d.ts +1 -1
  43. package/dist/src/driver/version.js +1 -1
  44. package/dist/src/driver/version.js.map +1 -1
  45. package/dist/src/driver/win-entry.d.ts +1 -1
  46. package/dist/src/driver/win-entry.js +4 -4
  47. package/dist/src/driver/win-entry.js.map +1 -1
  48. package/dist/src/extensions/gui/html.d.ts +10 -7
  49. package/dist/src/extensions/gui/html.js +53 -29
  50. package/dist/src/extensions/gui/html.js.map +1 -1
  51. package/dist/src/extensions/node/child_process/index.d.ts +4 -3
  52. package/dist/src/extensions/node/child_process/index.js +4 -3
  53. package/dist/src/extensions/node/child_process/index.js.map +1 -1
  54. package/dist/tests/cli/hints.test.js +9 -1
  55. package/dist/tests/cli/hints.test.js.map +1 -1
  56. package/dist/tests/cli/main.test.js +9 -0
  57. package/dist/tests/cli/main.test.js.map +1 -1
  58. package/dist/tests/driver/compiler.test.js +107 -0
  59. package/dist/tests/driver/compiler.test.js.map +1 -1
  60. package/dist/tests/driver/cxx-compile-flags.test.d.ts +1 -0
  61. package/dist/tests/driver/cxx-compile-flags.test.js +24 -0
  62. package/dist/tests/driver/cxx-compile-flags.test.js.map +1 -0
  63. package/dist/tests/driver/macos-sdk.test.d.ts +1 -0
  64. package/dist/tests/driver/macos-sdk.test.js +83 -0
  65. package/dist/tests/driver/macos-sdk.test.js.map +1 -0
  66. package/dist/tests/driver/platform-support.test.d.ts +1 -0
  67. package/dist/tests/driver/platform-support.test.js +41 -0
  68. package/dist/tests/driver/platform-support.test.js.map +1 -0
  69. package/dist/tests/driver/prebuilt.test.d.ts +1 -0
  70. package/dist/tests/driver/prebuilt.test.js +44 -0
  71. package/dist/tests/driver/prebuilt.test.js.map +1 -0
  72. package/dist/tests/driver/toolchain-provider.test.js +24 -0
  73. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  74. package/dist/tests/driver/toolchain.test.js +23 -0
  75. package/dist/tests/driver/toolchain.test.js.map +1 -1
  76. package/dist/tests/e2e/async-await.test.js +4 -2
  77. package/dist/tests/e2e/async-await.test.js.map +1 -1
  78. package/dist/tests/e2e/compile-node.test.js +21 -0
  79. package/dist/tests/e2e/compile-node.test.js.map +1 -1
  80. package/dist/tests/e2e/fetch.test.js +34 -6
  81. package/dist/tests/e2e/fetch.test.js.map +1 -1
  82. package/dist/tests/e2e/gui-example-pelican3d.test.d.ts +10 -0
  83. package/dist/tests/e2e/gui-example-pelican3d.test.js +94 -0
  84. package/dist/tests/e2e/gui-example-pelican3d.test.js.map +1 -0
  85. package/dist/tests/e2e/gui-script.test.js +48 -0
  86. package/dist/tests/e2e/gui-script.test.js.map +1 -1
  87. package/dist/tests/e2e/unhandled-rejection.test.d.ts +14 -0
  88. package/dist/tests/e2e/unhandled-rejection.test.js +107 -0
  89. package/dist/tests/e2e/unhandled-rejection.test.js.map +1 -0
  90. package/dist/tests/extensions/gui.test.js +44 -0
  91. package/dist/tests/extensions/gui.test.js.map +1 -1
  92. package/dist/tests/scripts/fetch-tarball.test.d.ts +1 -0
  93. package/dist/tests/scripts/fetch-tarball.test.js +70 -0
  94. package/dist/tests/scripts/fetch-tarball.test.js.map +1 -0
  95. package/doc/ai/cli.md +6 -0
  96. package/doc/ai/language-support.md +1 -0
  97. package/doc/ai/troubleshooting.md +32 -2
  98. package/doc/ai/zh-CN/cli.md +5 -0
  99. package/doc/ai/zh-CN/language-support.md +1 -0
  100. package/doc/ai/zh-CN/troubleshooting.md +27 -2
  101. package/doc/gui/build.md +7 -3
  102. package/doc/gui/dom.md +3 -1
  103. package/doc/gui/webgl.md +4 -0
  104. package/doc/gui-scripts.md +33 -17
  105. package/doc/gui.md +17 -1
  106. package/doc/implemented/async.md +1 -0
  107. package/doc/implemented/modules.md +9 -6
  108. package/doc/implemented/node/child-process.md +12 -4
  109. package/doc/implemented/runtime-core.md +1 -1
  110. package/doc/npm-package.md +23 -2
  111. package/doc/post/make-js-great-again.md +30 -0
  112. package/doc/requirements.md +31 -0
  113. package/doc/unimplemented/deviations.md +1 -0
  114. package/doc/zh-CN/gui/build.md +4 -2
  115. package/doc/zh-CN/gui/dom.md +3 -1
  116. package/doc/zh-CN/gui/webgl.md +1 -0
  117. package/doc/zh-CN/gui-scripts.md +23 -11
  118. package/doc/zh-CN/gui.md +11 -1
  119. package/doc/zh-CN/implemented/async.md +1 -0
  120. package/doc/zh-CN/implemented/modules.md +8 -4
  121. package/doc/zh-CN/implemented/node/child-process.md +10 -3
  122. package/doc/zh-CN/implemented/runtime-core.md +1 -1
  123. package/doc/zh-CN/npm-package.md +18 -1
  124. package/doc/zh-CN/post/make-js-great-again.md +32 -0
  125. package/doc/zh-CN/requirements.md +25 -0
  126. package/doc/zh-CN/unimplemented/deviations.md +1 -0
  127. package/package.json +7 -7
  128. package/runtime/ext_node/child_process/child_process.c +149 -7
  129. package/runtime/rt.h +4 -0
  130. package/runtime/xt_async.c +4 -0
  131. package/runtime/xt_generator.c +2 -0
  132. package/runtime/xt_loop.c +8 -4
  133. package/runtime/xt_promise.c +103 -0
  134. package/scripts/build-gui.ts +17 -5
  135. package/scripts/build-media.ts +89 -10
  136. package/scripts/build-runtime.ts +7 -2
  137. package/scripts/fetch-tarball.ts +71 -0
  138. package/scripts/gui-build-cache.ts +28 -0
  139. package/scripts/gui-build-options.ts +26 -2
  140. package/scripts/package-release.ts +8 -0
  141. package/src/cli/hints.ts +19 -2
  142. package/src/cli/main.ts +11 -0
  143. package/src/driver/app-artifacts.ts +5 -3
  144. package/src/driver/compiler.ts +167 -56
  145. package/src/driver/icon.ts +3 -2
  146. package/src/driver/macos-sdk.ts +52 -0
  147. package/src/driver/options.ts +6 -0
  148. package/src/driver/platform-support.ts +69 -0
  149. package/src/driver/prebuilt.ts +61 -0
  150. package/src/driver/runtime-objects.ts +26 -5
  151. package/src/driver/toolchain-provider.ts +11 -0
  152. package/src/driver/toolchain.ts +26 -1
  153. package/src/driver/version.ts +1 -1
  154. package/src/driver/win-entry.ts +4 -3
  155. package/src/extensions/gui/html.ts +61 -31
  156. package/src/extensions/node/child_process/index.ts +4 -3
@@ -141,9 +141,10 @@ __registerScript("a1b2…", __xt_script_a1b2);
141
141
  export default __html;
142
142
  ```
143
143
 
144
- - The id is a content hash of the body (sha256, first 32 hex chars); the body is
145
- replaced by an empty marker element so the engine can find it in document
146
- order.
144
+ - id is a content hash of the wrapped body (sha256, first 32 hex chars; a
145
+ deferred import's prelude is included, so two scripts with identical
146
+ statements but different imports stay distinct); the body is replaced by an
147
+ empty marker element so the engine can find it in document order.
147
148
  - `window` and `document` are **function parameters**, so script globals resolve
148
149
  as ordinary locals — **no compiler changes**, no global object.
149
150
  - `__registerScript(id, fn)` is a new runtime builtin that stores the closure in
@@ -151,6 +152,13 @@ export default __html;
151
152
  - Deferred scripts (`<script defer>`, or all inline scripts, as decided in M9c)
152
153
  run after the full document is parsed and the first layout is computed, in
153
154
  document order. `DOMContentLoaded` then `load` are fired on `window`.
155
+ - An inline body is split into imports and statements by the **same** routine as
156
+ a `<script src>` file (below): top-level `import`/`export … from` statements
157
+ are hoisted into the generated module, relative specifiers resolve from the
158
+ HTML file, and `export` modifiers are stripped. `import { readFileSync } from
159
+ "node:fs"` inside an inline `<script>` therefore links exactly like the same
160
+ import in an external script — one has to enable the `node` extension, but not
161
+ rewrite the page.
154
162
 
155
163
  ### 2b. External `<script src>`
156
164
 
@@ -162,21 +170,21 @@ are **deferred**, like a browser module script: the loader emits a synthetic
162
170
  at `loadHTML` time. The bundler wraps the whole local import graph in functions
163
171
  that return their exports (memoised by `__xt_defer`), so top-level module code
164
172
  runs after the document exists and two scripts importing the same module share
165
- one instance. Bare specifiers (`gui`, a package) stay eager.
173
+ one instance. Bare specifiers (`gui`, a package, `node:fs`) stay eager. An
174
+ inline `<script>` body goes through the identical split, so an import behaves
175
+ the same in both forms; the only difference is where the body comes from.
166
176
 
167
177
  A missing `src` file is still a clear error (turned into a bundler diagnostic),
168
178
  and `src` URLs (`https://…`, `data:…`, `//host`) are left untouched — they are
169
179
  not compile-time assets and never run.
170
180
 
171
- `src` URLs (`https://…`, `data:…`, `//host`) are left untouched — they are not
172
- compile-time assets and never run.
173
-
174
- A wrapped body keeps its own file as the origin of every statement in it (the
175
- loader reports the body's span in the generated module as a *source range*), so
176
- a diagnostic points at `app.js:12` rather than at a column of `index.html`, and
177
- a **plain-JavaScript** script stays untyped exactly like a `.js` module imported
178
- directly: `state.update = …` on an object literal is not a `TS4003`. A page can
179
- therefore ship its existing scripts unchanged.
181
+ A wrapped `<script src>` body keeps its own file as the origin of every
182
+ statement in it (the loader reports the body's span in the generated module as a
183
+ *source range*), so a diagnostic points at `app.js:12` rather than at a column
184
+ of `index.html`, and a **plain-JavaScript** script stays untyped exactly like a
185
+ `.js` module imported directly: `state.update = …` on an object literal is not a
186
+ `TS4003`. A page can therefore ship its existing scripts unchanged. An inline
187
+ body's origin is the HTML page itself.
180
188
 
181
189
  ```html
182
190
  <button id="b">0</button>
@@ -308,6 +316,10 @@ legacy `target` field over the handle.
308
316
  module wrapper at `loadHTML` time, so the import graph sees a parsed
309
317
  document and shares one instance per module. Missing files surface as
310
318
  diagnostics. All scripts run in document order. e2e coverage.
319
+ - **M9c′** ✅ — an inline `<script>` body is split by the same routine, so an
320
+ `import` (a `node:`/bare specifier included) links exactly as it does in a
321
+ `<script src>` file. It used to be wrapped verbatim, leaving an inline
322
+ `import` inside the script function where codegen reported TS5001.
311
323
  - **M9d** *(optional)* — detect inline HTML in template literals passed to
312
324
  `win.loadHTML(...)` and transform them too (fragile; deferred).
313
325
  - **M10 — polish** ✅ — `requestAnimationFrame`/`cancelAnimationFrame` on the
@@ -331,11 +343,12 @@ legacy `target` field over the handle.
331
343
  network fetches) do not run. Documented.
332
344
  - **Template-literal HTML** passed to `win.loadHTML(…)` is not transformed in
333
345
  M9b (only imported assets are). M9d addresses this if needed.
334
- - **Top-level `await`** inside a deferred `<script src>` module is not
346
+ - **Top-level `await`** inside a deferred `<script src>` or inline script is not
335
347
  supported: the module body is wrapped in a synchronous init function.
336
- - **Assets.** `<script src>` is resolved relative to the HTML asset; URL `src`
337
- values are ignored. The loader does not fetch network URLs; the runtime's
338
- global `fetch` is a separate API.
348
+ - **Assets.** `<script src>` is resolved relative to the HTML asset; an inline
349
+ body's imports resolve from the same place. URL `src` values are ignored. The
350
+ loader does not fetch network URLs; the runtime's global `fetch` is a separate
351
+ API.
339
352
 
340
353
  ## Testing
341
354
 
@@ -347,6 +360,9 @@ legacy `target` field over the handle.
347
360
  rewrites `*.foo` and records dependencies.
348
361
  - **M9b**: an e2e case with an inline `<script>` incrementing a counter on
349
362
  click, asserted through `console.log` from the handler.
363
+ - **M9c′**: unit cases asserting an inline body's imports are hoisted (and its
364
+ relative ones deferred), plus an e2e case where an inline script reads a file
365
+ through `node:fs` and calls a deferred helper module.
350
366
  - **M10**: e2e cases for `requestAnimationFrame` (runs each frame, re-queues,
351
367
  receives a timestamp, honours `cancelAnimationFrame`) and for
352
368
  `offsetWidth`/`offsetHeight`/`contains`.
package/doc/gui.md CHANGED
@@ -217,7 +217,16 @@ engine were Rust.
217
217
  - `vendor/` (SDL3/FreeType/HarfBuzz/glslang for the GUI archive, FFmpeg for `media`)
218
218
  is cached per OS/arch, keyed by `scripts/build-gui.ts` /
219
219
  `scripts/build-media.ts`. Windows x64 gets its own source cache plus the
220
- installed `vendor/ffmpeg-build-msvc/dist` prefix.
220
+ installed `vendor/ffmpeg-build-msvc/dist` prefix. On Linux/macOS the
221
+ installed `vendor/ffmpeg-build/dist` prefix has its own cache, keyed by
222
+ `FFMPEG_VERSION` and `scripts/build-media.ts` (which owns the configure
223
+ flags), so a hit skips FFmpeg's configure/make instead of rebuilding it on
224
+ every run: `scripts/build-media.ts` stamps the prefix with the release it
225
+ built and discards it when either the release or the macOS deployment
226
+ target changes. The FFmpeg
227
+ release tarball additionally has a cache keyed only by `FFMPEG_VERSION` and
228
+ shared by every job, so ffmpeg.org's intermittent TLS handshake cannot fail
229
+ a run that already downloaded it once.
221
230
  - Windows x64 builds both archives in CI and links a `gui` program that
222
231
  exercises the `media` link feature (plus an explicit `--feature media`
223
232
  build), using the same Visual Studio `vcvarsall` + bundled LLVM toolchain
@@ -245,6 +254,10 @@ engine were Rust.
245
254
  through a memoised module wrapper, so imported modules see the parsed
246
255
  document and share state. Missing files become diagnostics. Everything
247
256
  runs in document order.
257
+ - **M9c′** ✅ — an inline `<script>` body is split by the same routine, so its
258
+ imports are hoisted (a `node:`/bare specifier links like any module import)
259
+ instead of staying inside the wrapped function, where codegen reported
260
+ TS5001. Inline and `<script src>` now behave identically.
248
261
  10. **M10 — polish** ✅
249
262
  - `win.requestAnimationFrame(fn)` / `win.cancelAnimationFrame(id)`; callbacks
250
263
  run at the top of each frame with the frame timestamp and may mutate the
@@ -454,6 +467,9 @@ engine were Rust.
454
467
  memoised module wrapper (so module-level DOM work sees the parsed document),
455
468
  while missing files are diagnostics. Loader errors are caught by `loadGraph`
456
469
  and reported as build errors.
470
+ - **M9c′** ✅ inline `<script>` bodies are split by the same routine as external
471
+ ones, so an inline `import` is hoisted and links identically (including
472
+ `node:fs` with the `node` extension); only the body's origin differs.
457
473
  - **M10** ✅ `requestAnimationFrame`/`cancelAnimationFrame` on window handles
458
474
  (callbacks run with the frame timestamp before layout each frame) plus
459
475
  `offsetWidth`/`offsetHeight`/`contains` on element handles; e2e coverage in
@@ -30,6 +30,7 @@ Location: `runtime/xt_async.c`, `runtime/xt_coroutine.c`, `runtime/xt_promise.c`
30
30
  - Microtask queue (`xt_microtasks`); `xt_promise_ctor/resolve/reject/static` (including `withResolvers`), instance `then` / `catch` / `finally`.
31
31
  - `xt_drain_microtasks` empties the queue at program exit, and the event loop drains it between callbacks.
32
32
  - `xt_await` queues a microtask for a non-promise or settled value, and suspends the running async coroutine on a pending promise until it settles.
33
+ - Unhandled rejections are fatal, like Node's default `--unhandled-rejections=throw`. A promise that rejects and is never observed — no `.then` / `.catch`, no `await`, not adopted by another promise — is reported as `Unhandled promise rejection` on stderr once the event loop drains, and the process exits with status 1. The case this protects is the entry point: a program's `main()` is usually `async`, so an exception escaping it would otherwise reject an unobserved promise and the process would print nothing and exit 0. Attaching a handler later, even from a timer, still counts as observed.
33
34
 
34
35
  ## Timers and the event loop
35
36
 
@@ -21,12 +21,15 @@ Location: `src/parser/parser.ts` (syntax), `src/driver/modules.ts` (bundling),
21
21
 
22
22
  ## Deferred evaluation
23
23
 
24
- - The `gui` `.html` asset loader marks relative `<script src>` imports with a
25
- synthetic `__xt_deferred_module` binding. The bundler then wraps that module
26
- and its local import graph in functions that return their exports, and the
27
- generated script calls them after `loadHTML` (`__xt_defer` memoises each
28
- module). This matches a browser module script, where the graph is evaluated
29
- once the document exists.
24
+ - The `gui` `.html` asset loader marks relative imports of an inline `<script>`
25
+ body or a `<script src>` file with a synthetic `__xt_deferred_module` binding.
26
+ The bundler then wraps that module and its local import graph in functions that
27
+ return their exports, and the generated script calls them after `loadHTML`
28
+ (`__xt_defer` memoises each module). This matches a browser module script,
29
+ where the graph is evaluated once the document exists.
30
+ - Both script forms are split the same way: top-level `import`/`export … from`
31
+ statements are hoisted into the generated module and `export` modifiers are
32
+ stripped, so a bare or `node:` specifier links like any ordinary module import.
30
33
  - Modules also reachable through a normal (non-marker) import stay eager and get
31
34
  a thin wrapper over their already-initialised exports.
32
35
  - The wrapper body is synchronous: top-level `await` inside a deferred script
@@ -5,17 +5,25 @@
5
5
  Location: `src/extensions/node/child_process/index.ts`, `runtime/ext_node/child_process/child_process.c`
6
6
 
7
7
  `spawnSync(command, args[, options])` runs a program to completion and returns
8
- `{ status, stdout, stderr }`. `options.cwd` sets the working directory and
9
- `options.stdio: "inherit"` hands the child the parent's stdout/stderr (used by
10
- `xbintsc run`, so a compiled program's output streams live); otherwise
11
- stdout/stderr are captured as UTF-8 strings.
8
+ `{ status, stdout, stderr }`. `options.cwd` sets the working directory,
9
+ `options.env` replaces the child's environment, and `options.stdio: "inherit"`
10
+ hands the child the parent's stdout/stderr (used by `xbintsc run`, so a
11
+ compiled program's output streams live); otherwise stdout/stderr are captured
12
+ as UTF-8 strings.
12
13
 
13
14
  | Option | Notes |
14
15
  | --- | --- |
15
16
  | `cwd` | working directory for the child |
17
+ | `env` | the child's complete environment; omit it to inherit, spread `process.env` into it to extend |
16
18
  | `stdio: "inherit"` | share the parent's stdout/stderr instead of capturing |
17
19
  | `encoding` | accepted and ignored (output is always decoded as UTF-8) |
18
20
 
21
+ `env` follows Node's replace semantics: a present object *is* the environment,
22
+ so a variable the caller does not list is absent in the child. That is what lets
23
+ a caller hand a toolchain a specific `SDKROOT`, `INCLUDE` or `LIB` without
24
+ leaking its own shell setup — and what the macOS SDK fix relies on, because a
25
+ self-hosted compiler runs this same code.
26
+
19
27
  On Windows the child is launched with `CreateProcess` and its stdout/stderr are
20
28
  drained through anonymous pipes by reader threads. No temporary files are used,
21
29
  so a locked-down `%TEMP%` cannot break `spawnSync`, and output larger than a pipe
@@ -28,5 +28,5 @@ standard-library surface have their own pages:
28
28
  - Operator helpers: `xt_in` (`in`), `xt_delete` (`delete`), `xt_rest_args` (rest parameters / `arguments`).
29
29
  - Box: `box_new/get/set` (for closure-captured variables).
30
30
  - Functions and closures: `arg`, `closure_new/call/env/arity`.
31
- - Exceptions: `xt_try_enter` / `xt_try_exception` / `xt_try_leave` maintain the `_setjmp` frame stack; `xt_throw` long-jumps to the nearest `try` when a frame exists, otherwise prints `Uncaught ...` and exits. Codegen also maintains a shadow call stack (function name, source file and line) that `Error.stack` captures (see [Standard library](./stdlib.md)), and a fatal-signal handler dumps the same frames on a native crash. `--debug` additionally emits `!DICompileUnit` / `!DISubprogram` / `!DILocation` metadata and compiles with `-g -fno-omit-frame-pointer`, so gdb/lldb/WinDbg map instructions back to source lines.
31
+ - Exceptions: `xt_try_enter` / `xt_try_exception` / `xt_try_leave` maintain the `_setjmp` frame stack; `xt_throw` long-jumps to the nearest `try` when a frame exists, otherwise prints `Uncaught ...` and exits. A promise rejection nobody observes is fatal too: `xt_promise_report_unhandled` runs once the event loop drains, prints `Unhandled promise rejection` plus the reason and exits with status 1 (see [Async](./async.md)). Codegen also maintains a shadow call stack (function name, source file and line) that `Error.stack` captures (see [Standard library](./stdlib.md)), and a fatal-signal handler dumps the same frames on a native crash. `--debug` additionally emits `!DICompileUnit` / `!DISubprogram` / `!DILocation` metadata and compiles with `-g -fno-omit-frame-pointer`, so gdb/lldb/WinDbg map instructions back to source lines.
32
32
  - Objects / functions: `xt_object` with a prototype chain, `xt_function` with a property bag (static members and `prototype`); `xt_new` (instantiation), `xt_instance_of` (prototype chain), `xt_object_freeze/is_frozen/from_entries`; `xt_constructors.c` holds the first-class constructor cache, constructor prototypes and static-value dispatch.
@@ -216,9 +216,29 @@ necessarily next to the config file); set `xbintsc_CACHE_DIR` to put it
216
216
  elsewhere. Expect that fallback build to take noticeably longer than the ones
217
217
  after it.
218
218
 
219
+ The prebuilt archives are produced by the release runner's toolchain and target
220
+ the floors in
221
+ [requirements](./requirements.md#supported-platforms) (macOS 11.0, Windows 11,
222
+ glibc 2.39). When that toolchain is newer than the one on this machine — a newer
223
+ Xcode Command Line Tools/SDK or Visual Studio C++ toolset — the linker can
224
+ reject the archive with unresolved symbols (`LNK2038`, `LNK2019`, `Undefined
225
+ symbols`, `undefined reference`) or a `was built for newer macOS version`
226
+ warning. The driver then retries the link **automatically** with
227
+ `core`/`ext_node` compiled from the sources shipped in the package, so a
228
+ mismatched archive costs time, not a failed build:
229
+
230
+ ```text
231
+ xbintsc: the prebuilt runtime archive does not match this toolchain (…); compiling the C runtime from source
232
+ ```
233
+
234
+ The retry is not remembered: after a toolchain upgrade a later build links the
235
+ archive again. The bundled `gui.a`/`media.a` are the exception — they contain
236
+ SDL3/FFmpeg, whose sources are not part of the npm package, so they cannot be
237
+ rebuilt this way. If they are the incompatible archive, use a release built for
238
+ this toolchain or a source checkout.
239
+
219
240
  To ignore the platform archive on purpose and compile the runtime from the
220
- sources shipped in the package — for example after patching `runtime/`, or when
221
- the prebuilt archive does not match the environment — set:
241
+ sources shipped in the package — for example after patching `runtime/` — set:
222
242
 
223
243
  ```bash
224
244
  xbintsc_PREFER_PREBUILT=0 npx xbintsc build src/main.ts --out build
@@ -439,6 +459,7 @@ the same file under `node_modules/xbintsc/doc/ai/`.
439
459
  | The first build takes a long time | The optional `@xbintsc/<os>-<arch>` package was skipped (or this host has no prebuilt package), so the runtime C sources are compiled and cached. Check `npm ls @xbintsc/<os>-<arch>` and reinstall without `--omit=optional`; `--time` confirms. |
440
460
  | Permission errors writing `.xbintsc/` | Set `xbintsc_CACHE_DIR` to a writable directory, or run from a writable working directory. |
441
461
  | `module 'fs' is provided by the 'node' extension` | Add `--ext node`, or `"extensions": ["node"]` in the config. |
462
+ | Link fails with `LNK2038`, `LNK2019`, `Undefined symbols` or `was built for newer macOS version` | The prebuilt archive was built by a newer Xcode/Visual Studio toolchain. The driver retries automatically with the runtime's C sources; if the error persists it is `gui.a`/`media.a`, which cannot be rebuilt from the npm package — use a matching release or a source checkout. |
442
463
  | `GUI native library not found` / `media native library not found` | The optional platform package that ships `gui.a`/`gui.lib` or `media.a`/`media.lib` was skipped, or this host has no prebuilt GUI/media archive (Windows arm64). Reinstall without `--omit=optional`, use a Linux/macOS/Windows x64 release archive, or build the archive from a source checkout. An auto-detected `media` feature with a missing archive only warns; `--feature media` makes it an error. |
443
464
  | `require("xbintsc")` fails | The package is ESM-only; use `import` (or dynamic `import()`). |
444
465
  | Windows link errors about missing MSVC libraries | Run from a Native Tools prompt, install the VS C++ build tools, and check `npx xbintsc doctor`. |
@@ -0,0 +1,30 @@
1
+ # This Might Be the Most Exciting Thing for JS Developers This Year
2
+
3
+ ![3D pelican riding a bicycle](../../examples/gui/pelican-3d/preview.png)
4
+
5
+ The image above is a 3D pelican riding a bicycle that I built with JS + HTML + WebGL2, and the framerate is excellent too.
6
+
7
+ You might say, "Isn't that just a matter of having hands and an AI these days?"
8
+
9
+ But what if I told you that I compiled that very thing into a native binary with xbintsc — under 10 MB, able to run with no WebView environment at all, and able to mix Node.js and HTML in a single file, like this:
10
+
11
+ ```html
12
+ <html>
13
+ <body>
14
+ <div id="out">loading...</div>
15
+ <script lang="ts">
16
+ import { readFileSync } from "fs";
17
+
18
+ const out = document.getElementById("out");
19
+ const world = readFileSync("data.txt", "utf8");
20
+ out.textContent = "hello " + world;
21
+ </script>
22
+ </body>
23
+ </html>
24
+ ```
25
+
26
+ Keep in mind that Chrome alone used to take at least 100 MB. The xbintsc build is 10 MB and doesn't need a WebView environment — and that means far more than just a massive reduction in the footprint of desktop development, or a more correct implementation path.
27
+
28
+ What is truly epoch-making is this: it is now possible for JS developers to run the applications they want to build on embedded devices.
29
+
30
+ [xbintsc on GitHub](https://github.com/zy445566/xbintsc)
@@ -15,6 +15,32 @@ no requirements on any platform.
15
15
  Installing the compiler from npm instead of using a release archive? Node.js 22+
16
16
  then runs the compiler itself; see [Using xbintsc from npm](./npm-package.md).
17
17
 
18
+ ## Supported platforms
19
+
20
+ Release binaries and the prebuilt runtime archives target the oldest platform
21
+ versions xbintsc supports. CI builds them with the **oldest toolchain that can
22
+ still assemble a release for each host**, so a newer Xcode or Visual Studio on
23
+ your machine can link them — the reverse direction is what fails with `LNK2038`,
24
+ `Undefined symbols` or `was built for newer macOS version`.
25
+
26
+ | Platform | Produced binaries run on | Build baseline (CI) |
27
+ | --- | --- | --- |
28
+ | macOS x64 / arm64 | **macOS 11.0** (Big Sur) and newer | the oldest installed Xcode with clang 16+, `MACOSX_DEPLOYMENT_TARGET=11.0` |
29
+ | Windows x64 | **Windows 11** and newer | the `windows-2025` image's toolset (VS 2026) |
30
+ | Windows arm64 | **Windows 11** | the `windows-11-arm` image's toolset (currently VS 2026; no older arm64 image exists) |
31
+ | Linux x64 / arm64 | **glibc 2.39** (Ubuntu 24.04) and newer | clang 16 + lld 16 on `ubuntu-24.04` |
32
+
33
+ The compiler toolchain floors are Xcode 16 (Apple clang 16) on macOS, the
34
+ `windows-2025` image's Visual Studio (currently VS 2026) on Windows and clang 16
35
+ on Linux. Building therefore needs a newer host than the produced binary does:
36
+ Xcode 16 itself requires macOS 14.5+, so a macOS 11–14 build machine needs a
37
+ standalone LLVM 16+ (for example Homebrew's `llvm`). `xbintsc doctor` prints the
38
+ floor for the host it runs on. If the archives were built with a
39
+ newer toolchain than yours, `build` retries with the C runtime's shipped
40
+ sources (see [the npm package guide](./npm-package.md#the-first-build-and-the-cache));
41
+ the bundled `gui.a`/`media.a` cannot be rebuilt that way, because SDL3/FFmpeg
42
+ sources are not part of the package.
43
+
18
44
  clang 15 and older fail on the first string literal in any program, with:
19
45
 
20
46
  ```
@@ -112,6 +138,11 @@ xcode-select -p # prints the active developer directory
112
138
  xcrun --show-sdk-path # prints the SDK path
113
139
  ```
114
140
 
141
+ xbintsc runs the command in the second line itself and passes the result to
142
+ clang as `-isysroot`, so a clang selected by absolute path (the CI baseline, or
143
+ `xbintsc_CLANG`) still finds the SDK's C headers and link stubs. Setting
144
+ `SDKROOT` yourself takes precedence and pins a specific SDK.
145
+
115
146
  ## Linux — install clang and lld
116
147
 
117
148
  A normal glibc-based distribution plus `clang` and `lld` is enough:
@@ -16,6 +16,7 @@ These features **compile and run**, but the result does not fully match ECMAScri
16
16
  | Memory management | Non-moving mark-sweep collector behind `xt_alloc`: explicit roots, subsystem root providers and a conservative C-stack scan. Single-threaded and stop-the-world; no moving/compaction or generational mode |
17
17
  | Function `arity` / argument count | Direct calls to a locally visible function are checked by the default [semantic checker](../implemented/checker.md) (`TS4004`); calls through properties, imports, spread arguments or a reassigned binding are not. `fn.length` reports the declared arity |
18
18
  | `async` / `await` | Real suspension on a stackful coroutine; `await` always queues a microtask (non-promises and already-settled promises included), so interleaving with pending `.then` callbacks matches the specification. Top-level `await` blocks on the event loop rather than yielding the module body. `fs/promises` and `fetch` offload their blocking work to a four-worker pool and settle on the reactor. Async generators suspend at both `await` and `yield`; `for await...of` awaits one step per iteration |
19
+ | Unhandled rejections | Reported when the program ends rather than at the end of the tick: a rejection no handler observed is printed and makes the process exit 1 once the event loop drains, so a long-running process (a server) keeps running until it exits, and a handler attached later still suppresses the report. Node raises the fatal error right after the microtask queue drains |
19
20
  | Async generator cleanup | `for await...of` does not call the iterator's `return()` when the loop exits early. `yield*` inside an async generator delegates to synchronous iterables only. `.return()` at a suspended `yield` completes without running `finally` (the same limitation as synchronous generators) |
20
21
  | `super` | `super.x` / `super[x]` read from the parent holder of the class whose body is running (in an instance method: the parent prototype; in a `static` method or static block: the parent constructor), so any inheritance depth is correct |
21
22
  | `Error.stack` | Function-level frames only: each frame is `at name (file:line)` with the function's declaration line (updated to the construction line for the innermost frame); no column numbers and no frames for generator / `async` bodies (they switch stacks) |
@@ -39,8 +39,10 @@
39
39
  demuxer/parser/decoder 的最小静态构建,用它编译
40
40
  `runtime/ext_media/media.cpp`,并把所有内容合并到
41
41
  `runtime/lib/<os>-<arch>/media.a`(Windows 上 MSVC ABI 则为 `media.lib`)。
42
- `gui.a` **不会**链接 FFmpeg:编译器为可能播放媒体的程序选中 `media` 链接特性后
43
- 才链接 `media.a`(见[链接特性](#链接特性))。
42
+ 下载会重试发布主机常见的瞬时 TLS 故障;解压后 tarball 保留为
43
+ `vendor/ffmpeg-<version>.tar.xz`,因此重建(或按 `FFMPEG_VERSION` 缓存它的
44
+ CI)会解压本地副本而不再下载。`gui.a` **不会**链接 FFmpeg:编译器为可能播放
45
+ 媒体的程序选中 `media` 链接特性后才链接 `media.a`(见[链接特性](#链接特性))。
44
46
  - `src/extensions/gui/index.ts` 通过 `nativeObjects()` 暴露引擎归档(经由
45
47
  `findRuntimeLibrary`,因此 `.a`/`.lib` 都可用),并通过 `linkerFlags()`
46
48
  暴露操作系统框架。它的 `linkFeatures()`(位于
@@ -182,7 +182,9 @@ run();
182
182
  这两个参数(`window`、`document`)只是普通的函数参数,因此脚本的局部变量
183
183
  无需任何全局对象机制。外部脚本同样可用:`<script src="./counter.ts">` 会在
184
184
  编译期读取,其 import 会被提升(重写为从 HTML 文件解析),其主体以同样方式
185
- 包装——因此脚本模块可以 `import` 辅助函数,同时仍能看到 `document`。脚本是
185
+ 包装——因此脚本模块可以 `import` 辅助函数,同时仍能看到 `document`。内联主体
186
+ 走同一套拆分,因此 `import` 在两种写法中行为一致;开启 `node` 扩展后,
187
+ `node:fs` 等其他 `node:` 内置模块同样可用。脚本是
186
188
  **编译期资产**:运行时创建的 HTML(`innerHTML`、网络获取)永不执行,`src`
187
189
  URL(`https://…`、`data:…`)会被忽略。一切都按文档顺序在解析后运行(实际上
188
190
  是 deferred)。完整设计参见 `gui-scripts.md`。
@@ -133,6 +133,7 @@ transform feedback(入口为空操作:SDL_GPU 没有 transform feedback 阶
133
133
  - `tests/e2e/gui-media.test.ts` 还会把 `<video>` 的最新解码帧上传为纹理并读回像素。
134
134
  - `examples/gui/webgl-cube` 是一个旋转、受光、带纹理的立方体;它在浏览器中原样运行,并由 `tests/e2e/gui-example-webgl.test.ts` 覆盖。
135
135
  - `examples/gui/webgl2-cubes` 用一次绘制调用画出 64 个实例化立方体,使用 `#version 300 es` 与 VAO;由 `tests/e2e/gui-example-webgl2.test.ts` 覆盖。
136
+ - `examples/gui/pelican-3d` 是一个完整的光照场景(程序化天空、雾、实例化树木/云、带关节动画的鹈鹕和自行车),拆成九个 ES module,并用 `AudioContext` 合成音效;由 `tests/e2e/gui-example-pelican3d.test.ts` 覆盖。
136
137
 
137
138
  这些用例会在宿主选择的后端上运行:Linux(与 Windows)上是 Vulkan,macOS 上是 Metal,因此 SPIRV-Cross → MSL 路径与 SPIR-V 路径受同一组断言保护。
138
139
 
@@ -133,8 +133,9 @@ __registerScript("a1b2…", __xt_script_a1b2);
133
133
  export default __html;
134
134
  ```
135
135
 
136
- - id 是主体内容的哈希(sha256,前 32 个十六进制字符);主体被替换为一个空的
137
- 标记元素,以便引擎能按文档顺序找到它。
136
+ - id 是包装后主体内容的哈希(sha256,前 32 个十六进制字符;包含延迟 import 的
137
+ prelude,因此语句相同但 import 不同的两个脚本不会合并为同一条注册表记录);
138
+ 主体被替换为一个空的标记元素,以便引擎能按文档顺序找到它。
138
139
  - `window` 与 `document` 是**函数参数**,因此脚本的全局变量会作为普通局部
139
140
  变量解析 —— **无需编译器改动**,也没有全局对象。
140
141
  - `__registerScript(id, fn)` 是一个新的运行时内置函数,它把闭包存进全局的
@@ -142,6 +143,11 @@ export default __html;
142
143
  - 延迟脚本(`<script defer>`,或 M9c 中决定的所有内联脚本)在完整文档解析
143
144
  并计算首次布局之后按文档顺序运行。然后在 `window` 上依次触发
144
145
  `DOMContentLoaded` 再 `load`。
146
+ - 内联主体与 `<script src>` 文件走**同一套**拆分逻辑(见下):顶层
147
+ `import`/`export … from` 语句被提升进生成模块,相对说明符相对 HTML 文件
148
+ 解析,`export` 修饰符被去掉。因此内联 `<script>` 里的
149
+ `import { readFileSync } from "node:fs"` 与外链脚本里的同一条 import 链接
150
+ 方式完全一致 —— 需要开启 `node` 扩展,但不需要改写页面。
145
151
 
146
152
  ### 2b. 外部 `<script src>`
147
153
 
@@ -151,19 +157,19 @@ import 会像浏览器的模块脚本一样**延迟**:loader 生成一个合
151
157
  `__xt_deferred_module` 标记,生成的脚本在 `loadHTML` 时调用模块包装函数。
152
158
  bundler 把整个本地 import 图包装成返回各自导出的函数(由 `__xt_defer`
153
159
  记忆化),因此模块顶层代码在文档就绪后才运行,且两个脚本导入同一模块时共享
154
- 同一个实例。裸说明符(`gui`、npm 包)仍然立即加载。
160
+ 同一个实例。裸说明符(`gui`、npm 包、`node:fs`)仍然立即加载。内联
161
+ `<script>` 主体走完全相同的拆分,因此两种写法里的 import 行为一致;唯一区别
162
+ 是主体的来源。
155
163
 
156
164
  缺失的 `src` 文件仍会抛出清晰的错误(被转换为 bundler 诊断);
157
165
  `src` URL(`https://…`、`data:…`、`//host`)保持原样 —— 它们不是编译期资产,
158
166
  永不运行。
159
167
 
160
- `src` URL(`https://…`、`data:…`、`//host`)保持原样 —— 它们不是编译期资产,
161
- 永不运行。
162
-
163
- 被包装的脚本体以**它自己的文件**作为其中每条语句的来源(loader 会把该脚本体在生成模块中的
168
+ 被包装的 `<script src>` 主体以**它自己的文件**作为其中每条语句的来源(loader 会把该脚本体在生成模块中的
164
169
  区间作为 source range 上报),因此诊断会指向 `app.js:12`,而不是 `index.html` 的某一列;
165
170
  同时**纯 JavaScript** 脚本与直接 import 的 `.js` 模块一样不做类型检查:在对象字面量上写
166
- `state.update = …` 不会再报 `TS4003`。页面因此可以原样携带已有的脚本。
171
+ `state.update = …` 不会再报 `TS4003`。页面因此可以原样携带已有的脚本。内联主体的
172
+ 来源就是 HTML 页面本身。
167
173
 
168
174
  ```html
169
175
  <button id="b">0</button>
@@ -288,6 +294,9 @@ Event 的 `target` 是句柄,其描述符(`toString`)与同一 `div#id.cla
288
294
  通过带记忆化的模块包装函数延迟到 `loadHTML` 求值,因此 import 图能看到
289
295
  已解析的文档,且同一模块只实例化一次。缺失文件作为诊断浮现。所有脚本按
290
296
  文档顺序运行。e2e 覆盖。
297
+ - **M9c′** ✅ —— 内联 `<script>` 主体走同一套拆分逻辑,因此 `import`(包括
298
+ `node:`/裸说明符)与外链脚本里的链接方式完全一致。此前内联主体被原样包装,
299
+ 其 `import` 留在脚本函数体内,codegen 会报 TS5001。
291
300
  - **M9d** *(可选)* —— 检测传给 `win.loadHTML(...)` 的模板字面量中的内联
292
301
  HTML 并同样转换它们(脆弱;推迟)。
293
302
  - **M10 — 打磨** ✅ —— 窗口上的
@@ -312,10 +321,11 @@ Event 的 `target` 是句柄,其描述符(`toString`)与同一 `div#id.cla
312
321
  不会运行。已记录。
313
322
  - **模板字面量 HTML** 传给 `win.loadHTML(…)` 时在 M9b 中不会被转换(只有
314
323
  导入的资产会)。如有需要,M9d 会处理。
315
- - **外部 `<script src>` 文件之间的 import 绑定冲突。** 会被检测并报告
324
+ - **外部 `<script src>` 与内联脚本之间的 import 绑定冲突。** 会被检测并报告
316
325
  (给 import 起别名);之后可以做到带引用重写的按脚本重命名。
317
- - **资产。** `<script src>` 相对 HTML 资产解析;URL `src` 值被忽略。
318
- 加载器不会请求网络 URL;运行时全局 `fetch` 是另一套 API。
326
+ - **资产。** `<script src>` 相对 HTML 资产解析;内联主体的 import 也从同一位置
327
+ 解析。URL `src` 值被忽略。加载器不会请求网络 URL;运行时全局 `fetch` 是
328
+ 另一套 API。
319
329
 
320
330
  ## 测试
321
331
 
@@ -326,6 +336,8 @@ Event 的 `target` 是句柄,其描述符(`toString`)与同一 `div#id.cla
326
336
  - **M9a**:一个假扩展加载器的单元测试,断言 bundler 重写 `*.foo` 并记录依赖。
327
337
  - **M9b**:一个带内联 `<script>`、点击时递增计数器的 e2e 用例,通过处理器的
328
338
  `console.log` 断言。
339
+ - **M9c′**:单元用例断言内联主体的 import 被提升(相对 import 被延迟),外加
340
+ 一个 e2e 用例:内联脚本通过 `node:fs` 读取文件并调用被延迟的辅助模块。
329
341
  - **M10**:`requestAnimationFrame`(每帧运行、重新排队、接收时间戳、遵守
330
342
  `cancelAnimationFrame`)与 `offsetWidth`/`offsetHeight`/`contains` 的 e2e
331
343
  用例。
package/doc/zh-CN/gui.md CHANGED
@@ -157,7 +157,12 @@
157
157
  - `vendor/`(GUI 归档的 SDL3/FreeType/HarfBuzz/glslang,以及 `media` 的 FFmpeg,
158
158
  缓慢的部分)按 OS/arch 缓存,以 `scripts/build-gui.ts` /
159
159
  `scripts/build-media.ts` 为键。Windows x64 有独立的源码 + FFmpeg 安装前缀
160
- 缓存。
160
+ 缓存;Linux/macOS 上安装前缀 `vendor/ffmpeg-build/dist` 另有以
161
+ `FFMPEG_VERSION` 和 `scripts/build-media.ts`(决定 configure 参数)为键的
162
+ 缓存,命中后跳过 FFmpeg 的 configure/make,不必每次重编:脚本会在前缀里
163
+ 记录构建它的发布版本,版本或 macOS 部署目标变化时丢弃重建。FFmpeg 发布
164
+ tarball 另有只以 `FFMPEG_VERSION` 为键、所有 job 共享的缓存:即使
165
+ ffmpeg.org 的 TLS 握手不稳定,也不会让已经下载过它的运行失败。
161
166
  - Windows x64 在 CI 中构建两个归档并链接一个会触发 `media` 链接特性的 `gui`
162
167
  程序(另有一条显式 `--feature media` 的构建);托管 runner
163
168
  没有 GPU/Vulkan 驱动,因此 GUI/媒体示例的*运行*仍为试验性(失败只记录
@@ -182,6 +187,9 @@
182
187
  相对 import 通过带记忆化的模块包装函数延迟到 `loadHTML` 求值,因此被导入
183
188
  模块能看到已解析的文档并共享状态。缺失文件会成为诊断。一切都按文档顺序
184
189
  运行。
190
+ - **M9c′** ✅ —— 内联 `<script>` 主体走同一套拆分逻辑,其 import 被提升
191
+ (`node:`/裸说明符像普通模块 import 一样链接),而不是留在被包装的函数
192
+ 体内、由 codegen 报 TS5001。内联与外链现在行为一致。
185
193
  10. **M10 — 打磨** ✅
186
194
  - `win.requestAnimationFrame(fn)` / `win.cancelAnimationFrame(id)`;回调
187
195
  在每帧顶部以帧时间戳运行,并可修改 DOM
@@ -366,6 +374,8 @@
366
374
  - **M9c** ✅ 外部 `<script src>` 文件被读取,其主体被包装/注册;相对 import
367
375
  通过带记忆化的模块包装函数延迟到 `loadHTML`(模块级 DOM 工作因此能看到已解析
368
376
  文档);缺失文件会成为诊断。加载器错误被 `loadGraph` 捕获并报告为构建错误。
377
+ - **M9c′** ✅ 内联 `<script>` 主体与外链主体走同一套拆分逻辑,因此内联 `import`
378
+ 被提升并同样链接(开启 `node` 扩展后包括 `node:fs`);只有主体来源不同。
369
379
  - **M10** ✅ 窗口句柄上的 `requestAnimationFrame`/`cancelAnimationFrame`
370
380
  (回调在每帧布局之前以帧时间戳运行),以及元素句柄上的
371
381
  `offsetWidth`/`offsetHeight`/`contains`;e2e 覆盖在
@@ -30,6 +30,7 @@
30
30
  - 微任务队列(`xt_microtasks`);`xt_promise_ctor/resolve/reject/static`(含 `withResolvers`)、实例 `then` / `catch` / `finally`。
31
31
  - `xt_drain_microtasks` 在程序结束时清空队列,事件循环也会在回调之间清空它。
32
32
  - `xt_await` 对非 Promise / 已 settle 值排入一次微任务,对 pending promise 则挂起当前 async 协程直到 settle。
33
+ - 未处理的 rejection 是致命错误,与 Node 默认的 `--unhandled-rejections=throw` 一致。一个 reject 后始终无人观察的 promise——没有 `.then` / `.catch`、没有被 `await`、也没有被另一个 promise 采纳——会在事件循环排空后向 stderr 打印 `Unhandled promise rejection`,并以状态码 1 退出。它主要保护入口函数:程序的 `main()` 通常是 `async`,从其中逃出的异常原本只是 reject 一个无人观察的 promise,进程会静默退出 0。即使在定时器里稍后才挂上处理函数,也算已观察。
33
34
 
34
35
  ## 定时器与事件循环
35
36
 
@@ -21,10 +21,14 @@
21
21
 
22
22
  ## 延迟求值
23
23
 
24
- - `gui` 的 `.html` 资产加载器用合成的 `__xt_deferred_module` 绑定标记相对的
25
- `<script src>` import。bundler 会把该模块及其本地 import 图包装成返回各自
26
- 导出的函数,生成的脚本在 `loadHTML` 之后调用它们(`__xt_defer` 对每个模块
27
- 做记忆化)。这与浏览器的模块脚本一致:文档就绪后才求值 import 图。
24
+ - `gui` 的 `.html` 资产加载器用合成的 `__xt_deferred_module` 绑定标记内联
25
+ `<script>` 主体与 `<script src>` 文件的相对 import。bundler 会把该模块及其
26
+ 本地 import 图包装成返回各自导出的函数,生成的脚本在 `loadHTML` 之后调用它们
27
+ (`__xt_defer` 对每个模块做记忆化)。这与浏览器的模块脚本一致:文档就绪后才
28
+ 求值 import 图。
29
+ - 两种脚本写法使用同一套拆分逻辑:顶层 `import`/`export … from` 语句被提升进
30
+ 生成模块,`export` 修饰符被去掉,因此裸说明符或 `node:` 说明符与普通模块
31
+ import 一样链接。
28
32
  - 同时被普通(非标记)import 引用的模块保持立即求值,并获得一个返回已初始化
29
33
  导出的薄包装。
30
34
  - 包装体是同步的:延迟脚本模块中的顶层 `await` 不受支持。
@@ -5,16 +5,23 @@
5
5
  位置:`src/extensions/node/child_process/index.ts`、`runtime/ext_node/child_process/child_process.c`
6
6
 
7
7
  `spawnSync(command, args[, options])` 运行一个程序直到结束,返回
8
- `{ status, stdout, stderr }`。`options.cwd` 设置子进程工作目录;
9
- `options.stdio: "inherit"` 让子进程共用父进程的 stdout/stderr(`xbintsc run`
10
- 用它把被编译程序的输出实时透传),否则 stdout/stderr 会作为 UTF-8 字符串捕获。
8
+ `{ status, stdout, stderr }`。`options.cwd` 设置子进程工作目录,`options.env`
9
+ 替换子进程的环境变量,`options.stdio: "inherit"` 让子进程共用父进程的
10
+ stdout/stderr(`xbintsc run` 用它把被编译程序的输出实时透传),否则
11
+ stdout/stderr 会作为 UTF-8 字符串捕获。
11
12
 
12
13
  | 选项 | 说明 |
13
14
  | --- | --- |
14
15
  | `cwd` | 子进程的工作目录 |
16
+ | `env` | 子进程的完整环境变量;不传则继承父进程,想扩展就展开 `process.env` |
15
17
  | `stdio: "inherit"` | 共用父进程的 stdout/stderr,而不是捕获 |
16
18
  | `encoding` | 接受但忽略(输出始终按 UTF-8 解码) |
17
19
 
20
+ `env` 遵循 Node 的替换语义:传了对象,它就是子进程的完整环境,调用方没列出的
21
+ 变量在子进程里不存在。这正是调用方能把特定的 `SDKROOT`、`INCLUDE`、`LIB` 交给
22
+ 工具链、又不泄漏自身 shell 环境的原因;自举出来的编译器跑的就是这段代码,macOS
23
+ 的 SDK 修复也依赖它。
24
+
18
25
  Windows 上子进程通过 `CreateProcess` 启动,stdout/stderr 由读线程经匿名管道持续排空。
19
26
  不使用临时文件,因此受限的 `%TEMP%` 不会让 `spawnSync` 失败,输出超过管道缓冲区时
20
27
  也不会死锁。
@@ -27,5 +27,5 @@
27
27
  - 运算符辅助:`xt_in`(`in`)、`xt_delete`(`delete`)、`xt_rest_args`(剩余参数 / `arguments`)。
28
28
  - Box:`box_new/get/set`(用于闭包捕获变量)。
29
29
  - 函数与闭包:`arg`、`closure_new/call/env/arity`。
30
- - 异常:`xt_try_enter` / `xt_try_exception` / `xt_try_leave` 维护 `_setjmp` 帧栈;`xt_throw` 在存在帧时长跳到最近 `try`,否则打印 `Uncaught ...` 后退出。codegen 还会维护一条影子调用栈(函数名、源文件、行号):`Error.stack` 用它生成堆栈(见[标准库](./stdlib.md)),原生崩溃时致命信号处理器也会打印同一组帧。`--debug` 还会生成 `!DICompileUnit` / `!DISubprogram` / `!DILocation` 元数据,并用 `-g -fno-omit-frame-pointer` 编译,因此 gdb/lldb/WinDbg 可以把指令映射回源码行。
30
+ - 异常:`xt_try_enter` / `xt_try_exception` / `xt_try_leave` 维护 `_setjmp` 帧栈;`xt_throw` 在存在帧时长跳到最近 `try`,否则打印 `Uncaught ...` 后退出。无人观察的 promise rejection 同样是致命的:事件循环排空后 `xt_promise_report_unhandled` 会打印 `Unhandled promise rejection` 与原因,并以状态码 1 退出(见[异步](./async.md))。codegen 还会维护一条影子调用栈(函数名、源文件、行号):`Error.stack` 用它生成堆栈(见[标准库](./stdlib.md)),原生崩溃时致命信号处理器也会打印同一组帧。`--debug` 还会生成 `!DICompileUnit` / `!DISubprogram` / `!DILocation` 元数据,并用 `-g -fno-omit-frame-pointer` 编译,因此 gdb/lldb/WinDbg 可以把指令映射回源码行。
31
31
  - 对象 / 函数:`xt_object` 带原型链,`xt_function` 带属性包(静态成员与 `prototype`);`xt_new`(实例化)、`xt_instance_of`(原型链)、`xt_object_freeze/is_frozen/from_entries`;`xt_constructors.c` 保存一等公民构造器缓存、构造器原型与静态值分派。
@@ -201,8 +201,24 @@ Hello from xbintsc
201
201
  (不一定是配置文件所在目录);可以用 `xbintsc_CACHE_DIR` 改到别处。这种回退构建
202
202
  会明显比后续构建慢。
203
203
 
204
+ 预编译归档由发布机的工具链生成,面向
205
+ [使用前置要求](./requirements.md#支持的平台)中的下限(macOS 11.0、Windows 11、
206
+ glibc 2.39)。如果它比本机的工具链新——例如更新的 Xcode Command Line
207
+ Tools/SDK 或 Visual Studio C++ 生成工具——链接器可能拒绝该归档,报未定义符号
208
+ (`LNK2038`、`LNK2019`、`Undefined symbols`、`undefined reference`)或
209
+ `was built for newer macOS version` 警告。此时驱动会**自动**用包内自带的源码重新
210
+ 编译 `core`/`ext_node` 并重试链接,所以归档版本不匹配只会多花时间,不会让构建失败:
211
+
212
+ ```text
213
+ xbintsc: the prebuilt runtime archive does not match this toolchain (…); compiling the C runtime from source
214
+ ```
215
+
216
+ 这个回退不会被记住:升级工具链之后,后续构建仍会重新尝试预编译归档。随包的
217
+ `gui.a`/`media.a` 是例外——它们内含 SDL3/FFmpeg,npm 包里没有这些源码,无法这样
218
+ 重编。如果冲突的是它们,请使用与本机工具链匹配的发布包,或在源码检出中构建。
219
+
204
220
  如果确实想忽略平台包归档、改从主包自带的源码编译 C 运行时(例如修改了
205
- `runtime/`,或预编译归档与当前环境不匹配),可以设置:
221
+ `runtime/`),可以设置:
206
222
 
207
223
  ```bash
208
224
  xbintsc_PREFER_PREBUILT=0 npx xbintsc build src/main.ts --out build
@@ -408,6 +424,7 @@ npm 包通过可选平台包获得同一套预编译运行时,只有在跳过
408
424
  | 第一次构建很慢 | 可选包 `@xbintsc/<os>-<arch>` 被跳过(或该宿主没有预编译包),正在编译并缓存运行时 C 源码。用 `npm ls @xbintsc/<os>-<arch>` 检查,并去掉 `--omit=optional` 重新安装;`--time` 可确认。 |
409
425
  | 写 `.xbintsc/` 权限错误 | 用 `xbintsc_CACHE_DIR` 指向可写目录,或在可写目录中运行。 |
410
426
  | `module 'fs' is provided by the 'node' extension` | 加 `--ext node`,或在配置里写 `"extensions": ["node"]`。 |
427
+ | 链接报 `LNK2038`、`LNK2019`、`Undefined symbols` 或 `was built for newer macOS version` | 预编译归档由更新的 Xcode/Visual Studio 工具链构建。驱动会自动改用运行时的 C 源码重试链接;若仍然失败,冲突的是 `gui.a`/`media.a`,无法从 npm 包重编——请使用匹配的发布包或源码检出。 |
411
428
  | `GUI native library not found` / `media native library not found` | 携带 `gui.a`/`gui.lib` 或 `media.a`/`media.lib` 的可选平台包被跳过,或该宿主没有预编译的 GUI/media 归档(Windows arm64)。去掉 `--omit=optional` 重新安装,或使用 Linux/macOS/Windows x64 发布压缩包,或从源码检出构建。自动检测到的 `media` 特性缺归档只会 warning;`--feature media` 才会报错。 |
412
429
  | `require("xbintsc")` 失败 | 包只提供 ESM;使用 `import`(或动态 `import()`)。 |
413
430
  | Windows 链接时报缺少 MSVC 库 | 在 Native Tools 命令提示符中运行,安装 VS C++ 生成工具,并检查 `npx xbintsc doctor`。 |