国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

webpack詳解

lcodecorex / 3077人閱讀

摘要:但也是最復(fù)雜的一個(gè)。中當(dāng)一旦某個(gè)返回值結(jié)果不為便結(jié)束執(zhí)行列表中的插件中上一個(gè)插件執(zhí)行結(jié)果當(dāng)作下一個(gè)插件的入?yún)⒄{(diào)用并行執(zhí)行插件流程篇本文關(guān)于的流程講解是基于的。

webpack是現(xiàn)代前端開發(fā)中最火的模塊打包工具,只需要通過簡(jiǎn)單的配置,便可以完成模塊的加載和打包。那它是怎么做到通過對(duì)一些插件的配置,便可以輕松實(shí)現(xiàn)對(duì)代碼的構(gòu)建呢?

webpack的配置
const path = require("path");
module.exports = {
  entry: "./app/entry", // string | object | array
  // Webpack打包的入口
  output: {  // 定義webpack如何輸出的選項(xiàng)
    path: path.resolve(__dirname, "dist"), // string
    // 所有輸出文件的目標(biāo)路徑
    filename: "[chunkhash].js", // string
    // 「入口(entry chunk)」文件命名模版
    publicPath: "/assets/", // string
    // 構(gòu)建文件的輸出目錄
    /* 其它高級(jí)配置 */
  },
  module: {  // 模塊相關(guān)配置
    rules: [ // 配置模塊loaders,解析規(guī)則
      {
        test: /.jsx?$/,  // RegExp | string
        include: [ // 和test一樣,必須匹配選項(xiàng)
          path.resolve(__dirname, "app")
        ],
        exclude: [ // 必不匹配選項(xiàng)(優(yōu)先級(jí)高于test和include)
          path.resolve(__dirname, "app/demo-files")
        ],
        loader: "babel-loader", // 模塊上下文解析
        options: { // loader的可選項(xiàng)
          presets: ["es2015"]
        },
      },
  },
  resolve: { //  解析模塊的可選項(xiàng)
    modules: [ // 模塊的查找目錄
      "node_modules",
      path.resolve(__dirname, "app")
    ],
    extensions: [".js", ".json", ".jsx", ".css"], // 用到的文件的擴(kuò)展
    alias: { // 模塊別名列表
      "module": "new-module"
      },
  },
  devtool: "source-map", // enum
  // 為瀏覽器開發(fā)者工具添加元數(shù)據(jù)增強(qiáng)調(diào)試
  plugins: [ // 附加插件列表
    // ...
  ],
}

從上面我們可以看到,webpack配置中需要理解幾個(gè)核心的概念EntryOutputLoadersPluginsChunk

Entry:指定webpack開始構(gòu)建的入口模塊,從該模塊開始構(gòu)建并計(jì)算出直接或間接依賴的模塊或者庫

Output:告訴webpack如何命名輸出的文件以及輸出的目錄

Loaders:由于webpack只能處理javascript,所以我們需要對(duì)一些非js文件處理成webpack能夠處理的模塊,比如sass文件

Plugins:Loaders將各類型的文件處理成webpack能夠處理的模塊,plugins有著很強(qiáng)的能力。插件的范圍包括,從打包優(yōu)化和壓縮,一直到重新定義環(huán)境中的變量。但也是最復(fù)雜的一個(gè)。比如對(duì)js文件進(jìn)行壓縮優(yōu)化的UglifyJsPlugin插件

Chunk:coding split的產(chǎn)物,我們可以對(duì)一些代碼打包成一個(gè)多帶帶的chunk,比如某些公共模塊,去重,更好的利用緩存。或者按需加載某些功能模塊,優(yōu)化加載時(shí)間。在webpack3及以前我們都利用CommonsChunkPlugin將一些公共代碼分割成一個(gè)chunk,實(shí)現(xiàn)多帶帶加載。在webpack4 中CommonsChunkPlugin被廢棄,使用SplitChunksPlugin

webpack詳解

讀到這里,或許你對(duì)webpack有一個(gè)大概的了解,那webpack 是怎么運(yùn)行的呢?我們都知道,webpack是高度復(fù)雜抽象的插件集合,理解webpack的運(yùn)行機(jī)制,對(duì)于我們?nèi)粘6ㄎ粯?gòu)建錯(cuò)誤以及寫一些插件處理構(gòu)建任務(wù)有很大的幫助。

