Webpack についての深い理解

青灯夜游
リリース: 2020-07-11 16:46:37
転載
2179 人が閲覧しました

Webpack についての深い理解

Webpack は現在最も人気のあるパッケージング ツールの 1 つで、シンプルな構成、強力な機能、豊富なローダーとプラグイン システムを備えており、フロントエンド開発者に多くの利便性を提供します。著者は、この章を読む前に読者が webpack の使用経験があることを前提としているため、webpack の使用方法については詳しく説明しません。

この章を読むと、次のことを学ぶことができます:

  • Webpack パッケージ化されたコード構造
  • Webpack コア アーキテクチャ—タップ可能
  • Webpack イベント フロー
  • Webpack プラグイン実装メカニズム
  • Webpack ローダー実装メカニズム

Webpack パッケージ化されたコード構造

単純なパッケージ化最初に最も単純なメソッドを作成し、次にパッケージ化に Webpack を使用します:

// /webpack/bundles/simple/moduleA.js
window.printA = function printA() {
    console.log(`This is module A!`);
}
ログイン後にコピー
比較的基本的な Webpack 構成ファイル:

// /webpack/bundles/simple/webpack.config.js
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {
        main: './moduleA.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'simple.bundle.js'
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './index.html'
        })
    ]
}
ログイン後にコピー
Createブラウザ環境でテストするための HTML ファイル:

nbsp;html>


    <meta>
    <meta>
    <meta>
    <title>Webpack - Simple Bundle</title>


    

ログイン後にコピー
パッケージ化コマンド

webpack を実行した後、dist ディレクトリを取得し、simple.bundle を開きます。 js ファイル:

/******/ (function(modules) { // webpackBootstrap
/******/    // The module cache
/******/    var installedModules = {};
/******/
/******/    // The require function
/******/    function __webpack_require__(moduleId) {
/******/
/******/        // Check if module is in cache
/******/        if(installedModules[moduleId]) {
/******/            return installedModules[moduleId].exports;
/******/        }
/******/        // Create a new module (and put it into the cache)
/******/        var module = installedModules[moduleId] = {
/******/            i: moduleId,
/******/            l: false,
/******/            exports: {}
/******/        };
/******/
/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/        // Flag the module as loaded
/******/        module.l = true;
/******/
/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }
/******/
/******/
/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;
/******/
/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;
/******/
/******/    // define getter function for harmony exports
/******/    __webpack_require__.d = function(exports, name, getter) {
/******/        if(!__webpack_require__.o(exports, name)) {
/******/            Object.defineProperty(exports, name, {
/******/                configurable: false,
/******/                enumerable: true,
/******/                get: getter
/******/            });
/******/        }
/******/    };
/******/
/******/    // getDefaultExport function for compatibility with non-harmony modules
/******/    __webpack_require__.n = function(module) {
/******/        var getter = module && module.__esModule ?
/******/            function getDefault() { return module['default']; } :
/******/            function getModuleExports() { return module; };
/******/        __webpack_require__.d(getter, 'a', getter);
/******/        return getter;
/******/    };
/******/
/******/    // Object.prototype.hasOwnProperty.call
/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "";
/******/
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {

window.printA = function printA() {
    console.log(`This is module A!`);
}

/***/ })
/******/ ]);
ログイン後にコピー
主にこの段落を見てください:

// ......
var installedModules = {};
/******/
/******/    // The require function
/******/    function __webpack_require__(moduleId) {
/******/
/******/        // Check if module is in cache
/******/        if(installedModules[moduleId]) {
/******/            return installedModules[moduleId].exports;
/******/        }
/******/        // Create a new module (and put it into the cache)
/******/        var module = installedModules[moduleId] = {
/******/            i: moduleId,
/******/            l: false,
/******/            exports: {}
/******/        };
/******/
/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/        // Flag the module as loaded
/******/        module.l = true;
/******/
/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }
/******/
/******/
/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;
/******/
/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;
/******/
/******/    // define getter function for harmony exports
/******/    __webpack_require__.d = function(exports, name, getter) {
/******/        if(!__webpack_require__.o(exports, name)) {
/******/            Object.defineProperty(exports, name, {
/******/                configurable: false,
/******/                enumerable: true,
/******/                get: getter
/******/            });
/******/        }
/******/    };
/******/
/******/    // getDefaultExport function for compatibility with non-harmony modules
/******/    __webpack_require__.n = function(module) {
/******/        var getter = module && module.__esModule ?
/******/            function getDefault() { return module['default']; } :
/******/            function getModuleExports() { return module; };
/******/        __webpack_require__.d(getter, 'a', getter);
/******/        return getter;
/******/    };
/******/
/******/    // Object.prototype.hasOwnProperty.call
/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "";
/******/
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = 0);
// ......
ログイン後にコピー
webpack は内部的に

webpack_require メソッドを定義します。このメソッドの本質は非常にシンプルです:

Webpack についての深い理解

##複数のモジュール間に単純な依存関係があります

たとえば、moduleB.js は moduleA.js ファイルに依存します。

// /webpack/bundles/simpleDependencies/moduleA.js
module.exports = window.printA = function printA() {
    console.log(`This is module A!`);
}
ログイン後にコピー
// /webpack/bundles/simpleDependencies/moduleB.js
const printA = require('./moduleA');

module.exports = window.printB = function printB() {
    printA();
    console.log('This is module B!');
}
ログイン後にコピー

