從零開始手寫webpack(2)

上一版做了什么

  1. 實(shí)現(xiàn)了一個js打包器

這一版做了什么

  1. 增加loader支持
  2. 增加plugin支持

為什么要增加loader和plugin

在上一篇文章中提到,webpack 就是一個 打包 js代碼的打包器。至于webpack能打包 圖片、css、less、scss 等其他文件, 那都是loader或者plugin的功能。所以,為了打包器更強(qiáng)大,需要增加loaderplugin的支持

不足的點(diǎn)

我只是寫了一個簡單的實(shí)現(xiàn),真正的webpack比我這個強(qiáng)大太多,但是基本原理是一樣的。

先來一發(fā)整體工作流程圖吧

webpack工作原理.png

再來一個架構(gòu)圖

webpack架構(gòu)圖.png

工作流程

通過上述兩個圖,可以大概描述出webpack的工作原理了。通過loaderplugin的加持,webpack可以完成各種各樣的工作。

loader支持

編譯器在獲取源碼的時候,即可對源碼進(jìn)行操作,此時,loader便可以排上用場了,在loader中,對源碼可以做一些操作,然后講源碼返回。 這也是webpackloader的處理方式。具體修改為,在上一版的編譯器的構(gòu)建模塊函數(shù)中增加代碼。

getSource(modulePath) {
        const rules = this.config.module.rules;
        let content = fs.readFileSync(modulePath, 'utf8');
        for(let i = 0; i < rules.length; i++){
            const rule = rules[i];
            const { test, use } = rule;
            let len = use.length - 1;
            if (test.test(modulePath)) {
                const normalLoader = () => {
                    // loader 獲取對應(yīng)的loader函數(shù)
                    const loader = require(use[len--]);
                    content = loader(content);
                    if (len >= 0) {
                        normalLoader();
                    }
                }
                normalLoader();
            }
        }
        return content;
    }

plugin支持

針對plugin的支持,采用了和webpack一樣的模式,使用tapable這個庫

簡單介紹一下tapable這個庫。這是庫提供了一些觀察者處理方法,有同步hooks異步hooks. 具體使用方法,請看 tapable

所以在編譯器run方法執(zhí)行之前,我們需要注冊所有的觀察者. 則有了代碼:

constructor(config) {
        this.config = config;
        // 需要保存入口文件的路徑
        this.entryId;
        // 需要保存所有模塊的依賴
        this.modules = {};
        // 入口路徑
        this.entry = config.entry;
        // 工作路徑
        this.root = process.cwd();
        this.hooks = {
            entryOption: new SyncHook(),
            compile: new SyncHook(),
            afterCompile: new SyncHook(),
            afterPlugins: new SyncHook(),
            run: new SyncHook(),
            emit: new SyncHook(),
            done: new SyncHook()
        }

        const { plugins = [] } = this.config;
        // 注冊所有的plugin
        if (Array.isArray(plugins)) {
            plugins.forEach(plugin => {
                plugin.apply(this);
            });
        }
        // 調(diào)用plugin注冊完的鉤子
        this.hooks.afterPlugins.call();
    }

剩下的,我們就是需要在編譯器的各個步驟來觸發(fā)鉤子函數(shù)了.

例如, 在編譯開始之前、編譯進(jìn)行時、編譯完成后、文件發(fā)射前、文件發(fā)射后....

這個版本里面我只做了幾個簡單的觸發(fā)

run() {
        this.hooks.run.call();
        this.hooks.compile.call();
        // 執(zhí)行并且創(chuàng)建模塊的依賴關(guān)系
        this.buildModule(path.resolve(this.root, this.entry), true);
        this.hooks.afterCompile.call();
        // 發(fā)射一個打包后的文件
        this.emitFile();
        this.hooks.emit.call();
        this.hooks.done.call();
    }

完整源碼

待上傳到github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容