Angular项目导入.graphql文件时默认导入返回undefined,仅命名空间导入可正常生效
现象根因拆解
两种导入语法的取值差异,本质是模块类型声明、loader导出规范、ES/CommonJS模块互操作规则三者不匹配导致的
- 默认导入得到
undefined的触发逻辑
项目中配置的graphql webpack loader,在处理.graphql文件时,默认输出的是遵循CommonJS规范的模块结构:直接把编译好的GraphQL DocumentNode对象挂载到模块的顶层exports上,没有额外生成符合ES模块规范的default导出字段。
当使用import someQuery from 'xxx.graphql'的默认导入语法时,TypeScript编译逻辑会按照ES模块的取值规则,去读取目标模块的exports.default属性。由于这个属性不存在,最终绑定到someQuery上的值自然就是undefined。
很多开发场景下出现这个问题,还和不匹配的类型声明有关:不少开发者会照搬通用教程里的export default content形式的模块声明,让TypeScript误以为目标graphql模块存在default导出,但实际运行时loader输出的模块结构和声明不符,进一步放大了取值异常的问题。 - 命名空间导入可以正常取值的触发逻辑
当使用import * as someQuery from 'xxx.graphql'的命名空间导入语法时,TypeScript不会去读取exports.default字段,而是会把目标CommonJS模块的整个exports对象原封不动绑定到someQuery变量上。由于CommonJS下的exports本身就是编译好的DocumentNode对象,自然可以正常获取到查询内容。
可落地的修复方案
- 调整全局类型声明,匹配实际loader输出的模块结构
找到项目里的全局TypeScript声明文件(通常是src目录下的typings.d.ts或者index.d.ts),将graphql文件的模块声明从默认导出形式改为CommonJS导出形式:
declare module '*.graphql' { import { DocumentNode } from 'graphql'; const graphqlContent: DocumentNode; export = graphqlContent; }
声明修改完成后,默认导入语法即可正常取到对应的DocumentNode对象,和官方文档的使用表现对齐。
- 调整loader配置,强制输出ES模块兼容的default结构
找到项目webpack配置中处理graphql文件的loader规则,给graphql-tag的loader增加esModule: true配置项:
// webpack配置片段示例 { test: /\.(graphql|gql)$/, exclude: /node_modules/, loader: 'graphql-tag/loader', options: { esModule: true } }
配置生效后,loader处理完成的graphql模块会自动将DocumentNode对象挂载到exports.default字段,此时默认导入语法可以正常取值,也不会出现类型声明和实际运行结构不匹配的问题。
内容的提问来源于stack exchange,提问作者Kiwi
相关产品推荐
相关产品推荐