設定ファイルのエントリを
// /webpack/bundles/simpleDependencies/webpack.config.js
// ...
main: './moduleB.js'
// ...
ログイン後にコピー

Package に再度変更すると、次のコードが得られます:

// /webpack/bundles/simpleDependencies/dist/bundle.js
/******/ (function(modules) { // webpackBootstrap
/******/    // The module cache
/******/    var installedModules = {};
/******/
/******/    // The require function
/******/    function __webpack_require__(moduleId) {
/******/
/******/        // Check if module is in cache
/******/        if(installedModules[moduleId]) {
/******/            return installedModules[moduleId].exports;
/******/        }
/******/        // Create a new module (and put it into the cache)
/******/        var module = installedModules[moduleId] = {
/******/            i: moduleId,
/******/            l: false,
/******/            exports: {}
/******/        };
/******/
/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/        // Flag the module as loaded
/******/        module.l = true;
/******/
/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }
/******/
/******/
/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;
/******/
/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;
/******/
/******/    // define getter function for harmony exports
/******/    __webpack_require__.d = function(exports, name, getter) {
/******/        if(!__webpack_require__.o(exports, name)) {
/******/            Object.defineProperty(exports, name, {
/******/                configurable: false,
/******/                enumerable: true,
/******/                get: getter
/******/            });
/******/        }
/******/    };
/******/
/******/    // getDefaultExport function for compatibility with non-harmony modules
/******/    __webpack_require__.n = function(module) {
/******/        var getter = module && module.__esModule ?
/******/            function getDefault() { return module['default']; } :
/******/            function getModuleExports() { return module; };
/******/        __webpack_require__.d(getter, 'a', getter);
/******/        return getter;
/******/    };
/******/
/******/    // Object.prototype.hasOwnProperty.call
/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "";
/******/
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

const printA = __webpack_require__(1);

module.exports = window.printB = function printB() {
    printA();
    console.log('This is module B!');
}

/***/ }),
/* 1 */
/***/ (function(module, exports) {

module.exports = window.printA = function printA() {
    console.log(`This is module A!`);
}

/***/ })
/******/ ]);
ログイン後にコピー

この部分にはいくつかの変更が見つかります:

/* 0 */
/***/ (function(module, exports, __webpack_require__) {

const printA = __webpack_require__(1);

module.exports = window.printB = function printB() {
    printA();
    console.log('This is module B!');
}
ログイン後にコピー

moduleB.js では moduleA に依存する必要があるため、次のステップに進む前に、まず __webpack_require(1) を実行してモジュール A を取得する必要があります。

複数のエントリ

パッケージ化されたファイル内の moduleId は繰り返されないことに注意してください。エントリ ファイルが 2 つある場合、エントリ モジュール ID は 0 になり、それ以外の値になります。依存関係 モジュール ID は繰り返されません。次のファイルを作成します。このうち、index0.js は common.js と dependency.js に依存し、index1.js は 2 つのファイルindex0.js と common.js に依存します。

// /webpack/bundles/multi/common.js
module.exports = function() {
    console.log('This is common module!');
}
ログイン後にコピー
// /webpack/bundles/multi/dependency .js
module.exports = function() {
    console.log('This is dependency module!');
}
ログイン後にコピー
// /webpack/bundles/multi/index0.js
const common = require('./common');
const dependency = require('./dependency');

module.exports = window.print0 = function() {
    common();
    dependency();
    console.log('This is module 0!');
}
ログイン後にコピー
// /webpack/bundles/multi/index1.js
const common = require('./common');
const index0 = require('./index0');

module.exports = window.print1 = function() {
    common();
    console.log('This is module 1!');
}
ログイン後にコピー

webpack.config.js のファイル エントリを変更します:
// /webpack/bundles/multi/webpack.config.js
// ...
entry: {
    index0: './index0.js',
    index1: './index1.js'
},
output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js'
},
// ...
ログイン後にコピー

パッケージ化されたファイル:

// /webpack/bundles/multi/dist/index0.bundle.js
/******/ (function(modules) { // webpackBootstrap
/******/    // The module cache
/******/    var installedModules = {};
/******/
/******/    // The require function
/******/    function __webpack_require__(moduleId) {
/******/
/******/        // Check if module is in cache
/******/        if(installedModules[moduleId]) {
/******/            return installedModules[moduleId].exports;
/******/        }
/******/        // Create a new module (and put it into the cache)
/******/        var module = installedModules[moduleId] = {
/******/            i: moduleId,
/******/            l: false,
/******/            exports: {}
/******/        };
/******/
/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/        // Flag the module as loaded
/******/        module.l = true;
/******/
/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }
/******/
/******/
/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;
/******/
/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;
/******/
/******/    // define getter function for harmony exports
/******/    __webpack_require__.d = function(exports, name, getter) {
/******/        if(!__webpack_require__.o(exports, name)) {
/******/            Object.defineProperty(exports, name, {
/******/                configurable: false,
/******/                enumerable: true,
/******/                get: getter
/******/            });
/******/        }
/******/    };
/******/
/******/    // getDefaultExport function for compatibility with non-harmony modules
/******/    __webpack_require__.n = function(module) {
/******/        var getter = module && module.__esModule ?
/******/            function getDefault() { return module['default']; } :
/******/            function getModuleExports() { return module; };
/******/        __webpack_require__.d(getter, 'a', getter);
/******/        return getter;
/******/    };
/******/
/******/    // Object.prototype.hasOwnProperty.call
/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "";
/******/
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = 1);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {

module.exports = function() {
    console.log('This is common module!');
}

/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

const common = __webpack_require__(0);
const dependency = __webpack_require__(2);

module.exports = window.print0 = function() {
    common();
    dependency();
    console.log('This is module 0!');
}

/***/ }),
/* 2 */
/***/ (function(module, exports) {

module.exports = function() {
    console.log('This is dependency module!');
}

/***/ })
/******/ ]);
ログイン後にコピー
// /webpack/bundles/multi/dist/index1.bundle.js
/******/ (function(modules) { // webpackBootstrap
/******/    // The module cache
/******/    var installedModules = {};
/******/
/******/    // The require function
/******/    function __webpack_require__(moduleId) {
/******/
/******/        // Check if module is in cache
/******/        if(installedModules[moduleId]) {
/******/            return installedModules[moduleId].exports;
/******/        }
/******/        // Create a new module (and put it into the cache)
/******/        var module = installedModules[moduleId] = {
/******/            i: moduleId,
/******/            l: false,
/******/            exports: {}
/******/        };
/******/
/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/        // Flag the module as loaded
/******/        module.l = true;
/******/
/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }
/******/
/******/
/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;
/******/
/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;
/******/
/******/    // define getter function for harmony exports
/******/    __webpack_require__.d = function(exports, name, getter) {
/******/        if(!__webpack_require__.o(exports, name)) {
/******/            Object.defineProperty(exports, name, {
/******/                configurable: false,
/******/                enumerable: true,
/******/                get: getter
/******/            });
/******/        }
/******/    };
/******/
/******/    // getDefaultExport function for compatibility with non-harmony modules
/******/    __webpack_require__.n = function(module) {
/******/        var getter = module && module.__esModule ?
/******/            function getDefault() { return module['default']; } :
/******/            function getModuleExports() { return module; };
/******/        __webpack_require__.d(getter, 'a', getter);
/******/        return getter;
/******/    };
/******/
/******/    // Object.prototype.hasOwnProperty.call
/******/    __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "";
/******/
/******/    // Load entry module and return exports
/******/    return __webpack_require__(__webpack_require__.s = 3);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {

module.exports = function() {
    console.log('This is common module!');
}

/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

const common = __webpack_require__(0);
const dependency = __webpack_require__(2);

module.exports = window.print0 = function() {
    common();
    dependency();
    console.log('This is module 0!');
}

/***/ }),
/* 2 */
/***/ (function(module, exports) {

module.exports = function() {
    console.log('This is dependency module!');
}

/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {

const common = __webpack_require__(0);
const index0 = __webpack_require__(1);

module.exports = window.print1 = function() {
    common();
    console.log('This is module 1!');
}

/***/ })
/******/ ]);
ログイン後にコピー