不得不說的tapable

webpack本質(zhì)上是一種事件流的機(jī)制,它的工作流程就是將各個(gè)插件串聯(lián)起來,而實(shí)現(xiàn)這一切的核心就是Tapable,webpack中最核心的負(fù)責(zé)編譯的Compiler和負(fù)責(zé)創(chuàng)建bundles的Compilation都是Tapable的實(shí)例。在Tapable1.0之前,也就是webpack3及其以前使用的Tapable,提供了包括

plugin(name:string, handler:function)注冊(cè)插件到Tapable對(duì)象中

apply(…pluginInstances: (AnyPlugin|function)[])調(diào)用插件的定義,將事件監(jiān)聽器注冊(cè)到Tapable實(shí)例注冊(cè)表中

applyPlugins*(name:string, …)多種策略細(xì)致地控制事件的觸發(fā),包括applyPluginsAsyncapplyPluginsParallel等方法實(shí)現(xiàn)對(duì)事件觸發(fā)的控制,實(shí)現(xiàn)

(1)多個(gè)事件連續(xù)順序執(zhí)行
(2)并行執(zhí)行
(3)異步執(zhí)行
(4)一個(gè)接一個(gè)地執(zhí)行插件,前面的輸出是后一個(gè)插件的輸入的瀑布流執(zhí)行順序
(5)在允許時(shí)停止執(zhí)行插件,即某個(gè)插件返回了一個(gè)undefined的值,即退出執(zhí)行
我們可以看到,Tapable就像nodejs中EventEmitter,提供對(duì)事件的注冊(cè)on和觸發(fā)emit,理解它很重要,看個(gè)栗子:比如我們來寫一個(gè)插件

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

在webpack的生命周期中會(huì)適時(shí)的執(zhí)行

this.apply*("emit",options)

當(dāng)然上面提到的Tapable都是1.0版本之前的,如果想深入學(xué)習(xí),可以查看Tapable 和 事件流
那1.0的Tapable又是什么樣的呢?1.0版本發(fā)生了巨大的改變,不再是此前的通過plugin注冊(cè)事件,通過applyPlugins*觸發(fā)事件調(diào)用,那1.0的Tapable是什么呢?

暴露出很多的鉤子,可以使用它們?yōu)椴寮?chuàng)建鉤子函數(shù)
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
        })
    }
}

對(duì)于所有的hook的構(gòu)造函數(shù)均接受一個(gè)可選的string類型的數(shù)組

const hook = new SyncHook(["arg1", "arg2", "arg3"]);
// 調(diào)用tap方法注冊(cè)一個(gè)consument
order.hooks.goods.tap("QueryPlugin", (goodsId, number) => {
    return fetchGoods(goodsId, number);
})
// 再添加一個(gè)
order.hooks.goods.tap("LoggerPlugin", (goodsId, number) => {
    logger(goodsId, number);
})

// 調(diào)用
order.queryGoods("10000000", 1)

對(duì)于一個(gè) SyncHook,我們通過tap來添加消費(fèi)者,通過call來觸發(fā)鉤子的順序執(zhí)行。

對(duì)于一個(gè)非sync*類型的鉤子,即async*類型的鉤子,我們還可以通過其它方式注冊(cè)消費(fèi)者和調(diào)用

// 注冊(cè)一個(gè)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();
})

// 調(diào)用
// 返回Promise
order.consumerInfoPromise("user007", "1024");

//回調(diào)函數(shù)
order.consumerInfoAsync("user007", "1024")

通過上面的栗子,你可能已經(jīng)大致了解了Tapable的用法,它的用法

插件注冊(cè)數(shù)量

插件注冊(cè)的類型(sync, async, promise)

調(diào)用的方式(sync, async, promise)

實(shí)例鉤子的時(shí)候參數(shù)數(shù)量

是否使用了interception

Tapable詳解


對(duì)于Sync*類型的鉤子來說。

注冊(cè)在該鉤子下面的插件的執(zhí)行順序都是順序執(zhí)行。

只能使用tap注冊(cè),不能使用tapPromisetapAsync注冊(cè)

