手寫webpack——實現plugins的功能

實現plugins

zf-pack:

let fs = require('fs');
let path = require('path');
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');
let {SyncHook} = require('tapable');
// babylon 主要就是把源碼轉換成ast
// @babel/traverse
// @babel/types
// @babel/generator
class Compiler {
    constructor(config) {
        // entry output
        this.config = config;
        // 需要保存入口文件的路徑
        this.entryId;
        // 需要保存所有得到依賴模塊
        this.modules = {};
        this.entry = config.entry;
        // 工作路徑
        this.root = process.cwd();
        this.hooks = {
            entry: new SyncHook(),
            compile: new SyncHook(),
            afterCompile: new SyncHook(),
            afterPlugins: new SyncHook(),
            run: new SyncHook(),
            emit: new SyncHook(),
            done: new SyncHook(),
        };
        // 如果傳遞了plugins
        let plugins = this.config.plugins;
        if (Array.isArray(plugins)) {
            plugins.forEach(plugins => {
                plugins.apply(this);
            })
        }
        this.hooks.afterPlugins.call();
    }

    // 解析源碼
    parse(source, parentPath) { // AST解析語法樹
        let ast = babylon.parse(source);
        // console.log(ast);
        let dependencies = []; // 依賴的數組
        traverse(ast, {
            CallExpression(p) {
                let 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');
                    moduleName = './' + path.join(parentPath, moduleName);
                    // moduleName.replace(/\\/, '\\');
                    dependencies.push(moduleName);
                    node.arguments = [t.stringLiteral(moduleName)];
                }
            }
        });
        let sourceCode = generator(ast).code;
        console.log(sourceCode);
        return {
            sourceCode, dependencies
        }
    }
    getSource(modulePath) {
        let rules = this.config.module.rules;
        let content = fs.readFileSync(modulePath, 'utf-8');
        for (let i = 0; i <rules.length; i++) {
            let rule = rules[i];
            let {test, use} = rule;
            let len = use.length - 1;
            if (test.test(modulePath)) {
                // 獲取對應的loader函數
                function normalLoader() {
                    if (len >= 0) {
                        let loader = require(use[len--]);
                        content = loader(content);
                        normalLoader();
                    }
                }
                    normalLoader();

                }
            }
        return content;
    }
    // 構建模塊
    buildModule(modulePath, isEntry) {
        // 拿到模塊的內容
        let source = this.getSource(modulePath);
        // 模塊id  modulePath   modulePath-this.root
        let moduleName = './' + path.relative(this.root, modulePath);

        if (isEntry) {
            this.entryId = moduleName; // 保存入口的名字
        }
        // 解析需要把source源碼進行改造,返回一個依賴列表
        let {sourceCode, dependencies} = this.parse(source, path.dirname(moduleName));
        this.modules[moduleName] = sourceCode;
        dependencies.forEach(dep => {   // 副模塊
            console.log(dep);
            this.buildModule(path.join(this.root, dep), false);
        })
    }
    emitFile() {
        // 用數據,渲染我們的
        // 拿到輸出到哪個目錄下
        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});
        this.assests = {};
        // 資源中 路徑對應的代碼
        this.assests[main] = code;
        fs.writeFileSync(main, this.assests[main]);
    }
    run() {
        this.hooks.run.call();
        // 執行 並創建模塊的依賴關係 解析當前依賴
        this.hooks.compile.call();
        this.buildModule(path.resolve(this.root, this.entry), true);
        this.hooks.afterCompile.call();
        // 發射一個文件(打包後的文件)
        this.emitFile();
        this.hooks.emit.call();
        this.hooks.done.call();
    }
}

module.exports = Compiler;

使用項目

webpack.config.js
let webpack = require('webpack');
let path = require('path');
class P {
    constructor() {
        console.log('START');

    }
    apply(compiler) {
        compiler.hooks.emit.tap('emit', function () {
            console.log('emit');
        })
    }
}
module.exports = {
    entry:  './src/index.js',
    output: {
        filename: "index.js",
        path: path.resolve(__dirname, 'dist'),
    },
    mode: 'development',
    devServer: {
        port: 3000
    },
    plugins: [
        new P()
    ],
    module: {
        rules: [
            {
                test: /\.less/,
                use: [
                    path.resolve(__dirname, 'loader', 'style-loader'),
                    path.resolve(__dirname, 'loader', 'less-loader'),
                ]
            }
        ]
    }
};

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章