有了webpack的模版 并且有了各个模块之间的依赖关系,接下来我们就可以实现打包。接下来就开始实现Compiler.js中的最终打包(即emit)方法:写emit()方法之前,首先要安装一下ejs模块,我们需要用ejs模版引擎来解析刚才手写的webpck模版。进入到my-pick目录, 运行命令:npm i ejs -DCompiler.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.jsa.jsb.js到此为止,我们已经手动实现了webpack的打包功能。当然这只是webpack中的冰山一角,我们只是简单实现了解析模块的依赖关系,打包了js文件。像webpack的钩子,loader,plugins,css文件..等等都没有进行处理。可能会在以后的文章中会较为深入的解析webpack的loader和plugins是如何实现的。谢谢观看~喜欢点个????