React TS应用按环境变量条件导入JSON文件报错排查
问题根因说明
三类报错的核心原因
Import in body of module; reorder to top import/first:该报错来自ES模块规范的原生限制。ES6的import是编译时静态加载的语法,必须写在模块最顶层,不能嵌入条件判断、变量赋值等逻辑块,也不支持传入变量作为导入路径,因此这类写法从规范层面就不被允许。Cannot use keyword 'await' outside an async function:该报错来自项目编译配置限制。React 18默认的脚手架配置(未手动改造webpack/babel配置的前提下)默认不支持顶层await语法,await关键字只能在被async标记的函数内部使用,直接在模块顶层调用await import()会触发语法解析错误。Cannot find module '../main.json':这是最典型的打包工具静态分析问题。React项目默认基于webpack做依赖收集,webpack在编译阶段只会扫描代码中以字符串常量形式写死的路径,将对应文件加入打包产物。当你把完整路径存入变量再传给require()时,webpack编译阶段无法从变量推断出要引入的具体文件,不会把目标JSON加入打包依赖,运行时自然找不到模块。而你直接写import data from '../main.json'时,路径是明确的字符串常量,webpack能直接定位文件完成打包,因此可以正常运行。
可行实现方案
根据你的场景(无React组件的Context定义文件、TS编译目标ES2018),以下三种方案均可正常运行,按需选择即可:
方案1:静态导入所有可选JSON,条件判断取值(最推荐,零配置)
如果需要切换的JSON文件数量不多,这是最稳妥、无任何兼容问题的方案,完全符合ES模块规范,TS类型也能自动推导:
// src/contexts/world.tsx import mainData from '../main.json'; // 导入其他WORLD环境变量取值对应的JSON文件 import devData from '../dev.json'; const data = process.env.WORLD === 'main' ? mainData : devData; // 后续直接使用data编写Context逻辑即可
优势:不需要修改任何编译配置,打包工具能正常完成静态分析,不会出现路径解析问题,也没有异步加载的时序问题。
方案2:require保留静态路径前缀,仅可变部分使用变量
如果可选JSON文件较多,不想一次性静态导入所有文件,可以利用webpack的上下文模块机制:给require()传入至少一段固定的静态路径前缀,让webpack知道要扫描哪个目录下的对应类型文件,仅把可变的文件名/子路径部分用变量传入:
// src/contexts/world.tsx // 仅把可变的文件名存在变量里,路径前缀写死 const fileName = process.env.WORLD === 'main' ? 'main.json' : 'dev.json'; // 注意相对路径必须是当前文件到目标目录的正确静态路径 const data = require(`../${fileName}`); // 后续直接使用data即可
注意:该写法会让webpack把src目录下所有.json后缀的文件都加入打包依赖,如果目录下JSON文件数量极多,会小幅增加包体积,适合中等数量可选文件的场景。
方案3:封装异步逻辑使用动态import,无需顶层await
如果JSON文件体积大,想做按需加载(仅加载当前环境对应文件,减少主包体积),可以把动态import逻辑封装在async函数里,避开顶层await的限制。
如果你的文件里没有React组件,可以用立即执行函数初始化数据:
// src/contexts/world.tsx import { createContext } from 'react'; // 先给Context设置初始值 export const WorldContext = createContext<any>(null); let worldData: any = null; // 用立即执行async函数包裹动态加载逻辑 (async function initData() { const dataModule = process.env.WORLD === 'main' ? await import('../main.json') : await import('../dev.json'); worldData = dataModule.default; })(); export { worldData };
注意:该写法下worldData初始值为null,文件加载完成后才会被赋值,业务侧使用时需要做好加载态判断,避免拿到空值。
避坑提醒:任何场景下都不要把完整文件路径作为纯变量传给
import或require,打包工具的静态分析机制无法识别纯变量路径,必然会出现运行时找不到模块的问题。
内容的提问来源于stack exchange,提问作者gameveloster
相关产品推荐
相关产品推荐