明らかに、CommonsChunkPlugin プラグインを使用する前に、これら 2 つのコードには重複があります。ファイル。つまり、各入り口は個別にパッケージ化されます。

CommonsChunkPlugin プラグインを追加した後の状況を見てみましょう (webpack.config.js を変更):

// /webpack/bundles/CommonsChunkPlugin/webpack.config.js
plugins: [
    // ...
    new webpack.optimize.CommonsChunkPlugin({
        name: 'common',
        filename: 'common.js'
    })
]
ログイン後にコピー

このようにして、index0.bundle.js、index1.bundel の 3 つのファイルが生成されます。 .js そして common.js:
// /webpack/bundles/CommonsChunkPlugin/dist/common.js
/******/ (function(modules) { // webpackBootstrap
/******/    // install a JSONP callback for chunk loading
/******/    var parentJsonpFunction = window["webpackJsonp"];
/******/    window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
/******/        // add "moreModules" to the modules object,
/******/        // then flag all "chunkIds" as loaded and fire callback
/******/        var moduleId, chunkId, i = 0, resolves = [], result;
/******/        for(;i <p>common.js にはすべてのパブリック メソッドが含まれており、ブラウザ ウィンドウ オブジェクトに webpackJsonp という名前のメソッドが作成されています。 </p><pre class="brush:php;toolbar:false">// /webpack/bundles/CommonsChunkPlugin/dist/common.js
// ...
/******/    var parentJsonpFunction = window["webpackJsonp"];
/******/    window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
/******/        // add "moreModules" to the modules object,
/******/        // then flag all "chunkIds" as loaded and fire callback
/******/        var moduleId, chunkId, i = 0, resolves = [], result;
/******/        for(;i <p>このメソッドは、モジュールもキャッシュする __webpack_require__ に似ています。 Webpack がパブリック モジュールを事前に抽出してキャッシュし、その後、他の Bundle.js の webpackJsonp メソッドを使用してモジュールをロードできるだけです。 </p><pre class="brush:php;toolbar:false">// /webpack/bundles/CommonsChunkPlugin/dist/index0.bundle.js
webpackJsonp([1],[],[1]);
ログイン後にコピー
// /webpack/bundles/CommonsChunkPlugin/dist/index1.bundle.js
webpackJsonp([0],{

/***/ 3:
/***/ (function(module, exports, __webpack_require__) {

const common = __webpack_require__(0);
const index0 = __webpack_require__(1);

module.exports = window.print1 = function() {
    common();
    console.log('This is module 1!');
}

/***/ })

},[3]);
ログイン後にコピー

Webpack コア アーキテクチャ—タップ可能Webpack ソース コードを

github

からローカルにクローンします。まず、 webpack の全体的なプロセスを理解します。

Webpack についての深い理解

#lib/webpack.js はコンパイラ オブジェクトを返し、#lib/ で copyr.run()
    ## を呼び出します。 Compiler.js の run メソッドは、before-run と run の 2 つのイベントをトリガーし、readRecords を通じてファイルを読み取り、compile を通じてパッケージ化し、パッケージ化後に before-compile、compile、make およびその他のイベントをトリガーします。コンパイルがメイン プロセスです。 、このメソッド内で Compilation クラスがインスタンス化され、そのfinishメソッドとsealメソッドが呼び出されます。
  • lib/Compilation.js は、finish メソッドと seal メソッド、および重要なメソッド addEntry を定義します。このメソッドは、プライベート メソッド _addModuleChain を呼び出すことによって、モジュール タイプに応じた対応するモジュール ファクトリの取得とモジュールの作成、モジュールのビルドという 2 つのことを実行します。
  • lib/Compiler.js は、明示的に addEntry を呼び出しませんが、make イベントをトリガーします。lib/DllEntryPlugin.js は、make イベントをリッスンし、コールバック関数で addEntry を呼び出すプラグインです。
  • _addModuleChain の詳細な分析 (モジュールの構築で 2 番目に完了すること) は 3 つの部分に分割できます:

ローダーを呼び出してモジュール間の依存関係を処理します。

    ローダーによって処理されたファイルを抽象構文ツリー AST から acorn まで抽象化します。
  • AST を走査し、モジュールのすべての依存関係を構築します。
  • 特に、
  • webpack
のエントリ ファイルである

lib/webpack.js ファイルを確認してください。

const webpack = (options, callback) => {
    const webpackOptionsValidationErrors = validateSchema(
        webpackOptionsSchema,
        options
    );
    if (webpackOptionsValidationErrors.length) {
        throw new WebpackOptionsValidationError(webpackOptionsValidationErrors);
    }
    let compiler;
    if (Array.isArray(options)) {
        compiler = new MultiCompiler(options.map(options => webpack(options)));
    } else if (typeof options === "object") {
        options = new WebpackOptionsDefaulter().process(options);

        compiler = new Compiler(options.context);
        compiler.options = options;
        new NodeEnvironmentPlugin().apply(compiler);
        if (options.plugins && Array.isArray(options.plugins)) {
            for (const plugin of options.plugins) {
                plugin.apply(compiler);
            }
        }
        compiler.hooks.environment.call();
        compiler.hooks.afterEnvironment.call();
        compiler.options = new WebpackOptionsApply().process(options, compiler);
    } else {
        throw new Error("Invalid argument: options");
    }
    if (callback) {
        if (typeof callback !== "function")
            throw new Error("Invalid argument: callback");
        if (
            options.watch === true ||
            (Array.isArray(options) && options.some(o => o.watch))
        ) {
            const watchOptions = Array.isArray(options)
                ? options.map(o => o.watchOptions || {})
                : options.watchOptions || {};
            return compiler.watch(watchOptions, callback);
        }
        compiler.run(callback);
    }
    return compiler;
};
ログイン後にコピー
lib/webpack.js プロセスは大まかに次のとおりです。

  • 参数验证
  • 创建 Compiler (编译器)对象
  • 注册并执行 NodeEnvironmentPlugin
  • 执行钩子 environment 里的方法
  • 执行钩子 afterEnvironment 里的方法
  • 注册并执行各种插件
  • compiler 向外导出

显然,Compiler是我们需要深究的一个部分,因为 webpack 最终向外部返回也就是这个 Compiler 实例。大致了解下 Compiler 的实现:

class Compiler extends Tapable {
    constructor(context) {
        super();
        this.hooks = {
            // ...
        };
        this._pluginCompat.tap("Compiler", options => {
            // ...
        });
        // ... 
        this.resolvers = {
            normal: {
                // ...
            },
            loader: {
                // ...
            },
            context: {
                // ...
            }
        };
        // ...
    }
    watch(watchOptions, handler) {
        // ...
    }
    run(callback) {
        // ...
    }
    runAsChild(callback) {
        // ...
    }
    purgeInputFileSystem() {
        // ...
    }
    emitAssets(compilation, callback) {
        // ...
    }
    emitRecords(callback) {
        // ...
    }
    readRecords(callback) {
        // ...
    }
    createChildCompiler(
        compilation,
        compilerName,
        compilerIndex,
        outputOptions,
        plugins
    ) {
        // ...
    }
    isChild() {
        // ...
    }
    createCompilation() {
        // ...
    }
    newCompilation(params) {
        // ...
    }
    createNormalModuleFactory() {
        // ...
    }
    createContextModuleFactory() {
        // ...
    }
    newCompilationParams() {
        // ...
    }
    compile(callback) {
        // ...
    }
}
ログイン後にコピー

Compiler 继承自 Tapable,在其构造方法中,定义了一些事件钩子(hooks)、一些变量以及一些方法。这些变量以及方法目前看来还是非常抽象的,所以我们有必要去了解下 Tapable 的实现。

Tapable的Github主页 对 Tapable 的介绍如下:

  • The tapable packages exposes many Hook classes, which can be used to create hooks for plugins.

实际上,webpack基于事件流机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的Compiler和负责创建bundles的Compilation都是Tapable的实例。Tapable 向外暴露许多的钩子类,这些类可以很方便地为插件创建事件钩子。 Tapable 中定义了如下几种钩子类:

  • SyncHook
  • SyncBailHook
  • SyncWaterfallHook
  • SyncLoopHook
  • AsyncParallelHook
  • AsyncParallelBailHook
  • AsyncSeriesHook
  • AsyncSeriesBailHook
  • AsyncSeriesWaterfallHook

所有钩子类的构造函数都接收一个可选的参数,这个参数是一个由字符串参数组成的数组,如下:

const hook = new SyncHook(["arg1", "arg2", "arg3"]);
ログイン後にコピー

钩子概览

Tapable的钩子分为两类,同步和异步,其中异步又分为并行和串行:

Webpack についての深い理解

每种钩子都有各自的使用方式,如下表:

序号 钩子名 执行方式 使用要点
1 SyncHook 同步串行 不关心监听函数的返回值
2 SyncBailHook 同步串行 只要监听函数中有一个函数的返回值不为 null,则跳过剩下所有的逻辑
3 SyncWaterfallHook 同步串行 上一个监听函数的返回值可以传给下一个监听函数
4 SyncLoopHook 同步循环 当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环
5 AsyncParallelHook 异步并发 不关心监听函数的返回值
6 AsyncParallelBailHook 异步并发 只要监听函数的返回值不为 null,就会忽略后面的监听函数执行,直接跳跃到callAsync等触发函数绑定的回调函数,然后执行这个被绑定的回调函数
7 AsyncSeriesHook 异步串行 不关系callback()的参数
8 AsyncSeriesBailHook 异步串行 callback()的参数不为null,就会直接执行callAsync等触发函数绑定的回调函数
9 AsyncSeriesWaterfallHook 异步串行 上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数

Sync钩子

同步串行
(1) SyncHook
不关心监听函数的返回值

  • 使用
const { SyncHook } = require("tapable");
let queue = new SyncHook(['name']); //所有的构造函数都接收一个可选的参数,这个参数是一个字符串的数组。

// 订阅
queue.tap('1', function (name, name2) {// tap 的第一个参数是用来标识订阅的函数的
    console.log(name, name2, 1);
    return '1'
});
queue.tap('2', function (name) {
    console.log(name, 2);
});
queue.tap('3', function (name) {
    console.log(name, 3);
});

// 发布
queue.call('webpack', 'webpack-cli');// 发布的时候触发订阅的函数 同时传入参数

// 执行结果:
/*
webpack undefined 1 // 传入的参数需要和new实例的时候保持一致,否则获取不到多传的参数
webpack 2
webpack 3
*/
ログイン後にコピー
  • 原理
class SyncHook_MY{
    constructor(){
        this.hooks = [];
    }

    // 订阅
    tap(name, fn){
        this.hooks.push(fn);
    }

    // 发布
    call(){
        this.hooks.forEach(hook => hook(...arguments));
    }
}
ログイン後にコピー

(2) SyncBailHook
只要监听函数中有一个函数的返回值不为 null,则跳过剩下所有的逻辑

  • 使用
const {
    SyncBailHook
} = require("tapable");

let queue = new SyncBailHook(['name']); 

queue.tap('1', function (name) {
    console.log(name, 1);
});
queue.tap('2', function (name) {
    console.log(name, 2);
    return 'wrong'
});
queue.tap('3', function (name) {
    console.log(name, 3);
});

queue.call('webpack');

// 执行结果:
/* 
webpack 1
webpack 2
*/
ログイン後にコピー
  • 原理
class SyncBailHook_MY {
    constructor() {
        this.hooks = [];
    }

    // 订阅
    tap(name, fn) {
        this.hooks.push(fn);
    }

    // 发布
    call() {
        for (let i = 0, l = this.hooks.length; i <p>(3) SyncWaterfallHook<br>上一个监听函数的返回值可以传给下一个监听函数</p>
ログイン後にコピー
  • 使用
const {
    SyncWaterfallHook
} = require("tapable");

let queue = new SyncWaterfallHook(['name']);

// 上一个函数的返回值可以传给下一个函数
queue.tap('1', function (name) {
    console.log(name, 1);
    return 1;
});
queue.tap('2', function (data) {
    console.log(data, 2);
    return 2;
});
queue.tap('3', function (data) {
    console.log(data, 3);
});

queue.call('webpack');

// 执行结果:
/* 
webpack 1
1 2
2 3
*/
ログイン後にコピー
  • 原理
class SyncWaterfallHook_MY{
    constructor(){
        this.hooks = [];
    }
    
    // 订阅
    tap(name, fn){
        this.hooks.push(fn);
    }

    // 发布
    call(){
        let result = null;
        for(let i = 0, l = this.hooks.length; i <p>(4) SyncLoopHook<br>当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环。</p>
ログイン後にコピー
  • 使用
const {
    SyncLoopHook
} = require("tapable");

let queue = new SyncLoopHook(['name']); 

let count = 3;
queue.tap('1', function (name) {
    console.log('count: ', count--);
    if (count > 0) {
        return true;
    }
    return;
});

queue.call('webpack');

// 执行结果:
/* 
count:  3
count:  2
count:  1
*/
ログイン後にコピー
  • 原理
class SyncLoopHook_MY {
    constructor() {
        this.hook = null;
    }

    // 订阅
    tap(name, fn) {
        this.hook = fn;
    }

    // 发布
    call() {
        let result;
        do {
            result = this.hook(...arguments);
        } while (result)
    }
}
ログイン後にコピー

Async钩子

异步并行
(1) AsyncParallelHook
不关心监听函数的返回值。有三种注册/发布的模式,如下:

异步订阅 调用方法
tap callAsync
tapAsync callAsync
tapPromise promise
  • usage - tap
const {
    AsyncParallelHook
} = require("tapable");

let queue1 = new AsyncParallelHook(['name']);
console.time('cost');
queue1.tap('1', function (name) {
    console.log(name, 1);
});
queue1.tap('2', function (name) {
    console.log(name, 2);
});
queue1.tap('3', function (name) {
    console.log(name, 3);
});
queue1.callAsync('webpack', err => {
    console.timeEnd('cost');
});

// 执行结果
/* 
webpack 1
webpack 2
webpack 3
cost: 4.520ms
*/
ログイン後にコピー
  • usage - tapAsync
let queue2 = new AsyncParallelHook(['name']);
console.time('cost1');
queue2.tapAsync('1', function (name, cb) {
    setTimeout(() => {
        console.log(name, 1);
        cb();
    }, 1000);
});
queue2.tapAsync('2', function (name, cb) {
    setTimeout(() => {
        console.log(name, 2);
        cb();
    }, 2000);
});
queue2.tapAsync('3', function (name, cb) {
    setTimeout(() => {
        console.log(name, 3);
        cb();
    }, 3000);
});

queue2.callAsync('webpack', () => {
    console.log('over');
    console.timeEnd('cost1');
});

// 执行结果
/* 
webpack 1
webpack 2
webpack 3
over
time: 3004.411ms
*/
ログイン後にコピー
  • usage - promise
let queue3 = new AsyncParallelHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name, cb) {
   return new Promise(function (resolve, reject) {
       setTimeout(() => {
           console.log(name, 1);
           resolve();
       }, 1000);
   });
});

queue3.tapPromise('1', function (name, cb) {
   return new Promise(function (resolve, reject) {
       setTimeout(() => {
           console.log(name, 2);
           resolve();
       }, 2000);
   });
});

queue3.tapPromise('1', function (name, cb) {
   return new Promise(function (resolve, reject) {
       setTimeout(() => {
           console.log(name, 3);
           resolve();
       }, 3000);
   });
});

queue3.promise('webpack')
   .then(() => {
       console.log('over');
       console.timeEnd('cost3');
   }, () => {
       console.log('error');
       console.timeEnd('cost3');
   });
/* 
webpack 1
webpack 2
webpack 3
over
cost3: 3007.925ms
*/
ログイン後にコピー

异步串行
(1) AsyncSeriesHook
不关心callback()的参数。

  • usage - tap
const {
    AsyncSeriesHook
} = require("tapable");

// tap
let queue1 = new AsyncSeriesHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
    console.log(1);
    return "Wrong";
});
queue1.tap('2', function (name) {
    console.log(2);
});
queue1.tap('3', function (name) {
    console.log(3);
});
queue1.callAsync('zfpx', err => {
    console.log(err);
    console.timeEnd('cost1');
});
// 执行结果
/* 
1
2
3
undefined
cost1: 3.933ms
*/
ログイン後にコピー
  • usage - tapAsync
let queue2 = new AsyncSeriesHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, cb) {
    setTimeout(() => {
        console.log(name, 1);
        cb();
    }, 1000);
});
queue2.tapAsync('2', function (name, cb) {
    setTimeout(() => {
        console.log(name, 2);
        cb();
    }, 2000);
});
queue2.tapAsync('3', function (name, cb) {
    setTimeout(() => {
        console.log(name, 3);
        cb();
    }, 3000);
});

queue2.callAsync('webpack', (err) => {
    console.log(err);
    console.log('over');
    console.timeEnd('cost2');
}); 
// 执行结果
/* 
webpack 1
webpack 2
webpack 3
undefined
over
cost2: 6019.621ms
*/
ログイン後にコピー
  • usage - promise
let queue3 = new AsyncSeriesHook(['name']);
console.time('cost3');
queue3.tapPromise('1',function(name){
   return new Promise(function(resolve){
       setTimeout(function(){
           console.log(name, 1);
           resolve();
       },1000)
   });
});
queue3.tapPromise('2',function(name,callback){
    return new Promise(function(resolve){
        setTimeout(function(){
            console.log(name, 2);
            resolve();
        },2000)
    });
});
queue3.tapPromise('3',function(name,callback){
    return new Promise(function(resolve){
        setTimeout(function(){
            console.log(name, 3);
            resolve();
        },3000)
    });
});
queue3.promise('webapck').then(err=>{
    console.log(err);
    console.timeEnd('cost3');
});

// 执行结果
/* 
webapck 1
webapck 2
webapck 3
undefined
cost3: 6021.817ms
*/
ログイン後にコピー
  • 原理
class AsyncSeriesHook_MY {
    constructor() {
        this.hooks = [];
    }

    tapAsync(name, fn) {
        this.hooks.push(fn);
    }

    callAsync() {
        var slef = this;
        var args = Array.from(arguments);
        let done = args.pop();
        let idx = 0;

        function next(err) {
            // 如果next的参数有值,就直接跳跃到 执行callAsync的回调函数
            if (err) return done(err);
            let fn = slef.hooks[idx++];
            fn ? fn(...args, next) : done();
        }
        next();
    }
}
ログイン後にコピー

(2) AsyncSeriesBailHook
callback()的参数不为null,就会直接执行callAsync等触发函数绑定的回调函数。

  • usage - tap
const {
    AsyncSeriesBailHook
} = require("tapable");

// tap
let queue1 = new AsyncSeriesBailHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
    console.log(1);
    return "Wrong";
});
queue1.tap('2', function (name) {
    console.log(2);
});
queue1.tap('3', function (name) {
    console.log(3);
});
queue1.callAsync('webpack', err => {
    console.log(err);
    console.timeEnd('cost1');
});

// 执行结果:
/* 
1
null
cost1: 3.979ms
*/
ログイン後にコピー
  • usage - tapAsync
let queue2 = new AsyncSeriesBailHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, callback) {
    setTimeout(function () {
        console.log(name, 1);
        callback();
    }, 1000)
});
queue2.tapAsync('2', function (name, callback) {
    setTimeout(function () {
        console.log(name, 2);
        callback('wrong');
    }, 2000)
});
queue2.tapAsync('3', function (name, callback) {
    setTimeout(function () {
        console.log(name, 3);
        callback();
    }, 3000)
});
queue2.callAsync('webpack', err => {
    console.log(err);
    console.log('over');
    console.timeEnd('cost2');
});
// 执行结果

