乐趣区

深入理解webpack打包机制(四)

有了 webpack 的模版 并且有了各个模块之间的依赖关系,接下来我们就可以实现打包。接下来就开始实现 Compiler.js 中的最终打包 (即 emit) 方法:
写 emit()方法之前,首先要安装一下 ejs 模块,我们需要用 ejs 模版引擎来解析刚才手写的 webpck 模版。进入到 my-pick 目录,运行命令:npm i ejs -D
Compiler.js:
let path = require(‘path’);
let fs = require(‘fs’);
let babylon = require(‘babylon’);
let traverse = require(‘@babel/traverse’).default;
let t = require(‘@babel/types’);
let generator = require(‘@babel/generator’).default;
let ejs = require(‘ejs’);

class Compiler{
constructor(config){
this.config = config;
this.entry = config.entry;
this.entryId = ”;
this.modules = {};
this.rootPath = process.cwd();
}
run(){
this.buildModule(path.resolve(this.rootPath,this.entry),true);
this.emit();
}
buildModule(modulePath, isEntry){
let source = this.getSource(modulePath);
let moduleName = ‘./’+path.relative(this.rootPath,modulePath);
if(isEntry){this.entryId = moduleName};
let {sourceCode, dependencies} = this.parse(source, path.dirname(moduleName));
this.modules[moduleName] = sourceCode;
dependencies.forEach((depend)=>{
this.buildModule(path.join(this.rootPath,depend),false);
});
}
parse(source, parentPath){
let dependencies = [];
let ast = babylon.parse(source);
traverse(ast, {
CallExpression(p){
if(p.node.callee.name === ‘require’){
p.node.callee.name = ‘__webpack_require__’;
let moduleName = p.node.arguments[0].value;
moduleName = moduleName + (path.extname(moduleName)?”:’.js’);
moduleName = ‘./’+path.join(parentPath,moduleName);
dependencies.push(moduleName);
p.node.arguments = [t.stringLiteral(moduleName)];
}
}
});
let sourceCode = generator(ast).code;
return {sourceCode, dependencies};
}
getSource(sourcePath){
return fs.readFileSync(sourcePath,’utf8’);
}
emit(){
let main = path.join(this.config.output.path,this.config.output.filename);
let templateStr = this.getSource(path.resolve(__dirname, ‘template.ejs’));
let code = ejs.render(templateStr,{
entryId:this.entryId,modules:this.modules
});
this.assets = {};
this.assets[main] = code;
fs.writeFileSync(main,this.assets[main]);

}
}

module.exports = Compiler;
emit()方法就是最终我们实现 webpack 的打包方法。最后的 bundle.js 就是由该方法生成。首先,通过 path.join()方法和 config 中的 output 获取到最终的打包文件的路径。第二行又获取到之前写好的模版文件:template.ejs。最终通过 ejs 模块解析 并且传入主入口 entryId 和依赖关系对象 modules 生成最终的打包文件。生成了最终的打包文件后就很简单了,首先把最终的文件放到 this.assets 对象中,最后又通过 fs.readFileSync()写入 bundle.js 文件到输出路径。最后,回到 webpack 的目录: 运行自己的 pick 命令:npx my-pack. 即可看到 dist 目录中多了一个 bundle.js 的文件 bundle.js:
(function(modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports;
}
return __webpack_require__(__webpack_require__.s = “./src/index.js”);
})
/* 自执行函数 传入参数 */
({

“./src/index.js”:
(function(module, exports, __webpack_require__) {
eval(`console.log(‘index.js’);

__webpack_require__(“./src/a.js”);`);
}),

“./src/a.js”:
(function(module, exports, __webpack_require__) {
eval(`let b = __webpack_require__(“./src/b.js”);

console.log(‘a.js’);
console.log(b);`);
}),

“./src/b.js”:
(function(module, exports, __webpack_require__) {
eval(`module.exports = ‘b.js’;`);
}),

});
可以看到,所以的依赖关系都被传递到了 webpack 的自执行函数的参数中。通过右键 run,或者浏览器中打开。发现打印了我们写的代码:
index.js
a.js
b.js
到此为止,我们已经手动实现了 webpack 的打包功能。当然这只是 webpack 中的冰山一角,我们只是简单实现了解析模块的依赖关系,打包了 js 文件。像 webpack 的钩子,loader,plugins,css 文件.. 等等都没有进行处理。可能会在以后的文章中会较为深入的解析 webpack 的 loader 和 plugins 是如何实现的。谢谢观看~喜欢点个????

退出移动版