ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Relay Compiler Playground:将 Rust Relay 编译器编译为 Wasm 构建网页版编译器实验室

Relay Compiler Playground:将 Rust Relay 编译器编译为 Wasm 构建网页版编译器实验室 Relay Compiler Playground将 Rust Relay 编译器编译为 Wasm 构建网页版编译器实验室【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relayRelay Compiler Playground 是 Relay 项目中一个特殊的 Rust crate它把 Rust 版 Relay 编译器的核心管线语法解析、Schema 构建、IR 构建、变换、类型生成通过wasm-bindgen编译为 WebAssembly 模块并以浏览器网页的形式向开发者开放完整的编译过程可视化。本文将从该 crate 的定位出发讲解如何构建 Wasm 模块、发布 NPM 包、运行测试并结合源码分析它暴露的六类编译能力 API 与诊断返回结构让读者既能复现构建流程也能理解编译器各阶段的真实产物形态。Relay Compiler Playground 是什么在 compiler/crates/relay-compiler-playground/README.md 中这个 crate 的定位被一句话概括Compile parts of the Rust Relay compiler to Wasm and expose them as a web-based playground.也就是说它并不直接参与 Relay 的日常编译而是一个面向浏览器环境的编译器前端把 Rust 编写的 Relay 编译器“部分能力”编译成 Wasm再暴露为网页可调用的 JavaScript 接口。该模块对应的应用场景是 Relay 官网的 Compiler Explorer编译器实验室开发者可以在网页中同时编辑 GraphQL Schema 与查询文档实时查看编译各阶段的输出。从 Cargo.toml 可以看到它的依赖组成这正是它能力的来源graphql-syntaxGraphQL 可执行文档的语法解析graphql-ir在 Schema 约束下构建 IRIntermediate Representationgraphql-text-printer将 IR 打印回 GraphQL 文本relay-transforms应用 Relay 的各类编译变换inline、fragment spread、规范化等relay-codegen打印 Reader AST 与 Normalization ASTrelay-typegen生成 Flow / TypeScript 类型relay-schemaschema从 Schema 文本构建类型系统relay-config承载 ProjectConfig 与 FeatureFlagsintern字符串驻留string interning编译器内部的字符串优化基础设施。crate 的库类型被配置为crate-type [cdylib, rlib]其中cdylib正是为了输出 Wasm 动态库而设置。同时 Cargo.toml 中为 wasm-pack 配置了 release 档的优化参数[package.metadata.wasm-pack.profile.release] wasm-opt [-Oz, --enable-mutable-globals]并在[profile.release]中设置了opt-level s即优先压缩 Wasm 产物体积。构建 Wasm 模块构建前置条件是安装wasm-pack且版本不低于 0.10.0。若尚未安装用cargo install wasm-pack安装。此外 README 提到可能还需要添加rust-src组件rustup component add rust-src随后在 crate 目录下执行构建--target web表示产物面向浏览器原生 ES Modulecd compiler/crates/relay-compiler-playground wasm-pack build --target web构建完成后NPM 模块会生成在pkg/目录下即relay-compiler-playground/pkg。该目录包含编译好的.wasm文件、胶水 JavaScript 以及由 Cargo.toml 中的name、version等元数据自动生成的package.json。关于版本号的说明README 中明确“Bump the version incargo.toml. This will be used for the generatedpackage.json”即发布版本的唯一来源是 Cargo.toml 的version字段wasm-pack 会据此生成 NPM 包的版本。当前仓库中该 crate 的版本为0.0.3见 Cargo.toml。构建目标与平台差异README 在测试一节特别标注了一句NOTE: We build for node in tests and web to publish也就是说测试与发布使用不同的 Wasm target发布给浏览器使用wasm-pack build --target web测试用Node 环境跑 Jestwasm-pack build --target nodejs。两种目标生成的胶水代码加载方式不同需要按运行环境分别构建。发布 NPM 包发布流程分三步修改 Cargo.toml 中的version字段版本号会被 wasm-pack 同步用于生成的package.json按上文执行wasm-pack build --target web完成构建进入pkg目录并发布cd pkg npm publish运行与测试单元 / 集成测试仓库为该 crate 提供了基于 Jest 的测试封装。测试入口在tests/relay_compiler_playground-test.js它通过 index.js 直接require(./pkg/relay_compiler_playground)加载编译产物。而 crate 根目录的 package.json 只是一个“用于测试的包装”A wrapper around relay-compiler-playground used for testing依赖jest^27.0.3。测试运行流程如下cd compiler/crates/relay-compiler-playground wasm-pack build --target nodejs # 测试用 Node 目标 yarn yarn test测试用例非常完整覆盖了成功与失败两类路径成功路径Okparse_to_ast解析文档并断言输出以ExecutableDocument开头parse_to_ir解析后断言输出以Operation开头parse_to_reader_ast/parse_to_normalization_ast/transform与快照对比parse_to_types分别传入{language: flow}与{language: typescript}验证两种类型生成语言parse_to_reader_ast required验证required(action: LOG)指令在 Reader AST 中正确生成RequiredField。失败路径Err非法 FeatureFlags JSON如{this_key_does_not_exist: false}返回ConfigError且错误信息中会列出全部合法字段名语法错误的文档返回DocumentDiagnostics包含行、列区间与诊断消息引用 Schema 中不存在的字段如does_not_exist同样返回DocumentDiagnostics消息为The typeUserhas no fielddoes_not_existSchema 本身引用未定义类型如InvalidType时返回SchemaDiagnosticsparse_to_types传入非法语言如{language: should_not_exist}返回TypegenConfigError并提示合法取值javascript、typescript、flow。手动验证接入 Docusaurus 网站README 提供了“手动测试”路径构建 Wasm 后通过yarn link把本地包接入 Relay 官网Docusaurus 站点随后启动开发服务器验证。cd compiler/crates/relay-compiler-playground/pkg yarn link cd ~/fbsource/xplat/js/RKJSModules/Libraries/Relay/oss/__github__/website yarn link relay-compiler-playground # 可能需要清除 Docusaurus 缓存 npx docusaurus clear # 以开发模式启动网站 yarn start启动后访问http://localhost:3000/compiler-explorer即可在浏览器中打开 Compiler Explorer 页面。需要说明的是README 中的网站路径是针对 Facebook 内部代码库的绝对路径在当前仓库中对应的站点源码位于 website/src/pages/compiler-explorer.jsDocusaurus 相关配置见 website/docusaurus.config.js。暴露的编译器 API六个 Wasm 导出函数Wasm 模块的全部能力由 src/lib.rs 中的六个#[wasm_bindgen]导出函数提供。它们全部以字符串为输入、以字符串为输出且都遵循同一个模式内部执行一个*_impl纯 Rust 函数再通过serde_json::to_string序列化返回。返回值的 JSON 结构是一个结果枚举ResultString, PlaygroundError即要么是{Ok: ...}要么是{Err: {...}}。导出函数签名功能parse_to_ast(document_text) - String仅解析文档输出语法树AST的 Debug 格式parse_to_ir(schema_text, document_text) - String先构建 Schema再构建 IR输出 IR Debug 格式parse_to_reader_ast(feature_flags_json, schema_text, document_text) - String应用全部变换后输出 Reader AST运行时读取用parse_to_normalization_ast(feature_flags_json, schema_text, document_text) - String应用全部变换后输出 Normalization AST响应规范化用parse_to_types(feature_flags_json, typegen_config_json, schema_text, document_text) - String生成 Flow / TypeScript 类型声明transform(feature_flags_json, schema_text, document_text) - String应用全部变换后把 IR 打印回 GraphQL 文本AST 阶段parse_to_astparse_to_ast_implsrc/lib.rs的调用链最简单直接调用graphql_syntax::parse_executable对文档做语法解析解析结果通过format!({:?}, document)以 Debug 形式输出。它只验证语法正确性不涉及 Schema。测试中一个明显缺少选择集的错误文档会在此处报出Expected a selection: field, inline fragment, or fragment spread的诊断。IR 阶段parse_to_irparse_to_ir_implsrc/lib.rs引入了 Schema先用graphql_syntax::parse_executable解析文档再调用relay_schema::build_schema_with_extensions_parallel从 Schema 文本构建类型系统错误会以SchemaDiagnostics返回最后调用graphql_ir::build(schema, document.definitions)在 Schema 约束下做语义检查并构建 IR。因此在 IR 阶段文档中引用了 Schema 不存在的字段就会报出语义错误测试中的does_not_exist用例即在此被拦截。IR 产物按定义逐条format!({:?}, ...)输出并拼接。变换与产物打印reader / normalization / transform这三个函数共享同一条底层管线。get_programssrc/lib.rs负责完成“解析 → 构建 IR → 组装 Program → 应用全部变换”let document graphql_syntax::parse_executable(document_text, Generated)?; let ir graphql_ir::build(schema, document.definitions)?; let program Program::from_definitions(schema.clone(), ir); let base_fragment_names Arc::new(Default::default()); apply_transforms( project_config, Arc::new(program), base_fragment_names, Arc::new(NoopPerfLogger), None, None, vec![], )?apply_transforms来自relay_transforms返回的Programs结构体持有reader、normalization、typegen、operation_text等多个视图Program 集合分别对应运行时不同的产物需求。这也是为什么一次apply_transforms可以同时支撑三种不同的导出函数。parse_to_reader_ast_impl遍历programs.reader中的 fragments 与 operations通过relay_codegen::print_fragment/print_operation打印为 JSON 化的 Reader AST。快照中可以看到 Fragment 与 Operation 交替输出结构包括argumentDefinitions、selections、storageKey等字段对带required(action: LOG)的字段会生成RequiredField节点见 测试快照。parse_to_normalization_ast_impl遍历programs.normalization的 operations打印为 Normalization AST用于 Relay 运行时对服务器响应做规范化写入快照中每个字段都带有concreteType、storageKey、alias、args等字段。transform_impl遍历programs.operation_text中的 operations 与 fragments使用graphql_text_printer::print_operation/print_fragment把变换后的 IR 打印回纯 GraphQL 文本因此 Compiler Explorer 的 “Operation” 输出页看到的正是规范化后可直接发送的查询文本。类型生成parse_to_typesparse_to_types_implsrc/lib.rs额外接收一个typegen_config_json参数用于指定类型生成语言。流程为从feature_flags_json构建FeatureFlags从typegen_config_json构建TypegenConfig二者共同组装出ProjectConfig见get_project_configsrc/lib.rs构建FragmentLocations后对每个 fragment 调用relay_typegen::generate_fragment_type_exports_section对每个 operation先在 normalization 产物中找到对应 operation再调用generate_operation_type_exports_section与print_provided_variables生成类型与 variables 类型。从 测试快照 可以看到两种语言的典型差异Flow 输出declare export opaque type与field: ?number语法并import type { FragmentType } from relay-runtimeTypeScript 输出readonly字段、FragmentRefs与 $fragmentType等运行时类型标记。错误与诊断的序列化格式所有失败都以PlaygroundError枚举序列化返回其四个变体src/lib.rsDocumentDiagnostics(VecWasmDiagnostic)文档相关错误带位置信息SchemaDiagnostics(VecWasmDiagnostic)Schema 相关错误如引用未定义类型测试注释指出 Schema 诊断暂不包含位置信息行列均为 0ConfigError(String)FeatureFlags JSON 解析失败如字段不存在TypegenConfigError(String)Typegen 配置 JSON 解析失败如非法语言值。WasmDiagnosticsrc/lib.rs由map_diagnostics从编译器的内部Diagnostic转换而来诊断的 span 会通过TextSource::from_whole_document(...).to_span_range(...)换算成行/列区间line_start、line_end、column_start、column_end配合message一起输出方便网页编辑器把错误标记渲染在对应的源代码位置。从源码到网页Compiler Explorer 的接入方式Wasm 模块在 Relay 官网中的实际消费者是 website/src/pages/compiler-explorer.js。它展示了这套 API 的典型用法输入区三个标签页Schema、Document、Config。Config 页把六个 Feature Flags 渲染为复选框并允许在 Flow / TypeScript 之间切换类型生成语言输出区六个标签页Operation、AST、IR、Normalization AST、Reader AST、Types与上面六个 Wasm 导出函数一一对应状态持久化编辑器的内容会序列化到 URL hash 与 localStorage见 website/src/compiler-explorer/ExplorerState.js刷新页面、分享链接即可复现相同的编译输入异步加载Wasm 模块在useEffect中通过require(relay-compiler-playground)延迟加载并调用init()初始化返回 null 之前界面显示 “Loading...”——这是因为在 Docusaurus 构建期预渲染时加载 Wasm 会崩溃必须放到浏览器运行时FeatureFlags 的 JSON 化useSerializedFeatureFlags会把 UI 上的布尔开关按enumenabled/disabled与bool两种形态归一化后传给 Wasm 函数与 Rust 侧FeatureFlags的反序列化结构对齐。默认的输入样例定义在 website/src/compiler-explorer/ExplorerStateConstants.js一个User类型的 Schemaname、age、best_friend加上一个包含 fragment spread 的MyQuery文档与测试用例保持一致打开页面即可直接体验六个输出页。小结Relay Compiler Playground 的价值在于把 Rust 版 Relay 编译器的内部管线完整搬到浏览器语法AST、语义IR、变换结果GraphQL 文本、Reader AST、Normalization AST 与类型定义Flow / TypeScript全部可以实时生成与对比。对编译器开发者而言它是一台可交互的调试台对 Relay 使用者而言它是理解编译器各阶段产物形态的直观窗口。整个 crate 体积小、边界清晰六个 Wasm 导出函数 一个结果枚举即可串联起graphql-syntax → graphql-ir → relay-transforms → relay-codegen / relay-typegen的完整调用链是学习 Rust Relay 编译器模块划分与调用关系的极佳入口。【免费下载链接】relayRelay is a JavaScript framework for building>项目地址: https://gitcode.com/gh_mirrors/relay29/relay创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进