深入了解webpack

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ù)

引用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捶箱,一起剝皮案震驚了整個濱河市智什,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌丁屎,老刑警劉巖荠锭,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異晨川,居然都是意外死亡证九,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門共虑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來愧怜,“玉大人,你說我怎么就攤上這事妈拌∮堤常” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長猜惋。 經(jīng)常有香客問我疾党,道長,這世上最難降的妖魔是什么惨奕? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任雪位,我火速辦了婚禮,結(jié)果婚禮上梨撞,老公的妹妹穿的比我還像新娘雹洗。我一直安慰自己,他們只是感情好卧波,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布时肿。 她就那樣靜靜地躺著,像睡著了一般港粱。 火紅的嫁衣襯著肌膚如雪螃成。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天查坪,我揣著相機(jī)與錄音寸宏,去河邊找鬼。 笑死偿曙,一個胖子當(dāng)著我的面吹牛氮凝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播望忆,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼罩阵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了启摄?” 一聲冷哼從身側(cè)響起稿壁,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎歉备,沒想到半個月后傅是,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡威创,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年落午,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肚豺。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡溃斋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吸申,到底是詐尸還是另有隱情梗劫,我是刑警寧澤享甸,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站梳侨,受9級特大地震影響蛉威,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜走哺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一蚯嫌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丙躏,春花似錦择示、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至废恋,卻和暖如春谈秫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鱼鼓。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工拟烫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚓哩。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓构灸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親岸梨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 在現(xiàn)在的前端開發(fā)中稠氮,前后端分離曹阔、模塊化開發(fā)、版本控制隔披、文件合并與壓縮赃份、mock數(shù)據(jù)等等一些原本后端的思想開始...
    Charlot閱讀 5,439評論 1 32
  • 說在前面:這些文章均是本人花費(fèi)大量精力研究整理,如有轉(zhuǎn)載請聯(lián)系作者并注明引用奢米,謝謝本文的受眾人群不是webpack...
    RockSAMA閱讀 6,921評論 2 7
  • 寫在開頭 先說說為什么要寫這篇文章, 最初的原因是組里的小朋友們看了webpack文檔后, 表情都是這樣的: (摘...
    Lefter閱讀 5,286評論 4 31
  • 2018年3月6日 星期二 大風(fēng) 春天悄悄的來了抓韩, 冬天悄悄地走了, 大地生機(jī)勃勃鬓长,樹...
    陳泉妡閱讀 86評論 0 0
  • 今天涉波,李老師給我講:烏龜?shù)纳?很強(qiáng)英上,遇到什么困難都不怕炭序。我明天要鋼琴考級,我的心像裝了一只小鹿一樣砰...
    Cherry多多閱讀 215評論 0 0