手寫簡(jiǎn)易版webpack

詳細(xì)實(shí)現(xiàn)收錄在https://github.com/jdkwky/webstudydeep/tree/webstudydeep/webpackstudy 中,主要以webpack03、webpack04、pwebpack文件為主,如果覺得對(duì)您有幫助歡迎給個(gè)star。


npm link

使用 npm link 創(chuàng)建自己本地開發(fā)包然后通過npm link modelName 引入到需要引用該模塊的項(xiàng)目中

舉例:

pwebpack 是我們的本地打包js文件的項(xiàng)目,當(dāng)前pwebpack是空項(xiàng)目;

  1. cd pwebpack & npm init;
  2. package.json 中的name字段為"pwebpack"(name字段可以更改但是后面引入的包名也會(huì)跟著這個(gè)名字的改變而改變)
  3. package.json 中 加入 "bin":"./bin/pwebpack.js";
  4. 在"./bin/pwebpack.js"文件中寫如下代碼
#! /usr/bin/env node

// 作用 是告訴系統(tǒng)此文件用node執(zhí)行 并且引用那個(gè)環(huán)境下的node模塊

console.log('start');
  1. 在需要引入pwebpack包中的文件中執(zhí)行 npm link pwebpack;
  2. 測(cè)試 npx pwebpack; 輸出 "start" 即成功,修改一下pwebpack.js文件中的輸出字段,重新npx pwebpack一下更新新的數(shù)據(jù)。

手寫簡(jiǎn)易版 webpack

  1. 入口文件
const path = require('path');
// 獲取配置文件內(nèi)容
const config = require(path.resolve('webpack.config.js'));
// 引用編譯類
const Compiler = require('../lib/Compiler.js');
// 創(chuàng)建對(duì)象
const compiler = new Compiler(config);
// 調(diào)用run 方法
compiler.run();

  1. Compiler 類
const path = require('path');
const fs = require('fs');
const babylon = require('babylon');
const traverse = require('@babel/traverse').default;
const types = require('@babel/types');
const generator = require('@babel/generator').default;
const ejs = require('ejs');
const { SyncHook } = require('tapable');

class Compiler {
    constructor(config) {
        this.config = config || {};
        // 保存所有模塊依賴
        this.modules = {};
        // 入口文件
        this.entry = config.entry;
        this.entryId = '';

        // 工作目錄
        this.root = process.cwd();

    }

    // 獲取資源
    getSource(modulePath) {
        let content = fs.readFileSync(modulePath, 'utf8');
        return content;
    }

    // 解析語(yǔ)法
    parse(source, parentPath) {
        // AST 語(yǔ)法樹解析
        const ast = babylon.parse(source);

        // 依賴的數(shù)組
        const dependencies = [];
        traverse(ast, {
            CallExpression(p) {
                const node = p.node;
                if (node.callee.name == 'require') {
                    node.callee.name = '__webpack_require__';
                    let moduleName = node.arguments[0].value;
                    moduleName =
                        moduleName + (path.extname(moduleName) ? '' : '.js'); // ./a.js
                    moduleName = './' + path.join(parentPath, moduleName); // ./src/a/js
                    dependencies.push(moduleName);
                    node.arguments = [types.stringLiteral(moduleName)]; // 改掉源碼
                }
            }
        });

        const sourceCode = generator(ast).code;
        return { sourceCode, dependencies };
    }

