前言
webpack 给我们提供了一个批量导入函数 require.context()
,该函数签名如下:
require.context(directory, useSubdirectories, regExp)
- directory: 批量导入目标文件夹路径
- useSubdirectories: 是否遍历子目录
- regExp: 匹配需要导入文件名时的正则
例:
// 导入当前目录 resources 文件夹内的所有 .js 文件(不包括子目录)
require.context("./resources", false, /\.js$/);
原理
要实现万能导入,需要先了解 require.context()
的返回值,他将返回一个函数:
该函数传入一个文件的 path 后(函数里形参叫 req
),将把 path 传入 webpackContextResolve()
函数,之后执行 __webpack_require__
导入模块。
那 webpackContextResolve()
是什么原理?
我们发现 webpackContextResolve()
只是去 map
这个对象中取了属性 path 对应的值,并且保证他一定可以导入(如果不能导入会抛出 Error
)
那 map
这个对象是什么?
我们看一下完整代码:
var map = {
"./a.js": "./src/require/resources/a.js",
"./b.js": "./src/require/resources/b.js",
"./c.js": "./src/require/resources/c.js"
};
function webpackContext(req) {
var id = webpackContextResolve(req);
return __webpack_require__(id);
}
function webpackContextResolve(req) {
if(!__webpack_require__.o(map, req)) {
var e = new Error("Cannot find module '" + req + "'");
e.code = 'MODULE_NOT_FOUND';
throw e;
}
return map[req];
}
webpackContext.keys = function webpackContextKeys() {
return Object.keys(map);
};
webpackContext.resolve = webpackContextResolve;
module.exports = webpackContext;
webpackContext.id = "./src/require/resources sync \\.js$";
到此为止,我们终于知道了 map
原来是可以取得相对项目根目录文件路径的一个对象。
回过头来,我们重点只需关注如何传入 path ,因为只要给 require.context
传入了 path ,他就会去执行导入模块。
使用
在上文源代码中,我们发现 require.context
的返回函数 webpackContext
有一个 keys
方法,它可以给我们提供所有的 path !
于是,得到我们的万能批量导入方法:
const req = require.context("./resources", false, /\.js$/)
req.keys().map(path => {
// 兼容 cjs 和 esm 模块导入
req(path).default || req(path)
})
到此为止,./resources
文件夹内的所有 .js
文件均已被自动批量导入。
如果要操作此处导入的文件内容,可以在 req(path)
返回值处操作:
const req = require.context("./resources", false, /\.js$/);
req.keys().map(path => {
const context = req(path).default || req(path);
// 此处 context 即为每个 .js 文件的导出内容
console.log(context);
});
结果:
注:如果没有兼容 esm 和 cjs ,你得到的只会是不均匀的结果
总结
require.context
可以被用在各种需要大量 import 或者 require 的地方,比如导入一大批 .svg
文件,亦或是导入大量分模块的路由表,Vuex module 等等(此时将 req()
返回值组装进相应的对象即可)。
做一个不再手写 import 的打工人。