webpack详解

webpack是当代前端开辟中最火的模块打包东西,只须要经由过程简朴的设置,便可以完成模块的加载和打包。那它是怎样做到经由过程对一些插件的设置,便可以轻松完成对代码的构建呢?

webpack的设置

const path = require('path');
module.exports = {
  entry: "./app/entry", // string | object | array
  // Webpack打包的进口
  output: {  // 定义webpack怎样输出的选项
    path: path.resolve(__dirname, "dist"), // string
    // 一切输出文件的目的途径
    filename: "[chunkhash].js", // string
    // 「进口(entry chunk)」文件定名模版
    publicPath: "/assets/", // string
    // 构建文件的输出目次
    /* 别的高等设置 */
  },
  module: {  // 模块相干设置
    rules: [ // 设置模块loaders,剖析划定规矩
      {
        test: /\.jsx?$/,  // RegExp | string
        include: [ // 和test一样,必需婚配选项
          path.resolve(__dirname, "app")
        ],
        exclude: [ // 必不婚配选项(优先级高于test和include)
          path.resolve(__dirname, "app/demo-files")
        ],
        loader: "babel-loader", // 模块上下文剖析
        options: { // loader的可选项
          presets: ["es2015"]
        },
      },
  },
  resolve: { //  剖析模块的可选项
    modules: [ // 模块的查找目次
      "node_modules",
      path.resolve(__dirname, "app")
    ],
    extensions: [".js", ".json", ".jsx", ".css"], // 用到的文件的扩大
    alias: { // 模块别号列表
      "module": "new-module"
      },
  },
  devtool: "source-map", // enum
  // 为浏览器开辟者东西增加元数据加强调试
  plugins: [ // 附加插件列表
    // ...
  ],
}

从上面我们可以看到,webpack设置中须要明白几个中心的观点EntryOutputLoadersPluginsChunk

  • Entry:指定webpack最先构建的进口模块,从该模块最先构建并计算出直接或间接依靠的模块也许库
  • Output:关照webpack怎样定名输出的文件以及输出的目次
  • Loaders:因为webpack只能处置惩罚javascript,所以我们须要对一些非js文件处置惩罚成webpack可以处置惩罚的模块,比方sass文件
  • Plugins:Loaders将各范例的文件处置惩罚成webpack可以处置惩罚的模块,plugins有着很强的才能。插件的局限包含,从打包优化和紧缩,一直到从新定义环境中的变量。但也是最庞杂的一个。比方对js文件举行紧缩优化的UglifyJsPlugin插件
  • Chunk:coding split的产品,我们可以对一些代码打包成一个零丁的chunk,比方某些大众模块,去重,更好的应用缓存。也许按需加载某些功能模块,优化加载时刻。在webpack3及之前我们都应用CommonsChunkPlugin将一些大众代码分割成一个chunk,完成零丁加载。在webpack4 中CommonsChunkPlugin被烧毁,运用SplitChunksPlugin

webpack详解

读到这里,也许你对webpack有一个也许的相识,那webpack 是怎样运转的呢?我们都晓得,webpack是高度庞杂笼统的插件鸠合,明白webpack的运转机制,关于我们一样平常定位构建毛病以及写一些插件处置惩罚构建使命有很大的协助。

不能不说的tapable

webpack本质上是一种事宜流的机制,它的事情流程就是将各个插件串连起来,而完成这一切的中心就是Tapable,webpack中最中心的担任编译的Compiler和担任建立bundles的Compilation都是Tapable的实例。在Tapable1.0之前,也就是webpack3及其之前运用的Tapable,供应了包含

  • plugin(name:string, handler:function)注册插件到Tapable对象中
  • apply(…pluginInstances: (AnyPlugin|function)[])挪用插件的定义,将事宜监听器注册到Tapable实例注册表中
  • applyPlugins*(name:string, …)多种战略仔细地掌握事宜的触发,包含applyPluginsAsyncapplyPluginsParallel等要领完成对事宜触发的掌握,完成

(1)多个事宜一连递次实行
(2)并行实行
(3)异步实行
(4)一个接一个地实行插件,前面的输出是后一个插件的输入的瀑布流实行递次
(5)在许可时住手实行插件,即某个插件返回了一个undefined的值,即退出实行
我们可以看到,Tapable就像nodejs中EventEmitter,供应对事宜的注册on和触发emit,明白它很主要,看个栗子:比方我们来写一个插件

function CustomPlugin() {}
CustomPlugin.prototype.apply = function(compiler) {
  compiler.plugin('emit', pluginFunction);
}

在webpack的生命周期中会合时的实行

this.apply*("emit",options)

固然上面提到的Tapable都是1.0版本之前的,假如想深切进修,可以检察Tapable 和 事宜流
那1.0的Tapable又是什么样的呢?1.0版本发生了庞大的转变,不再是此前的经由过程plugin注册事宜,经由过程applyPlugins*触发事宜挪用,那1.0的Tapable是什么呢?

暴露出许多的钩子,可以运用它们为插件建立钩子函数

const {
    SyncHook,
    SyncBailHook,
    SyncWaterfallHook,
    SyncLoopHook,
    AsyncParallelHook,
    AsyncParallelBailHook,
    AsyncSeriesHook,
    AsyncSeriesBailHook,
    AsyncSeriesWaterfallHook
 } = require("tapable");

我们来看看 怎样运用。

class Order {
    constructor() {
        this.hooks = { //hooks
            goods: new SyncHook(['goodsId', 'number']),
            consumer: new AsyncParallelHook(['userId', 'orderId'])
        }
    }

    queryGoods(goodsId, number) {
        this.hooks.goods.call(goodsId, number);
    }

    consumerInfoPromise(userId, orderId) {
        this.hooks.consumer.promise(userId, orderId).then(() => {
            //TODO
        })
    }

    consumerInfoAsync(userId, orderId) {
        this.hooks.consumer.callAsync(userId, orderId, (err, data) => {
            //TODO
        })
    }
}

关于一切的hook的组织函数均接收一个可选的string范例的数组

const hook = new SyncHook(["arg1", "arg2", "arg3"]);
// 挪用tap要领注册一个consument
order.hooks.goods.tap('QueryPlugin', (goodsId, number) => {
    return fetchGoods(goodsId, number);
})
// 再增加一个
order.hooks.goods.tap('LoggerPlugin', (goodsId, number) => {
    logger(goodsId, number);
})

// 挪用
order.queryGoods('10000000', 1)

关于一个 SyncHook,我们经由过程tap来增加消费者,经由过程call来触发钩子的递次实行。

关于一个非sync*范例的钩子,即async*范例的钩子,我们还可以经由过程别的体式格局注册消费者和挪用

// 注册一个sync 钩子
order.hooks.consumer.tap('LoggerPlugin', (userId, orderId) => {
   logger(userId, orderId);
})

order.hooks.consumer.tapAsync('LoginCheckPlugin', (userId, orderId, callback) => {
    LoginCheck(userId, callback);
})

order.hooks.consumer.tapPromise('PayPlugin', (userId, orderId) => {
    return Promise.resolve();
})

// 挪用
// 返回Promise
order.consumerInfoPromise('user007', '1024');

//回调函数
order.consumerInfoAsync('user007', '1024')

经由过程上面的栗子,你能够已大抵相识了Tapable的用法,它的用法

  • 插件注册数目
  • 插件注册的范例(sync, async, promise)
  • 挪用的体式格局(sync, async, promise)
  • 实例钩子的时刻参数数目
  • 是不是运用了interception

Tapable详解

《webpack详解》
关于Sync*范例的钩子来讲。

  • 注册在该钩子下面的插件的实行递次都是递次实行。
  • 只能运用tap注册,不能运用tapPromisetapAsync注册
// 一切的钩子都继续于Hook
class Sync* extends Hook { 
    tapAsync() { // Sync*范例的钩子不支撑tapAsync
        throw new Error("tapAsync is not supported on a Sync*");
    }
    tapPromise() {// Sync*范例的钩子不支撑tapPromise
        throw new Error("tapPromise is not supported on a Sync*");
    }
    compile(options) { // 编译代码来依据肯定的战略实行Plugin
        factory.setup(this, options);
        return factory.create(options);
    }
}

关于Async*范例钩子

  • 支撑taptapPromisetapAsync注册
class AsyncParallelHook extends Hook {
    constructor(args) {
        super(args);
        this.call = this._call = undefined;
    }

    compile(options) {
        factory.setup(this, options);
        return factory.create(options);
    }
}
class Hook {
    constructor(args) {
        if(!Array.isArray(args)) args = [];
        this._args = args; // 实例钩子的时刻的string范例的数组
        this.taps = []; // 消费者
        this.interceptors = []; // interceptors
        this.call = this._call =  // 以sync范例体式格局来挪用钩子
        this._createCompileDelegate("call", "sync");
        this.promise = 
        this._promise = // 以promise体式格局
        this._createCompileDelegate("promise", "promise");
        this.callAsync = 
        this._callAsync = // 以async范例体式格局来挪用
        this._createCompileDelegate("callAsync", "async");
        this._x = undefined; // 
    }

    _createCall(type) {
        return this.compile({
            taps: this.taps,
            interceptors: this.interceptors,
            args: this._args,
            type: type
        });
    }

    _createCompileDelegate(name, type) {
        const lazyCompileHook = (...args) => {
            this[name] = this._createCall(type);
            return this[name](...args);
        };
        return lazyCompileHook;
    }
    // 挪用tap 范例注册
    tap(options, fn) {
        // ...
        options = Object.assign({ type: "sync", fn: fn }, options);
        // ...
        this._insert(options);  // 增加到 this.taps中
    }
    // 注册 async范例的钩子
    tapAsync(options, fn) {
        // ...
        options = Object.assign({ type: "async", fn: fn }, options);
        // ...
        this._insert(options); // 增加到 this.taps中
    }
    注册 promise范例钩子
    tapPromise(options, fn) {
        // ...
        options = Object.assign({ type: "promise", fn: fn }, options);
        // ...
        this._insert(options); // 增加到 this.taps中
    }
    
}

每次都是挪用taptapSynctapPromise注册差别范例的插件钩子,经由过程挪用callcallAsyncpromise体式格局挪用。实在挪用的时刻为了依据肯定的实行战略实行,挪用compile要领疾速编译出一个要领来实行这些插件。

const factory = new Sync*CodeFactory();
class Sync* extends Hook { 
    // ...
    compile(options) { // 编译代码来依据肯定的战略实行Plugin
        factory.setup(this, options);
        return factory.create(options);
    }
}

class Sync*CodeFactory extends HookCodeFactory {
    content({ onError, onResult, onDone, rethrowIfPossible }) {
        return this.callTapsSeries({
            onError: (i, err) => onError(err),
            onDone,
            rethrowIfPossible
        });
    }
}

compile中挪用HookCodeFactory#create要领编译天生实行代码。


class HookCodeFactory {
    constructor(config) {
        this.config = config;
        this.options = undefined;
    }

    create(options) {
        this.init(options);
        switch(this.options.type) {
            case "sync":  // 编译天生sync, 效果直接返回
                return new Function(this.args(), 
                "\"use strict\";\n" + this.header() + this.content({
                    // ...
                    onResult: result => `return ${result};\n`,
                    // ...
                }));
            case "async": // async范例, 异步实行,末了将挪用插件实行效果来挪用callback,
                return new Function(this.args({
                    after: "_callback"
                }), "\"use strict\";\n" + this.header() + this.content({
                    // ...
                    onResult: result => `_callback(null, ${result});\n`,
                    onDone: () => "_callback();\n"
                }));
            case "promise": // 返回promise范例,将效果放在resolve中
                // ...
                code += "return new Promise((_resolve, _reject) => {\n";
                code += "var _sync = true;\n";
                code += this.header();
                code += this.content({
                    // ...
                    onResult: result => `_resolve(${result});\n`,
                    onDone: () => "_resolve();\n"
                });
                // ...
                return new Function(this.args(), code);
        }
    }
    // callTap 就是实行一些插件,并将效果返回
    callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
        let code = "";
        let hasTapCached = false;
        // ...
        code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
        const tap = this.options.taps[tapIndex];
        switch(tap.type) {
            case "sync":
                // ...
                if(onResult) {
                    code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
                        before: tap.context ? "_context" : undefined
                    })});\n`;
                } else {
                    code += `_fn${tapIndex}(${this.args({
                        before: tap.context ? "_context" : undefined
                    })});\n`;
                }
                
                if(onResult) { // 效果透传
                    code += onResult(`_result${tapIndex}`);
                }
                if(onDone) { // 关照插件实行终了,可以实行下一个插件
                    code += onDone();
                }
                break;
            case "async": //异步实行,插件运转完后再将效果经由过程实行callback透传
                let cbCode = "";
                if(onResult)
                    cbCode += `(_err${tapIndex}, _result${tapIndex}) => {\n`;
                else
                    cbCode += `_err${tapIndex} => {\n`;
                cbCode += `if(_err${tapIndex}) {\n`;
                cbCode += onError(`_err${tapIndex}`);
                cbCode += "} else {\n";
                if(onResult) {
                    cbCode += onResult(`_result${tapIndex}`);
                }
                
                cbCode += "}\n";
                cbCode += "}";
                code += `_fn${tapIndex}(${this.args({
                    before: tap.context ? "_context" : undefined,
                    after: cbCode //cbCode将效果透传
                })});\n`;
                break;
            case "promise": // _fn${tapIndex} 就是第tapIndex 个插件,它必需是个Promise范例的插件
                code += `var _hasResult${tapIndex} = false;\n`;
                code += `_fn${tapIndex}(${this.args({
                    before: tap.context ? "_context" : undefined
                })}).then(_result${tapIndex} => {\n`;
                code += `_hasResult${tapIndex} = true;\n`;
                if(onResult) {
                    code += onResult(`_result${tapIndex}`);
                }
            // ...
                break;
        }
        return code;
    }
    // 依据插件的注册递次,依据递次递归挪用实行插件
    callTapsSeries({ onError, onResult, onDone, rethrowIfPossible }) {
        // ...
        const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");
        const next = i => {
            // ...
            const done = () => next(i + 1);
            // ...
            return this.callTap(i, {
                // ...
                onResult: onResult && ((result) => {
                    return onResult(i, result, done, doneBreak);
                }),
                // ...
            });
        };
        return next(0);
    }

    callTapsLooping({ onError, onDone, rethrowIfPossible }) {
        
        const syncOnly = this.options.taps.every(t => t.type === "sync");
        let code = "";
        if(!syncOnly) {
            code += "var _looper = () => {\n";
            code += "var _loopAsync = false;\n";
        }
        code += "var _loop;\n";
        code += "do {\n";
        code += "_loop = false;\n";
        // ...
        code += this.callTapsSeries({
            // ...
            onResult: (i, result, next, doneBreak) => { // 一旦某个插件返回不为undefined,  即一只挪用某个插件实行,假如为undefined,最先挪用下一个
                let code = "";
                code += `if(${result} !== undefined) {\n`;
                code += "_loop = true;\n";
                if(!syncOnly)
                    code += "if(_loopAsync) _looper();\n";
                code += doneBreak(true);
                code += `} else {\n`;
                code += next();
                code += `}\n`;
                return code;
            },
            // ...
        })
        code += "} while(_loop);\n";
        // ...
        return code;
    }
    // 并行挪用插件实行
    callTapsParallel({ onError, onResult, onDone, rethrowIfPossible, onTap = (i, run) => run() }) {
        // ...
        // 遍历注册都一切插件,并挪用
        for(let i = 0; i < this.options.taps.length; i++) {
            // ...
            code += "if(_counter <= 0) break;\n";
            code += onTap(i, () => this.callTap(i, {
                // ...
                onResult: onResult && ((result) => {
                    let code = "";
                    code += "if(_counter > 0) {\n";
                    code += onResult(i, result, done, doneBreak);
                    code += "}\n";
                    return code;
                }),
                // ...
            }), done, doneBreak);
        }
        // ...
        return code;
    }
}

HookCodeFactory#create中挪用到content要领,此要领将依据此钩子的实行战略,挪用差别的要领来实行编译 天生终究的代码。

  • SyncHook中挪用callTapsSeries编译天生终究实行插件的函数,callTapsSeries做的就是将插件列表中插件依据注册递次遍历实行。
class SyncHookCodeFactory extends HookCodeFactory {
    content({ onError, onResult, onDone, rethrowIfPossible }) {
        return this.callTapsSeries({
            onError: (i, err) => onError(err),
            onDone,
            rethrowIfPossible
        });
    }
}
  • SyncBailHook中当一旦某个返回值效果不为undefined便完毕实行列表中的插件
 class SyncBailHookCodeFactory extends HookCodeFactory {
    content({ onError, onResult, onDone, rethrowIfPossible }) {
        return this.callTapsSeries({
            // ...
            onResult: (i, result, next) => `if(${result} !== undefined) {\n${onResult(result)};\n} else {\n${next()}}\n`,
            // ...
        });
    }
}
  • SyncWaterfallHook中上一个插件实行效果看成下一个插件的入参
class SyncWaterfallHookCodeFactory extends HookCodeFactory {
    content({ onError, onResult, onDone, rethrowIfPossible }) {
        return this.callTapsSeries({
            // ...
            onResult: (i, result, next) => {
                let code = "";
                code += `if(${result} !== undefined) {\n`;
                code += `${this._args[0]} = ${result};\n`;
                code += `}\n`;
                code += next();
                return code;
            },
            onDone: () => onResult(this._args[0]),
        });
    }
}
  • AsyncParallelHook挪用callTapsParallel并行实行插件
class AsyncParallelHookCodeFactory extends HookCodeFactory {
    content({ onError, onDone }) {
        return this.callTapsParallel({
            onError: (i, err, done, doneBreak) => onError(err) + doneBreak(true),
            onDone
        });
    }
}

webpack流程篇

本文关于webpack 的流程解说是基于webpack4的。

webpack 进口文件

从webpack项目的package.json文件中我们找到了进口实行函数,在函数中引入webpack,那末进口将会是lib/webpack.js,而假如在shell中实行,那末将会走到./bin/webpack.js,我们就以lib/webpack.js为进口最先吧!

{
  "name": "webpack",
  "version": "4.1.1",
  ...
  "main": "lib/webpack.js",
  "web": "lib/webpack.web.js",
  "bin": "./bin/webpack.js",
  ...
  }

webpack进口

const webpack = (options, callback) => {
    // ...
    // 考证options正确性
    // 预处置惩罚options
    options = new WebpackOptionsDefaulter().process(options); // webpack4的默许设置
    compiler = new Compiler(options.context); // 实例Compiler
    // ...
    // 若options.watch === true && callback 则开启watch线程
    compiler.watch(watchOptions, callback);
    compiler.run(callback);
    return compiler;
};

webpack 的进口文件实在就实例了Compiler并挪用了run要领开启了编译,webpack的编译都依据下面的钩子挪用递次实行。

  • before-run 消灭缓存
  • run 注册缓存数据钩子
  • before-compile
  • compile 最先编译
  • make 从进口剖析依靠以及间接依靠模块,建立模块对象
  • build-module 模块构建
  • seal 构建效果封装, 不可再变动
  • after-compile 完成构建,缓存数据
  • emit 输出到dist目次

编译&构建流程

webpack中担任构建和编译都是Compilation

class Compilation extends Tapable {
    constructor(compiler) {
        super();
        this.hooks = {
            // hooks
        };
        // ...
        this.compiler = compiler;
        // ...
        // template
        this.mainTemplate = new MainTemplate(this.outputOptions);
        this.chunkTemplate = new ChunkTemplate(this.outputOptions);
        this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(
            this.outputOptions
        );
        this.runtimeTemplate = new RuntimeTemplate(
            this.outputOptions,
            this.requestShortener
        );
        this.moduleTemplates = {
            javascript: new ModuleTemplate(this.runtimeTemplate),
            webassembly: new ModuleTemplate(this.runtimeTemplate)
        };

        // 构建天生的资本
        this.chunks = [];
        this.chunkGroups = [];
        this.modules = [];
        this.additionalChunkAssets = [];
        this.assets = {};
        this.children = [];
        // ...
    }
    // 
    buildModule(module, optional, origin, dependencies, thisCallback) {
        // ...
        // 挪用module.build要领举行编译代码,build中 实际上是应用acorn编译天生AST
        this.hooks.buildModule.call(module);
        module.build(/**param*/);
    }
    // 将模块增加到列表中,并编译模块
    _addModuleChain(context, dependency, onModule, callback) {
            // ...
            // moduleFactory.create建立模块,这里会先应用loader处置惩罚文件,然后天生模块对象
            moduleFactory.create(
                {
                    contextInfo: {
                        issuer: "",
                        compiler: this.compiler.name
                    },
                    context: context,
                    dependencies: [dependency]
                },
                (err, module) => {
                    const addModuleResult = this.addModule(module);
                    module = addModuleResult.module;
                    onModule(module);
                    dependency.module = module;
                    
                    // ...
                    // 挪用buildModule编译模块
                    this.buildModule(module, false, null, null, err => {});
                }
        });
    }
    // 增加进口模块,最先编译&构建
    addEntry(context, entry, name, callback) {
        // ...
        this._addModuleChain( // 挪用_addModuleChain增加模块
            context,
            entry,
            module => {
                this.entries.push(module);
            },
            // ...
        );
    }

    
    seal(callback) {
        this.hooks.seal.call();

        // ...
        const chunk = this.addChunk(name);
        const entrypoint = new Entrypoint(name);
        entrypoint.setRuntimeChunk(chunk);
        entrypoint.addOrigin(null, name, preparedEntrypoint.request);
        this.namedChunkGroups.set(name, entrypoint);
        this.entrypoints.set(name, entrypoint);
        this.chunkGroups.push(entrypoint);

        GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk);
        GraphHelpers.connectChunkAndModule(chunk, module);

        chunk.entryModule = module;
        chunk.name = name;

         // ...
        this.hooks.beforeHash.call();
        this.createHash();
        this.hooks.afterHash.call();
        this.hooks.beforeModuleAssets.call();
        this.createModuleAssets();
        if (this.hooks.shouldGenerateChunkAssets.call() !== false) {
            this.hooks.beforeChunkAssets.call();
            this.createChunkAssets();
        }
        // ...
    }


    createHash() {
        // ...
    }
    
    // 天生 assets 资本并 保存到 Compilation.assets 中 给webpack写插件的时刻会用到
    createModuleAssets() {
        for (let i = 0; i < this.modules.length; i++) {
            const module = this.modules[i];
            if (module.buildInfo.assets) {
                for (const assetName of Object.keys(module.buildInfo.assets)) {
                    const fileName = this.getPath(assetName);
                    this.assets[fileName] = module.buildInfo.assets[assetName]; 
                    this.hooks.moduleAsset.call(module, fileName);
                }
            }
        }
    }

    createChunkAssets() {
     // ...
    }
}

在webpack make钩子中, tapAsync注册了一个DllEntryPlugin, 就是将进口模块经由过程挪用compilation.addEntry要领将一切的进口模块增加到编译构建行列中,开启编译流程。

compiler.hooks.make.tapAsync("DllEntryPlugin", (compilation, callback) => {
        compilation.addEntry(
            this.context,
            new DllEntryDependency(
                this.entries.map((e, idx) => {
                    const dep = new SingleEntryDependency(e);
                    dep.loc = `${this.name}:${idx}`;
                    return dep;
                }),
                this.name
            ),
            // ...
        );
    });

随后在addEntry 中挪用_addModuleChain最先编译。在_addModuleChain起首会天生模块,末了构建。

class NormalModuleFactory extends Tapable {
    // ...
    create(data, callback) {
        // ...
        this.hooks.beforeResolve.callAsync(
            {
                contextInfo,
                resolveOptions,
                context,
                request,
                dependencies
            },
            (err, result) => {
                if (err) return callback(err);

                // Ignored
                if (!result) return callback();
                // factory 钩子会触发 resolver 钩子实行,而resolver钩子中会应用acorn 处置惩罚js天生AST,再应用acorn处置惩罚前,会运用loader加载文件
                const factory = this.hooks.factory.call(null);

                factory(result, (err, module) => {
                    if (err) return callback(err);

                    if (module && this.cachePredicate(module)) {
                        for (const d of dependencies) {
                            d.__NormalModuleFactoryCache = module;
                        }
                    }

                    callback(null, module);
                });
            }
        );
    }
}

在编译完成后,挪用compilation.seal要领关闭,天生资本,这些资本保存在compilation.assets, compilation.chunk, 在给webpack写插件的时刻会用到

class Compiler extends Tapable {
    constructor(context) {
        super();
        this.hooks = {
            beforeRun: new AsyncSeriesHook(["compilation"]),
            run: new AsyncSeriesHook(["compilation"]),
            emit: new AsyncSeriesHook(["compilation"]),
            afterEmit: new AsyncSeriesHook(["compilation"]),
            compilation: new SyncHook(["compilation", "params"]),
            beforeCompile: new AsyncSeriesHook(["params"]),
            compile: new SyncHook(["params"]),
            make: new AsyncParallelHook(["compilation"]),
            afterCompile: new AsyncSeriesHook(["compilation"]),
            // other hooks
        };
        // ...
    }

    run(callback) {
        const startTime = Date.now();

        const onCompiled = (err, compilation) => {
            // ...

            this.emitAssets(compilation, err => {
                if (err) return callback(err);

                if (compilation.hooks.needAdditionalPass.call()) {
                    compilation.needAdditionalPass = true;

                    const stats = new Stats(compilation);
                    stats.startTime = startTime;
                    stats.endTime = Date.now();
                    this.hooks.done.callAsync(stats, err => {
                        if (err) return callback(err);

                        this.hooks.additionalPass.callAsync(err => {
                            if (err) return callback(err);
                            this.compile(onCompiled);
                        });
                    });
                    return;
                }
                // ...
            });
        };

        this.hooks.beforeRun.callAsync(this, err => {
            if (err) return callback(err);
            this.hooks.run.callAsync(this, err => {
                if (err) return callback(err);

                this.readRecords(err => {
                    if (err) return callback(err);

                    this.compile(onCompiled);
                });
            });
        });
    }
    // 输出文件到构建目次
    emitAssets(compilation, callback) {
        // ...
        this.hooks.emit.callAsync(compilation, err => {
            if (err) return callback(err);
            outputPath = compilation.getPath(this.outputPath);
            this.outputFileSystem.mkdirp(outputPath, emitFiles);
        });
    }
    
    newCompilationParams() {
        const params = {
            normalModuleFactory: this.createNormalModuleFactory(),
            contextModuleFactory: this.createContextModuleFactory(),
            compilationDependencies: new Set()
        };
        return params;
    }

    compile(callback) {
        const params = this.newCompilationParams();
        this.hooks.beforeCompile.callAsync(params, err => {
            if (err) return callback(err);
            this.hooks.compile.call(params);
            const compilation = this.newCompilation(params);

            this.hooks.make.callAsync(compilation, err => {
                if (err) return callback(err);
                compilation.finish();
                // make 钩子实行后,挪用seal天生资本
                compilation.seal(err => {
                    if (err) return callback(err);
                    this.hooks.afterCompile.callAsync(compilation, err => {
                        if (err) return callback(err);
                        // emit, 天生终究文件
                        return callback(null, compilation);
                    });
                });
            });
        });
    }
}

末了输出

seal实行后,便会挪用emit钩子,依据webpack config文件的output设置的path属性,将文件输出到指定的path.

末了

腾讯IVWEB团队的工程化解决方案feflow已开源:Github主页:https://github.com/feflow/feflow

假如对您的团队也许项目有协助,请给个Star支撑一下哈~

    原文作者:ixlei
    原文地址: https://segmentfault.com/a/1190000013657042
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