在最近学习webpack的过程中,讲解了个关于webpack打包整合代码的过程,觉得很有意思,然后自己想了一下,分享出来。 比方说,有如下代码:
// src/index.js
console.log("index module");
const a = require("./a.js");
const c = require("./c.js");
console.log("index import a, and a is " + a);
console.log("index import c, and c is " + c);
// src/c.js
console.log("module c");
const a = require("./a.js");
console.log("c import a, and a is " + a);
module.exports = "c";
// src/a.js
console.log("module a");
module.exports = "a";
webpack打包的时候由于浏览器不认识require或import,所以他会将requre的内容和依赖方进行拼接,具体怎么个拼接规则呢?
- 列出
入口文件
的依赖和其依赖模块的依赖(递归执行) - 将被依赖的文件和
入口文件进行整合
比如上面的例子的依赖关系如下
index.js -- [a.js, c.js]
c.js ------ [a.js]
然后webpack打包时会将依赖整合然后形成模块的结合,但是又不能直接拼在一起,所以就有了如下的代码:
const modules = {
"./a.js": function (module, exports) {
console.log("module a");
module.exports = "a";
},
"./c.js": function (module, exports, require) {
console.log("module c");
const a = require("./a.js");
console.log("c import a, and a is " + a);
module.exports = "c";
},
"./index.js": function (module, exports, require) {
console.log("index module");
const a = require("./a.js");
const c = require("./c.js");
console.log("index import a, and a is " + a);
console.log("index import c, and c is " + c);
}
}
如上述的代码所示,在commonjs中,exports=module.exports,所以这里module和exports易得,那么这个require在哪里呢?这里require就需要我们自己去造,怎么造呢?
(function (modules) {
function require(moduleID) {
const func = modules[moduleID];
const module = {
exports: {}
};
func(module, module.exports, require);
return module.exports;
}
require("./index.js");
})(modules)
如上我们就可以将webpack打包整合代码的过程模拟出来,但是注意这里多了一个modules的变量,不是很完美,而且require对于同一模块的加载有缓存,所以需要稍微改动一下。
(function (modules) {
const map = new Map();
function require(moduleID) {
if (map.has(moduleID)) {
return map.get(moduleID);
}
const func = modules[moduleID];
const module = {
exports: {}
};
func(module, module.exports, require);
map.set(moduleID, module.exports);
return module.exports;
}
require("./index.js");
})({
"./a.js": function (module, exports) {
console.log("module a");
module.exports = "a";
},
"./c.js": function (module, exports, require) {
console.log("module c");
const a = require("./a.js");
console.log("c import a, and a is " + a);
module.exports = "c";
},
"./index.js": function (module, exports, require) {
console.log("index module");
const a = require("./a.js");
const c = require("./c.js");
console.log("index import a, and a is " + a);
console.log("index import c, and c is " + c);
}
})
之后我们对比webpack打包整合代码之后的main.js的代码一下呢(去除了webpack打包之后的注释):
(() => {
var __webpack_modules__ = ({
"./src/a.js":module => {
eval("console.log("module a");\r\nmodule.exports = "a";\n\n//# sourceURL=webpack://day-02/./src/a.js?");
},
"./src/c.js": (module, __unused_webpack_exports, __webpack_require__) => {
eval("console.log("module c");\r\nconst a = __webpack_require__(/*! ./a.js */ "./src/a.js");\r\nconsole.log("c import a, and a is " + a);\r\nmodule.exports = "c";\n\n//# sourceURL=webpack://day-02/./src/c.js?");
},
"./src/index.js": (__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
eval("console.log("index module");\r\nconst a = __webpack_require__(/*! ./a.js */ "./src/a.js");\r\nconst c = __webpack_require__(/*! ./c.js */ "./src/c.js");\r\nconsole.log("index import a, and a is " + a);\r\nconsole.log("index import c, and c is " + c);\n\n//# sourceURL=webpack://day-02/./src/index.js?");
}
});
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = __webpack_module_cache__[moduleId] = {
exports: {}
};
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
var __webpack_exports__ = __webpack_require__("./src/index.js");
})();
可以看到webpack打包整合代码之后输出的代码和我们写的差不了多少。
但是可以看到的是,在每一个源文件依赖中是使用eval进行代码执行的,这是为啥呢?而且每一个eval最后都会有代码注释,这又是干啥用的呢?
关于eval这里我需要额外引入一个小知识点:
var a = 1;
var b = 2;
var obj = null;
obj.toString();
毫无意外上面的代码会报错,且报错内容如下:
点进去看的时候会看到源代码所在的位置和代码内容。而//# sourceURL=${src}
,这一段内容虽然不参与JS代码执行,但是却被浏览器识别,当我们在代码中使用eval并且加上上述JS注释时:
var a = 1;
var b = 2;
eval(`var obj = null;
obj.toString();//# sourceURL=./src/a.js`)
加上如上eval时,浏览器报错如下
可以很明显的看到,右上角报错的原地址和之前的不一样,点进去查看报错代码时,代码也和之前不一样
所以用eval执行,可以开启一个新的JS执行环境,从而使当前代码和原代码脱离,在其中的报错也会和源代码隔离开
对于我们自己写的代码,也可以改为和webpack打包之后加上eval一样的代码如下:
(function (modules) {
const map = new Map();
function require(moduleID) {
if (map.has(moduleID)) {
return map.get(moduleID);
}
const func = modules[moduleID];
const module = {
exports: {}
};
func(module, module.exports, require);
map.set(moduleID, module.exports);
return module.exports;
}
require("./index.js");
})({
"./a.js": function (module, exports) {
eval("console.log("module a");\nmodule.exports = "a";//# sourceURL=./src/a.js")
},
"./c.js": function (module, exports, require) {
eval("console.log("module c");\nconst a = require("./a.js");\nconsole.log("c import a, and a is " + a);\nmodule.exports = "c";//# sourceURL=./src/c.js")
},
"./index.js": function (module, exports, require) {
eval("console.log("index module");\nconst a = require("./a.js");\nconst c = require("./c.js");\nconsole.log("index import a, and a is " + a);\nconsole.log("index import c, and c is " + c);//# sourceURL=./src/index.js")
}
})
这里可以在其中某个eval中报一个错,各位小伙伴也可以拿去试试。
ok,差不多就这样,我理解的webpack打包时,整合代码的过程。欢迎各位大佬指正