站長(zhǎng)資訊網(wǎng)
        最全最豐富的資訊網(wǎng)站

        深入理解webpack

        深入理解webpack

        webpack是目前最為流行的打包工具之一,其配置簡(jiǎn)單,功能強(qiáng)大,擁有豐富的加載器和插件系統(tǒng),為前端開(kāi)發(fā)者提供了諸多便利。筆者默認(rèn)各位看官在閱讀本章之前已經(jīng)有了一定的使用經(jīng)驗(yàn),所以對(duì)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)

        簡(jiǎn)單打包
        我們首先寫(xiě)一個(gè)最簡(jiǎn)單的方法,然后使用webpack進(jìn)行打包:

        // /webpack/bundles/simple/moduleA.js window.printA = function printA() {     console.log(`This is module A!`); }

        一個(gè)比較基本的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)建一個(gè)HTML文件用于在瀏覽器環(huán)境下測(cè)試:

        <!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 后我們獲得了一個(gè) dist 目錄,我們打開(kāi) 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)部定義了一個(gè) webpack_require 的方法,這個(gè)方法的實(shí)質(zhì)很簡(jiǎn)單:

        深入理解webpack

        多模塊間存在簡(jiǎn)單依賴(lài)
        例如 moduleB.js 依賴(lài)于 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 中,需要依賴(lài) moduleA ,因而需要先執(zhí)行 __webpack_require(1) 拿到模塊A后,再進(jìn)行下一步。

        多入口
        需要注意,打包的文件中moudleId是不會(huì)重復(fù)的,如果有兩個(gè)入口文件的情況,則入口模塊id都為0,其他依賴(lài)模塊id不重復(fù)。我們創(chuàng)建如下幾個(gè)文件,其中 index0.js 依賴(lài)于 common.js 與 dependency.js ,而 index1.js 依賴(lài)于 index0.js 和 common.js 兩個(gè)文件。

        // /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 這個(gè)插件之前,這兩個(gè)文件是存在重復(fù)代碼的。也就是每個(gè)入口都會(huì)獨(dú)立進(jìn)行打包。
        我們看如果添加了 CommonsChunkPlugin 這個(gè)插件后的情況(修改 webpack.config.js):

        // /webpack/bundles/CommonsChunkPlugin/webpack.config.js plugins: [     // ...     new webpack.optimize.CommonsChunkPlugin({         name: 'common',         filename: 'common.js'     }) ]

        這樣一來(lái)會(huì)生成三個(gè)文件,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 對(duì)象中創(chuàng)建了一個(gè)名為 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 /******/    }; // ...

        這個(gè)方法與 __webpack_require__ 較為類(lèi)似,同樣也是將模塊緩存進(jìn)來(lái)。只不過(guò) webpack 會(huì)預(yù)先抽取公共模塊,先將其緩存進(jìn)來(lái),而后可以在其他的 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 的一個(gè)整體流程:

        深入理解webpack

        • lib/webpack.js中返回一個(gè)compiler對(duì)象,并調(diào)用了compiler.run()
        • lib/Compiler.js中,run方法觸發(fā)了before-run、run兩個(gè)事件,然后通過(guò)readRecords讀取文件,通過(guò)compile進(jìn)行打包,打包后觸發(fā)before-compile、compile、make等事件;compile是主要流程,該方法中實(shí)例化了一個(gè)Compilation類(lèi),并調(diào)用了其finish及seal方法。
        • lib/Compilation.js中定義了finish及seal方法,還有一個(gè)重要方法addEntry。這個(gè)方法通過(guò)調(diào)用其私有方法_addModuleChain完成了兩件事:根據(jù)模塊的類(lèi)型獲取對(duì)應(yīng)的模塊工廠并創(chuàng)建模塊;構(gòu)建模塊。
        • lib/Compiler.js中沒(méi)有顯式調(diào)用addEntry,而是觸發(fā)make事件,lib/DllEntryPlugin.js為一個(gè)監(jiān)聽(tīng)make事件的插件,在回調(diào)函數(shù)中調(diào)用了addEntry。

        具體分析_addModuleChain,其完成的第二件事構(gòu)建模塊又可以分為三部分:

        • 調(diào)用loader處理模塊之間的依賴(lài)。
        • 將loader處理后的文件通過(guò)acorn抽象成抽象語(yǔ)法樹(shù)AST。
        • 遍歷AST,構(gòu)建該模塊的所有依賴(lài)。

        具體看 lib/webpack.js 這個(gè)文件,此文件為 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 (編譯器)對(duì)象
        • 注冊(cè)并執(zhí)行 NodeEnvironmentPlugin
        • 執(zhí)行鉤子 environment 里的方法
        • 執(zhí)行鉤子 afterEnvironment 里的方法
        • 注冊(cè)并執(zhí)行各種插件
        • compiler 向外導(dǎo)出

        顯然,Compiler是我們需要深究的一個(gè)部分,因?yàn)?webpack 最終向外部返回也就是這個(gè) 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)、一些變量以及一些方法。這些變量以及方法目前看來(lái)還是非常抽象的,所以我們有必要去了解下 Tapable 的實(shí)現(xiàn)。

        Tapable的Github主頁(yè) 對(duì) Tapable 的介紹如下:

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

        實(shí)際上,webpack基于事件流機(jī)制,它的工作流程就是將各個(gè)插件串聯(lián)起來(lái),而實(shí)現(xiàn)這一切的核心就是Tapable,webpack中最核心的負(fù)責(zé)編譯的Compiler和負(fù)責(zé)創(chuàng)建bundles的Compilation都是Tapable的實(shí)例。Tapable 向外暴露許多的鉤子類(lèi),這些類(lèi)可以很方便地為插件創(chuàng)建事件鉤子。 Tapable 中定義了如下幾種鉤子類(lèi):

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

        所有鉤子類(lèi)的構(gòu)造函數(shù)都接收一個(gè)可選的參數(shù),這個(gè)參數(shù)是一個(gè)由字符串參數(shù)組成的數(shù)組,如下:

        const hook = new SyncHook(["arg1", "arg2", "arg3"]);

        鉤子概覽

        Tapable的鉤子分為兩類(lèi),同步和異步,其中異步又分為并行和串行:

        深入理解webpack

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

        序號(hào) 鉤子名 執(zhí)行方式 使用要點(diǎn)
        1 SyncHook 同步串行 不關(guān)心監(jiān)聽(tīng)函數(shù)的返回值
        2 SyncBailHook 同步串行 只要監(jiān)聽(tīng)函數(shù)中有一個(gè)函數(shù)的返回值不為 null,則跳過(guò)剩下所有的邏輯
        3 SyncWaterfallHook 同步串行 上一個(gè)監(jiān)聽(tīng)函數(shù)的返回值可以傳給下一個(gè)監(jiān)聽(tīng)函數(shù)
        4 SyncLoopHook 同步循環(huán) 當(dāng)監(jiān)聽(tīng)函數(shù)被觸發(fā)的時(shí)候,如果該監(jiān)聽(tīng)函數(shù)返回true時(shí)則這個(gè)監(jiān)聽(tīng)函數(shù)會(huì)反復(fù)執(zhí)行,如果返回 undefined 則表示退出循環(huán)
        5 AsyncParallelHook 異步并發(fā) 不關(guān)心監(jiān)聽(tīng)函數(shù)的返回值
        6 AsyncParallelBailHook 異步并發(fā) 只要監(jiān)聽(tīng)函數(shù)的返回值不為 null,就會(huì)忽略后面的監(jiān)聽(tīng)函數(shù)執(zhí)行,直接跳躍到callAsync等觸發(fā)函數(shù)綁定的回調(diào)函數(shù),然后執(zhí)行這個(gè)被綁定的回調(diào)函數(shù)
        7 AsyncSeriesHook 異步串行 不關(guān)系callback()的參數(shù)
        8 AsyncSeriesBailHook 異步串行 callback()的參數(shù)不為null,就會(huì)直接執(zhí)行callAsync等觸發(fā)函數(shù)綁定的回調(diào)函數(shù)
        9 AsyncSeriesWaterfallHook 異步串行 上一個(gè)監(jiān)聽(tīng)函數(shù)的中的callback(err, data)的第二個(gè)參數(shù),可以作為下一個(gè)監(jiān)聽(tīng)函數(shù)的參數(shù)

        Sync鉤子

        同步串行
        (1) SyncHook
        不關(guān)心監(jiān)聽(tīng)函數(shù)的返回值

        • 使用
        const { SyncHook } = require("tapable"); let queue = new SyncHook(['name']); //所有的構(gòu)造函數(shù)都接收一個(gè)可選的參數(shù),這個(gè)參數(shù)是一個(gè)字符串的數(shù)組。  // 訂閱 queue.tap('1', function (name, name2) {// tap 的第一個(gè)參數(shù)是用來(lái)標(biāo)識(shí)訂閱的函數(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ā)布的時(shí)候觸發(fā)訂閱的函數(shù) 同時(shí)傳入?yún)?shù)  // 執(zhí)行結(jié)果: /* webpack undefined 1 // 傳入的參數(shù)需要和new實(shí)例的時(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)聽(tīng)函數(shù)中有一個(gè)函數(shù)的返回值不為 null,則跳過(guò)剩下所有的邏輯

        • 使用
        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
        上一個(gè)監(jiān)聽(tīng)函數(shù)的返回值可以傳給下一個(gè)監(jiān)聽(tīng)函數(shù)

        • 使用
        const {     SyncWaterfallHook } = require("tapable");  let queue = new SyncWaterfallHook(['name']);  // 上一個(gè)函數(shù)的返回值可以傳給下一個(gè)函數(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)聽(tīng)函數(shù)被觸發(fā)的時(shí)候,如果該監(jiān)聽(tīng)函數(shù)返回true時(shí)則這個(gè)監(jiān)聽(tīng)函數(shù)會(huì)反復(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)聽(tīng)函數(shù)的返回值。有三種注冊(cè)/發(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,就會(huì)直接執(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
        上一個(gè)監(jiān)聽(tīng)函數(shù)的中的callback(err, data)的第二個(gè)參數(shù),可以作為下一個(gè)監(jiān)聽(tīng)函數(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ā):

        類(lèi)型 名字 事件名
        [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

        幾個(gè)關(guān)鍵的事件對(duì)應(yīng)打包的階段:

        • entry-option:初始化options
        • run:開(kāi)始編譯
        • make:從entry開(kāi)始遞歸分析依賴(lài)并對(duì)依賴(lài)進(jìn)行build
        • build-moodule:使用loader加載文件并build模塊
        • normal-module-loader:對(duì)loader加載的文件用acorn編譯,生成抽象語(yǔ)法樹(shù)AST
        • program:開(kāi)始對(duì)AST進(jìn)行遍歷,當(dāng)遇到require時(shí)觸發(fā)call require事件
        • seal:所有依賴(lài)build完成,開(kāi)始對(duì)chunk進(jìn)行優(yōu)化(抽取公共模塊、加hash等)
        • optimize-chunk-assets:壓縮代碼
        • emit:把各個(gè)chunk輸出到結(jié)果文件

        了解以上事件,你可以很容易地寫(xiě)出一個(gè)插件。

        …未完待續(xù)

        引用

        • Webpack-源碼二,整體調(diào)用流程與Tapable事件流
        • webpack4.0源碼分析之Tapable

        相關(guān)教程推薦:《Web pack入門(mén)視頻教程》

        贊(0)
        分享到: 更多 (0)
        網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)
        主站蜘蛛池模板: 日韩视频中文字幕精品偷拍| 久久99精品久久只有精品| 最新精品亚洲成a人在线观看| 99精品国产自在现线观看| 午夜精品久久久久久| 欧美国产亚洲精品高清不卡| 精品免费久久久久久久| 亚洲麻豆精品国偷自产在线91| 国产精品1024视频| 久久精品男人影院| 97精品国产福利一区二区三区 | 完整观看高清秒播国内外精品资源 | 久久青青草原精品影院| 国产亚洲精品美女久久久| 亚洲色精品88色婷婷七月丁香| 久久亚洲中文字幕精品一区| 国产激情精品一区二区三区| 欧美高清在线精品一区| 97r久久精品国产99国产精| 精品亚洲成AV人在线观看| 自拍偷在线精品自拍偷| 蜜桃麻豆www久久国产精品| 黑巨人与欧美精品一区| 国产精品九九九| 99精品国产一区二区| 青青草国产精品欧美成人| 国内精品在线视频| 好吊妞视频精品| 久久99精品国产麻豆宅宅| 九九在线精品视频专区| 麻豆精品成人免费国产片| 337P亚洲精品色噜噜| 99熟女精品视频一区二区三区| 国产成人精品a视频一区| 黄床大片免费30分钟国产精品| 久久99精品免费一区二区| 国内精品国产成人国产三级| 精品欧美一区二区在线看片| 精品欧美一区二区在线观看| 无码人妻精品一区二| 亚洲精品无码专区在线在线播放|