Skip to content

[扩展] 不确定的动态依赖

场景

我们知道,下面情况下,webpack 是会将 a 模块打包到最终结果的。

js
if (Math.random() < 0.5) {
  require("./a");
}

但是,如果是这种情况呢?具体的文件名根据不同的 dom 元素的 value 决定的,这样的打包情况就成为不确定的动态依赖了。

  • 目录结构如下:
sh
utils
    --a.js
    --b.js
index.js
  • 代码
js
//webpack不确定到底用户使用哪个模块,所以会将utils下面所有的文件都打包进打包结果中
const module = document.getElementById("txt").value;

if (Math.random() < 0.5) {
  require("./utils/" + module);
}
  • 打包结果:
js
"./src/utils/a.js":xxx
"./src/utils/b.js":xxx
"./src/index.js":xxx

而上面 webpack 在执行 require("./utils/" + module);的时候,是自动使用了一个函数---require.context(),而我们要介绍的就是这个函数。

require.context()

我们现在看下 require.context() 的用法。 //index.js

js
// 仅在webpack运行过程中有效
// 参数1:目录,哪个目录中的模块需要添加到打包结果
// 参数2:是否递归寻找子目录,如果为true,表示需要寻找子目录
// 参数3:正则表达式,凡是匹配的才会加入到打包结果
const context = require.context("./utils", true, /\.js$/);

此时,我们执行npx webpack分析编译结果: 编译结果:

js
//我们发现上面的代码被webpack转换为这样的了,实际上就是转换为__webpack_require__函数。
__webpack_require__("./src/utils sync recursive \\.js$");
//上面__webpack_require__导入了一个模块,我们看编译结果下这个模块具体样子:
"./src/utils sync recursive":function(module, exports, __webpack_require__){
  //webpack在这里可能导入的用法全部整合在一个对象中了{"key","文件指纹"}
var map = {
	"./a.js": "./src/utils/a.js",
	"./b.js": "./src/utils/b.js"
};
}
//根据这句代码可知,./src/utils sync recursive这个模块最后返回了一个函数(编译结果中webpackContext就是个函数),那就意味着require.context返回结果是一个函数
module.exports = webpackContext;

接下来我们看一下,返回的这个函数的用法:

js
//context("对应的上面map对象的key"),会把那个文件内容输出
const context = require.context("./utils", true, /\.js$/);
console.log(context("./a.js")); //输出"我是a模块"

我们再认识一个方法:

js
//将打包的哪些模块以数组的形式列举出来
console.log(context.keys()); //["./a.js","./b.js"]

灵活使用

以上我们知道了 webpack 是怎么打包不确定依赖的,也了解了 require.context()函数的用法。 那我们一般什么时候用得上呢?

  • 需求: 我们假设 utils 目录现在做了一个完整的功能,我们想将这个模块给合并到一个文件中,整体导出。
sh
utils
    --a.js
    --b.js
    --index.js 负责整合整个utils的模块

按照我们所学的知识,可能会在 utils/index.js 中这样

js
exports.a = require("./a");
exports.b = require("./b");
//这样是一个方法,但是有一个弊端:
1.如果某天`utils/a.js`名字更改为`/c.js`,我们就得手动修改
2.如果添加`utils/d.js`文件,我们就得手动在这个文件中添加`exports.c = require("./c");`
我们需要的是一个自动化的方案

解决 在 utils/index.js 中写一段整合整个 utils 的逻辑

js
const context = require.context("./", true, /\.js$/);
for (const key of context.keys()) {
  console.log(key); //./a.js ./b.js ./index.js
  if (key !== "./index.js") {
    // 截取出文件名,用做导出的模块名
    let filename = key.substr(2, 1);
    exports[filename] = context(key); //导出模块,模块名和模块内容
  }
}

在入口文件 index.js 中引入

js
const utils = require("./utils");
console.log("utils", utils);
//utils最终运行结果: { a: '我是a.js', b: '我是b.js' }

小结

我们以后可能会学到 vue-router 和 vuex,他们虽然是将模块拆开写的,但是整体都是一部分,知识拆分成模块方便维护。 这个知识有点难,属于扩展课程,了解即可。

MIT License