深入理解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是如何实现的。谢谢观看~喜欢点个