    // 構(gòu)建模塊
    buildModule(modulePath, isEntry) {
        // 模塊路徑  是否是入口文件
        // 拿到模塊內(nèi)容
        const source = this.getSource(modulePath);

        // 獲取模塊id 需要相對(duì)路徑

        const moduleName = './' + path.relative(this.root, modulePath);

        if (isEntry) {
            this.entryId = moduleName;
        }
        // 解析代碼塊
        const { sourceCode, dependencies } = this.parse(
            source,
            path.dirname(moduleName)
        );

        this.modules[moduleName] = sourceCode;
        dependencies.forEach(dep => {
            this.buildModule(path.join(this.root, dep), false);
        });
    }
    // 發(fā)射文件
    emitFile() {
        // 輸出到哪個(gè)目錄下
        let main = path.join(
            this.config.output.path,
            this.config.output.filename
        );
        let templateStr = this.getSource(path.join(__dirname, 'main.ejs'));
        let code = ejs.render(templateStr, {
            entryId: this.entryId,
            modules: this.modules
        });
        // 可能打包多個(gè)
        this.assets = {};
        // 路徑對(duì)應(yīng)的代碼
        this.assets[main] = code;
        fs.writeFileSync(main, this.assets[main]);
    }
    // 運(yùn)行
    run() {
        // 執(zhí)行創(chuàng)建模塊的依賴關(guān)系
        // 得到入口文件的絕對(duì)路徑
       
        this.buildModule(path.resolve(this.root, this.entry), true);
        this.emitFile();
    }
}

  1. 添加簡(jiǎn)易版 loader plugin(簡(jiǎn)易版都是同步鉤子)解析

構(gòu)造函數(shù)constructor函數(shù)中

// 插件
this.hooks = {
    entryOption: new SyncHook(),
    compile: new SyncHook(),
    afterCompile: new SyncHook(),
    afterPlugins: new SyncHook(),
    run: new SyncHook(),
    emit: new SyncHook(),
    done: new SyncHook()
};
// 解析plugins 通過tapable
const plugins = this.config.plugins;
if (Array.isArray(plugins)) {
     plugins.forEach(plugin => {
         plugin.apply(this);
     });
}
this.hooks.afterPlugins.call();

getSource函數(shù)中

// 解析loader
const rules = this.config.module.rules || [];

for (let i = 0; i < rules.length; i++) {
    let rule = rules[i];
    const { test, use } = rule || {};
    let len = use.length;
    if (test.test(modulePath)) {
        // 需要通過 loader 進(jìn)行轉(zhuǎn)化
        while (len > 0) {
            let loader = require(use[--len]);
            content = loader(content);
        }
    }
}

plugin 中 tapable鉤子

同步鉤子

const { SyncHook } = require('tapable');

const hook = new SyncHook(['name']);

hook.tap('hello', name => {
    console.log(`hello ${name}`);
});

hook.tap('Hello again', name => {
    console.log(`Hello ${name},again`);
});

hook.call('wky');
// 輸出   hello wky , Hello wky , again

簡(jiǎn)易版實(shí)現(xiàn)

class SyncHook {
    constructor() {
        this.tasks = [];
    }
    tap(name, fn) {
        this.tasks.push(fn);
    }
    call(...args) {
        this.tasks.forEach(task => {
            task(...args);
        });
    }
}

const hook = new SyncHook(['name']);

hook.tap('hello', name => {
    console.log(`hello ${name}`);
});

hook.tap('Hello again', name => {
    console.log(`Hello ${name},again`);
});

hook.call('wky');

SyncBailHook 熔斷性執(zhí)行

const { SyncBailHook } = require('tapable');

const hook = new SyncBailHook(['name']);
hook.tap('node', function(name) {
    console.log('node', name);
    return '停止學(xué)習(xí)';
});
hook.tap('react', function(name) {
    console.log('react', name);
});

hook.call('wky');

// node wky 停止學(xué)習(xí), 就不會(huì)返回執(zhí)行下面的代碼

實(shí)現(xiàn)原理

class SyncBailHook {
    constructor() {
        this.tasks = [];
    }
    tap(name, fn) {
        this.tasks.push(fn);
    }
    call(...args) {
        let index = 0,
            length = this.tasks.length,
            tasks = this.tasks;
        let result;
        do {
            result = tasks[index](...args);
            index++;
        } while (result == null && index < length);
    }
}

const hook = new SyncBailHook(['name']);
hook.tap('node', function(name) {
    console.log('node', name);
    return '停止學(xué)習(xí)';
});
hook.tap('react', function(name) {
    console.log('react', name);
});

hook.call('wkyyc');