/* 
webpack 1
webpack 2
wrong
over
cost2: 3014.616ms
*/
ログイン後にコピー
  • usage - promise
let queue3 = new AsyncSeriesBailHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(name, 1);
            resolve();
        }, 1000)
    });
});
queue3.tapPromise('2', function (name, callback) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log(name, 2);
            reject();
        }, 2000)
    });
});
queue3.tapPromise('3', function (name, callback) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            console.log(name, 3);
            resolve();
        }, 3000)
    });
});
queue3.promise('webpack').then(err => {
    console.log(err);
    console.log('over');
    console.timeEnd('cost3');
}, err => {
    console.log(err);
    console.log('error');
    console.timeEnd('cost3');
});
// 执行结果:
/* 
webpack 1
webpack 2
undefined
error
cost3: 3017.608ms
*/
ログイン後にコピー

(3) AsyncSeriesWaterfallHook
上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数

  • usage - tap
const {
    AsyncSeriesWaterfallHook
} = require("tapable");

// tap
let queue1 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost1');
queue1.tap('1', function (name) {
    console.log(name, 1);
    return 'lily'
});
queue1.tap('2', function (data) {
    console.log(2, data);
    return 'Tom';
});
queue1.tap('3', function (data) {
    console.log(3, data);
});
queue1.callAsync('webpack', err => {
    console.log(err);
    console.log('over');
    console.timeEnd('cost1');
});

