资讯动态

深入解析 @graphql-tools/import:用 `import` 语法模块化拆分与聚合 GraphQL Schema

发布时间:2026/10/13 3:10:31 来源:尧图企业网站定制
后端API设计【免费下载链接】graphql-tools:wrench: Utility library for GraphQL to build, stitch and mock GraphQL schemas in the SDL-first approach项目地址https://gitcode.com/gh_mirrors/gr/graphql-tools点击查看免费下载graphql-tools/import是 graphql-tools 生态中负责以文件为单位组织 GraphQL SDL的核心工具包它解析.graphql/.gql文件顶部的#import指令递归加载被引用文件并把依赖类型、指令、fragment 以及它们之间的传递依赖合并成一个完整的 GraphQL 文档。读完本文你将掌握processImport的完整 API 用法、全部#import语法形式、tsconfig 风格路径别名配置、require:前缀解析、Apollo Federation 兼容处理以及底层依赖图构建与 tree-shaking 的实现原理能直接在大中型项目与 monorepo 中用这套机制替代手工拼接 SDL。一、包概览与安装graphql-tools/import的当前版本为 7.2.2见 package.json它不负责合并多个独立 schema 文件再对齐冲突而是把一个入口文件连同其声明的所有依赖递归展开成单个文档因此常作为 schema 构建流水线的第一环。从该包的 package.json 可以确认以下适用前提Node.js 要求 16.0.07.0.0 起移除了 Node 14 支持graphql peer 依赖支持^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0运行时依赖仅graphql-tools/utils、resolve-from与tslib且sideEffects: false可以安全地被 tree-shaking 工具裁掉未用分支模块格式同时提供 CommonJSdist/cjs与 ESMdist/esm出口TypeScript 类型随包发布。安装方式与其他 npm 包一致npm install graphql-tools/import # 或 yarn add graphql-tools/import二、#import语法速查graphql-tools/import采用注释即指令的设计任何以# import或#import开头的行行内允许#与import之间带空格见 7.2.0 变更都会被识别为导入语句而不是普通注释。核心解析逻辑集中在 parseImportLine 与两个正则IMPORT_FROM_REGEX/IMPORT_DEFAULT_REGEX见 源码 L64-L65。支持的全部语法形式如下语法含义示例# import [Type] from [File]导入单个类型# import User from ./user.graphql# import [Type].[Field] from [File]导入类型的单个字段# import Query.posts from ./queries.graphql# import [Type], [Type] from [File]一次性导入多个类型# import User, Post from ./models.graphql# import * from [File]导入文件中的全部定义# import * from ./all.graphql# import [File]默认导入等价于*# import ./schema.graphql# import A from require:./a.graphql经 Noderequire.resolve解析路径7.2.0 起见下文第五节细节规则引号与分号单引号、双引号均可行尾可选分号测试parseImportLine: optional semicolon验证了这一点逗号与空格多导入支持任意空格排列如# import A ,B from schema.graphql模块名from后可以是node_modules中的包名解析时先按相对路径找找不到再回退到resolve-from的模块解析源码 resolveFilePath扩展名.graphql、.gql、.graphqls均可块字符串保护extractImportLines会跟踪块字符串与普通字符串状态块字符串内部的# import ...不会被误当作导入对应测试extractImportLines: ignores # import inside block strings见 import-schema.spec.ts同时#注释行中出现的也不会干扰解析状态机文件内位置7.2.0 起#import不再要求必须是文件的第一行非空内容出现在任意位置都会被处理。非法格式会抛出带格式说明的错误源码中给出了受支持的四种形式作为错误提示文案L931-L940Import statement is not valid: ... Supported forms: # import [Type].[Field] from [File] # import [Type], [Type] from [File] # import * from [File] # import [File]三、核心 APIprocessImport包的全部能力都汇聚在 processImport 这一个函数上签名如下export function processImport( filePath: string, cwd?: string, // 默认取 globalThis.process?.cwd() predefinedImports?: Recordstring, string, visitedFiles?: VisitedFilesMap, // 默认 new Map() pathAliases?: PathAliases, ): DocumentNode3.1 基本用法最简调用只需传入入口文件路径返回合并后的DocumentNodeimport { processImport } from graphql-tools/import; import { print } from graphql; const document processImport(./src/schema/index.graphql); console.log(print(document));例如入口文件# import User from ./user.graphql # import Query.posts from ./queries.graphql type Query { me: User }processImport会返回同时包含User定义、posts字段及其依赖、以及本地Query定义的完整文档随后可以直接交给graphql-tools/schema的makeExecutableSchema或graphql-tools/load使用。3.2cwd基准目录第二个参数作为相对路径解析的基准目录测试中统一传入__dirname。源码中入口文件会被规范化为join(cwd /root.graphql)后再做相对解析因此传入脚本所在目录是推荐做法。3.3predefinedImports内存中的虚拟文件当某些 schema 不是来自磁盘文件、而是以字符串形式存在于内存时可以把它放进predefinedImports映射键为 import 语句中的名字。这在测试global schema modules见 import-schema.spec.ts L971-L989中得到验证# import Shared from shared配合{ shared: type Shared { first: String } }即可生效。源码中该映射的优先级最高——命中predefinedImports的文件既不会走路径别名也不会读磁盘L198-L207测试predefined imports take precedence over path aliases也确认了这一优先级顺序。3.4visitedFiles追踪访问过的文件第四个参数接收一个VisitedFilesMap类型为Mapstring, Mapstring, SetDefinitionNode键是解析后的绝对路径用于两件事循环依赖防护文件在递归处理其自身 imports 之前就会被写入visitedFiles从而打破 A→B→A 的无限循环源码注释与测试circular imports均有说明调试与去重调用方可以事后拿到每个文件贡献了哪些定义。文档导入测试should accept a map as fourth argument见 import-documents.spec.ts演示了如何遍历该 map 获得所有被访问文件的相对路径列表。import { processImport } from graphql-tools/import; const visitedFiles new Map(); processImport(./schema/index.graphql, __dirname, undefined, visitedFiles); for (const filePath of visitedFiles.keys()) { console.log(filePath); // 所有被递归访问的 .graphql 文件绝对路径 }3.5 返回值与去重processImport在合并阶段会把每个DefinitionNode用print序列化后放入Set去重因此同一类型被多个文件引用时只会出现一份定义对应测试imports missing named imports for file imported multiple time without duplicates。7.1.18 的性能优化正是围绕这一步展开详见第九节。四、依赖图传递依赖、tree-shaking 与内置类型processImport不是简单的文件拼接它构建的是定义名 → 依赖集的依赖图。核心数据结构在源码 extractDependencies 与 visitDefinition 中建立关键行为如下。4.1 传递关闭transitive closure导入某个类型时该类型引用到的所有其他类型字段返回类型、implements接口、union 成员、参数类型、指令参数会被递归一并拉入且 7.2.0 起这条规则被真正闭合正向依赖字段、implements、union 成员会被传递关闭反向实现者implements某接口的类型只在接口本身被导入时才附加——因此# import Query.posts不会把Query的其他字段全部拉进来见 CHANGELOG 7.2.0 的详细说明。典型修复场景7.2.0是types.graphql中type Foo { pet: Pet }、union Pet Cat | Dog、type Cat implements Animal此前只导入Foo会得到非法 schemaUnknown type Animal因为Cat/Dog经 union 被带入而Animal被遗漏新逻辑会一并补上接口依赖。4.2 内置类型与指令豁免以下名称不会被视为待导入依赖因此无需额外导入源码 L46-L62内置标量String、Float、Int、Boolean、ID、Upload内置指令deprecated、skip、include、cacheControl、connection、client、specifiedBy、oneOf以及 federation v1 的key、provides、requires、external。其中oneOf是 7.1.7 加入内置指令列表的specifiedBy则在 6.2.4 加入。4.3 通配符导入的 tree-shaking# import *并不会盲目拷贝目标文件全部内容仍会执行 tree-shaking仅保留被引用的定义。唯一的例外7.2.0是——若某个未被引用的类型带有 federationkey指令即子图实体它会被保留因为实体即使当前 schema 未引用也是合法的不带key的未引用类型依然会被剪除。对应 fixture 见 import-star-federation-key/a.graphql 与 entities.graphql其中UnusedWithoutKey被移除而PilatesInstructor key(...)被保留。4.4 本地优先与字段级导入命名冲突本地定义优先于导入定义测试import with collision本地User覆盖导入版本字段级导入Type.field语法支持按字段粒度导入并在合并后重新生成所属类型的字段导出源码addDefinition中对definition.fields的处理L263-L310。对应测试覆盖了# import Query.posts这类场景。五、require:前缀借助 Node 模块解析加载类型7.2.0 引入的require:前缀让 GraphQL import 复用 Node.js 的require.resolve语义。用法源码 L199-L201# import Post from require:blog-graphql-types/schema.graphql # import A from require:./a.graphql解析时使用createRequire(cwd).resolve(...)因此require:./a.graphql以导入文件所在位置而非进程 cwd为基准解析相对路径require:blog-graphql-types/schema.graphql可以解析 node_modules 中包的子路径文件这非常适合把GraphQL 类型定义随 npm 包分发的 monorepo 场景。仓库内的 fixture require-paths/b.graphql 就是# import A from require:./a.graphql的标准用例对应测试见 import-schema.spec.ts。六、路径别名把 tsconfig 的paths语法带到 GraphQL7.1.0 起支持用 TypeScript 开发者熟悉的tsconfig.json#paths风格来消除脆弱的多级相对路径7.1.1 又补上了绝对路径别名如/*→src/*与错误定位增强。6.1 配置结构import { processImport, type PathAliases } from graphql-tools/import; const pathAliases: PathAliases { // 可选相对 mapping 的解析基准目录默认是 cwd rootDir: /project/src/graphql, mappings: { models/*: /project/graphql/models/*, // 通配符展开 types/*: /project/graphql/types, // 通配符但指向目录 schema: /project/schema/main.graphql, // 精确映射 common: ./common/types.graphql, // 相对路径相对 rootDir 解析 }, }; const document processImport(./schema/index.graphql, __dirname, {}, new Map(), pathAliases);对应 schema 文件内即可写#import User from models/User.graphql #import Product from types/Product.graphql6.2 支持的映射模式源码 applyPathAlias 按 tsconfig 规则实现精确映射b-schema: /path/to/b.graphql仅当 import 路径与键完全相等时生效通配符展开键以*结尾时*替换为 import 路径中前缀之后的剩余部分models/*models/user.graphql→.../models/user.graphql通配符不展开键以*结尾而 mapping 不以*结尾时所有匹配请求都指向同一文件/目录绝对路径别名键可为/*7.1.1用于模拟 tsconfig 中/*: [src/*]的写法。测试覆盖了精确映射exact/a.graphql、通配符多级展开multiple-levels、相对 rootDir 解析resolves mapped paths relative to root dir、混合别名与相对导入mixed-use、循环导入 别名circular、绝对别名absolute以及 Node 风格#*子路径别名node-subpath等场景全部位于 import-schema.spec.ts 与 fixtures/path-aliases 目录下可作为配置参考。6.3 与 predefinedImports 的优先级路径别名只作用于需要读盘的导入若同一名字同时出现在predefinedImports中以内存内容为准测试predefined imports take precedence over path aliases验证了这一点。七、Apollo Federation 支持graphql-tools/import对 federation 有完整的兼容处理这对用子图组织的大型项目至关重要。7.1 Federation v1 指令key、provides、requires、external被内置豁免无需显式导入指令定义且key类型在通配符导入下会被保留见 4.3。测试importSchema: key directive/external directive/requires directive验证了extend typeexternal等组合的合并结果。7.2 Federation v2link与extend schema7.0.19 起支持extend schema语法并把link加入内置指令清单federated schema 不再需要手写directive link ...定义7.0.20 起会把 federated links 纳入依赖与定义逻辑。源码 importFederatedSchemaLinks 会从extend schema link(...)中提取联邦导入并预注册合法名称包括federation v2.0extends、external、inaccessible、key、override、provides、requires、shareable、tag、FieldSetv2.1composeDirectivev2.3interfaceObjectv2.5authenticated、requiresScopesv2.6policyv2.8context、fromContextv2.9cost、listSizev2.10connect、source。即 7.1.3 记载的federation v2.10 特性导入支持落到源码中的完整清单。测试importSchema: link directive与has context for which federated directives are repeatableimport-schema.spec.ts L530-L560覆盖了link(url: https://specs.apollo.dev/federation/v2.6, import: [key])这类写法的合并输出。需要说明的是源码注释明确指出该版本清单并非严格按版本号一一对应部分指令实际出现于 v2.0 之后import 阶段只是不阻塞真正的版本校验交由 federation 组合器完成。八、导入文档fragment 与注释保留除 schema 外processImport同样适用于 GraphQL 操作文档query / fragment对应测试见 import-documents.spec.ts。典型用法# import b.graphql # 默认导入等价于 * # import FooFragment from c.graphql query FooQuery { foo { ...FooFragment } }嵌套 fragmentfragment 中 spread 的其它 fragment 会被传递解析6.6.0 起支持嵌套 fragment 导入多层嵌套同样成立注释保留7.2.0合并文档时不再丢失定义前的 GraphQL 注释。实现上processImport对每个定义同时缓存纯净版与带注释版两种打印结果源码 printDefinitionWithLeadingComments使用graphql-tools/utils的getLeadingCommentBlock去重比较用纯定义、最终输出用带注释版。典型收益是# eslint-disable-next-line这类工具注释能原样保留fixture with-comments.graphql 即演示了该场景。九、错误定位与性能优化9.1 错误信息带源码位置7.1.1 起找不到依赖类型时报错会携带精确的源码位置。典型报错测试断言Couldnt find type Post in any of the schemas. /path/to/fixtures/type-not-found/a.graphql:2:9 1 | type Dummy { 2 | test: Post | ^ 3 | }实现上extractDependencies对每个依赖名都保存了引用它的NameNode集合DependencySet源码 L368-L390报错时把节点传入createGraphQLError(..., { nodes })生成带位置信息的GraphQLError源码 L330-L338。缺失类型出现在 type 字段、interface、input、union、directive、多次引用等情形均有对应测试type-not-found 系列测试。另一类错误是导入的名字不存在例如# import NotExported from ./a.graphql会抛出${name} is not exported by ${from} imported by ${filePath}源码 L748-L751。9.2 大 schema 的性能优化CHANGELOG 中记录了两次针对依赖图遍历的纯性能优化输出保持逐字节一致7.1.18按节点身份对print做 memoization。此前去重阶段会对同一节点反复打印任一被广泛引用的类型会出现在所有依赖它的定义集合里复杂度接近 O(n²)memoize 后每个唯一节点最多打印一次。CHANGELOG 记录在一个约 1.2 万行、跨三个项目的 schema 上下游 codegen 阶段从约 133s 降到约 17s7.1.19移除addDefinition依赖遍历中的冗余工作——把visitedFiles.get(filePath)提升出递归热点、把字段级依赖名推导按字段身份缓存源码中对应hoistedFileDefinitionMap与fieldDependencyCacheL229-L236。在上述基础上同一 codegen 过程又从约 17s 降到约 13s。源码注释同时坦言剩余的 O(n²) 开销来自闭包遍历本身每个定义的传递闭包仍被独立重建进一步优化留待后续。以上数值均为 CHANGELOG 中记载的特定基准实际收益需结合具体 schema 的规模与连接密度评估。十、在 GraphQL Tools 生态中的衔接graphql-tools/import通常不作为终点使用与graphql-tools/load配合loadSchema/loadTypedefs在加载阶段即可处理 import 表达式该包的 README.md 指向了 schema-loading 章节的 Using Import Expression 用法与graphql-tools/merge配合processImport的输出DocumentNode可直接作为mergeTypeDefs的输入测试文件中的importSchema辅助函数正是这一组合见 import-schema.spec.ts L13-L28与graphql-tools/schema配合合并后的文档交给makeExecutableSchema生成可执行 schema。小结graphql-tools/import用一套简洁的注释语法解决了 SDL 模块化的核心痛点递归导入、传递依赖闭合、tree-shaking、循环防护、路径别名、federation 兼容与文档级 fragment 合并一应俱全。在 monorepo 或多团队协作的项目中配合require:前缀与PathAliases可以把 GraphQL 类型定义像普通 npm 包一样分发与引用同时保持入口 schema 的简洁与可维护性。若需深入源码建议从 packages/import/src/index.ts 的processImport→visitFile→processImports→addDefinition这条调用链读起并对照 import-schema.spec.ts 与 fixtures 目录下的真实 fixture 验证行为。赞分享后端API设计【免费下载链接】graphql-tools:wrench: Utility library for GraphQL to build, stitch and mock GraphQL schemas in the SDL-first approach项目地址https://gitcode.com/gh_mirrors/gr/graphql-tools点击查看免费下载相关推荐graphql-import 使用指南用 import 指令拆分与组装 GraphQL SDL 模块graphql import 使用指南用 import 指令拆分与组装 GraphQL SDL 模块 本文以 Prisma 1.x 文档中的 GraphQL后端数据库GraphQLPrisma 项目中的 graphql-import在 GraphQL SDL 中实现 Schema 模块化导入与合并Prisma 项目中的 graphql import在 GraphQL SDL 中实现 Schema 模块化导入与合并 导读 在 Prisma 生态中一个服后端数据库GraphQLgraphql-import在 Prisma 生态中用 SDL 模块化组织 GraphQL Schemagraphql import在 Prisma 生态中用 SDL 模块化组织 GraphQL Schema graphql import 是一个以 GraphQ后端数据库GraphQL上一篇如何把GR00T-WholeBodyControl移植到全新人形本体以Unitree H2为例的分步教程与文件清单下一篇狗头军师的135条参考资料如何分级MBTI和网络话术为什么不是命运判词创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