同步瀑布鉤子(上一個(gè)監(jiān)聽函數(shù)的值會(huì)傳遞給下一個(gè)監(jiān)聽函數(shù))


const { SyncWaterfallHook } = require('tapable');

const hook = new SyncWaterfallHook(['name']);
hook.tap('node', function(name) {
    console.log('node', name);
    return 'node 學(xué)的還不錯(cuò)';
});
hook.tap('react', function(data) {
    console.log('react', data);
});

hook.call('wky');

實(shí)現(xiàn)原理

class SyncWaterfallHook {
    constructor() {
        this.tasks = [];
    }

    tap(name, fn) {
        this.tasks.push(fn);
    }
    call(...args) {
        const [firstFn, ...others] = this.tasks;
        others.reduce((sum, task) => task(sum), firstFn(...args));
    }
}

const hook = new SyncWaterfallHook(['name']);
hook.tap('node', function(name) {
    console.log('node', name);
    return 'node 學(xué)的還不錯(cuò)';
});
hook.tap('react', function(data) {
    console.log('react', data);
});

hook.call('wkyyc');

異步鉤子, 并行執(zhí)行的異步鉤子,當(dāng)注冊(cè)的所有異步回調(diào)都并行執(zhí)行完畢之后再執(zhí)行callAsync或者promise中的函數(shù)

const { AsyncParallelHook } = require('tapable');
const hook = new AsyncParallelHook(['name']);

hook.tapAsync('hello', (name, cb) => {
    setTimeout(() => {
        console.log(`hello ${name}`);
        cb();
    }, 1000);
});

hook.tapAsync('hello again', (name, cb) => {
    setTimeout(() => {
        console.log(`Hello ${name} again`);
        cb();
    }, 2000);
});

hook.callAsync('wkyyc', () => {
    console.log('end');
});

實(shí)現(xiàn)原理

class AsyncParallelHook {
    constructor() {
        this.tasks = [];
    }
    tapAsync(name, fn) {
        this.tasks.push(fn);
    }
    callAsync(...args) {
        // 要最后執(zhí)行的函數(shù)
        const callbackFn = args.pop();
        let index = 0;
        const next = () => {
            index++;
            if (index == this.tasks.length) {
                callbackFn();
            }
        };

        this.tasks.forEach(task => {
            task(...args, next);
        });
    }
}
const hook = new AsyncParallelHook(['name']);

hook.tapAsync('hello', (name, cb) => {
    setTimeout(() => {
        console.log(`hello ${name}`);
        cb();
    }, 1000);
});

hook.tapAsync('hello again', (name, cb) => {
    setTimeout(() => {
        console.log(`Hello ${name} again`);
        cb();
    }, 3000);
});

hook.callAsync('wkyyc', () => {
    console.log('end');
});

異步串行執(zhí)行

const { AsyncSeriesHook } = require('tapable');
const hook = new AsyncSeriesHook(['name']);

hook.tapPromise('hello', name => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`hello ${name}`);
            resolve();
        }, 1000);
    });
});

hook.tapPromise('hello again', data => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`Hello ${data} again`);
            resolve();
        }, 1000);
    });
});

hook.promise('wkyyc').then(() => {
    console.log('end');
});

實(shí)現(xiàn)原理

class AsyncSeriesHook {
    constructor() {
        this.tasks = [];
    }

    tapPromise(name, fn) {
        this.tasks.push(fn);
    }
    promise(...args) {
        const [firstFn, ...others] = this.tasks;

        return others.reduce(
            (sum, task) =>
                sum.then(() => {
                    return task(...args);
                }),
            firstFn(...args)
        );
    }
}
const hook = new AsyncSeriesHook(['name']);
hook.tapPromise('hello', name => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`hello ${name}`);
            resolve();
        }, 1000);
    });
});

hook.tapPromise('hello again', data => {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log(`Hello ${data} again`);
            resolve();
        }, 1000);
    });
});

hook.promise('wkyyc').then(() => {
    console.log('end');
});

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

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