// 执行结果:
/* 
webpack 1
2 'lily'
3 'Tom'
null
over
cost1: 5.525ms
*/
ログイン後にコピー
  • usage - tapAsync
let queue2 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost2');
queue2.tapAsync('1', function (name, callback) {
    setTimeout(function () {
        console.log('1: ', name);
        callback(null, 2);
    }, 1000)
});
queue2.tapAsync('2', function (data, callback) {
    setTimeout(function () {
        console.log('2: ', data);
        callback(null, 3);
    }, 2000)
});
queue2.tapAsync('3', function (data, callback) {
    setTimeout(function () {
        console.log('3: ', data);
        callback(null, 3);
    }, 3000)
});
queue2.callAsync('webpack', err => {
    console.log(err);
    console.log('over');
    console.timeEnd('cost2');
});
// 执行结果:
/* 
1:  webpack
2:  2
3:  3
null
over
cost2: 6016.889ms
*/
ログイン後にコピー
  • usage - promise
let queue3 = new AsyncSeriesWaterfallHook(['name']);
console.time('cost3');
queue3.tapPromise('1', function (name) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log('1:', name);
            resolve('1');
        }, 1000)
    });
});
queue3.tapPromise('2', function (data, callback) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            console.log('2:', data);
            resolve('2');
        }, 2000)
    });
});
queue3.tapPromise('3', function (data, callback) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            console.log('3:', data);
            resolve('over');
        }, 3000)
    });
});
queue3.promise('webpack').then(err => {
    console.log(err);
    console.timeEnd('cost3');
}, err => {
    console.log(err);
    console.timeEnd('cost3');
});
// 执行结果:
/* 
1: webpack
2: 1
3: 2
over
cost3: 6016.703ms
*/
ログイン後にコピー
  • 原理