// 所有的鉤子都繼承于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) { // 編譯代碼來按照一定的策略執(zhí)行Plugin
        factory.setup(this, options);
        return factory.create(options);
    }
}

對(duì)于Async*類型鉤子

支持taptapPromisetapAsync注冊(cè)

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; // 實(shí)例鉤子的時(shí)候的string類型的數(shù)組
        this.taps = []; // 消費(fèi)者
        this.interceptors = []; // interceptors
        this.call = this._call =  // 以sync類型方式來調(diào)用鉤子
        this._createCompileDelegate("call", "sync");
        this.promise = 
        this._promise = // 以promise方式
        this._createCompileDelegate("promise", "promise");
        this.callAsync = 
        this._callAsync = // 以async類型方式來調(diào)用
        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;
    }
    // 調(diào)用tap 類型注冊(cè)
    tap(options, fn) {
        // ...
        options = Object.assign({ type: "sync", fn: fn }, options);
        // ...
        this._insert(options);  // 添加到 this.taps中
    }
    // 注冊(cè) async類型的鉤子
    tapAsync(options, fn) {
        // ...
        options = Object.assign({ type: "async", fn: fn }, options);
        // ...
        this._insert(options); // 添加到 this.taps中
    }
    注冊(cè) promise類型鉤子
    tapPromise(options, fn) {
        // ...
        options = Object.assign({ type: "promise", fn: fn }, options);
        // ...
        this._insert(options); // 添加到 this.taps中
    }
    
}

每次都是調(diào)用taptapSynctapPromise注冊(cè)不同類型的插件鉤子,通過調(diào)用callcallAsyncpromise方式調(diào)用。其實(shí)調(diào)用的時(shí)候?yàn)榱税凑找欢ǖ膱?zhí)行策略執(zhí)行,調(diào)用compile方法快速編譯出一個(gè)方法來執(zhí)行這些插件。

const factory = new Sync*CodeFactory();
class Sync* extends Hook { 
    // ...
    compile(options) { // 編譯代碼來按照一定的策略執(zhí)行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中調(diào)用HookCodeFactory#create方法編譯生成執(zhí)行代碼。

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

    create(options) {
        this.init(options);
        switch(this.options.type) {
            case "sync":  // 編譯生成sync, 結(jié)果直接返回
                return new Function(this.args(), 
                ""use strict";
" + this.header() + this.content({
                    // ...
                    onResult: result => `return ${result};
`,
                    // ...
                }));
            case "async": // async類型, 異步執(zhí)行,最后將調(diào)用插件執(zhí)行結(jié)果來調(diào)用callback,
                return new Function(this.args({
                    after: "_callback"
                }), ""use strict";
" + this.header() + this.content({
                    // ...
                    onResult: result => `_callback(null, ${result});
`,
                    onDone: () => "_callback();
"
                }));
            case "promise": // 返回promise類型,將結(jié)果放在resolve中
                // ...
                code += "return new Promise((_resolve, _reject) => {
";
                code += "var _sync = true;
";
                code += this.header();
                code += this.content({
                    // ...
                    onResult: result => `_resolve(${result});
`,
                    onDone: () => "_resolve();
"
                });
                // ...
                return new Function(this.args(), code);
        }
    }
    // callTap 就是執(zhí)行一些插件,并將結(jié)果返回
    callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
        let code = "";
        let hasTapCached = false;
        // ...
        code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};
`;
        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
                    })});
`;
                } else {
                    code += `_fn${tapIndex}(${this.args({
                        before: tap.context ? "_context" : undefined
                    })});
`;
                }
                
                if(onResult) { // 結(jié)果透?jìng)?                    code += onResult(`_result${tapIndex}`);
                }
                if(onDone) { // 通知插件執(zhí)行完畢,可以執(zhí)行下一個(gè)插件
                    code += onDone();
                }
                break;
            case "async": //異步執(zhí)行,插件運(yùn)行完后再將結(jié)果通過執(zhí)行callback透?jìng)?                let cbCode = "";
                if(onResult)
                    cbCode += `(_err${tapIndex}, _result${tapIndex}) => {
`;
                else
                    cbCode += `_err${tapIndex} => {
`;
                cbCode += `if(_err${tapIndex}) {
`;
                cbCode += onError(`_err${tapIndex}`);
                cbCode += "} else {
