使用babel-plugin-require-context-hook搭配Expo时出现Metro错误:__requireContext is not defined
我来帮你拆解这个问题,你遇到的情况其实是典型的「为测试环境做的配置,不小心影响了生产/开发环境的打包流程」,咱们一步步解决:
问题根源分析
你为了让Jest支持require.context(),在babel.config.js里全局启用了require-context-hook插件,同时在代码里加了条件注册逻辑。但这里有两个核心矛盾:
- Metro不认识插件注入的
__requireContext:当Expo用Metro打包时,Babel会把所有require.context()调用转换成__requireContext,但Metro运行环境里没有这个函数的定义(你的注册代码只在test环境执行,Metro的NODE_ENV是development或production,所以注册逻辑没跑)。 - Metro本身不原生支持
require.context():即使你去掉Babel插件,直接在Expo代码里用require.context(),Metro也会报错说这个方法不存在。
解决方案(按推荐优先级排序)
1. 拆分Babel配置,仅在测试环境启用插件
最稳妥的方式是让require-context-hook插件只对Jest生效,Metro打包时完全不碰它。修改你的babel.config.js:
module.exports = function (api) { api.cache(true); // 检测当前是否为测试环境 const isTest = process.env.NODE_ENV === 'test'; return { presets: ['babel-preset-expo'], plugins: [ [ "@babel/plugin-proposal-optional-chaining-assign", { "version": "2023-07" } ], // 仅在测试环境加载require-context-hook插件 ...(isTest ? ["require-context-hook"] : []) ] }; };
然后保留你代码里的条件注册逻辑:
if (process.env.NODE_ENV === 'test') { require('babel-plugin-require-context-hook/register')(); }
这样Jest测试时会正常处理require.context(),而Metro打包时不会转换这个方法。但如果你的require.context()在Expo运行时也需要执行,Metro会报错require.context is not a function——这时候需要用Metro支持的方式替代。
2. 用Metro原生支持的import.meta.glob替代require.context()
Expo SDK 49+的Metro已经支持import.meta.glob(Vite风格的批量导入),这是官方推荐的动态批量导入方案,完全替代require.context()。
比如你之前用:
const components = require.context('./components', true, /\.jsx$/);
可以替换为兼容测试和Expo环境的代码:
let components; if (process.env.NODE_ENV === 'test') { require('babel-plugin-require-context-hook/register')(); components = require.context('./components', true, /\.jsx$/); } else { // 非测试环境用Metro支持的批量导入 const componentModules = import.meta.glob('./components/**/*.jsx', { eager: true }); // 包装成和require.context()一致的API格式 components = (path) => componentModules[`./components/${path}`].default; components.keys = () => Object.keys(componentModules).map(key => key.replace('./components/', '')); }
这样不管是测试环境还是Expo环境,都能正常工作,无需修改Babel全局配置。
3. 给Metro环境注入__requireContext实现(不推荐)
如果你坚持要在Expo环境用require.context(),可以在Metro环境全局注册__requireContext函数。在你的Expo入口文件(比如App.js最顶部)添加:
if (process.env.NODE_ENV !== 'test') { // 模拟__requireContext的实现,适配你的实际需求 global.__requireContext = (baseDir, recursive, regExp) => { const files = import.meta.glob(`${baseDir}/**/*`, { eager: true }); const matchedKeys = Object.keys(files) .filter(key => regExp.test(key.replace(`${baseDir}/`, ''))) .map(key => key.replace(`${baseDir}/`, '')); const context = (key) => files[`${baseDir}/${key}`].default; context.keys = () => matchedKeys; return context; }; }
这个方案需要你自己维护__requireContext的实现,后续如果require.context()的API变化,你还要同步修改,所以优先级最低。
验证步骤
- 运行
npm test或yarn test,确认Jest测试仍然正常通过 - 运行
expo start,等待Metro打包完成,确认__requireContext is not defined错误消失,Expo app能正常启动
内容来源于stack exchange