class AsyncSeriesWaterfallHook_MY {
    constructor() {
        this.hooks = [];
    }

    tapAsync(name, fn) {
        this.hooks.push(fn);
    }

    callAsync() {
        let self = this;
        var args = Array.from(arguments);

        let done = args.pop();
        console.log(args);
        let idx = 0;
        let result = null;

        function next(err, data) {
            if (idx >= self.hooks.length) return done();
            if (err) {
                return done(err);
            }
            let fn = self.hooks[idx++];
            if (idx == 1) {

                fn(...args, next);
            } else {
                fn(data, next);
            }
        }
        next();
    }
}
ログイン後にコピー

Tapable事件流

webpack中的事件归纳如下,这些事件出现的顺序固定,但不一定每次打包所有事件都触发:

#[E]applyPluginsAsyncWaterfallbefore-resolve[B]applyPluginsWaterfallfactory[B]applyPluginsWaterfallresolver #[A]applyPluginsresolve[A]applyPluginsresolve-step#[G][G][A] [G][E][C][B][A]# #[A][C][C]#[C][C][C][C][C][C][C][C][C][C] [C][C][C][C][C][A][E][B][A ][A][A][A][A][ A][A][A][D][A][C][A][A][A][A][A][A][A][A]# #[A][A][A][A]
種類 名字 イベント名
[C] applyPluginsBailResult entry-option
[A] applyPlugins after-plugins
[A] applyPlugins リゾルバー後
[A] applyPlugins 環境
[A] applyPlugins 環境後
[ D] applyPluginsAsyncSeries run
[A] applyPlugins normal-module-factory
[A] applyPlugins context-module-factory
[A] applyPlugins compile
[A] applyPlugins this-compilation
[A] applyPlugins compilation
[F] applyPluginsParallel make
applyPluginsParallelBailResult file
applyPluginsParallelBailResult ディレクトリ
applyPlugins resolve-step
applyPluginsParallelBailResult result
applyPluginsAsyncWaterfall after-resolve
applyPluginsBailResult create-module
applyPluginsWaterfall module
applyPlugins build-module
applyPlugins normal-module-loader
applyPluginsBailResult プログラム
applyPluginsBailResult ステートメント
applyPluginsBailResult evaluate CallExpression
applyPluginsBailResult var data
applyPluginsBailResult 評価識別子
applyPluginsBailResult 評価識別子require
applyPluginsBailResult call require
applyPluginsBailResult evaluate Literal
applyPluginsBailResult call require:amd:array
applyPluginsBailResult リテラルの評価
applyPluginsBailResult call require:commonjs:item
applyPluginsBailResult statement
applyPluginsBailResult evaluate MemberExpression
applyPluginsBailResult evaluate 識別子コンソール。 log
applyPluginsBailResult call console.log
applyPluginsBailResult expression console.log
applyPluginsBailResult expression console
applyPlugins succeed-module
applyPluginsAsyncWaterfall 解決前
applyPluginsWaterfall factory
applyPlugins build-module
applyPlugins succeed-module
applyPlugins seal
applyPlugins optimize
applyPlugins optimize-modules
applyPlugins after-optimize-modules
applyPlugins optimize-チャンク
applyPlugins after-optimize-chunks
applyPluginsAsyncSeries optimize-tree
applyPlugins after-optimize-tree
applyPluginsBailResult Should-record
applyPlugins revive-modules
applyPlugins optimize-module-order
applyPlugins before-module-ids
applyPlugins optimize-module-ids
applyPlugins after-optimize-module-ids
applyPlugins レコードモジュール
applyPlugins revive-chunks
applyPlugins optimize-chunk-order
applyPlugins before-chunk-ids
applyPlugins optimize-chunk-ids
applyPlugins after-optimize-chunk-ids
applyPlugins レコードチャンク
[A] applyPlugins before-hash
[A] applyPlugins ハッシュ
[A] applyPlugins ハッシュフォーチャンク
[ A] applyPlugins chunk-hash
[A] applyPlugins after-hash
[A] applyPlugins before-chunk-assets
[B] applyPluginsWaterfall global-hash-paths
[C] applyPluginsBailResult global-hash
[B] applyPluginsウォーターフォール ブートストラップ
[B] applyPluginsウォーターフォール local-vars
[B] applyPluginsWaterfall require
[B] applyPluginsWaterfall module-obj
[B] applyPluginsWaterfall module-require
[B] applyPluginsウォーターフォール require-extensions
[B] applyPluginsウォーターフォール アセットパス
[B] applyPluginsWaterfall startup
[B] applyPluginsWaterfall module-require
[B] applyPluginsWaterfall render
[B] applyPluginsWaterfall module
[B] applyPluginsWaterfall render
[B] applyPluginsWaterfall package
[B] applyPluginsWaterfall module
[B] applyPluginsWaterfall render
[B] applyPluginsWaterfall パッケージ
[B] applyPluginsWaterfall モジュール
[B] applyPluginsWaterfall render-with-entry
[B] applyPluginsWaterfall asset-path
[B] applyPluginsWaterfall asset-path
[A] applyPlugins chunk-asset
[A] applyPlugins 追加チャンクアセット
[A] applyPlugins レコード
[D] applyPluginsAsyncSeries 追加アセット
[D] applyPluginsAsyncSeries optimize-チャンクアセット
[A] applyPlugins チャンクアセット最適化後
[D] applyPluginsAsyncSeries optimize-assets
[A] applyPlugins after-optimize -assets
[D] applyPluginsAsyncSeries コンパイル後
[C] applyPluginsBailResult should-emit
[D] applyPluginsAsyncSeries emit
[B] applyPluginsWaterfall asset-path
[D] applyPluginsAsyncSeries after-emit
[A] applyPlugins done

几パッケージを実行するためのイベントのセグメント:

  • entry-option:初期化オプション
  • run:開始の評価
  • make:エントリー開始の分析依存依存してbuild
  • build-moodule:loader追加ファイルを使用してbuild模块
  • normal-module-loader:loader追加ファイルをacorn编译、抽象语法树AST
  • #を使用します##プログラム:開始对AST进行遍历、当遇到達必要時触発呼び出し要求イベント
  • シール:すべて依存ビルド完了、開始对チャンク进行优化(抜取パブリックモ块、加ハッシュ等)
  • optimize-chunk-assets:圧縮コード
  • emit:各チャンクを結果ファイルに出力します
以上のイベントを理解すると、簡単にファイルを書き出すことができます。

#...未完了续

参照

    Webpack-ソースコード二、整体用フローとタップ可能イベントストリーム
  • webpack4.0ソースコード分析之タップ可能

  • 関連教程推荐:《
Webパック入门ビデオ教程

以上がWebpack についての深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:jianshu.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート