深刻理解webpack打包機制(四)

有了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是如何實現的。謝謝觀看~喜歡點個?瀏覽器

相關文章
相關標籤/搜索