这是生成的依赖树
{
'./src/index.js': {
code: '"use strict";\n' +
'\n' +
'var _add = _interopRequireDefault(require("./add.js"));\n' +
'\n' +
'var _count = _interopRequireDefault(require("./count.js"));\n' +
'\n' +
'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +
'\n' +
'console.log((0, _add["default"])(1, 2));\n' +
'console.log((0, _count["default"])(3, 1));',
deps: {
'./add.js': 'D:\\webpack5进阶\\05.myWebpack\\src\\add.js',
'./count.js': 'D:\\webpack5进阶\\05.myWebpack\\src\\count.js'
}
},
'D:\\webpack5进阶\\05.myWebpack\\src\\add.js': {
code: '"use strict";\n' +
'\n' +
'Object.defineProperty(exports, "__esModule", {\n' +
' value: true\n' +
'});\n' +
'exports["default"] = void 0;\n' +
'\n' +
'var _new = _interopRequireDefault(require("./new.js"));\n' +
'\n' +
'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +
'\n' +
'function add(x, y) {\n' +
' return x + y;\n' +
'}\n' +
'\n' +
'console.log(_new["default"]);\n' +
'var _default = add;\n' +
'exports["default"] = _default;',
deps: { './new.js': 'D:\\webpack5进阶\\05.myWebpack\\src\\new.js' }
},
'D:\\webpack5进阶\\05.myWebpack\\src\\count.js': {
code: '"use strict";\n' +
'\n' +
'Object.defineProperty(exports, "__esModule", {\n' +
' value: true\n' +
'});\n' +
'exports["default"] = void 0;\n' +
'\n' +
'function count(x, y) {\n' +
' return x - y;\n' +
'}\n' +
'\n' +
'var _default = count;\n' +
'exports["default"] = _default;',
deps: {}
},
'D:\\webpack5进阶\\05.myWebpack\\src\\new.js': {
code: '"use strict";\n' +
'\n' +
'Object.defineProperty(exports, "__esModule", {\n' +
' value: true\n' +
'});\n' +
'exports["default"] = void 0;\n' +
'var _default = 123;\n' +
'exports["default"] = _default;',
deps: {}
}
}
这是简单版的bundle生成方法
generate(depsGraph) {
const bundle = `
(function(depsGraph){
// require目的:为了加载入口文件
function require(module){
// 定义模块内部的require函数
function localRequire(relativePath){
// 为了找到要引入模块的绝对路径,通过require加载
return require(depsGraph[module].deps[relativePath]);
}
// 定义暴露对象(将来我们要暴露的内容)
var exports = {};
(function(require,exports,code){
eval(code)
})(localRequire, exports, depsGraph[module].code);
// 作为require函数的返回值返回出去
// 后面的require函数能得到暴露的内容
return exports;
}
// 加载入口文件
require('${this.options.entry}');
})(${JSON.stringify(depsGraph)})
`;
// 生成输出文件的绝对路径
const filePath = path.resolve(this.options.output.path, this.options.output.filename);
// 写入文件
fs.writeFileSync(filePath, bundle, 'utf-8');
}
首先可以从依赖树里面看到,es6的模块语法都已经被转为了require和exports之类的。
关于require:
执行generate方法时,首先会从入口文件开始执行require('${this.options.entry}')
,然后require函数里面,有一个立即执行方法,这个方法可以执行入口文件里面的代码:
(function(require,exports,code){
eval(code)
})(localRequire, exports, depsGraph[module].code);
如果这些代码里面有模块引入(require),那么又会继续递归调用require函数,并且根据依赖树把模块引入的相对路径转为绝对路径,这样就可以在依赖树里面找到该模块内部的代码了,那么require函数的立即执行方法又可以继续执行引入模块内部的代码了。
关于exports:
但是仅仅递归调用require是不够的,因为require返回的是一个模块,外层模块必须基于require的返回值来进行下一步操作,所以要对exports进行处理,其实处理很简单,对于有exports语法的模块,在require方法内部先定义一个空的exports对象var exports = {};
,也就是我们的暴露对象,然后再在这个对象上添加要暴露出去的属性或方法,最后返回这个对象即可。那么当我外层模块require时,就可以拿到内层模块的想要暴露出来的值了。
总结:
所谓的模块化其实最后都生成了一段代码,最重要的有三点,第一点是依赖树,第二点是require的递归调用,第三点是exports的返回值。