@unicity-astrid/build 构建流水线深度解析:从 TypeScript 类到 WASM Capsule 的编译器
【免费下载链接】sdk-jsJavaScript and TypeScript SDK for building Astrid capsules.项目地址https://gitcode.com/gh_mirrors/sdkjs10/sdk-js点击查看免费下载导读unicity-astrid/build是 sdk-js 仓库中负责把用 TypeScript/JavaScript 编写的 Astrid capsule 工程编译为wasm32-wasip2WebAssembly 组件的构建编排器。它以package.json Capsule.toml工程为输入先后驱动 WIT 事件类型生成、tsc编译、esbuild 打包与 ComponentizeJS 组件化最终产出 Rust 内核可以打包进.capsule归档的.wasm文件。阅读本文后你将掌握这条构建流水线的每一个阶段、生成的 bridge 入口与运行时调度机制、编译期强制校验规则、CLI 与环境变量配置以及如何基于仓库内示例工程实际运行一次构建。定位JavaScript 侧与 Rust proc macro 对应的外部工具链在 Rust 生态中astrid-sdk-macros通过 proc macro 在cargo build时完成 capsule 的代码生成与装配而 JavaScript/TypeScript 没有编译期宏机制同样的工作必须由外部工具在astrid build阶段完成。unicity-astrid/build源码位于 packages/astrid-build/src/index.mjs就是这套工具。一个关键设计约束是capsule 业务代码不应直接依赖这个包。它由 Rust 内核的astrid-build二进制在检测到某个工程同时包含package.json与Capsule.toml时自动调用负责发出 Rust 侧用来组装.capsule归档的 WASM 组件。换句话说unicity-astrid/build站在构建链路的中游上游是 Rust 内核的构建器下游是 ComponentizeJS 与 esbuild。包的元数据可以印证这一点见 packages/astrid-build/package.json通过bin字段暴露命令行入口astrid-js-build指向./src/index.mjs依赖bytecodealliance/componentize-js0.19.3、bytecodealliance/jco1.17.8、esbuild^0.25.12、typescript^5.6.0engines.node 20且是纯 ESM 包type: module构建脚本只是no build step — pure ESM。七阶段构建流水线文档给出的完整流水线如下源码实现见 main() 函数sdk-js/packages/astrid-build/src/index.mjs project-dir --out wasm-path 1. Read package.json (name, version) 2. wit-events codegen: walk project/wit/, emit project/gen/*.d.ts mirroring wit_events! 3. tsc: compile src/*.ts → dist/*.js, using projects tsconfig.json 4. Emit gen/_entry.src.mjs that imports the users compiled entry, constructs the SDK bridge, re-exports the four WIT export names 5. esbuild bundle: gen/_entry.src.mjs unicity-astrid/sdk → gen/_entry.mjs (one self-contained ESM file, astrid:* specifiers marked external) 6. ComponentizeJS programmatic API: gen/_entry.mjs wit/ → target/name.wasm (with disableFeatures: all five, so the output has zero WASI imports) 7. Print {wasmPath, bytes} as the final stdout line for the caller to parse第 1 步读取工程元数据resolveProjectMetadataindex.mjs 第 72-80 行要求工程目录下必须存在package.json且name字段必须是非空字符串否则直接报错退出version缺失时回退为0.0.0。工程名随后用于默认的 wasm 输出路径project/target/name.wasm。第 2 步WIT 事件类型代码生成codegenWitEventswit-codegen.mjs 第 23-54 行遍历工程的wit/目录解析每个.wit文件向gen/file.d.ts输出 TypeScript 类型声明镜像 Rust 宏wit_events!的输出。它必须在 tsc 之前运行这样生成的.d.ts在类型检查阶段即可见如果工程没有wit/目录则静默跳过返回{ files: 0, types: 0 }具备幂等性。类型映射规则emitTs与各emit*函数wit-codegen.mjs 第 56-187 行WIT 构造生成的 TS 类型kebab-case 类型名PascalCase 接口/类型名与 Rust 生成的 struct 名一致kebab-case 字段名snake_case 字段对应 Rust 侧#[serde(rename_all snake_case)]optionTT \| undefined字段使用可选?:语法listTT[]tupleT,U,...[T, U, ...]variant带tag与可选value的可辨识联合镜像serde(tag tag, content value)flags变体名字符串数组type X XFlag[]; type XFlag case \| ...u64/s64bigint64 位整数跨 WIT 边界时在 jco 绑定中为 bigintbool/string/char/ 整数与浮点boolean/string/number代码生成还做了接口级命名空间隔离由于agent、approval、elicit等规范接口都可能定义record responseemitTs会为每个接口生成一个export namespace并通过typeOwner索引让跨接口引用自动全限定例如types.Message避免同名类型互相污染。第 3 步tsc 编译compileTypeScriptindex.mjs 第 82-99 行检查工程根目录是否存在tsconfig.json不存在则跳过编译打印提示。存在时它通过typescript/bin/tsc的 CLI 而非编程式 API 调用原因在注释中写得很清楚CLI 是经过契约测试的路径能完整支持 project references而编程式 API 需要重新实现该特性。编译失败即die(tsc failed)。输出目录取自 tsconfig 解析出的outDir缺省回退为project/dist。第 4 步生成 bridge 入口gen/_entry.src.mjsemitEntryindex.mjs 第 126-166 行向gen/目录写出一个自动生成的薄包装模块其要点import用户编译后的入口模块——这一步会触发capsule/tool/install/upgrade等装饰器执行从而填充 SDK 的注册表从 SDK 运行时导入createBridge并构造 bridge 实例重新导出 WIT 要求的四个 guest 导出函数astridHookTrigger、run、astridInstall、astridUpgrade各自转调 bridge 的对应方法。入口中的相对导入路径使用 POSIX 风格posixRelative这是因为 ComponentizeJS 的 splicer 会拒绝file://URL 导入——它会把整个 URL 当作相对路径处理。第 5 步esbuild 打包为单一 ESM 文件bundleindex.mjs 第 280-319 行把生成的入口与unicity-astrid/sdk内联成一个自包含 ESM 文件gen/_entry.mjs。几个关键配置及其动机platform: browser让 esbuild 优先遵循包内的browser字段映射例如 node-forge 把crypto/buffer/process映射为空模块、libp2p 把node:crypto使用文件映射到基于 WebCrypto 的.browser.js变体——因为 capsule 运行时 StarlingMonkey 提供globalThis.crypto但不提供node:crypto/node:events等 Node 内置external: [astrid:*]把 WIT 模块说明符标记为外部esbuild 不去解析由 ComponentizeJS 持有这些导入conditions: [import, module, browser]有意的不含node因为 capsule 运行时无法满足node:*内置minify: false保留可读的错误信息体积压缩交给 ComponentizeJS配合自定义插件nodeBuiltinStubPlugin。Node 内置垫片插件index.mjs 第 190-278 行值得单独说明它为fs、path、crypto、zlib、ws等模块含node:前缀形式生成调用即抛错的 Proxy 桩。设计上惰性抛错首次调用才抛而非导入时抛很多面向 Node 与浏览器双端发布的库会在顶层解构crypto.createHmac但只有管理工具等从不执行的代码路径才会真正调用它。若导入时就抛错会连累本可正常工作的路径惰性抛错让打包成功、不可达路径保持不可达。插件为每个桩模块显式声明了可能被解构导入的命名导出列表遇到新的缺失导出时向该列表追加即可。第 6 步ComponentizeJS 组件化runComponentizeindex.mjs 第 410-432 行调用bytecodealliance/componentize-js的编程式 API将gen/_entry.mjs与 WIT 世界组合为target/name.wasm并返回组件字节数、组件化耗时与 host 导入数。关键参数见下文ComponentizeJS 配置一节。第 7 步输出可解析的构建结果构建结束后stdout 的最后一行输出一段 JSONindex.mjs 第 465-467 行astrid-js-build-result: {wasmPath:/abs/path/target/name.wasm,bytes:12345}Rust 内核调用方无需解析日志直接解析这一行即可拿到二进制路径与字节数。此前每一阶段也都会打印进度行user entry ...、generated entry ...、bundled ...、wrote ... (x.xx MB, y.yys, N host imports)。Rust 侧astrid-build随后对生成的.wasm调用pack_capsule_archive产出结构上与 Rust 构建的 capsule 完全一致的dist/name.capsule归档。Bridge 生成的是什么gen/_entry.mjs是四层职责的薄包装导入用户代码装饰器触发并填充注册表→ 通过unicity-astrid/sdk/runtime的createBridge()构建 bridge → 转发astridHookTrigger/run/astridInstall/astridUpgrade四个 WIT guest 导出。bridge 运行时packages/astrid-sdk/src/runtime/bridge.ts在astridHookTrigger内按 action 名分发tool_describe惰性构建聚合的工具 schema 列表{ tools: [...], description: ... }首次调用后缓存bridge.ts 第 84-91 行tool_execute_name解析ToolExecuteRequest→ 按mutable标志决定是否从 KV 加载__state→ 调用用户 handler → 成功时保存状态 → 通过 IPC 发布到tool.v1.execute.name.result→ 返回{ action: continue, data: undefined }bridge.ts 第 118-155 行interceptor / command 的 topic 名调用用户 handler结果直接经capsule-result.data返回不经 IPC 发布与 Rust 宏行为一致bridge.ts 第 164-203 行语义生命周期 hook如before_tool_call采用fail-open策略事件解析、状态加载、handler 或 reply 任何一步出错都只记log.warn后返回continue不让单个 hook 拖垮整条链bridge.ts 第 206-253 行。生命周期导出遵循 Rust#[astrid::run]的可运行/仅挂机二象性run()若没有注册run方法WIT 仍要求该导出存在但立即返回内核不会误判为守护进程若注册了则加载状态但不自动持久化循环型 runnable 的状态保存是用户的显式kv.set责任bridge.ts 第 291-312 行astridInstall()实例化类、调用install方法并持久化实例状态astridUpgrade()读取getConfig(prev_version)作为参数传给upgrade方法随后持久化。bridge 的另一重要约束是异步必须同步化syncWaitbridge.ts 第 413-437 行 对应源码见 bridge.ts 第 413-437 行要求 handler 返回的 Promise 在引擎微任务排空前 settle——ComponentizeJS 只能驱动基于 host 导入的 await纯setTimeout/setInterval会挂起。因此 handler 内应只使用 Astrid SDK 调用或干脆写成同步函数。编译期强制校验错误在注册时暴露装饰器违规在**类首次求值注册时间**即抛出而不是延迟到运行时错误检查分布在 packages/astrid-sdk/src/runtime/registry.ts 的装饰器实现与注册表中一个类上有两个install方法 → 模块加载时抛错两个upgrade方法 → 抛错两个run方法 → 抛错两个同名tool(name)→ 抛错两个同 topic 的interceptor(topic)→ 抛错install/upgrade/run声明在 private 或 static 方法上 → 抛错除此之外注册表还强制每个 WASM 模块只允许一个capsule类registerCapsuleregistry.ts 第 75-87 行并用pendingByCtor弱映射缓冲类字段先于capsule出现时提前触发的装饰器条目待capsule执行时统一收养registry.ts 第 94-106 行。类型层面schema 输入类型由构建期的tsc负责类型检查WIT 事件类型则对照生成的gen/file.d.ts检查——用户代码import type { events } from ../gen/events.js后字段名、可选性、联合类型都受编译期保护。CLI 与配置编排器当前接受的参数对应 parseArgs参数说明project-dircapsule 工程目录必填解析为绝对路径--out wasm-path组件化 wasm 的输出路径可选默认project/target/name.wasm未知参数会直接报错退出。Rust 内核定位本脚本时遵循两条路径$ASTRID_JS_BUILD环境变量绝对路径覆盖仅开发用project/node_modules/unicity-astrid/build/src/index.mjs沿目录向上查找类似 Node 的解析器因此 npm workspaces 的依赖提升hoisting场景也能命中。ComponentizeJS 配置五项禁用是正确性要求编排器固定传入runComponentize 调用处worldName: capsule——即astrid-capsule.wit中声明的 worlddisableFeatures: [stdio, random, clocks, http, fetch-event]——禁用全部五项功能剥离所有 WASI 导入使产物对 WASI 零依赖。这两项不可配置禁用 WASI 是正确性要求而非选项。否则组件在针对内核链接器实例化时会因内核不提供 WASI 而失败。实现细节上构建器并不直接使用工程自身的wit/目录做组件化而是编排出合成 WIT 依赖树stageCapsuleWitindex.mjs 第 357-408 行规范 host ABI 位于仓库根contracts/submodule 挂载源码中默认CANONICAL_WIT_DIR为contracts/host可用环境变量ASTRID_HOST_WIT_DIR覆盖缺失时要求执行git submodule update --init --recursive每个按域拆分的冻结包版本如ipc1.0.0.wit、http1.1.0.wit被复制进project/gen/wit/deps/astrid-pkgversion/——WIT 约定拒绝同一目录下存在同一包的两个版本因此每个冻结版本各占一个依赖目录随后写出一个合成的capsule.wit声明package astrid-sdk:capsule与world capsuleimport全部按域 host 接口astrid:io/*1.0.0、astrid:fs/host1.0.0、astrid:http/host1.1.0、astrid:process/host1.1.0等并include四个 guest 导出世界interceptor、background、installable、upgradable。这一步精确镜像 Rust SDK 的astrid-sys合成 world保证两种 SDK 产出的 capsule 面向同一个 world。实战用示例工程跑通一次构建仓库自带的 examples/test-capsule 是 Phase 1 的纵向切片示例完整覆盖toolmutable 与无状态、interceptor、hook、install、upgrade、WIT 类型化事件发布等 SDK 表面examples/test-capsule/src/index.ts。工程布局Capsule.toml声明包名test-capsule、astrid-version 0.5.0、组件file test-capsule.wasmtype executable、能力kv [*]以及发布/订阅的 WIT 拓扑如发布tool.v1.execute.*、订阅test.v1.event与 hook 事件package.jsonbuild脚本为astrid-js-build . --out target/test-capsule.wasm依赖unicity-astrid/sdk开发依赖unicity-astrid/build与typescriptwit/events.wit定义test:capsule-events1.0.0包含record test-eventid: string、count: u32、label: optionstring、tags: liststring与enum severitytsconfig.json供第 3 步 tsc 使用。核心类代码片段节选自 examples/test-capsule/src/index.tsimport { capsule, tool, interceptor, hook, install, upgrade, log, ipc, type HookEvent, type HookResult, } from unicity-astrid/sdk; import type { events } from ../gen/events.js; type TestEvent events.TestEvent; capsule export class TestCapsule { counter 0; tool(increment, { mutable: true }) increment(_args: object): { counter: number } { this.counter (this.counter 1) 0; log.info(counter incremented to ${this.counter}); return { counter: this.counter }; } tool(emit_event) emitEvent(_args: object): { published: boolean } { const event: TestEvent { id: evt-001, count: 1, label: test, tags: [demo], }; ipc.publishJson(test.v1.event.fired, event); return { published: true }; } interceptor(test.v1.event) handleEvent(_payload: unknown): { handled: boolean } { return { handled: true }; } hook(before_tool_call) beforeToolCall(event: HookEvent): HookResult | undefined { const payload event.json{ tool?: string }(); return payload.tool forbidden ? { skip: true } : undefined; } install onInstall(): void { log.info(test-capsule installed); } upgrade onUpgrade(prevVersion: string): void { log.info(test-capsule upgraded from ${prevVersion}); } }注意emit_event中TestEvent类型来自gen/events.d.ts——这正是第 2 步 wit-events 代码生成在类型层面的落地WIT 里的optionstring变成可选字段label?: stringliststring变成tags: string[]字段名id、count等与 WIT 声明一一对应。本地开发与运行在 packages/astrid-build 目录或仓库根 workspace下npm install node src/index.mjs ../../examples/test-capsule两点前置条件先构建 SDK构建器从packages/astrid-sdk/dist/runtime/index.js解析 bridge 运行时resolveSdkRuntime若缺失会提示先在 workspace 根执行npx tsc -b packages/astrid-sdk挂载规范 WIT submodulecontracts/目录需通过git submodule update --init --recursive挂载当前镜像仓库中contracts/为空需从上游获取否则第 6 步会因找不到规范 host WIT 而失败。运行后标准输出会依次显示各阶段进度最后一行是astrid-js-build-result: {...}的 JSON。产物target/test-capsule.wasm即为内核可打包的 wasm32-wasip2 组件。许可unicity-astrid/build采用 MIT 与 Apache-2.0 双许可。如需了解细节可查看仓库根目录的 README.md 与 CHANGELOG.md许可文本位于仓库根目录的 LICENSE 文件。赞分享【免费下载链接】sdk-jsJavaScript and TypeScript SDK for building Astrid capsules.项目地址https://gitcode.com/gh_mirrors/sdkjs10/sdk-js点击查看免费下载相关推荐Astrid Capsule 构建指南使用 astrid-build 将 Rust 与遗留 MCP 项目打包为 .capsuleAstrid Capsule 构建指南使用 astrid build 将 Rust 与遗留 MCP 项目打包为 .capsule Astrid 是可移植、基于Stencil 编译器架构深度解析从 stencil build 到生产级 Web Components 的完整构建管线Stencil 编译器架构深度解析从 stencil build 到生产级 Web Components 的完整构建管线 本文以 Stencil 开源仓库中的开发工具前端前端构建Quartz v5 架构深度解析从 npx quartz build 到页面渲染的完整构建流水线Quartz v5 架构深度解析从 npx quartz build 到页面渲染的完整构建流水线 导读 本文以官方架构文档 docs/advanced/arc前端开发工具CLI上一篇MP4视频修复终极指南如何用Untrunc快速恢复损坏的视频文件下一篇如何在Vue应用中快速实现手势控制移动开发终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考