";
                if(onResult) {
                    cbCode += onResult(`_result${tapIndex}`);
                }
                
                cbCode += "}
";
                cbCode += "}";
                code += `_fn${tapIndex}(${this.args({
                    before: tap.context ? "_context" : undefined,
                    after: cbCode //cbCode將結(jié)果透?jìng)?                })});
`;
                break;
            case "promise": // _fn${tapIndex} 就是第tapIndex 個(gè)插件,它必須是個(gè)Promise類型的插件
                code += `var _hasResult${tapIndex} = false;
`;
                code += `_fn${tapIndex}(${this.args({
                    before: tap.context ? "_context" : undefined
                })}).then(_result${tapIndex} => {
`;
                code += `_hasResult${tapIndex} = true;
`;
                if(onResult) {
                    code += onResult(`_result${tapIndex}`);
                }
            // ...
                break;
        }
        return code;
    }
    // 按照插件的注冊(cè)順序,按照順序遞歸調(diào)用執(zhí)行插件
    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 = () => {
";
            code += "var _loopAsync = false;
";
        }
        code += "var _loop;
";
        code += "do {
";
        code += "_loop = false;
";
        // ...
        code += this.callTapsSeries({
            // ...
            onResult: (i, result, next, doneBreak) => { // 一旦某個(gè)插件返回不為undefined,  即一只調(diào)用某個(gè)插件執(zhí)行,如果為undefined,開始調(diào)用下一個(gè)
                let code = "";
                code += `if(${result} !== undefined) {
`;
                code += "_loop = true;
";
                if(!syncOnly)
                    code += "if(_loopAsync) _looper();
";
                code += doneBreak(true);
                code += `} else {
`;
                code += next();
                code += `}
`;
                return code;
            },
            // ...
        })
        code += "} while(_loop);
";
        // ...
        return code;
    }
    // 并行調(diào)用插件執(zhí)行
    callTapsParallel({ onError, onResult, onDone, rethrowIfPossible, onTap = (i, run) => run() }) {
        // ...
        // 遍歷注冊(cè)都所有插件,并調(diào)用
        for(let i = 0; i < this.options.taps.length; i++) {
            // ...
            code += "if(_counter <= 0) break;
";
            code += onTap(i, () => this.callTap(i, {
                // ...
                onResult: onResult && ((result) => {
                    let code = "";
                    code += "if(_counter > 0) {
";
                    code += onResult(i, result, done, doneBreak);
                    code += "}
";
                    return code;
                }),
                // ...
            }), done, doneBreak);
        }
        // ...
        return code;
    }
}

HookCodeFactory#create中調(diào)用到content方法,此方法將按照此鉤子的執(zhí)行策略,調(diào)用不同的方法來執(zhí)行編譯 生成最終的代碼。

SyncHook中調(diào)用callTapsSeries編譯生成最終執(zhí)行插件的函數(shù),callTapsSeries做的就是將插件列表中插件按照注冊(cè)順序遍歷執(zhí)行。

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

SyncBailHook中當(dāng)一旦某個(gè)返回值結(jié)果不為undefined便結(jié)束執(zhí)行列表中的插件

 class SyncBailHookCodeFactory extends HookCodeFactory {
    content({ onError, onResult, onDone, rethrowIfPossible }) {
        return this.callTapsSeries({
            // ...
            onResult: (i, result, next) => `if(${result} !== undefined) {
${onResult(result)};
} else {
${next()}}
`,
            // ...
        });
    }
}

SyncWaterfallHook中上一個(gè)插件執(zhí)行結(jié)果當(dāng)作下一個(gè)插件的入?yún)?/p>

class SyncWaterfallHookCodeFactory extends HookCodeFactory {
    content({ onError, onResult, onDone, rethrowIfPossible }) {
        return this.callTapsSeries({
            // ...
            onResult: (i, result, next) => {
                let code = "";
                code += `if(${result} !== undefined) {
`;
                code += `${this._args[0]} = ${result};
`;
                code += `}
`;
                code += next();
                return code;
            },
            onDone: () => onResult(this._args[0]),
        });
    }
}

AsyncParallelHook調(diào)用callTapsParallel并行執(zhí)行插件

