导读:本文围绕 Relay 19 数据驱动依赖(Data Driven Dependencies,简称 3D)的配置环节展开,完整解读 Relay Compiler 配置文件中
moduleImportConfig字段的结构、子字段语义与各模式示例。读完本文,你将掌握如何在不改动代码的前提下自动启用 Server 3D,以及如何通过dynamicModuleProvider与surface两个字段为 OSS 项目开启 Client 3D,并能从源码与测试用例层面理解每个配置项在编译期的真实作用。
一、从"零配置"开始:Server 3D 的自动启用
在 Relay 中,Server 3D(即 3D 组件所需数据全部由 GraphQL 服务端解析)是默认自动启用的,无需对 Relay Compiler 配置做任何改动:
Server 3D is automatically enabled without any changes to your configuration.这意味着只要你的 GraphQL 片段中使用了@match/@module指令,Relay Compiler 就会在编译产物中生成对应的模块加载逻辑,运行时按需下载匹配到的那一个具体类型的组件与数据。与 Server 3D 相对的是Client 3D——所有渲染数据由客户端侧的 Relay Resolvers 解析——这类场景必须显式地在 Relay Compiler 配置文件中新增一个moduleImportConfig字段才能开启。两者更完整的背景可参见 3D 系列引言。
二、moduleImportConfig字段总览
在 OSS(开源)项目中,开启 Client 3D 的方式是在 relay compiler 的配置文件中追加moduleImportConfig。该字段在 Relay 19 的编译配置中实际上包含三个子字段(两个常用 + 一个内部扩展):
| 子字段 | 说明 | 是否 OSS 必填 |
|---|---|---|
dynamicModuleProvider | 定义 3D 组件在运行时如何被动态导入 | 必填 |
surface | 定义启用 Client 3D 的"作用面"(surface) | 必填 |
operationModuleProvider | 定义生成在NormalizationModuleImport节点上的导入语句,用于 exec-time Client 3D | 一般省略 |
这一结构在仓库的 Rust 配置类型中有精确对应。在 compiler/crates/relay-config/src/module_import_config.rs 中:
pub struct ModuleImportConfig { /// Defines the custom import statement to be generated on the /// `ModuleImport` node in ASTs, used for dynamically loading /// components at runtime. pub dynamic_module_provider: Option<ModuleProvider>, /// Defines the custom import statement to be generated for the /// `operationModuleProvider` function on the `NormalizationModuleImport` /// node in ASTs. Used in exec time client 3D. pub operation_module_provider: Option<ModuleProvider>, /// Defines the surface upon which @module is enabled. pub surface: Option<Surface>, }同时,relay-compiler-config-schema.json 给出了该字段的 JSON Schema 约束(additionalProperties: false,即不认识的子字段会被直接拒绝):
"moduleImportConfig": { "description": "Configuration for @module.", "type": "object", "properties": { "dynamicModuleProvider": { "description": "Defines the custom import statement to be generated on the\n`ModuleImport` node in ASTs, used for dynamically loading\ncomponents at runtime." }, "operationModuleProvider": { "description": "Defines the custom import statement to be generated for the\n`operationModuleProvider` function on the `NormalizationModuleImport`\nnode in ASTs. Used in exec time client 3D." }, "surface": { "description": "Defines the surface upon which @module is enabled." } }, "additionalProperties": false }需要注意:moduleImportConfig在整个配置中的默认值为{ "dynamicModuleProvider": null, "operationModuleProvider": null, "surface": null }(见同一 schema 文件第 126–130 行),因此不写这个字段时,行为等价于未启用 Client 3D——这与"Server 3D 自动开启"的设计是一致的。
三、dynamicModuleProvider:3D 组件如何被导入
dynamicModuleProvider决定 Relay Compiler 为 3D 组件生成怎样的动态导入语句。它由mode与statement两个子字段构成,其中mode有两种取值:
3.1 模式一:JSResource(Meta 内部使用)
将mode设为JSResource,表示告知 Relay Compiler:3D 组件应以JSResource(一种可动态加载的 JavaScript 模块)的方式导入。此时可以完全省略statement子字段:
"moduleImportConfig": { "dynamicModuleProvider": { "mode": "JSResource" } }从 Rust 类型定义可以看到,ModuleProvider是一个以mode为标签(tag)的枚举,JSResource分支不携带任何额外数据(module_import_config.rs):
#[serde(tag = "mode")] pub enum ModuleProvider { /// Generates a module provider using JSResource JSResource, /// Generates a custom JS import, Use `<$module>` as the placeholder /// for the actual module. e.g. `"() => import('<$module>')"` Custom { statement: StringKey }, }3.2 模式二:Custom(OSS 推荐)
将mode设为Custom,表示你希望编写自定义的 import 语句来加载 3D 组件。此时必须填写statement子字段,并在语句中使用占位符<$module>代替实际的 3D 组件名。编译时 Relay Compiler 会把<$module>替换成@module(name: "...")指令中指定的组件名。
"moduleImportConfig": { "dynamicModuleProvider": { "mode": "Custom", "statement": "function() { var JSResource = require('JSResource'); return JSResource('m#<$module>'); }" } }上面是 Meta 内部常用的写法(经JSResource('m#<模块名>')动态加载)。而在 OSS 项目中,statement可以是你需要的任何导入方式,例如基于 CommonJS 的相对路径 require:
"moduleImportConfig": { "dynamicModuleProvider": { "mode": "Custom", "statement": "() => require('./.<$module>')" }, "surface": "resolvers" }仓库的编译测试夹具中也有同款配置(query-with-module-directive-custom-import.graphql):
%project_config% { "moduleImportConfig": { "dynamicModuleProvider": { "mode": "Custom", "statement": "() => import('<$module>')" } }, "language": "flow" }对应的 JSResource 模式夹具见 query-with-module-directive-jsresource-import.graphql,两个夹具使用完全相同的@module(name: "MarkdownUserNameRenderer.react")片段,仅靠moduleImportConfig的差异来验证两种模式下生成的导入代码不同——这直观说明:dynamicModuleProvider只影响"如何导入",不影响 3D 的语义。
四、surface:Client 3D 的作用范围
surface用于声明 Client 3D 在哪些场景下生效。在 Meta 内部存在三种取值,而在 OSS 中只有一种正确用法。
4.1resolvers(OSS 唯一选项)
resolvers表示 Client 3D 仅对完全由客户端 Relay Resolvers 决定的字段生效,可视为最"纯正"的 Client 3D 用法:
"moduleImportConfig": { "dynamicModuleProvider": { "mode": "JSResource", }, "surface": "resolvers" }在 OSS 中,surface应固定设置为resolvers。这与 Relay Resolvers 在开源生态中的定位一致:OSS 项目使用 Client 3D 的前提,正是数据来自客户端 resolver。仓库的集成测试夹具 client-3D-resolvers-enabled-server-3D-fragment.graphql 展示了一个同时使用@match与surface: "resolvers"的混合用例:
%project_config% { "moduleImportConfig": { "dynamicModuleProvider": { "mode": "JSResource" }, "surface": "resolvers" }, "language": "flow" }4.2None(省略该字段,Meta 内部场景)
不写surface字段即表示该值。这是 xplat 中大多数项目的现状,属于Server 3D 的一种特殊变体:3D 组件的模块信息由 Relay Compiler 保存在客户端一侧,而不再随数据从服务端下发。需要特别说明的是,这种变体相对 Server 3D 没有任何性能收益,它诞生的初衷是为了支持 xplat 与 WWW 之间的代码共享。本文前述两个dynamicModuleProvider示例(JSResource 与 Custom)在不配置surface时,都属于这一None场景。
4.3all(两者兼用)
all表示resolvers与None两种场景同时启用:
"moduleImportConfig": { "dynamicModuleProvider": { "mode": "JSResource", }, "surface": "all" }从 JSON Schema 的Surface定义可以看到,合法取值实际上只有"resolvers"与"all"两个(relay-compiler-config-schema.json):
"Surface": { "type": "string", "enum": [ "resolvers", "all" ] }对应的 Rust 枚举(module_import_config.rs)同样只有Resolvers与All两个变体,"None" 在类型系统中正是surface: None的 Rust 表达。
五、一份可落地的 OSS 完整配置
将以上内容汇总,一份开启 Client 3D 的 OSS Relay Compiler 配置如下(以 JSON 形式追加到你的 compiler 配置文件中的项目级配置里):
"moduleImportConfig": { "dynamicModuleProvider": { "mode": "Custom", "statement": "() => require('./.<$module>')" }, "surface": "resolvers" }要点回顾:
mode固定为"Custom",statement按你的模块系统自由定制,占位符<$module>会被编译期替换为真实组件名;surface固定为"resolvers";- 若你的项目使用 ES Module,可将
statement改为"() => import('<$module>')"(仓库测试夹具即采用此写法)。
关于配置文件本身的写法,可以参考仓库自带的示例 compiler/test-project/relay.config.json,其中演示了root、sources、projects等外层结构的组织方式,moduleImportConfig即作为projects.<name>下的一个属性存在。配置完成后,通过 Relay Compiler 重新生成产物,即可在客户端使用@module+MatchContainer渲染由 Relay Resolvers 驱动的 3D 组件,具体组件侧写法参见 Client 3D 使用指南。
六、源码级验证:配置如何驱动编译行为
moduleImportConfig并非摆设,它直接参与 Relay 编译流水线。从测试代码可见其调用关系:compiler/crates/relay-transforms/tests/generate_data_driven_dependency_metadata.rs 将ModuleImportConfig { dynamic_module_provider: JSResource, surface: Resolvers }传入transform_match转换,再交给generate_data_driven_dependency_metadata生成 3D 元数据:
let module_import_config = ModuleImportConfig { dynamic_module_provider: Some(ModuleProvider::JSResource), operation_module_provider: None, surface: Some(Surface::Resolvers), }; apply_transform_for_test(fixture, |program| { let flags = FeatureFlags::default(); let program = transform_match(program, &flags, module_import_config, Default::default())?; let program = generate_data_driven_dependency_metadata(&program); Ok(program) })从源码结构看,transform_match(compiler/crates/relay-transforms/src/match_/match_transform.rs)会在编译期处理@match/@module指令并生成ModuleImportAST 节点,而dynamicModuleProvider中mode的不同取值(JSResource/Custom)会决定该节点上importStatement的最终文本形态;surface则控制该转换在哪些场景(resolver 驱动 / 普通 Server 3D / 两者兼有)被应用。配置与实现一一对应,这也解释了为什么错误配置(例如statement缺少<$module>占位符、或mode拼写错误)会在编译阶段被 schema 校验拦截。
七、配置注意事项小结
- Server 3D 无需配置:只要不写
moduleImportConfig,@match/@module依旧按 Server 3D 工作; - Client 3D 必须配置:缺
dynamicModuleProvider或surface都不会启用 resolver 驱动的 3D; <$module>占位符不可省略:Custom模式的statement中若没有该占位符,编译器无法把语句绑定到具体组件名;- 区分
surface语义:OSS 使用resolvers;all与省略字段(None 场景)主要用于 Meta 内部 xplat 的代码共享诉求,OSS 项目中按需选用即可; - schema 兜底:
additionalProperties: false与严格的枚举约束意味着配置错误会在编译期尽早暴露,而不是留到运行时。
至此,你已完整掌握 Relay 19 中 3D 功能的配置面:既清楚 Server 3D 的零配置默认行为,也明白 Client 3D 的moduleImportConfig从字段到源码实现的完整链路。
- 前端
- 开发工具
【免费下载链接】relay
Relay is a JavaScript framework for building>项目地址:https://gitcode.com/gh_mirrors/relay29/relay
相关推荐
Apache DolphinScheduler StarRocks 数据源接入指南:表单配置、驱动依赖与 JDBC 安全参数源码解析
Apache DolphinScheduler StarRocks 数据源接入指南:表单配置、驱动依赖与 JDBC 安全参数源码解析 StarRocks 是当前
任务调度数据编排工作流自动化后端大数据Relay 19 `useQueryLoader` 完全指南:以事件驱动的方式预取查询数据
Relay 19 useQueryLoader 完全指南:以事件驱动的方式预取查询数据 useQueryLoader 是 React Relay 中用于实现 r
前端开发工具ESP-IDF 构建系统 v2 组件依赖配置完全指南:从 REQUIRES/PRIV_REQUIRES 到配置驱动依赖
ESP IDF 构建系统 v2 组件依赖配置完全指南:从 REQUIRES/PRIV_REQUIRES 到配置驱动依赖 本指南以 docs/en/api gui
物联网嵌入式