ホームページ >ウェブフロントエンド >jsチュートリアル >Webpack についての深い理解

Webpack は現在最も人気のあるパッケージング ツールの 1 つで、シンプルな構成、強力な機能、豊富なローダーとプラグイン システムを備えており、フロントエンド開発者に多くの利便性を提供します。著者は、この章を読む前に読者が 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 メソッドを定義します。このメソッドの本質は非常にシンプルです:

たとえば、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/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'
})
]
// /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 の全体的なプロセスを理解します。

ローダーを呼び出してモジュール間の依存関係を処理します。
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 的介绍如下:
实际上,webpack基于事件流机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的Compiler和负责创建bundles的Compilation都是Tapable的实例。Tapable 向外暴露许多的钩子类,这些类可以很方便地为插件创建事件钩子。 Tapable 中定义了如下几种钩子类:
所有钩子类的构造函数都接收一个可选的参数,这个参数是一个由字符串参数组成的数组,如下:
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
钩子概览
Tapable的钩子分为两类,同步和异步,其中异步又分为并行和串行:

每种钩子都有各自的使用方式,如下表:
| 序号 | 钩子名 | 执行方式 | 使用要点 |
|---|---|---|---|
| 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 |
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
*/
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
*/
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()的参数。
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
*/
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
*/
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等触发函数绑定的回调函数。
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
*/
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
*/
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)的第二个参数,可以作为下一个监听函数的参数
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
*/
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
*/
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中的事件归纳如下,这些事件出现的顺序固定,但不一定每次打包所有事件都触发:
| 種類 | 名字 | イベント名 |
|---|---|---|
| [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 |
| applyPluginsAsyncWaterfall | before-resolve | |
| applyPluginsWaterfall | factory | |
| applyPluginsWaterfall | resolver | |
| applyPlugins | resolve | |
| applyPlugins | resolve-step | |
| 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 |
几パッケージを実行するためのイベントのセグメント:
#...未完了续
参照Webパック入门ビデオ教程
以上がWebpack についての深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。