示例源码详解)
从 build.rs 到 JsRuntimeDeno Core 启动快照Startup Snapshot示例源码详解【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno本文以 Deno 仓库中的libs/core/examples/snapshot官方示例为骨架完整讲解如何用deno_core的create_snapshot()在构建期生成一份内嵌启动快照并用#[op2]、extension!宏定义一个带 Rust 操作的极简 JavaScript 运行时。读完后你可以掌握在 Cargo 构建脚本中创建快照、把快照字节嵌入最终二进制、通过RuntimeOptions::startup_snapshot恢复运行时以及各配置字段的含义与验证方式。示例定位把“滚动博客教程”固化为持续构建的活文档示例 README 说明了这个示例的由来它大致遵循 Deno 官方博客 “Roll Your Own JavaScript Runtime: Part 3” 的思路用一个内嵌启动快照embedded startup snapshot来创建JsRuntime。由于该系列博客文章含其前两篇已经不再准确Deny 团队把这个示例直接放进仓库让它随主干持续参与构建从而保证文档与 API 现状不脱节。仓库中示例的实际目录是 libs/core/examples/snapshot包含以下文件文件作用build.rs构建脚本调用create_snapshot()生成快照字节并写入OUT_DIRsrc/main.rs运行时程序加载快照、注册 op、执行example.jssrc/runtime.js快照中预加载的 ESM 模块my:runtime封装对 Rust op 的调用example.js演示脚本import { callRust } from my:runtime并调用tests/output.rs集成测试跑一遍二进制并断言标准输出其中 Cargo.toml 有一条值得注意的工程约定文件内注释原样说明Cargo 的 example 目标不会发现/使用build.rs文件所以这个示例被做成根deno_coreworkspace 的成员包包名为build-your-own-js-snapshot。这意味着它在根目录执行cargo build时会被编译从而保证这个示例/文档始终与开发同步。这正是 README 所说“continuous 构建以保持更新”的落地机制它不是一个[[example]]而是一个带build build.rs的 workspace 成员。构建期在 build.rs 中创建快照build.rs 是整个示例的核心流程分四步1. 定义 ESM 扩展并指定入口点extension!( runjs_extension, // 必须指定 entrypoint这样我们的模块才会在打快照时被加载 esm_entry_point my:runtime, esm [ dir src, my:runtime runtime.js, ], );esm_entry_point my:runtime是关键它保证src/runtime.js会在快照创建过程中被执行并固化进快照之后运行时就不需要再解析/编译这个模块了。2. 构造CreateSnapshotOptions并调用create_snapshot()let options CreateSnapshotOptions { cargo_manifest_dir: env!(CARGO_MANIFEST_DIR), startup_snapshot: None, extensions: vec![runjs_extension::init()], with_runtime_cb: None, skip_op_registration: false, extension_transpiler: None, }; let warmup_script None; let snapshot create_snapshot(options, warmup_script).expect(Error creating snapshot);对照 libs/core/runtime/snapshot.rs 中的定义各字段含义为字段说明cargo_manifest_dirCargo 编译的目录应始终填CARGO_MANIFEST_DIRstartup_snapshot可选的“基础快照”在其之上叠加构建新快照透传给RuntimeOptions::startup_snapshot示例填None从零开始skip_op_registration初始化快照运行时是否跳过 op 注册透传给RuntimeOptions::skip_op_registration示例为falseextensions要固化进快照的扩展列表透传给RuntimeOptions::extensionsextension_transpiler可选转译器可在模块源码进入快照前做修改例如 TypeScript 转 JSwith_runtime_cb可选回调在拍快照前对运行时做进一步修改3. 把快照字节落盘到 OUT_DIRlet out_dir PathBuf::from(env::var_os(OUT_DIR).unwrap()); let file_path out_dir.join(RUNJS_SNAPSHOT.bin); fs::write(file_path, snapshot.output).expect(Failed to write snapshot);4. 输出cargo:rerun-if-changed让构建依赖正确for path in snapshot.files_loaded_during_snapshot { println!(cargo:rerun-if-changed{}, path.display()); }这一点在 snapshot.rs 的create_snapshot文档注释 中被明确列为使用规范返回的CreateSnapshotOutput是#[must_use]其中files_loaded_during_snapshot在快照创建期间从文件系统加载的文件必须在构建脚本中逐条打印cargo:rerun-if-changed否则这些文件变更时 Cargo 不会重新触发构建。CreateSnapshotOutput另外还提供consumed_lazy_specifiers哪些lazy_loaded_esm/lazy_loaded_js模块的源码已被编译进快照调用方可据此避免在最终二进制中重复内嵌这些源码output快照文件的最终字节。此外create_snapshot()还支持第二个参数warmup_script当提供时它会基于刚拍好的冷快照新建 isolate、执行一段预热脚本、再重新序列化得到“热快照”见 snapshot.rs 中的 warmup 流程。本示例传None以保持精简。运行期main.rs 中加载快照并执行 JSsrc/main.rs 展示了快照的消费侧整体调用链是定义 op 与扩展main.rs 第 28–33 行#[op2(fast)] fn op_call_rust(#[string] value: String) { println!(Received this value from JS: {value}); } extension!(runjs_extension, ops [op_call_rust,],);这里体现了 README 中提到的两点 API 演进op 使用新的#[op2]宏fast表示启用 fast call 路径扩展使用extension!(...)宏取代旧的Extension::builder()。构建带快照的JsRuntimemain.rs 第 39–44 行let mut js_runtime JsRuntime::new(RuntimeOptions { module_loader: Some(Rc::new(FsModuleLoader)), startup_snapshot: Some(RUNTIME_SNAPSHOT), extensions: vec![runjs_extension::init()], ..Default::default() });startup_snapshot: Some(RUNTIME_SNAPSHOT)就是恢复快照的那一步——V8 直接从快照恢复 isolate跳过冷启动阶段的模块解析与解释执行。加载并求值主模块、跑事件循环main.rs 第 46–51 行load_main_es_module→mod_evaluate→run_event_loop(PollEventLoopOptions::default())最后await求值结果。快照字节的来源main.rs 第 56–57 行static RUNTIME_SNAPSHOT: [u8] include_bytes!(concat!(env!(OUT_DIR), /RUNJS_SNAPSHOT.bin));include_bytes!把构建脚本产出的RUNJS_SNAPSHOT.bin直接编译进二进制这就是“embedded startup snapshot”的字面含义。外层main()用单线程 tokio 运行时new_current_thread()enable_all()驱动block_on(run_js(./example.js))因为JsRuntime的模块加载是异步 API。JavaScript 侧runtime.js 与 example.js 的分工src/runtime.js 是一个极简的“JS 接口层”export function callRust(stringValue) { const { op_call_rust } Deno.core.ops; op_call_rust(stringValue); }它通过Deno.core.ops取到 Rust 侧注册的 op 并透传参数。由于它被声明为my:runtime入口点这份模块连同其求值后的状态在打快照时就被固化了。example.js 则是用户脚本演示从快照里的模块做具名导入import { callRust } from my:runtime; callRust(Hello from example.js);my:runtime这种非file:的虚拟 scheme 正是快照预加载模块的典型用法运行时看到my:runtime时不需要任何 loader 逻辑模块已经在内存里了。运行与验证输出断言如何工作README 给出的运行方式是在core/examples/snapshot目录即本仓库中的libs/core/examples/snapshot执行cargo run。tests/output.rs 则把它变成可回归的断言测试通过Command执行cargo run --release --quiet --features deno_core/v8output.rs 第 26–38 行断言退出码成功、stderr 为空且 stdout 恰好等于Received this value from JS: Hello from example.js\n。测试注释中有两条实用信息CI 以--release模式运行因此测试也复用它以共享缓存deno_core默认不启用任何 JS 引擎后端deno_v8门面要求恰好启用v8或quickjs之一而嵌套的cargo run是一次全新构建、不会继承外层 feature所以必须显式加--features deno_core/v8。这也提示了一个适用前提该示例依赖deno_coreworkspace 依赖且运行环境需要 V8或 QuickJS后端 feature 之一。与早期博客文章的差异与裁剪README 原始内容README 的 “Differences” 一节明确列出了相对博客文章的差异值得照单继承API 差异create_snapshot()API 发生了多处变化——对照当前 CreateSnapshotOptions 结构体 可见它现在是一个显式的 options 结构体含cargo_manifest_dir、with_runtime_cb、extension_transpiler等字段并返回包含files_loaded_during_snapshot的 output调用方需据此维护 Cargo 构建依赖扩展的新特性#[op2]宏、extension!(...)宏取代Extension::builder()、基于 ESM 的扩展本示例的esm_entry_point即为体现。功能裁剪不实现TsModuleLoader博客第二篇中用于支持 TypeScript 的模块加载器以保持示例简洁——这也解释了为什么示例里 JS 侧全部是.js且extension_transpiler传None。小结这套快照模式的可复用要点把示例抽象成模板一个带内嵌快照的自定义 JS 运行时需要四个动作且每一步都有仓库内可核对的实现依据在build.rs中用extension!声明扩展含esm_entry_point调用 create_snapshot把snapshot.output写入OUT_DIR并对files_loaded_during_snapshot逐个打印cargo:rerun-if-changed参见 build.rs在源码里用include_bytes!(concat!(env!(OUT_DIR), /XXX.bin))嵌入快照参见 main.rs 第 56–57 行创建JsRuntime时传入startup_snapshot: Some(快照)参见 main.rs 第 39–44 行用集成测试锁定端到端输出防止 API 演进悄悄破坏行为参见 tests/output.rs。需要再次说明的适用边界该示例是deno_coreworkspace 的成员包随仓库根目录构建它演示的是 V8 快照的基本用法未覆盖 TypeScript 支持、懒加载模块lazy_loaded_esm/lazy_loaded_js与consumed_lazy_specifiers相关能力等进阶场景这些可继续从 libs/core/runtime/snapshot.rs 的文档注释与CreateSnapshotOutput定义出发深入。【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考