webpack是目前最為流行的打包工具之一渗饮,其配置簡單蚕冬,功能強(qiáng)大,擁有豐富的加載器和插件系統(tǒng)疆瑰,為前端開發(fā)者提供了諸多便利眉反。筆者默認(rèn)各位看官在閱讀本章之前已經(jīng)有了一定的使用經(jīng)驗(yàn),所以對webpack的使用方式不做贅述穆役。
閱讀本章寸五,你可以了解到如下內(nèi)容:
- Webpack打包后代碼結(jié)構(gòu)
- Webpack核心架構(gòu) —— Tapable
- Webpack事件流
- Webpack插件實(shí)現(xiàn)機(jī)制
- Webpack加載器實(shí)現(xiàn)機(jī)制
Webpack打包后代碼結(jié)構(gòu)
簡單打包
我們首先寫一個最簡單的方法,然后使用webpack進(jìn)行打包:
// /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'
})
]
}
創(chuàng)建一個HTML文件用于在瀏覽器環(huán)境下測試:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Webpack - Simple Bundle</title>
</head>
<body>
</body>
</html>
執(zhí)行打包命令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內(nèi)部定義了一個 webpack_require 的方法梳杏,這個方法的實(shí)質(zhì)很簡單:
多模塊間存在簡單依賴
例如 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'
// ...
再次打包,我們獲得如下代碼:
// /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!`);
}
/***/ })
/******/ ]);
我們可以發(fā)現(xiàn)這塊有點(diǎn)變化:
/* 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 叛溢,因而需要先執(zhí)行 __webpack_require(1) 拿到模塊A后,再進(jìn)行下一步劲适。
多入口
需要注意楷掉,打包的文件中moudleId是不會重復(fù)的,如果有兩個入口文件的情況减响,則入口模塊id都為0靖诗,其他依賴模塊id不重復(fù)郭怪。我們創(chuàng)建如下幾個文件支示,其中 index0.js 依賴于 common.js 與 dependency.js ,而 index1.js 依賴于 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 這個插件之前,這兩個文件是存在重復(fù)代碼的攒庵。也就是每個入口都會獨(dú)立進(jìn)行打包嘴纺。
我們看如果添加了 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.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 < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/ if(executeModules) {
/******/ for(i=0; i < executeModules.length; i++) {
/******/ result = __webpack_require__(__webpack_require__.s = executeModules[i]);
/******/ }
/******/ }
/******/ return result;
/******/ };
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // objects to store loaded and loading chunks
/******/ var installedChunks = {
/******/ 2: 0
/******/ };
/******/
/******/ // 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 = "";
/******/
/******/ // on error function for async loading
/******/ __webpack_require__.oe = function(err) { console.error(err); throw err; };
/******/ })
/************************************************************************/
/******/ ([
/* 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!');
}
/***/ })
/******/ ]);
common.js 已經(jīng)包含了所有的公共方法栽渴,并且在瀏覽器 window 對象中創(chuàng)建了一個名為 webpackJsonp 的方法。
// /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 < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/ if(executeModules) {
/******/ for(i=0; i < executeModules.length; i++) {
/******/ result = __webpack_require__(__webpack_require__.s = executeModules[i]);
/******/ }
/******/ }
/******/ return result;
/******/ };
// ...
/******/ // objects to store loaded and loading chunks
/******/ var installedChunks = {
/******/ 2: 0
/******/ };
// ...
這個方法與 __webpack_require__ 較為類似稳懒,同樣也是將模塊緩存進(jìn)來闲擦。只不過 webpack 會預(yù)先抽取公共模塊,先將其緩存進(jìn)來场梆,而后可以在其他的 bundle.js 中使用 webpackJsonp 方法進(jìn)行模塊加載墅冷。
// /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核心架構(gòu) —— Tapable
從github
上將webpack源碼克隆至本地,我們可以先了解到 webpack 的一個整體流程:
- lib/webpack.js中返回一個compiler對象或油,并調(diào)用了compiler.run()
- lib/Compiler.js中寞忿,run方法觸發(fā)了before-run、run兩個事件顶岸,然后通過readRecords讀取文件腔彰,通過compile進(jìn)行打包,打包后觸發(fā)before-compile辖佣、compile霹抛、make等事件;compile是主要流程凌简,該方法中實(shí)例化了一個Compilation類上炎,并調(diào)用了其finish及seal方法。
- lib/Compilation.js中定義了finish及seal方法,還有一個重要方法addEntry藕施。這個方法通過調(diào)用其私有方法_addModuleChain完成了兩件事:根據(jù)模塊的類型獲取對應(yīng)的模塊工廠并創(chuàng)建模塊傲诵;構(gòu)建模塊缚够。
- lib/Compiler.js中沒有顯式調(diào)用addEntry,而是觸發(fā)make事件,lib/DllEntryPlugin.js為一個監(jiān)聽make事件的插件贫途,在回調(diào)函數(shù)中調(diào)用了addEntry。
具體分析_addModuleChain封断,其完成的第二件事構(gòu)建模塊又可以分為三部分:
- 調(diào)用loader處理模塊之間的依賴掀序。
- 將loader處理后的文件通過acorn抽象成抽象語法樹AST。
- 遍歷AST救氯,構(gòu)建該模塊的所有依賴找田。
具體看 lib/webpack.js
這個文件,此文件為 webpack
的入口文件着憨。
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
中流程大致如下:
- 參數(shù)驗(yàn)證
- 創(chuàng)建
Compiler
(編譯器)對象 - 注冊并執(zhí)行
NodeEnvironmentPlugin
- 執(zhí)行鉤子
environment
里的方法 - 執(zhí)行鉤子
afterEnvironment
里的方法 - 注冊并執(zhí)行各種插件
- 將
compiler
向外導(dǎo)出
顯然墩衙,Compiler是我們需要深究的一個部分,因?yàn)?webpack 最終向外部返回也就是這個 Compiler 實(shí)例甲抖。大致了解下 Compiler
的實(shí)現(xiàn):
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
漆改,在其構(gòu)造方法中,定義了一些事件鉤子(hooks
)准谚、一些變量以及一些方法挫剑。這些變量以及方法目前看來還是非常抽象的,所以我們有必要去了解下 Tapable
的實(shí)現(xiàn)柱衔。
Tapable的Github主頁 對 Tapable
的介紹如下:
- The tapable packages exposes many Hook classes, which can be used to create hooks for plugins.
實(shí)際上樊破,webpack基于事件流機(jī)制,它的工作流程就是將各個插件串聯(lián)起來秀存,而實(shí)現(xiàn)這一切的核心就是Tapable捶码,webpack中最核心的負(fù)責(zé)編譯的Compiler和負(fù)責(zé)創(chuàng)建bundles的Compilation都是Tapable的實(shí)例。Tapable
向外暴露許多的鉤子類或链,這些類可以很方便地為插件創(chuàng)建事件鉤子惫恼。 Tapable
中定義了如下幾種鉤子類:
- SyncHook
- SyncBailHook
- SyncWaterfallHook
- SyncLoopHook
- AsyncParallelHook
- AsyncParallelBailHook
- AsyncSeriesHook
- AsyncSeriesBailHook
- AsyncSeriesWaterfallHook
所有鉤子類的構(gòu)造函數(shù)都接收一個可選的參數(shù),這個參數(shù)是一個由字符串參數(shù)組成的數(shù)組澳盐,如下:
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
鉤子概覽
Tapable的鉤子分為兩類祈纯,同步和異步,其中異步又分為并行和串行:
每種鉤子都有各自的使用方式叼耙,如下表:
序號 | 鉤子名 | 執(zhí)行方式 | 使用要點(diǎn) |
---|---|---|---|
1 | SyncHook | 同步串行 | 不關(guān)心監(jiān)聽函數(shù)的返回值 |
2 | SyncBailHook | 同步串行 | 只要監(jiān)聽函數(shù)中有一個函數(shù)的返回值不為 null腕窥,則跳過剩下所有的邏輯 |
3 | SyncWaterfallHook | 同步串行 | 上一個監(jiān)聽函數(shù)的返回值可以傳給下一個監(jiān)聽函數(shù) |
4 | SyncLoopHook | 同步循環(huán) | 當(dāng)監(jiān)聽函數(shù)被觸發(fā)的時候,如果該監(jiān)聽函數(shù)返回true時則這個監(jiān)聽函數(shù)會反復(fù)執(zhí)行筛婉,如果返回 undefined 則表示退出循環(huán) |
5 | AsyncParallelHook | 異步并發(fā) | 不關(guān)心監(jiān)聽函數(shù)的返回值 |
6 | AsyncParallelBailHook | 異步并發(fā) | 只要監(jiān)聽函數(shù)的返回值不為 null簇爆,就會忽略后面的監(jiān)聽函數(shù)執(zhí)行癞松,直接跳躍到callAsync等觸發(fā)函數(shù)綁定的回調(diào)函數(shù),然后執(zhí)行這個被綁定的回調(diào)函數(shù) |
7 | AsyncSeriesHook | 異步串行 | 不關(guān)系callback()的參數(shù) |
8 | AsyncSeriesBailHook | 異步串行 | callback()的參數(shù)不為null入蛆,就會直接執(zhí)行callAsync等觸發(fā)函數(shù)綁定的回調(diào)函數(shù) |
9 | AsyncSeriesWaterfallHook | 異步串行 | 上一個監(jiān)聽函數(shù)的中的callback(err, data)的第二個參數(shù),可以作為下一個監(jiān)聽函數(shù)的參數(shù) |
Sync鉤子
同步串行
(1) SyncHook
不關(guān)心監(jiān)聽函數(shù)的返回值
- 使用
const { SyncHook } = require("tapable");
let queue = new SyncHook(['name']); //所有的構(gòu)造函數(shù)都接收一個可選的參數(shù)响蓉,這個參數(shù)是一個字符串的數(shù)組。
// 訂閱
queue.tap('1', function (name, name2) {// tap 的第一個參數(shù)是用來標(biāo)識訂閱的函數(shù)的
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);
});
// 發(fā)布
queue.call('webpack', 'webpack-cli');// 發(fā)布的時候觸發(fā)訂閱的函數(shù) 同時傳入?yún)?shù)
// 執(zhí)行結(jié)果:
/*
webpack undefined 1 // 傳入的參數(shù)需要和new實(shí)例的時候保持一致哨毁,否則獲取不到多傳的參數(shù)
webpack 2
webpack 3
*/
- 原理
class SyncHook_MY{
constructor(){
this.hooks = [];
}
// 訂閱
tap(name, fn){
this.hooks.push(fn);
}
// 發(fā)布
call(){
this.hooks.forEach(hook => hook(...arguments));
}
}
(2) SyncBailHook
只要監(jiān)聽函數(shù)中有一個函數(shù)的返回值不為 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');
// 執(zhí)行結(jié)果:
/*
webpack 1
webpack 2
*/
- 原理
class SyncBailHook_MY {
constructor() {
this.hooks = [];
}
// 訂閱
tap(name, fn) {
this.hooks.push(fn);
}
// 發(fā)布
call() {
for (let i = 0, l = this.hooks.length; i < l; i++) {
let hook = this.hooks[i];
let result = hook(...arguments);
if (result) {
break;
}
}
}
}
(3) SyncWaterfallHook
上一個監(jiān)聽函數(shù)的返回值可以傳給下一個監(jiān)聽函數(shù)
- 使用
const {
SyncWaterfallHook
} = require("tapable");
let queue = new SyncWaterfallHook(['name']);
// 上一個函數(shù)的返回值可以傳給下一個函數(shù)
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');
// 執(zhí)行結(jié)果:
/*
webpack 1
1 2
2 3
*/
- 原理
class SyncWaterfallHook_MY{
constructor(){
this.hooks = [];
}
// 訂閱
tap(name, fn){
this.hooks.push(fn);
}
// 發(fā)布
call(){
let result = null;
for(let i = 0, l = this.hooks.length; i < l; i++) {
let hook = this.hooks[i];
result = i == 0 ? hook(...arguments): hook(result);
}
}
}
(4) SyncLoopHook
當(dāng)監(jiān)聽函數(shù)被觸發(fā)的時候,如果該監(jiān)聽函數(shù)返回true時則這個監(jiān)聽函數(shù)會反復(fù)執(zhí)行扼褪,如果返回 undefined 則表示退出循環(huán)想幻。
- 使用
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');
// 執(zhí)行結(jié)果:
/*
count: 3
count: 2
count: 1
*/
- 原理
class SyncLoopHook_MY {
constructor() {
this.hook = null;
}
// 訂閱
tap(name, fn) {
this.hook = fn;
}
// 發(fā)布
call() {
let result;
do {
result = this.hook(...arguments);
} while (result)
}
}
Async鉤子
異步并行
(1) AsyncParallelHook
不關(guān)心監(jiān)聽函數(shù)的返回值。有三種注冊/發(fā)布的模式话浇,如下:
異步訂閱 | 調(diào)用方法 |
---|---|
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');
});
// 執(zhí)行結(jié)果
/*
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');
});
// 執(zhí)行結(jié)果
/*
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
不關(guān)心callback()的參數(shù)脏毯。
- 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');
});
// 執(zhí)行結(jié)果
/*
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');
});
// 執(zhí)行結(jié)果
/*
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');
});
// 執(zhí)行結(jié)果
/*
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的參數(shù)有值,就直接跳躍到 執(zhí)行callAsync的回調(diào)函數(shù)
if (err) return done(err);
let fn = slef.hooks[idx++];
fn ? fn(...args, next) : done();
}
next();
}
}
(2) AsyncSeriesBailHook
callback()的參數(shù)不為null凳枝,就會直接執(zhí)行callAsync等觸發(fā)函數(shù)綁定的回調(diào)函數(shù)抄沮。
- 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');
});
// 執(zhí)行結(jié)果:
/*
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');
});
// 執(zhí)行結(jié)果
/*
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');
});
// 執(zhí)行結(jié)果:
/*
webpack 1
webpack 2
undefined
error
cost3: 3017.608ms
*/
(3) AsyncSeriesWaterfallHook
上一個監(jiān)聽函數(shù)的中的callback(err, data)的第二個參數(shù),可以作為下一個監(jiān)聽函數(shù)的參數(shù)
- 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');
});
// 執(zhí)行結(jié)果:
/*
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');
});
// 執(zhí)行結(jié)果:
/*
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');
});
// 執(zhí)行結(jié)果:
/*
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中的事件歸納如下跋核,這些事件出現(xiàn)的順序固定岖瑰,但不一定每次打包所有事件都觸發(fā):
類型 | 名字 | 事件名 |
---|---|---|
[C] | applyPluginsBailResult | entry-option |
[A] | applyPlugins | after-plugins |
[A] | applyPlugins | after-resolvers |
[A] | applyPlugins | environment |
[A] | applyPlugins | after-environment |
[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 |
[E] | applyPluginsAsyncWaterfall | before-resolve |
[B] | applyPluginsWaterfall | factory |
[B] | applyPluginsWaterfall | resolver |
[A] | applyPlugins | resolve |
[A] | applyPlugins | resolve-step |
[G] | applyPluginsParallelBailResult | file |
[G] | applyPluginsParallelBailResult | directory |
[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 | program |
[C] | applyPluginsBailResult | statement |
[C] | applyPluginsBailResult | evaluate CallExpression |
[C] | applyPluginsBailResult | var data |
[C] | applyPluginsBailResult | evaluate Identifier |
[C] | applyPluginsBailResult | evaluate Identifier 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 | statement |
[C] | applyPluginsBailResult | evaluate MemberExpression |
[C] | applyPluginsBailResult | evaluate Identifier console.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 | seal |
[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-path |
[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 | package |
[B] | applyPluginsWaterfall | modules |
[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 | after-compile |
[C] | applyPluginsBailResult | should-emit |
[D] | applyPluginsAsyncSeries | emit |
[B] | applyPluginsWaterfall | asset-path |
[D] | applyPluginsAsyncSeries | after-emit |
[A] | applyPlugins | done |
幾個關(guān)鍵的事件對應(yīng)打包的階段:
- entry-option:初始化options
- run:開始編譯
- make:從entry開始遞歸分析依賴并對依賴進(jìn)行build
- build-moodule:使用loader加載文件并build模塊
- normal-module-loader:對loader加載的文件用acorn編譯,生成抽象語法樹AST
- program:開始對AST進(jìn)行遍歷砂代,當(dāng)遇到require時觸發(fā)call require事件
- seal:所有依賴build完成蹋订,開始對chunk進(jìn)行優(yōu)化(抽取公共模塊、加hash等)
- optimize-chunk-assets:壓縮代碼
- emit:把各個chunk輸出到結(jié)果文件
了解以上事件刻伊,你可以很容易地寫出一個插件露戒。
...未完待續(xù)
引用