有了webpack的模版 而且有了各個模塊之間的依賴關係,接下來咱們就能夠實現打包。
接下來就開始實現Compiler.js中的最終打包(即emit)方法:css
寫emit()方法以前,首先要安裝一下ejs模塊,咱們須要用ejs模版引擎來解析剛纔手寫的webpck模版。
進入到my-pick目錄, 運行命令:npm i ejs -Dnode
Compiler.js:webpack
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:web
(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,或者瀏覽器中打開。發現打印了咱們寫的代碼:npm
index.js a.js b.js
到此爲止,咱們已經手動實現了webpack的打包功能。固然這只是webpack中的冰山一角,咱們只是簡單實現瞭解析模塊的依賴關係,打包了js文件。像webpack的鉤子,loader,plugins,css文件..等等都沒有進行處理。
可能會在之後的文章中會較爲深刻的解析webpack的loader和plugins是如何實現的。謝謝觀看~喜歡點個?瀏覽器