xbintsc 0.3.46 → 0.3.49
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/AGENTS.md +95 -0
- package/README.md +25 -0
- package/README.zh-CN.md +23 -0
- package/dist/src/cli/hints.d.ts +54 -0
- package/dist/src/cli/hints.js +165 -0
- package/dist/src/cli/hints.js.map +1 -0
- package/dist/src/cli/main.js +73 -9
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +26 -0
- package/dist/src/codegen/generator/tables.js +64 -12
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/diagnostics/source-text.d.ts +22 -0
- package/dist/src/diagnostics/source-text.js +76 -0
- package/dist/src/diagnostics/source-text.js.map +1 -0
- package/dist/src/driver/bundler/graph.js +2 -1
- package/dist/src/driver/bundler/graph.js.map +1 -1
- package/dist/src/driver/compiler.js +3 -2
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/lexer/scanner/strings.js +16 -3
- package/dist/src/lexer/scanner/strings.js.map +1 -1
- package/dist/tests/cli/hints.test.d.ts +9 -0
- package/dist/tests/cli/hints.test.js +143 -0
- package/dist/tests/cli/hints.test.js.map +1 -0
- package/dist/tests/cli/main.test.js +6 -4
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/codegen/llvm.test.js +17 -2
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/helpers.js +3 -2
- package/dist/tests/helpers.js.map +1 -1
- package/dist/tests/lexer/strings.test.js +14 -2
- package/dist/tests/lexer/strings.test.js.map +1 -1
- package/doc/DESIGN.md +117 -0
- package/doc/ai/README.md +63 -0
- package/doc/ai/build-recipe.md +137 -0
- package/doc/ai/cli.md +142 -0
- package/doc/ai/contributing.md +196 -0
- package/doc/ai/extensions.md +148 -0
- package/doc/ai/language-support.md +152 -0
- package/doc/ai/troubleshooting.md +163 -0
- package/doc/ai/zh-CN/README.md +56 -0
- package/doc/ai/zh-CN/build-recipe.md +132 -0
- package/doc/ai/zh-CN/cli.md +127 -0
- package/doc/ai/zh-CN/contributing.md +173 -0
- package/doc/ai/zh-CN/extensions.md +139 -0
- package/doc/ai/zh-CN/language-support.md +147 -0
- package/doc/ai/zh-CN/troubleshooting.md +150 -0
- package/doc/gui-scripts.md +350 -0
- package/doc/gui.md +646 -0
- package/doc/icon.md +265 -0
- package/doc/implemented.md +373 -0
- package/doc/node-implemented.md +588 -0
- package/doc/node-unimplemented.md +167 -0
- package/doc/post/announce.md +43 -0
- package/doc/requirements.md +145 -0
- package/doc/unimplemented.md +286 -0
- package/doc/xbintsc.config.schema.json +67 -0
- package/doc/zh-CN/DESIGN.md +104 -0
- package/doc/zh-CN/gui-scripts.md +329 -0
- package/doc/zh-CN/gui.md +588 -0
- package/doc/zh-CN/icon.md +241 -0
- package/doc/zh-CN/implemented.md +365 -0
- package/doc/zh-CN/node-implemented.md +533 -0
- package/doc/zh-CN/node-unimplemented.md +141 -0
- package/doc/zh-CN/plan-require-node-modules.md +284 -0
- package/doc/zh-CN/post/announce.md +47 -0
- package/doc/zh-CN/requirements.md +134 -0
- package/doc/zh-CN/unimplemented.md +247 -0
- package/llms.txt +45 -0
- package/package.json +4 -1
- package/runtime/ext_gui/gui.cpp +3 -1
- package/runtime/ext_gui/renderer.cpp +13 -11
- package/runtime/ext_gui/renderer_image.cpp +12 -8
- package/runtime/ext_gui/renderer_shaders.h +131 -4
- package/runtime/ext_gui/renderer_shaders_data.h +1809 -0
- package/runtime/ext_gui/renderer_text.cpp +12 -8
- package/runtime/ext_gui/shaders.hlsl +98 -0
- package/runtime/ext_gui/spirv/fill.frag +19 -0
- package/runtime/ext_gui/spirv/fill.vert +42 -0
- package/runtime/ext_gui/spirv/image.frag +16 -0
- package/runtime/ext_gui/spirv/quad.vert +30 -0
- package/runtime/ext_gui/spirv/text.frag +16 -0
- package/scripts/build-gui-shaders.mjs +204 -0
- package/scripts/build-gui.ts +35 -0
- package/scripts/check-file-length.ts +5 -1
- package/src/cli/hints.ts +194 -0
- package/src/cli/main.ts +82 -9
- package/src/codegen/generator/tables.ts +60 -14
- package/src/diagnostics/source-text.ts +78 -0
- package/src/driver/bundler/graph.ts +2 -1
- package/src/driver/compiler.ts +3 -2
- package/src/lexer/scanner/strings.ts +16 -3
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/xbintsc.config.schema.json",
|
|
4
|
+
"title": "xbintsc project config",
|
|
5
|
+
"description": "Project build configuration read from xbintsc.config.json.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": true,
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Optional editor hint pointing at this schema."
|
|
12
|
+
},
|
|
13
|
+
"entry": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Entry TypeScript file, resolved against this config's directory."
|
|
16
|
+
},
|
|
17
|
+
"outDir": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Directory for build outputs (default: build/)."
|
|
20
|
+
},
|
|
21
|
+
"output": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Explicit output path, overriding outDir."
|
|
24
|
+
},
|
|
25
|
+
"optimize": {
|
|
26
|
+
"description": "Optimization level (default: 2).",
|
|
27
|
+
"enum": ["0", "1", "2", "3"]
|
|
28
|
+
},
|
|
29
|
+
"extensions": {
|
|
30
|
+
"type": "array",
|
|
31
|
+
"items": { "type": "string" },
|
|
32
|
+
"description": "Bundled extensions to enable, e.g. [\"gui\", \"node\"]."
|
|
33
|
+
},
|
|
34
|
+
"extNative": {
|
|
35
|
+
"type": "array",
|
|
36
|
+
"items": { "type": "string" },
|
|
37
|
+
"description": "Paths to native C++/Rust extension manifests."
|
|
38
|
+
},
|
|
39
|
+
"force": {
|
|
40
|
+
"type": "boolean",
|
|
41
|
+
"description": "Ignore the incremental cache."
|
|
42
|
+
},
|
|
43
|
+
"app": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"description": "Application metadata and packaging.",
|
|
46
|
+
"additionalProperties": false,
|
|
47
|
+
"properties": {
|
|
48
|
+
"name": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "Display / bundle name."
|
|
51
|
+
},
|
|
52
|
+
"icon": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "Application icon (PNG, ICO or ICNS)."
|
|
55
|
+
},
|
|
56
|
+
"bundle": {
|
|
57
|
+
"type": "boolean",
|
|
58
|
+
"description": "macOS only: also produce a <name>.app bundle."
|
|
59
|
+
},
|
|
60
|
+
"bundleId": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "macOS CFBundleIdentifier, e.g. com.example.demo."
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# xbintsc 设计
|
|
2
|
+
|
|
3
|
+
> 语言 / Language:[English](../DESIGN.md) | **简体中文**
|
|
4
|
+
|
|
5
|
+
实现一个 TypeScript 的二进制编译器。
|
|
6
|
+
|
|
7
|
+
## 实现架构
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
source.ts
|
|
11
|
+
│ lexer src/lexer 词法:完整 token 集合、模板、正则、ASI
|
|
12
|
+
▼
|
|
13
|
+
tokens
|
|
14
|
+
│ parser src/parser 递归下降解析 → AST(src/ast)
|
|
15
|
+
▼
|
|
16
|
+
AST
|
|
17
|
+
│ binder src/binder 作用域 / 符号 / 提升 / 闭包捕获分析
|
|
18
|
+
▼
|
|
19
|
+
bound AST
|
|
20
|
+
│ codegen src/codegen LLVM IR 文本(llvm.ts),values.ts 定义值模型
|
|
21
|
+
▼
|
|
22
|
+
module.ll ──clang──► module.o ──link──► 可执行文件
|
|
23
|
+
▲
|
|
24
|
+
runtime/ C 运行时(NaN-boxing 值)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 值模型
|
|
28
|
+
|
|
29
|
+
`xt_value` 是一个 64 位字。double 不做装箱直接存放;其它类型使用高 16 位 tag
|
|
30
|
+
加 48 位 payload 的带标签指针。表示法在 `src/codegen/values.ts` 与 `runtime/rt.h`
|
|
31
|
+
中定义一次,编译器与运行时共享。
|
|
32
|
+
|
|
33
|
+
### 调用约定
|
|
34
|
+
|
|
35
|
+
所有编译期函数使用统一 ABI:
|
|
36
|
+
|
|
37
|
+
```c
|
|
38
|
+
xt_value fn(xt_value env, int32_t argc, xt_value *argv);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`env` 通过 box 以引用方式传递捕获变量,直接调用与闭包调用共用一条代码路径。
|
|
42
|
+
不易内联的 JS 语义(`+` 隐式转换、关系比较、属性访问、打印)委托给 `@xt_*` 运行时调用。
|
|
43
|
+
|
|
44
|
+
### 运行时
|
|
45
|
+
|
|
46
|
+
`runtime/xt_runtime.c` 实现字符串、对象、数组、闭包、算术、比较、异常与
|
|
47
|
+
类 Node 的 `console.log` 打印。现在 `xt_alloc` 背后使用非移动标记-清扫回收器
|
|
48
|
+
(显式根、子系统根提供者与保守 C 栈扫描),其内部实现对编译器保持隔离。
|
|
49
|
+
|
|
50
|
+
### llc 的替代
|
|
51
|
+
|
|
52
|
+
本机未安装 LLVM/`llc`,但 `clang` 可以直接编译 LLVM IR 文本,因此流程为
|
|
53
|
+
`IR 文本 → clang -c → 目标文件 → 链接 C 运行时`。这保持了 "IR 绑定 + 本地编译"
|
|
54
|
+
的目标,同时跨平台。
|
|
55
|
+
|
|
56
|
+
## 目录结构
|
|
57
|
+
|
|
58
|
+
| 目录 | 职责 |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `src/lexer` | 词法分析(Scanner、TokenKind) |
|
|
61
|
+
| `src/ast` | AST 节点、工厂、访问器 |
|
|
62
|
+
| `src/parser` | 递归下降解析器(含类型语法) |
|
|
63
|
+
| `src/binder` | 作用域与符号解析、闭包捕获 |
|
|
64
|
+
| `src/codegen` | 值模型与 LLVM IR 生成 |
|
|
65
|
+
| `src/diagnostics` | 源文件、诊断、哈希 |
|
|
66
|
+
| `src/driver` | 流水线驱动、增量缓存、clang 工具链封装 |
|
|
67
|
+
| `src/extensions` | 可插拔扩展注册表与 Node 扩展 |
|
|
68
|
+
| `src/cli` | 命令行入口 |
|
|
69
|
+
| `runtime` | C 运行时与 `rt.h` |
|
|
70
|
+
| `tests` | 按模块划分的测试(含 e2e) |
|
|
71
|
+
| `scripts` | 运行时构建等脚本 |
|
|
72
|
+
| `.github/workflows` | 多系统多版本 CI |
|
|
73
|
+
|
|
74
|
+
## 增量编译
|
|
75
|
+
|
|
76
|
+
`src/driver/cache.ts` 以入口源码哈希、编译器版本、编译选项、平台与扩展集合
|
|
77
|
+
作为缓存键;当所有产物仍然存在时直接跳过构建。运行时的 C 源同样按内容哈希
|
|
78
|
+
缓存目标文件。
|
|
79
|
+
|
|
80
|
+
## 扩展机制
|
|
81
|
+
|
|
82
|
+
扩展是普通对象(见 `src/extensions/registry.ts`):声明额外的 C 运行时代码、
|
|
83
|
+
链接参数,以及它对外提供的可导入模块,并把导出的绑定映射到运行时符号(统一
|
|
84
|
+
`(argc, argv)` ABI)。Node 的 `import { readFileSync } from "fs"` 就是通过
|
|
85
|
+
`src/extensions/node/fs` + `runtime/ext_node/fs` 接入的,核心编译器无需了解任何
|
|
86
|
+
平台细节。
|
|
87
|
+
|
|
88
|
+
`nativeObjects` 把同一形状扩展到流程之外构建的代码:用 C++ 或 Rust 编译出的
|
|
89
|
+
`extern "C"` 对象文件或静态库,可以经 JSON manifest 链接并暴露
|
|
90
|
+
(`src/extensions/native.ts`、`--ext-native`)。驱动会原样链接这些产物,生成器
|
|
91
|
+
像绑定 C 运行时那样绑定它们的符号,因此扩展使用何种语言对核心编译器透明。Windows
|
|
92
|
+
同样覆盖:Windows 下 xbintsc 使用系统 clang 按 MSVC ABI 链接,示例即针对该 ABI 构建。
|
|
93
|
+
参见 `examples/extensions/` 与 `runtime/xt_ext.h` / `runtime/xt_ext.rs`。
|
|
94
|
+
|
|
95
|
+
## 自举路线
|
|
96
|
+
|
|
97
|
+
编译器本身使用 TypeScript 编写并输出 IR 文本,`runtime` 已与编译器解耦。后续可
|
|
98
|
+
用 TS 重写运行时,并由 xbintsc 编译自身,逐步达成自举。
|
|
99
|
+
|
|
100
|
+
## 测试
|
|
101
|
+
|
|
102
|
+
按模块划分:`tests/lexer`、`tests/parser`、`tests/binder`、`tests/codegen`、
|
|
103
|
+
`tests/driver`、`tests/extensions`、`tests/cli`、`tests/e2e`。e2e 会在存在
|
|
104
|
+
`clang` 时真正编译并运行二进制,否则自动跳过。
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# GUI 扩展 —— DOM 句柄、事件与 AOT `<script>`
|
|
2
|
+
|
|
3
|
+
> 语言 / Language:[English](../gui-scripts.md) | **简体中文**
|
|
4
|
+
|
|
5
|
+
状态:**设计** —— 本文档规定 `gui` 扩展如何从只读的 HTML/CSS 渲染(M7)成长为
|
|
6
|
+
一个小的、*交互式* DOM,并带有**编译期(AOT)脚本**,同时不引入 JavaScript
|
|
7
|
+
引擎,也不让 GUI 的关注点泄漏进核心编译器。
|
|
8
|
+
|
|
9
|
+
它是里程碑 **M8**(DOM 对象模型 + 变更 + 元素事件)与 **M9**(AOT `<script>`
|
|
10
|
+
流水线)背后的计划。`gui.md` 中的锁定决策仍然成立;本文档只是细化“没有页面
|
|
11
|
+
JS”的含义。
|
|
12
|
+
|
|
13
|
+
## 动机
|
|
14
|
+
|
|
15
|
+
如今引擎渲染一棵 HTML/CSS 树,只暴露*窗口级*事件(`win.on("click", …)`)。
|
|
16
|
+
处理器接收到的 `e.target` 是一个 CSS 描述符字符串(`div#main.card`),DOM
|
|
17
|
+
是只读的(仅用于诊断),改变 UI 的唯一方式是 `win.loadHTML(...)`,它会重建
|
|
18
|
+
所有内容。
|
|
19
|
+
|
|
20
|
+
对于静态演示这已经足够,但接下来的明显问题 ——“我能写 `<script>` 并对特定
|
|
21
|
+
元素的点击做出反应吗?”—— 还没有答案。xbintsc 是一个**纯 AOT 编译器**,
|
|
22
|
+
**没有运行时解释器/JIT**,因此 `eval` 式的脚本执行是不可能的。运行脚本唯一
|
|
23
|
+
自洽的方式就是**提前编译它们**。
|
|
24
|
+
|
|
25
|
+
## 目标
|
|
26
|
+
|
|
27
|
+
1. **元素句柄** —— `document.querySelector(...)` 返回一个具有稳定标识的对象
|
|
28
|
+
(同一元素 `a === b`)以及可读写的 DOM 属性(`id`、`className`、
|
|
29
|
+
`textContent`、`innerHTML`、`style`、`classList`、特性、遍历、几何)。
|
|
30
|
+
2. **变更** —— `appendChild` / `removeChild` / `insertBefore` /
|
|
31
|
+
`replaceChild` / `textContent=` / `innerHTML=` / `classList.*` /
|
|
32
|
+
`style.setProperty` 更新树;引擎延迟地重新计算样式 + 重新布局并重绘。
|
|
33
|
+
3. **元素事件** —— `el.addEventListener(type, fn, options?)` 带捕获与冒泡
|
|
34
|
+
阶段、`removeEventListener`、`dispatchEvent`、`el.click()`,事件对象带
|
|
35
|
+
`target` / `currentTarget` / `preventDefault` / `stopPropagation`。
|
|
36
|
+
4. **AOT `<script>`** —— 用 TypeScript 编写的 `<script>` 主体在**编译期**被
|
|
37
|
+
提取,由*现有* xbintsc 前端作为普通模块编译,并在文档解析后由引擎调用。
|
|
38
|
+
5. **零核心编译器耦合** —— 词法器/解析器/绑定器/代码生成器对 HTML 或 GUI
|
|
39
|
+
一无所知。扩展只贡献模块/对象(以及 M9 新增的**资产加载器钩子**)。
|
|
40
|
+
6. **向后兼容** —— 现有程序与测试继续工作:`win.on(...)` 处理器继续接收
|
|
41
|
+
*字符串* `e.target`。
|
|
42
|
+
|
|
43
|
+
## 非目标
|
|
44
|
+
|
|
45
|
+
- **JS 引擎**(QuickJS/V8/…)。仍明确不在范围内。没有运行时 `eval`,没有
|
|
46
|
+
解释器,没有 JIT。
|
|
47
|
+
- **运行时的动态脚本。** 脚本是编译期资产。通过网络获取或在运行时产生的
|
|
48
|
+
HTML **不会**执行其脚本。
|
|
49
|
+
- **安全沙箱 / 浏览器语义。** 没有源(origin)模型;脚本就是普通的原生 TS,
|
|
50
|
+
可以 `import` 编译器允许的任何内容。这一点被记录为“不是浏览器”。
|
|
51
|
+
- **完整 DOM/BOM。** 我们实现 UI 所需的实用子集。
|
|
52
|
+
|
|
53
|
+
## 锁定决策(细化)
|
|
54
|
+
|
|
55
|
+
| # | 决策 |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| 1 | **没有页面 JS 引擎。** 行为由原生 TS 实现。`<script>` 主体由 xbintsc *AOT 编译*为原生代码,并通过 `xt_call_with_this` 调用——仍然没有解释器。 |
|
|
58
|
+
| 8 | **脚本是编译期资产。** HTML 资产加载器把内联 `<script>` 主体转换为已编译模块,并替换为 `data-xt-id` 标记;运行时只*调用*已编译的函数。 |
|
|
59
|
+
| 9 | **DOM 由引擎拥有;句柄是引擎对象。** 句柄使用与窗口句柄相同的共享原型对象模型;隐藏字段携带窗口引用、文档代(generation)与节点索引。 |
|
|
60
|
+
| 10 | **`window`/`document` 作为参数注入**,而非全局变量,因此不会向编译器添加全局对象机制。 |
|
|
61
|
+
|
|
62
|
+
## 架构
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
<script>…</script> 编译期(bundler)
|
|
66
|
+
│ gui .html 资产加载器
|
|
67
|
+
▼
|
|
68
|
+
┌──────────────────────┐ ┌───────────────────────────┐
|
|
69
|
+
│ 转换 HTML 主体 │ │ 生成 __xt_script_<hash> │ 一个普通的
|
|
70
|
+
│ → function │ │ (window, document) │ xbintsc
|
|
71
|
+
│ <script data-xt-id> │ │ + __registerScript(hash) │ 模块
|
|
72
|
+
└──────────────────────┘ └───────────────────────────┘
|
|
73
|
+
│ │
|
|
74
|
+
▼ ▼
|
|
75
|
+
运行时 HTML 字符串 链接进二进制的原生代码
|
|
76
|
+
│ win.loadHTML(html)
|
|
77
|
+
▼
|
|
78
|
+
引擎解析 HTML,找到 `data-xt-id` 标记,在脚本注册表中查找每个 hash
|
|
79
|
+
并调用 `fn(windowHandle, documentHandle)`
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 1. `Extension.assetLoaders`(唯一的新核心钩子)
|
|
83
|
+
|
|
84
|
+
核心编译器目前没有办法让扩展影响*文件如何加载*。M9 向
|
|
85
|
+
`src/extensions/registry.ts` 添加一个通用钩子:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
interface AssetLoadResult {
|
|
89
|
+
/** 替换 bundler 所看到的文件内容。 */
|
|
90
|
+
moduleSource: string;
|
|
91
|
+
/** 该资产依赖的额外文件(变化时重新编译)。 */
|
|
92
|
+
dependencies?: string[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface Extension {
|
|
96
|
+
// …现有字段…
|
|
97
|
+
/** 可选:把导入的资产重写为 TS 模块。按扩展名索引。 */
|
|
98
|
+
assetLoaders?(): Record<string, (path: string, source: string) => AssetLoadResult>;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
bundler(`src/driver/bundler/graph.ts`)在 `readFileSync` 之后、解析*之前*
|
|
103
|
+
立即查询注册表。核心保持平台无关:它只知道“扩展可能把 `*.foo` 的字节转换为
|
|
104
|
+
TS”。`gui` 扩展注册一个 `.html` 加载器。因此,普通的
|
|
105
|
+
`import html from "./index.html"` 默认产生一个**字符串常量**,并且(当文件
|
|
106
|
+
包含 `<script>` 时)产生一个**有副作用的模块**,它注册已编译的脚本主体并
|
|
107
|
+
默认导出重写后的 HTML。
|
|
108
|
+
|
|
109
|
+
### 2. `.html` 转换
|
|
110
|
+
|
|
111
|
+
给定:
|
|
112
|
+
|
|
113
|
+
```html
|
|
114
|
+
<button id="b">0</button>
|
|
115
|
+
<script lang="ts">
|
|
116
|
+
const b = document.getElementById("b");
|
|
117
|
+
let n = 0;
|
|
118
|
+
b.addEventListener("click", () => { b.textContent = String(++n); });
|
|
119
|
+
</script>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
加载器生成如下形状的模块:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// generated
|
|
126
|
+
const __html = "<button id=\"b\">0</button>\n<script data-xt-id=\"a1b2…\"></script>";
|
|
127
|
+
function __xt_script_a1b2(window: any, document: any): void {
|
|
128
|
+
const b = document.getElementById("b");
|
|
129
|
+
let n = 0;
|
|
130
|
+
b.addEventListener("click", () => { b.textContent = String(++n); });
|
|
131
|
+
}
|
|
132
|
+
__registerScript("a1b2…", __xt_script_a1b2);
|
|
133
|
+
export default __html;
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- id 是主体内容的哈希(sha256,前 32 个十六进制字符);主体被替换为一个空的
|
|
137
|
+
标记元素,以便引擎能按文档顺序找到它。
|
|
138
|
+
- `window` 与 `document` 是**函数参数**,因此脚本的全局变量会作为普通局部
|
|
139
|
+
变量解析 —— **无需编译器改动**,也没有全局对象。
|
|
140
|
+
- `__registerScript(id, fn)` 是一个新的运行时内置函数,它把闭包存进全局的
|
|
141
|
+
`unordered_map<string, xt_value>`。
|
|
142
|
+
- 延迟脚本(`<script defer>`,或 M9c 中决定的所有内联脚本)在完整文档解析
|
|
143
|
+
并计算首次布局之后按文档顺序运行。然后在 `window` 上依次触发
|
|
144
|
+
`DOMContentLoaded` 再 `load`。
|
|
145
|
+
|
|
146
|
+
### 2b. 外部 `<script src>`
|
|
147
|
+
|
|
148
|
+
`<script src="./app.ts">` 相对 HTML 文件读取、解析并拆分:其顶层
|
|
149
|
+
`import`/`export … from` 语句被**提升**到生成的模块(相对说明符被重写以便
|
|
150
|
+
从 HTML 文件所在目录解析),其余语句被包装进脚本函数。这样外部脚本既能在
|
|
151
|
+
`loadHTML` 时运行,又能 `import` 其他模块。
|
|
152
|
+
|
|
153
|
+
两个限制被强制执行并以清晰的错误报告,而不是悄悄出错:
|
|
154
|
+
|
|
155
|
+
- 缺失的 `src` 文件会抛错(被转换为 bundler 诊断),并且
|
|
156
|
+
- 两个 `src` 脚本导入同一个本地绑定名会冲突;用户必须给其中一个起别名。
|
|
157
|
+
(不冲突或完全相同的导入没问题。)
|
|
158
|
+
|
|
159
|
+
`src` URL(`https://…`、`data:…`、`//host`)保持原样 —— 它们不是编译期资产,
|
|
160
|
+
永不运行。
|
|
161
|
+
|
|
162
|
+
```html
|
|
163
|
+
<button id="b">0</button>
|
|
164
|
+
<script src="./counter.ts"></script>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
// counter.ts
|
|
169
|
+
import { double } from "./helper";
|
|
170
|
+
const b = document.getElementById("b");
|
|
171
|
+
let n = 0;
|
|
172
|
+
b.addEventListener("click", () => { n = double(n) + 1; b.textContent = String(n); });
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 3. 运行时执行
|
|
176
|
+
|
|
177
|
+
`win.loadHTML(html)`:
|
|
178
|
+
|
|
179
|
+
1. 和今天一样解析文档,
|
|
180
|
+
2. 扫描 `data-xt-id` 标记,
|
|
181
|
+
3. 对每个标记调用 `registry[id](winHandle, docHandle)`,
|
|
182
|
+
4. 触发首次布局,然后触发 `DOMContentLoaded` 与 `load`。
|
|
183
|
+
|
|
184
|
+
因为调用发生在解析之后,脚本内部的 `document.getElementById(...)` 总能找到
|
|
185
|
+
其元素。`innerHTML = …` 永不执行内嵌脚本(标记是惰性的)。
|
|
186
|
+
|
|
187
|
+
## 元素句柄对象模型(M8)
|
|
188
|
+
|
|
189
|
+
句柄是普通的运行时对象(`xt_object_new_with_proto`),每种类型共享一个原型,
|
|
190
|
+
与窗口句柄完全一样:
|
|
191
|
+
|
|
192
|
+
| 隐藏字段 | 含义 |
|
|
193
|
+
| --- | --- |
|
|
194
|
+
| `__xt_gui_win` | 所属窗口对象(通过 `xt_gui_window_from_this` 解析) |
|
|
195
|
+
| `__xt_gui_gen` | 创建该句柄时的文档代 |
|
|
196
|
+
| `__xt_gui_node` | 窗口节点表中的索引(元素/文本句柄) |
|
|
197
|
+
| `__xt_gui_doc` | 单例 `document` 句柄为 `true` |
|
|
198
|
+
|
|
199
|
+
**标识是稳定的**:窗口维护 `node_order: vector<Node*>` 以及
|
|
200
|
+
`node_index`/`node_handles: map<Node*, …>`,因此两次查询同一元素返回*同一个*
|
|
201
|
+
对象值(`a === b`)。
|
|
202
|
+
|
|
203
|
+
**已分离的节点**在文档池中保持存活,因此跨 `remove()` 持有的句柄仍可解析
|
|
204
|
+
(墓碑语义);代不匹配会使过期句柄变为无操作,而不是悬空指针。
|
|
205
|
+
|
|
206
|
+
### 访问器,而非字段
|
|
207
|
+
|
|
208
|
+
读写的 DOM 属性用 `xt_object_define_getter` / `xt_object_define_setter`
|
|
209
|
+
定义,它们已经支持原型继承且 `this` = 接收者。因此 `el.textContent = "x"`
|
|
210
|
+
与 `el.id = "y"` 可用,而值存放在 C++ 树中。`classList` 与 `style` 是持有
|
|
211
|
+
元素句柄反向引用的小对象(无需闭包/env;arena 永不释放,因此循环无害)。
|
|
212
|
+
|
|
213
|
+
### 已实现的表面(M8)
|
|
214
|
+
|
|
215
|
+
- `document`:`querySelector`、`querySelectorAll`、`getElementById`、
|
|
216
|
+
`createElement`、`createTextNode`、`body`、`documentElement`、
|
|
217
|
+
`addEventListener` / `removeEventListener` / `dispatchEvent`。
|
|
218
|
+
- node(元素/文本/文档):`tagName` / `nodeName` / `nodeType`、`id`、
|
|
219
|
+
`className`、`textContent` / `innerText`、`innerHTML`、`parentNode` /
|
|
220
|
+
`parentElement`、`children` / `childNodes` / `childElementCount`、
|
|
221
|
+
`firstElementChild` / `lastElementChild`、`nextElementSibling` /
|
|
222
|
+
`previousElementSibling`、`isConnected`、`classList`、`style`。
|
|
223
|
+
- 特性:`getAttribute` / `setAttribute` / `hasAttribute` /
|
|
224
|
+
`removeAttribute`。
|
|
225
|
+
- 查询:`querySelector` / `querySelectorAll` / `matches`(以子树为范围)。
|
|
226
|
+
- 变更:`appendChild` / `insertBefore` / `removeChild` / `replaceChild` /
|
|
227
|
+
`remove` / `cloneNode(deep?)`。
|
|
228
|
+
- 几何/焦点:`getBoundingClientRect`、`focus`、`blur`、`click`。
|
|
229
|
+
- 事件:`addEventListener(type, fn, optionsOrCapture?)`、
|
|
230
|
+
`removeEventListener`、`dispatchEvent`。
|
|
231
|
+
|
|
232
|
+
### 延迟重算样式 / 重新布局
|
|
233
|
+
|
|
234
|
+
每次变更都调用 `XtDocument::invalidate()` 并设置 `win->struct_dirty`。在任何
|
|
235
|
+
同步读取(`computedStyle`、`getBoundingClientRect`、`queryCount`、`layoutTree`、
|
|
236
|
+
`paintList`、`hitTest` 等)之前以及每帧之前,如果有任何待处理项,
|
|
237
|
+
`xt_gui_flush_dom()` 会**一次性**重算样式 + 重新布局。这使变更既廉价又批量,
|
|
238
|
+
同时让读取立即一致。
|
|
239
|
+
|
|
240
|
+
## 事件与冒泡
|
|
241
|
+
|
|
242
|
+
元素监听器存放在窗口上(`node_listeners`),以节点为键(而不是放在 `Node`
|
|
243
|
+
自身上,这样树保持聚焦于布局)。
|
|
244
|
+
|
|
245
|
+
分发:
|
|
246
|
+
|
|
247
|
+
1. 构建传播路径 `target → … → root`,
|
|
248
|
+
2. 创建一个 Event 对象(`type`、`target` = 节点句柄、`currentTarget`、
|
|
249
|
+
`bubbles`、`defaultPrevented`、内部 `__stop` / `__stopImmediate`),
|
|
250
|
+
3. **捕获**阶段:root → target(用 `capture: true` 注册的监听器),
|
|
251
|
+
4. **冒泡**阶段:target → root,
|
|
252
|
+
5. 旧式窗口级 `win.on(type, fn)` 处理器**最后**运行。
|
|
253
|
+
|
|
254
|
+
`once` 监听器在被调用之前移除;`stopPropagation` 结束当前阶段链,
|
|
255
|
+
`stopImmediatePropagation` 还会跳过同一节点上靠后的监听器。`el.click()` 在
|
|
256
|
+
该节点合成 `click`(仅 DOM);真实的指针/滚轮/键盘输入会分发 **DOM 事件与
|
|
257
|
+
旧式窗口负载两者**。
|
|
258
|
+
|
|
259
|
+
### 向后兼容
|
|
260
|
+
|
|
261
|
+
旧式窗口负载保留其**字符串** `e.target`
|
|
262
|
+
(`tests/e2e/gui-*.test.ts` 断言 `e.target === "div#inner"`)。只有*元素*
|
|
263
|
+
Event 的 `target` 是句柄,其描述符(`toString`)与同一 `div#id.class` 字符串
|
|
264
|
+
匹配。`make_dom_event` 有意**不**把旧式 `target` 字段复制到句柄上。
|
|
265
|
+
|
|
266
|
+
## 里程碑
|
|
267
|
+
|
|
268
|
+
- **M8 — DOM 对象模型 + 变更 + 元素事件** ✅
|
|
269
|
+
- `runtime/ext_gui/dom_api.{h,cpp}`、`document.{h,cpp}`、`dom.{h,cpp}`、
|
|
270
|
+
`gui_engine.h`、`gui.cpp`、`window.cpp`、e2e 覆盖。
|
|
271
|
+
- 无编译器改动;可独立测试。
|
|
272
|
+
- **M9 — AOT `<script>`** ✅
|
|
273
|
+
- **M9a** ✅ —— `src/extensions/registry.ts` 中的 `Extension.assetLoaders` +
|
|
274
|
+
`src/driver/bundler/{graph,merge}.ts` 中的 bundler 集成;单元测试。
|
|
275
|
+
- **M9b** ✅ —— gui `.html` 加载器(`src/extensions/gui/html.ts`)、
|
|
276
|
+
`__registerScript` 内置函数、脚本注册表(`runtime/ext_gui/script.cpp`)、
|
|
277
|
+
`loadHTML` 执行与 `DOMContentLoaded`/`load`;e2e 覆盖。
|
|
278
|
+
- **M9c** ✅ —— 读取 `<script src>` 文件,其顶层 import 被提升(说明符被
|
|
279
|
+
重写以便从 HTML 文件解析),其主体被包装并注册;缺失文件与 import 绑定
|
|
280
|
+
冲突作为诊断浮现。所有脚本按文档顺序运行(实际上是 deferred)。e2e
|
|
281
|
+
覆盖。
|
|
282
|
+
- **M9d** *(可选)* —— 检测传给 `win.loadHTML(...)` 的模板字面量中的内联
|
|
283
|
+
HTML 并同样转换它们(脆弱;推迟)。
|
|
284
|
+
- **M10 — 打磨** ✅ —— 窗口上的
|
|
285
|
+
`requestAnimationFrame`/`cancelAnimationFrame`、元素上的
|
|
286
|
+
`offsetWidth`/`offsetHeight`/`contains`、文档。
|
|
287
|
+
|
|
288
|
+
### 落地顺序
|
|
289
|
+
|
|
290
|
+
1. M8(本分支)—— 纯引擎工作,无编译器影响。
|
|
291
|
+
2. M9a —— 通用钩子 + bundler 接线(可用假加载器做单元测试)。
|
|
292
|
+
3. M9b —— 最小端到端脚本路径(仅内联 `<script>`)。
|
|
293
|
+
4. M9c —— 外部 `<script src>`。
|
|
294
|
+
5. M10 —— 动画帧与辅助方法。
|
|
295
|
+
|
|
296
|
+
## 未决风险
|
|
297
|
+
|
|
298
|
+
- **句柄生命周期。** 通过代计数器 + 分离池解决;过期句柄变为无操作。`Node*`
|
|
299
|
+
地址永不暴露给 TS。
|
|
300
|
+
- **无沙箱。** `<script>` 可以 `import fs`。记录为“不是浏览器”;真正的
|
|
301
|
+
源/沙箱模型不在范围内。
|
|
302
|
+
- **动态 HTML 脚本。** 运行时创建的 HTML(`innerHTML`、网络获取)中的脚本
|
|
303
|
+
不会运行。已记录。
|
|
304
|
+
- **模板字面量 HTML** 传给 `win.loadHTML(…)` 时在 M9b 中不会被转换(只有
|
|
305
|
+
导入的资产会)。如有需要,M9d 会处理。
|
|
306
|
+
- **外部 `<script src>` 文件之间的 import 绑定冲突。** 会被检测并报告
|
|
307
|
+
(给 import 起别名);之后可以做到带引用重写的按脚本重命名。
|
|
308
|
+
- **资产。** `<script src>` 相对 HTML 资产解析;URL `src` 值被忽略。没有
|
|
309
|
+
fetch/网络加载。
|
|
310
|
+
|
|
311
|
+
## 测试
|
|
312
|
+
|
|
313
|
+
- **M8**:`tests/e2e/gui-*.test.ts` 中的新 e2e 用例,覆盖句柄标识、遍历、特性、
|
|
314
|
+
`classList`、内联 `style`、`createElement` + `appendChild` + `removeChild`、
|
|
315
|
+
`getBoundingClientRect`、元素冒泡、`stopPropagation`、`el.click()` 以及
|
|
316
|
+
**窗口级 `e.target` 兼容性**。
|
|
317
|
+
- **M9a**:一个假扩展加载器的单元测试,断言 bundler 重写 `*.foo` 并记录依赖。
|
|
318
|
+
- **M9b**:一个带内联 `<script>`、点击时递增计数器的 e2e 用例,通过处理器的
|
|
319
|
+
`console.log` 断言。
|
|
320
|
+
- **M10**:`requestAnimationFrame`(每帧运行、重新排队、接收时间戳、遵守
|
|
321
|
+
`cancelAnimationFrame`)与 `offsetWidth`/`offsetHeight`/`contains` 的 e2e
|
|
322
|
+
用例。
|
|
323
|
+
|
|
324
|
+
## 需要更新的文档
|
|
325
|
+
|
|
326
|
+
- `gui.md` —— 非目标(“执行页面 `<script>`” → “执行*运行时*页面脚本;
|
|
327
|
+
编译期脚本为 AOT 编译”)、锁定决策 #1、面向 TS 的 API、里程碑、进度日志。
|
|
328
|
+
- `implemented.md` / `unimplemented.md` —— 把新的 DOM/脚本能力相应迁移。
|
|
329
|
+
- `README.md` —— 提及 DOM API + AOT 脚本。
|