Heim > Artikel > Web-Frontend > Tiefes Verständnis von Webpack
Webpack ist derzeit eines der beliebtesten Paketierungstools. Es verfügt über eine einfache Konfiguration, leistungsstarke Funktionen, umfangreiche Loader und Plug-in-Systeme und bietet Front-End-Entwicklern viele Annehmlichkeiten. Der Autor geht davon aus, dass die Leser vor dem Lesen dieses Kapitels über einige Erfahrungen mit der Verwendung von Webpack verfügen, daher werde ich nicht näher auf die Verwendung von Webpack eingehen.
Wenn Sie dieses Kapitel lesen, können Sie Folgendes lernen:
Webpack-Paketcodestruktur
Einfache Verpackung
Wir schreiben zuerst die einfachste Methode und verwenden dann Webpack zum Verpacken:
// /webpack/bundles/simple/moduleA.js window.printA = function printA() { console.log(`This is module A!`); }
Eine relativ einfache Webpack-Konfigurationsdatei:
// /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' }) ] }
Erstellen Sie eine HTML-Datei zum Testen in einer Browserumgebung:
nbsp;html> <meta> <meta> <meta> <title>Webpack - Simple Bundle</title>
Führen Sie den Verpackungsbefehl webpack
aus. Nachdem wir ein dist
-Verzeichnis erhalten haben, öffnen wir die simple.bundle.js
-Datei:
/******/ (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!`); } /***/ }) /******/ ]);
Hauptsächlich schauen in diesem Absatz:
// ...... 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 definiert intern eine webpack_require-Methode. Das Wesentliche dieser Methode ist sehr einfach:
Es gibt einfache Abhängigkeiten zwischen mehreren Modulen
Zum Beispiel hängt moduleB.js von der Datei moduleA.js ab.
// /webpack/bundles/simpleDependencies/moduleA.js module.exports = window.printA = function printA() { console.log(`This is module A!`); }rrree
Ändern Sie den Eintrag in der Konfigurationsdatei in
// /webpack/bundles/simpleDependencies/moduleB.js const printA = require('./moduleA'); module.exports = window.printB = function printB() { printA(); console.log('This is module B!'); }
Paket erneut, wir erhalten den folgenden Code:
// /webpack/bundles/simpleDependencies/webpack.config.js // ... main: './moduleB.js' // ...
Wir können einige Änderungen in diesem Teil finden:
// /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!`); } /***/ }) /******/ ]);
In moduleB.js müssen Sie sich auf moduleA verlassen, also müssen Sie zuerst __webpack_require(1) ausführen, um Modul A zu erhalten, bevor Sie mit dem nächsten Schritt fortfahren.
Mehrfacheintrag
Es ist zu beachten, dass die Modul-ID in der gepackten Datei nicht wiederholt wird. Wenn zwei Eintragsdateien vorhanden sind, ist die Eintragsmodul-ID 0 und Andere Abhängigkeiten Modul-IDs werden nicht wiederholt. Wir erstellen die folgenden Dateien, von denen index0.js von common.js und dependency.js abhängt, während index1.js von den beiden Dateien index0.js und common.js abhängt.
/* 0 */ /***/ (function(module, exports, __webpack_require__) { const printA = __webpack_require__(1); module.exports = window.printB = function printB() { printA(); console.log('This is module B!'); }
// /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!'); }
Ändern Sie den Dateieintrag in webpack.config.js:
// /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!'); }
Gepackte Datei:
// /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!'); } /***/ }) /******/ ]);
Vor der Verwendung des CommonsChunkPlugin-Plugins sind diese beiden offensichtlich doppelt vorhanden die Dateien. Das heißt, jeder Eingang wird einzeln verpackt.
Sehen wir uns die Situation nach dem Hinzufügen des CommonsChunkPlugin-Plug-Ins an (ändern Sie webpack.config.js):
// /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!'); } /***/ }) /******/ ]);
Auf diese Weise werden drei Dateien generiert: index0.bundle.js, index1.bundel .js und common.js:
// /webpack/bundles/CommonsChunkPlugin/webpack.config.js plugins: [ // ... new webpack.optimize.CommonsChunkPlugin({ name: 'common', filename: 'common.js' }) ]
common.js hat alle öffentlichen Methoden eingebunden und eine Methode namens webpackJsonp im Browserfensterobjekt erstellt.
// /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>Diese Methode ähnelt __webpack_require__, das auch Module zwischenspeichert. Es ist nur so, dass Webpack öffentliche Module vorextrahiert und sie zuerst zwischenspeichert. Anschließend können Sie die Methode webpackJsonp in anderen bundle.js verwenden, um das Modul zu laden. </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 rrree<p><span style="font-size: 20px;"><strong>Webpack-Kernarchitektur – Tapable</strong></span></p><p>Klonen Sie den Webpack-Quellcode von <code>github</code> auf lokal. Wir können zunächst etwas über Webpack A im Allgemeinen lernen Prozess: </p><p><img src="https://img.php.cn/upload/image/128/550/593/1594456641514594.jpg" title="1594456641514594.jpg" alt="Tiefes Verständnis von Webpack"></p>
Eine detaillierte Analyse von _addModuleChain, der zweite Schritt zum Erstellen des Moduls, kann in drei Teile unterteilt werden:
Einzelheiten finden Sie in der Datei lib/webpack.js
, der Eintragsdatei von webpack
.
// /webpack/bundles/CommonsChunkPlugin/dist/index0.bundle.js webpackJsonp([1],[],[1]);
lib/webpack.js
Der Ablauf läuft ungefähr wie folgt ab:
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 | Eingabeoption |
[A] | applyPlugins | after-plugins |
[A] | applyPlugins | After-Resolver |
[A] | applyPlugins | Umgebung |
[A] | applyPlugins | Nachumgebung |
[ D] | applyPluginsAsyncSeries | run |
[A] | applyPlugins | normal-module-factory |
[A] | applyPlugins | context-module-factory |
[A] | applyPlugins | kompilieren |
[A] | applyPlugins | diese-Zusammenstellung |
[A] | applyPlugins | Kompilierung |
[F] | applyPluginsParallel | make |
[E] | applyPluginsAsyncWaterfall | before-resolve |
[B] | applyPluginsWaterfall | factory |
[B] | applyPluginsWaterfall | Resolver |
[A] | applyPlugins | resolve |
[A] | applyPlugins | resolve-step |
[G] | applyPluginsParallelBailResult | Datei |
[G] | applyPluginsParallelBailResult | Verzeichnis |
[A] | applyPlugins | resolve-step |
[G] | applyPluginsParallelBailResult | result |
[E] | applyPluginsAsyncWaterfall | after-resolve |
[C] | applyPluginsBailResult | create-module |
[B] | applyPluginsWaterfall | module |
[A] | applyPlugins | build-module |
[A] | applyPlugins | normal-module-loader |
[C] | applyPluginsBailResult | Programm |
[C] | applyPluginsBailResult | Anweisung |
[C] | applyPluginsBailResult | CallExpression auswerten |
[C] | applyPluginsBailResult | Var-Daten |
[C] | applyPluginsBailResult | Bezeichner auswerten |
[C] | applyPluginsBailResult | Bezeichner auswerten require |
[C] | applyPluginsBailResult | call require |
[C] | applyPluginsBailResult | evaluate Literal |
[C] | applyPluginsBailResult | call require:amd:array |
[C] | applyPluginsBailResult | evaluate Literal |
[C] | applyPluginsBailResult | call require:commonjs:item |
[C] | applyPluginsBailResult | Anweisung |
[C] | applyPluginsBailResult | MemberExpression auswerten |
[C] | applyPluginsBailResult | Identifier-Konsole auswerten. log |
[C] | applyPluginsBailResult | call console.log |
[C] | applyPluginsBailResult | expression console.log |
[C] | applyPluginsBailResult | expression console |
[A] | applyPlugins | succeed-module |
[E] | applyPluginsAsyncWaterfall | before-resolve |
[B] | applyPluginsWaterfall | factory |
[A ] | applyPlugins | build-module |
[A] | applyPlugins | succeed-module |
[A] | applyPlugins | Siegel |
[A] | applyPlugins | optimize |
[A] | applyPlugins | optimize-modules |
[ A] | applyPlugins | after-optimize-modules |
[A] | applyPlugins | optimize- chunks |
[A] | applyPlugins | after-optimize-chunks |
[D] | applyPluginsAsyncSeries | optimize-tree |
[A] | applyPlugins | after-optimize-tree |
[C] | applyPluginsBailResult | should-record |
[A] | applyPlugins | revive-modules |
[A] | applyPlugins | optimize-module-order |
[A] | applyPlugins | before-module-ids |
[A] | applyPlugins | optimize-module-ids |
[A] | applyPlugins | after-optimize-module-ids |
[A] | applyPlugins | record-modules |
[A] | applyPlugins | Revive-Chunks |
[A] | ApplyPlugins | Optimize-Chunk-Order |
[A] | applyPlugins | before-chunk-ids |
[A] | applyPlugins | optimize-chunk-ids |
[A] | applyPlugins | after-optimize-chunk-ids |
[A] | applyPlugins | record-chunks |
[A] | applyPlugins | before-hash |
[A] | applyPlugins | Hash |
[A] | applyPlugins | Hash-for-Chunk |
[ A] | ApplyPlugins | Chunk-Hash |
[A] | ApplyPlugins | After-Hash |
[A] | applyPlugins | before-chunk-assets |
[B] | applyPluginsWaterfall | global-hash-paths |
[C] | applyPluginsBailResult | global-hash |
[B] | applyPluginsWaterfall | bootstrap |
[B] | applyPluginsWaterfall | local-vars |
[B] | applyPluginsWaterfall | require |
[B] | applyPluginsWaterfall | module-obj |
[B] | applyPluginsWaterfall | module-require |
[B] | applyPluginsWaterfall | require-extensions |
[B] | applyPluginsWaterfall | Asset-Pfad |
[B] | applyPluginsWaterfall | Startup |
[B] | applyPluginsWaterfall | module-require |
[B] | applyPluginsWaterfall | render |
[B] | applyPluginsWaterfall | Modul |
[B] | applyPluginsWaterfall | render |
[B] | applyPluginsWaterfall | Paket |
[B] | applyPluginsWaterfall | Modul |
[B] | applyPluginsWaterfall | render |
[B] | applyPluginsWaterfall | Paket |
[B] | applyPluginsWaterfall | module |
[B] | applyPluginsWaterfall | render-with-entry |
[B] | applyPluginsWaterfall | asset-path |
[B] | applyPluginsWaterfall | asset-path |
[A] | applyPlugins | chunk-asset |
[A] | applyPlugins | additional-chunk-assets |
[A] | applyPlugins | record |
[D] | applyPluginsAsyncSeries | additional-assets |
[D] | applyPluginsAsyncSeries | optimize- chunk-assets |
[A] | applyPlugins | after-optimize-chunk-assets |
[D] | applyPluginsAsyncSeries | optimize-assets |
[A] | applyPlugins | after-optimize -assets |
[D] | applyPluginsAsyncSeries | nach dem Kompilieren |
[C] | applyPluginsBailResult | should-emit |
[D] | applyPluginsAsyncSeries | emit |
[B] | applyPluginsWaterfall | Asset-Pfad |
[D] | applyPluginsAsyncSeries | After-Emit |
[A] | Plugins anwenden | fertig |
几个关键的事件对应打包的阶段:
了解以上事件,你可以很容易地写出一个插件.
...未完待续
引用
相关教程推荐:《Web pack入门视频教程 》
Das obige ist der detaillierte Inhalt vonTiefes Verständnis von Webpack. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!