class AsyncParallelHookCodeFactory extends HookCodeFactory {
    content({ onError, onDone }) {
        return this.callTapsParallel({
            onError: (i, err, done, doneBreak) => onError(err) + doneBreak(true),
            onDone
        });
    }
}
webpack流程篇

本文關(guān)于webpack 的流程講解是基于webpack4的。

webpack 入口文件

從webpack項(xiàng)目的package.json文件中我們找到了入口執(zhí)行函數(shù),在函數(shù)中引入webpack,那么入口將會(huì)是lib/webpack.js,而如果在shell中執(zhí)行,那么將會(huì)走到./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) => {
    // ...
    // 驗(yàn)證options正確性
    // 預(yù)處理options
    options = new WebpackOptionsDefaulter().process(options); // webpack4的默認(rèn)配置
    compiler = new Compiler(options.context); // 實(shí)例Compiler
    // ...
    // 若options.watch === true && callback 則開啟watch線程
    compiler.watch(watchOptions, callback);
    compiler.run(callback);
    return compiler;
};

webpack 的入口文件其實(shí)就實(shí)例了Compiler并調(diào)用了run方法開啟了編譯,webpack的編譯都按照下面的鉤子調(diào)用順序執(zhí)行。

before-run 清除緩存

run 注冊(cè)緩存數(shù)據(jù)鉤子

before-compile

compile 開始編譯

make 從入口分析依賴以及間接依賴模塊,創(chuàng)建模塊對(duì)象

build-module 模塊構(gòu)建

seal 構(gòu)建結(jié)果封裝, 不可再更改

after-compile 完成構(gòu)建,緩存數(shù)據(jù)

emit 輸出到dist目錄

編譯&構(gòu)建流程

webpack中負(fù)責(zé)構(gòu)建和編譯都是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)
        };

        // 構(gòu)建生成的資源
        this.chunks = [];
        this.chunkGroups = [];
        this.modules = [];
        this.additionalChunkAssets = [];
        this.assets = {};
        this.children = [];
        // ...
    }
    // 
    buildModule(module, optional, origin, dependencies, thisCallback) {
        // ...
        // 調(diào)用module.build方法進(jìn)行編譯代碼,build中 其實(shí)是利用acorn編譯生成AST
        this.hooks.buildModule.call(module);
        module.build(/**param*/);
    }
    // 將模塊添加到列表中,并編譯模塊
    _addModuleChain(context, dependency, onModule, callback) {
            // ...
            // moduleFactory.create創(chuàng)建模塊,這里會(huì)先利用loader處理文件,然后生成模塊對(duì)象
            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;
                    
                    // ...
                    // 調(diào)用buildModule編譯模塊
                    this.buildModule(module, false, null, null, err => {});
                }
        });
    }
    // 添加入口模塊,開始編譯&構(gòu)建
    addEntry(context, entry, name, callback) {
        // ...
        this._addModuleChain( // 調(diào)用_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寫插件的時(shí)候會(huì)用到
    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注冊(cè)了一個(gè)DllEntryPlugin, 就是將入口模塊通過調(diào)用compilation.addEntry方法將所有的入口模塊添加到編譯構(gòu)建隊(duì)列中,開啟編譯流程。

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 中調(diào)用_addModuleChain開始編譯。在_addModuleChain首先會(huì)生成模塊,最后構(gòu)建。

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 鉤子會(huì)觸發(fā) resolver 鉤子執(zhí)行,而resolver鉤子中會(huì)利用acorn 處理js生成AST,再利用acorn處理前,會(huì)使用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);
                });
            }
        );
    }
}

在編譯完成后,調(diào)用compilation.seal方法封閉,生成資源,這些資源保存在compilation.assets, compilation.chunk, 在給webpack寫插件的時(shí)候會(huì)用到

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);
                });
            });
        });
    }
    // 輸出文件到構(gòu)建目錄
    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 鉤子執(zhí)行后,調(diào)用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執(zhí)行后,便會(huì)調(diào)用emit鉤子,根據(jù)webpack config文件的output配置的path屬性,將文件輸出到指定的path.

最后

騰訊IVWEB團(tuán)隊(duì)的工程化解決方案feflow已經(jīng)開源:Github主頁:https://github.com/feflow/feflow

如果對(duì)您的團(tuán)隊(duì)或者項(xiàng)目有幫助,請(qǐng)給個(gè)Star支持一下哈~

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/93357.html

相關(guān)文章

  • [js高手之路]深入淺出webpack教程系列2-配置文件webpack.config.js詳解(上

    摘要:接著上文,重新在文件夾下面新建一個(gè)項(xiàng)目文件夾,然后用初始化項(xiàng)目的配置文件,然后安裝,然后創(chuàng)建基本的項(xiàng)目文件夾結(jié)構(gòu),好了,我們的又一個(gè)基本項(xiàng)目結(jié)構(gòu)就搭建好了第一開始通過文件配置我們的項(xiàng)目首先在項(xiàng)目文件夾下面,新建一個(gè)文件,這個(gè)文件可 接著上文,重新在webpack文件夾下面新建一個(gè)項(xiàng)目文件夾demo2,然后用npm init --yes初始化項(xiàng)目的package.json配置文件,然后安...

    moven_j 評(píng)論0 收藏0
  • [js高手之路]深入淺出webpack教程系列3-配置文件webpack.config.js詳解(下

    摘要:本文繼續(xù)接著上文,繼續(xù)寫下的其他配置用法一把兩個(gè)文件打包成一個(gè),怎么配置在上文中的中,用數(shù)組配置文件代碼,就是當(dāng)前文件所在的絕對(duì)路徑輸出路徑,要用絕對(duì)路徑打包之后輸出的文件名然后在目錄下面新建一個(gè)文件,代碼如下之前的文件的代碼告訴你怎么學(xué)習(xí) 本文繼續(xù)接著上文,繼續(xù)寫下webpack.config.js的其他配置用法. 一、把兩個(gè)文件打包成一個(gè),entry怎么配置? 在上文中的webpa...

    xiangchaobin 評(píng)論0 收藏0
  • 詳解Webpack+Babel+React開發(fā)環(huán)境的搭建

    摘要:安裝要開始使用在項(xiàng)目中進(jìn)行開發(fā)前我們首先需要在全局環(huán)境中進(jìn)行安裝。使用它可以將的語法轉(zhuǎn)換為的語法,以便在現(xiàn)在有的環(huán)境執(zhí)行。,是一段正則,表示進(jìn)行匹配的資源類型。為指定應(yīng)該被忽略的文件,我們?cè)谶@兒指定了。 1.認(rèn)識(shí)Webpack 構(gòu)建應(yīng)用前我們先來了解一下Webpack, Webpack是一個(gè)模塊打包工具,能夠把各種文件(例如:ReactJS、Babel、Coffeescript、Les...

    yuanzhanghu 評(píng)論0 收藏0
  • webpack各配置詳解

    摘要:配置一些開發(fā)環(huán)境的提示工具例如當(dāng)項(xiàng)目中報(bào)錯(cuò)可以準(zhǔn)確的定位到哪個(gè)文件報(bào)錯(cuò)對(duì)比項(xiàng)構(gòu)建速度重新構(gòu)建速度代碼提示定位原始源代碼生成后的代碼可根據(jù)場(chǎng)景使用這兩個(gè)值調(diào)試代碼項(xiàng)目中配置引入文件的快捷路徑中導(dǎo)出的對(duì)象內(nèi)配置快捷方式名對(duì)應(yīng)的路 devtool: 配置一些開發(fā)環(huán)境的提示工具 例如: devtool: cheap-module-eval-source-map 當(dāng)項(xiàng)目中報(bào)錯(cuò)可以準(zhǔn)確的定位到哪個(gè)...

    enrecul101 評(píng)論0 收藏0
  • Webpack 配置詳解(含 4)——關(guān)注細(xì)節(jié)

    摘要:由于這種差異我們將對(duì)預(yù)處理器文件的配置封裝為函數(shù),由參數(shù)生成對(duì)應(yīng)配置,將文件放入文件內(nèi),將配置放在文件內(nèi)。 前言 源代碼 熟悉 webpack 與 webpack4 配置。 webpack4 相對(duì)于 3 的最主要的區(qū)別是所謂的零配置,但是為了滿足我們的項(xiàng)目需求還是要自己進(jìn)行配置,不過我們可以使用一些 webpack 的預(yù)設(shè)值。同時(shí) webpack 也拆成了兩部分,webpack 和 w...

    jsbintask 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<