Webpack은 현재 가장 널리 사용되는 패키징 도구 중 하나입니다. 간단한 구성, 강력한 기능, 풍부한 로더 및 플러그인 시스템을 갖추고 있어 프런트엔드 개발자에게 많은 편의를 제공합니다. 저자는 이 장을 읽기 전에 독자들이 webpack을 사용해 본 경험이 있다고 가정하고 있으므로 webpack 사용법에 대해서는 자세히 다루지 않겠습니다.
이 장을 읽으면 다음 내용을 배울 수 있습니다.
Webpack 패키지 코드 구조
간단한 패키징
먼저 가장 간단한 방법을 작성한 후 webpack을 사용하여 패키징합니다:
// /webpack/bundles/simple/moduleA.js window.printA = function printA() { console.log(`This is module A!`); }
비교적 기본적인 webpack 구성 파일:
// /webpack/bundles/simple/webpack.config.js const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { main: './moduleA.js' }, output: { path: path.resolve(__dirname, 'dist'), filename: 'simple.bundle.js' }, plugins: [ new HtmlWebpackPlugin({ template: './index.html' }) ] }
브라우저 환경에서 테스트용 HTML 파일 생성 :
nbsp;html> <meta> <meta> <meta> <title>Webpack - Simple Bundle</title>
패키징 명령 webpack
을 실행한 후 dist
디렉터리를 얻고 simple.bundle.js
파일을 열었습니다. webpack
后我们获得了一个 dist
目录,我们打开 simple.bundle.js
文件:
/******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports) { window.printA = function printA() { console.log(`This is module A!`); } /***/ }) /******/ ]);
主要看这段:
// ...... var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 0); // ......
webpack内部定义了一个 webpack_require 的方法,这个方法的实质很简单:
多模块间存在简单依赖
例如 moduleB.js 依赖于 moduleA.js 文件。
// /webpack/bundles/simpleDependencies/moduleA.js module.exports = window.printA = function printA() { console.log(`This is module A!`); }
// /webpack/bundles/simpleDependencies/moduleB.js const printA = require('./moduleA'); module.exports = window.printB = function printB() { printA(); console.log('This is module B!'); }
将配置文件中的入口更改为
// /webpack/bundles/simpleDependencies/webpack.config.js // ... main: './moduleB.js' // ...
再次打包,我们获得如下代码:
// /webpack/bundles/simpleDependencies/dist/bundle.js /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { const printA = __webpack_require__(1); module.exports = window.printB = function printB() { printA(); console.log('This is module B!'); } /***/ }), /* 1 */ /***/ (function(module, exports) { module.exports = window.printA = function printA() { console.log(`This is module A!`); } /***/ }) /******/ ]);
我们可以发现这块有点变化:
/* 0 */ /***/ (function(module, exports, __webpack_require__) { const printA = __webpack_require__(1); module.exports = window.printB = function printB() { printA(); console.log('This is module B!'); }
在 moduleB.js 中,需要依赖 moduleA ,因而需要先执行 __webpack_require(1) 拿到模块A后,再进行下一步。
多入口
需要注意,打包的文件中moudleId是不会重复的,如果有两个入口文件的情况,则入口模块id都为0,其他依赖模块id不重复。我们创建如下几个文件,其中 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 这个插件之前,这两个文件是存在重复代码的。也就是每个入口都会独立进行打包。
我们看如果添加了 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 <p>common.js 已经包含了所有的公共方法,并且在浏览器 window 对象中创建了一个名为 webpackJsonp 的方法。</p><pre class="brush:php;toolbar:false">// /webpack/bundles/CommonsChunkPlugin/dist/common.js // ... /******/ var parentJsonpFunction = window["webpackJsonp"]; /******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) { /******/ // add "moreModules" to the modules object, /******/ // then flag all "chunkIds" as loaded and fire callback /******/ var moduleId, chunkId, i = 0, resolves = [], result; /******/ for(;i <p>这个方法与 __webpack_require__ 较为类似,同样也是将模块缓存进来。只不过 webpack 会预先抽取公共模块,先将其缓存进来,而后可以在其他的 bundle.js 中使用 webpackJsonp 方法进行模块加载。</p><pre class="brush:php;toolbar:false">// /webpack/bundles/CommonsChunkPlugin/dist/index0.bundle.js webpackJsonp([1],[],[1]);
// /webpack/bundles/CommonsChunkPlugin/dist/index1.bundle.js webpackJsonp([0],{ /***/ 3: /***/ (function(module, exports, __webpack_require__) { const common = __webpack_require__(0); const index0 = __webpack_require__(1); module.exports = window.print1 = function() { common(); console.log('This is module 1!'); } /***/ }) },[3]);
Webpack核心架构 —— Tapable
从github
上将webpack源码克隆至本地,我们可以先了解到 webpack 的一个整体流程:
具体分析_addModuleChain,其完成的第二件事构建模块又可以分为三部分:
具体看 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
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) { // ... } }주로 이 단락을 살펴보세요: 🎜
const hook = new SyncHook(["arg1", "arg2", "arg3"]);🎜webpack은 내부적으로 🎜webpack_require🎜 메소드를 정의합니다. 이 메소드의 본질은 매우 간단합니다: 🎜🎜🎜🎜🎜여러 모듈 사이에는 단순한 종속성이 있습니다🎜🎜예를 들어 moduleB.js는 moduleA.js에 종속됩니다. 문서. 🎜
const { SyncHook } = require("tapable"); let queue = new SyncHook(['name']); //所有的构造函数都接收一个可选的参数,这个参数是一个字符串的数组。 // 订阅 queue.tap('1', function (name, name2) {// tap 的第一个参数是用来标识订阅的函数的 console.log(name, name2, 1); return '1' }); queue.tap('2', function (name) { console.log(name, 2); }); queue.tap('3', function (name) { console.log(name, 3); }); // 发布 queue.call('webpack', 'webpack-cli');// 发布的时候触发订阅的函数 同时传入参数 // 执行结果: /* webpack undefined 1 // 传入的参数需要和new实例的时候保持一致,否则获取不到多传的参数 webpack 2 webpack 3 */
class SyncHook_MY{ constructor(){ this.hooks = []; } // 订阅 tap(name, fn){ this.hooks.push(fn); } // 发布 call(){ this.hooks.forEach(hook => hook(...arguments)); } }🎜구성 파일의 항목을 🎜
const { SyncBailHook } = require("tapable"); let queue = new SyncBailHook(['name']); queue.tap('1', function (name) { console.log(name, 1); }); queue.tap('2', function (name) { console.log(name, 2); return 'wrong' }); queue.tap('3', function (name) { console.log(name, 3); }); queue.call('webpack'); // 执行结果: /* webpack 1 webpack 2 */🎜Package로 다시 변경하면 다음 코드를 얻게 됩니다.🎜
class SyncBailHook_MY { constructor() { this.hooks = []; } // 订阅 tap(name, fn) { this.hooks.push(fn); } // 发布 call() { for (let i = 0, l = this.hooks.length; i 🎜이 부분에서 몇 가지 변경 사항을 찾을 수 있습니다.🎜<pre class="brush:php;toolbar:false">const { SyncWaterfallHook } = require("tapable"); let queue = new SyncWaterfallHook(['name']); // 上一个函数的返回值可以传给下一个函数 queue.tap('1', function (name) { console.log(name, 1); return 1; }); queue.tap('2', function (data) { console.log(data, 2); return 2; }); queue.tap('3', function (data) { console.log(data, 3); }); queue.call('webpack'); // 执行结果: /* webpack 1 1 2 2 3 */🎜moduleB.js에서는 moduleA에 의존해야 합니다. 따라서 먼저 실행해야 합니다. __webpack_require(1) 모듈 A를 가져온 후 다음 단계로 진행합니다. 🎜🎜🎜여러 항목🎜🎜패키지 파일의 moduleId는 반복되지 않습니다. 항목 파일이 두 개인 경우 항목 모듈 ID는 0이 되며 다른 종속 모듈의 ID는 반복되지 않습니다. 다음 파일을 생성하는데, 그 중 index0.js는 common.js와 dependency.js에 의존하고, index1.js는 index0.js와 common.js 두 파일에 의존합니다. 🎜
class SyncWaterfallHook_MY{ constructor(){ this.hooks = []; } // 订阅 tap(name, fn){ this.hooks.push(fn); } // 发布 call(){ let result = null; for(let i = 0, l = this.hooks.length; i <pre class="brush:php;toolbar:false">const { SyncLoopHook } = require("tapable"); let queue = new SyncLoopHook(['name']); let count = 3; queue.tap('1', function (name) { console.log('count: ', count--); if (count > 0) { return true; } return; }); queue.call('webpack'); // 执行结果: /* count: 3 count: 2 count: 1 */
class SyncLoopHook_MY { constructor() { this.hook = null; } // 订阅 tap(name, fn) { this.hook = fn; } // 发布 call() { let result; do { result = this.hook(...arguments); } while (result) } }
const { AsyncParallelHook } = require("tapable"); let queue1 = new AsyncParallelHook(['name']); console.time('cost'); queue1.tap('1', function (name) { console.log(name, 1); }); queue1.tap('2', function (name) { console.log(name, 2); }); queue1.tap('3', function (name) { console.log(name, 3); }); queue1.callAsync('webpack', err => { console.timeEnd('cost'); }); // 执行结果 /* webpack 1 webpack 2 webpack 3 cost: 4.520ms */🎜webpack.config.js의 파일 항목 수정: 🎜
let queue2 = new AsyncParallelHook(['name']); console.time('cost1'); queue2.tapAsync('1', function (name, cb) { setTimeout(() => { console.log(name, 1); cb(); }, 1000); }); queue2.tapAsync('2', function (name, cb) { setTimeout(() => { console.log(name, 2); cb(); }, 2000); }); queue2.tapAsync('3', function (name, cb) { setTimeout(() => { console.log(name, 3); cb(); }, 3000); }); queue2.callAsync('webpack', () => { console.log('over'); console.timeEnd('cost1'); }); // 执行结果 /* webpack 1 webpack 2 webpack 3 over time: 3004.411ms */🎜패키지 파일: 🎜
let queue3 = new AsyncParallelHook(['name']); console.time('cost3'); queue3.tapPromise('1', function (name, cb) { return new Promise(function (resolve, reject) { setTimeout(() => { console.log(name, 1); resolve(); }, 1000); }); }); queue3.tapPromise('1', function (name, cb) { return new Promise(function (resolve, reject) { setTimeout(() => { console.log(name, 2); resolve(); }, 2000); }); }); queue3.tapPromise('1', function (name, cb) { return new Promise(function (resolve, reject) { setTimeout(() => { console.log(name, 3); resolve(); }, 3000); }); }); queue3.promise('webpack') .then(() => { console.log('over'); console.timeEnd('cost3'); }, () => { console.log('error'); console.timeEnd('cost3'); }); /* webpack 1 webpack 2 webpack 3 over cost3: 3007.925ms */
const { AsyncSeriesHook } = require("tapable"); // tap let queue1 = new AsyncSeriesHook(['name']); console.time('cost1'); queue1.tap('1', function (name) { console.log(1); return "Wrong"; }); queue1.tap('2', function (name) { console.log(2); }); queue1.tap('3', function (name) { console.log(3); }); queue1.callAsync('zfpx', err => { console.log(err); console.timeEnd('cost1'); }); // 执行结果 /* 1 2 3 undefined cost1: 3.933ms */🎜분명히 CommonsChunkPlugin 플러그인을 사용하기 전에는 이 두 파일에 중복 코드가 있었습니다. 즉, 각 입구는 독립적으로 포장됩니다. 🎜CommonsChunkPlugin 플러그인을 추가한 후의 상황을 살펴보겠습니다(webpack.config.js 수정). 🎜
let queue2 = new AsyncSeriesHook(['name']); console.time('cost2'); queue2.tapAsync('1', function (name, cb) { setTimeout(() => { console.log(name, 1); cb(); }, 1000); }); queue2.tapAsync('2', function (name, cb) { setTimeout(() => { console.log(name, 2); cb(); }, 2000); }); queue2.tapAsync('3', function (name, cb) { setTimeout(() => { console.log(name, 3); cb(); }, 3000); }); queue2.callAsync('webpack', (err) => { console.log(err); console.log('over'); console.timeEnd('cost2'); }); // 执行结果 /* webpack 1 webpack 2 webpack 3 undefined over cost2: 6019.621ms */🎜이렇게 하면 index0.bundle.js, index1.bundel.js, common.js 세 개의 파일이 생성됩니다. : 🎜
let queue3 = new AsyncSeriesHook(['name']); console.time('cost3'); queue3.tapPromise('1',function(name){ return new Promise(function(resolve){ setTimeout(function(){ console.log(name, 1); resolve(); },1000) }); }); queue3.tapPromise('2',function(name,callback){ return new Promise(function(resolve){ setTimeout(function(){ console.log(name, 2); resolve(); },2000) }); }); queue3.tapPromise('3',function(name,callback){ return new Promise(function(resolve){ setTimeout(function(){ console.log(name, 3); resolve(); },3000) }); }); queue3.promise('webapck').then(err=>{ console.log(err); console.timeEnd('cost3'); }); // 执行结果 /* webapck 1 webapck 2 webapck 3 undefined cost3: 6021.817ms */🎜common.js는 모든 공개 메소드를 포함하고 브라우저 창 객체에 webpackJsonp라는 메소드를 생성했습니다. 🎜
class AsyncSeriesHook_MY { constructor() { this.hooks = []; } tapAsync(name, fn) { this.hooks.push(fn); } callAsync() { var slef = this; var args = Array.from(arguments); let done = args.pop(); let idx = 0; function next(err) { // 如果next的参数有值,就直接跳跃到 执行callAsync的回调函数 if (err) return done(err); let fn = slef.hooks[idx++]; fn ? fn(...args, next) : done(); } next(); } }🎜이 방법은 모듈도 캐시하는 __webpack_require__와 유사합니다. 단지 webpack이 공개 모듈을 미리 추출하고 먼저 캐시한 다음 다른 Bundle.js의 webpackJsonp 메소드를 사용하여 모듈을 로드할 수 있다는 것입니다. 🎜
const { AsyncSeriesBailHook } = require("tapable"); // tap let queue1 = new AsyncSeriesBailHook(['name']); console.time('cost1'); queue1.tap('1', function (name) { console.log(1); return "Wrong"; }); queue1.tap('2', function (name) { console.log(2); }); queue1.tap('3', function (name) { console.log(3); }); queue1.callAsync('webpack', err => { console.log(err); console.timeEnd('cost1'); }); // 执行结果: /* 1 null cost1: 3.979ms */
let queue2 = new AsyncSeriesBailHook(['name']); console.time('cost2'); queue2.tapAsync('1', function (name, callback) { setTimeout(function () { console.log(name, 1); callback(); }, 1000) }); queue2.tapAsync('2', function (name, callback) { setTimeout(function () { console.log(name, 2); callback('wrong'); }, 2000) }); queue2.tapAsync('3', function (name, callback) { setTimeout(function () { console.log(name, 3); callback(); }, 3000) }); queue2.callAsync('webpack', err => { console.log(err); console.log('over'); console.timeEnd('cost2'); }); // 执行结果 /* webpack 1 webpack 2 wrong over cost2: 3014.616ms */🎜🎜🎜Webpack 핵심 아키텍처 - Tapable🎜🎜🎜🎜웹팩 소스 코드를
github
에서 로컬로 복제하면 먼저 웹팩의 전체 프로세스를 이해할 수 있습니다. 🎜🎜🎜🎜🎜lib/webpack.js 컴파일러 객체를 호출하고 컴파일러.run()을 호출합니다.🎜🎜lib/Compiler.js에서 run 메소드는 before-run과 run의 두 가지 이벤트를 트리거한 다음 readRecords를 통해 파일을 읽고 compile과 같은 Trigger 이벤트를 패키징합니다. before-compile, compile, make 등; compile이 주요 프로세스입니다. 이 메서드에서는 Compilation 클래스가 인스턴스화되고 해당 final 및 seal 메서드가 호출됩니다. 🎜🎜lib/Compilation.js는 마무리 및 밀봉 방법과 중요한 addEntry 방법을 정의합니다. 이 메소드는 전용 메소드인 _addModuleChain을 호출하여 두 가지 작업을 수행합니다. 모듈 유형에 따라 해당 모듈 팩토리를 가져오고 모듈을 생성합니다. 🎜🎜lib/Compiler.js는 addEntry를 명시적으로 호출하지 않지만 make 이벤트를 트리거합니다. lib/DllEntryPlugin.js는 make 이벤트를 수신하고 콜백 함수에서 addEntry를 호출하는 플러그인입니다. 🎜🎜🎜모듈 빌드를 위해 두 번째로 완료되는 _addModuleChain에 대한 자세한 분석은 세 부분으로 나눌 수 있습니다. 🎜🎜🎜모듈 간의 종속성을 처리하기 위해 로더를 호출합니다. 🎜🎜로더가 처리한 파일을 도토리를 통해 추상 구문 트리 AST로 추상화합니다. 🎜🎜AST를 탐색하고 모듈의 모든 종속성을 빌드합니다. 🎜🎜🎜자세한 내용은 lib/webpack.js
파일을 참조하세요. 이 파일은 webpack
의 항목 파일입니다. 🎜let queue3 = new AsyncSeriesBailHook(['name']); console.time('cost3'); queue3.tapPromise('1', function (name) { return new Promise(function (resolve, reject) { setTimeout(function () { console.log(name, 1); resolve(); }, 1000) }); }); queue3.tapPromise('2', function (name, callback) { return new Promise(function (resolve, reject) { setTimeout(function () { console.log(name, 2); reject(); }, 2000) }); }); queue3.tapPromise('3', function (name, callback) { return new Promise(function (resolve) { setTimeout(function () { console.log(name, 3); resolve(); }, 3000) }); }); queue3.promise('webpack').then(err => { console.log(err); console.log('over'); console.timeEnd('cost3'); }, err => { console.log(err); console.log('error'); console.timeEnd('cost3'); }); // 执行结果: /* webpack 1 webpack 2 undefined error cost3: 3017.608ms */🎜
lib/webpack.js
프로세스는 대략 다음과 같습니다. 🎜
Compiler
(编译器)对象NodeEnvironmentPlugin
environment
里的方法afterEnvironment
里的方法compiler
向外导出显然,Compiler是我们需要深究的一个部分,因为 webpack 最终向外部返回也就是这个 Compiler 实例。大致了解下 Compiler
的实现:
class Compiler extends Tapable { constructor(context) { super(); this.hooks = { // ... }; this._pluginCompat.tap("Compiler", options => { // ... }); // ... this.resolvers = { normal: { // ... }, loader: { // ... }, context: { // ... } }; // ... } watch(watchOptions, handler) { // ... } run(callback) { // ... } runAsChild(callback) { // ... } purgeInputFileSystem() { // ... } emitAssets(compilation, callback) { // ... } emitRecords(callback) { // ... } readRecords(callback) { // ... } createChildCompiler( compilation, compilerName, compilerIndex, outputOptions, plugins ) { // ... } isChild() { // ... } createCompilation() { // ... } newCompilation(params) { // ... } createNormalModuleFactory() { // ... } createContextModuleFactory() { // ... } newCompilationParams() { // ... } compile(callback) { // ... } }
Compiler
继承自 Tapable
,在其构造方法中,定义了一些事件钩子(hooks
)、一些变量以及一些方法。这些变量以及方法目前看来还是非常抽象的,所以我们有必要去了解下 Tapable
的实现。
Tapable的Github主页 对 Tapable
的介绍如下:
实际上,webpack基于事件流机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的Compiler和负责创建bundles的Compilation都是Tapable的实例。Tapable
向外暴露许多的钩子类,这些类可以很方便地为插件创建事件钩子。 Tapable
中定义了如下几种钩子类:
所有钩子类的构造函数都接收一个可选的参数,这个参数是一个由字符串参数组成的数组,如下:
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
钩子概览
Tapable的钩子分为两类,同步和异步,其中异步又分为并行和串行:
每种钩子都有各自的使用方式,如下表:
序号 | 钩子名 | 执行方式 | 使用要点 |
---|---|---|---|
1 | SyncHook | 同步串行 | 不关心监听函数的返回值 |
2 | SyncBailHook | 同步串行 | 只要监听函数中有一个函数的返回值不为 null,则跳过剩下所有的逻辑 |
3 | SyncWaterfallHook | 同步串行 | 上一个监听函数的返回值可以传给下一个监听函数 |
4 | SyncLoopHook | 同步循环 | 当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环 |
5 | AsyncParallelHook | 异步并发 | 不关心监听函数的返回值 |
6 | AsyncParallelBailHook | 异步并发 | 只要监听函数的返回值不为 null,就会忽略后面的监听函数执行,直接跳跃到callAsync等触发函数绑定的回调函数,然后执行这个被绑定的回调函数 |
7 | AsyncSeriesHook | 异步串行 | 不关系callback()的参数 |
8 | AsyncSeriesBailHook | 异步串行 | callback()的参数不为null,就会直接执行callAsync等触发函数绑定的回调函数 |
9 | AsyncSeriesWaterfallHook | 异步串行 | 上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数 |
Sync钩子
同步串行
(1) SyncHook
不关心监听函数的返回值
const { SyncHook } = require("tapable"); let queue = new SyncHook(['name']); //所有的构造函数都接收一个可选的参数,这个参数是一个字符串的数组。 // 订阅 queue.tap('1', function (name, name2) {// tap 的第一个参数是用来标识订阅的函数的 console.log(name, name2, 1); return '1' }); queue.tap('2', function (name) { console.log(name, 2); }); queue.tap('3', function (name) { console.log(name, 3); }); // 发布 queue.call('webpack', 'webpack-cli');// 发布的时候触发订阅的函数 同时传入参数 // 执行结果: /* webpack undefined 1 // 传入的参数需要和new实例的时候保持一致,否则获取不到多传的参数 webpack 2 webpack 3 */
class SyncHook_MY{ constructor(){ this.hooks = []; } // 订阅 tap(name, fn){ this.hooks.push(fn); } // 发布 call(){ this.hooks.forEach(hook => hook(...arguments)); } }
(2) SyncBailHook
只要监听函数中有一个函数的返回值不为 null,则跳过剩下所有的逻辑
const { SyncBailHook } = require("tapable"); let queue = new SyncBailHook(['name']); queue.tap('1', function (name) { console.log(name, 1); }); queue.tap('2', function (name) { console.log(name, 2); return 'wrong' }); queue.tap('3', function (name) { console.log(name, 3); }); queue.call('webpack'); // 执行结果: /* webpack 1 webpack 2 */
class SyncBailHook_MY { constructor() { this.hooks = []; } // 订阅 tap(name, fn) { this.hooks.push(fn); } // 发布 call() { for (let i = 0, l = this.hooks.length; i <p>(3) SyncWaterfallHook<br>上一个监听函数的返回值可以传给下一个监听函数</p>
const { SyncWaterfallHook } = require("tapable"); let queue = new SyncWaterfallHook(['name']); // 上一个函数的返回值可以传给下一个函数 queue.tap('1', function (name) { console.log(name, 1); return 1; }); queue.tap('2', function (data) { console.log(data, 2); return 2; }); queue.tap('3', function (data) { console.log(data, 3); }); queue.call('webpack'); // 执行结果: /* webpack 1 1 2 2 3 */
class SyncWaterfallHook_MY{ constructor(){ this.hooks = []; } // 订阅 tap(name, fn){ this.hooks.push(fn); } // 发布 call(){ let result = null; for(let i = 0, l = this.hooks.length; i <p>(4) SyncLoopHook<br>当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环。</p>
const { SyncLoopHook } = require("tapable"); let queue = new SyncLoopHook(['name']); let count = 3; queue.tap('1', function (name) { console.log('count: ', count--); if (count > 0) { return true; } return; }); queue.call('webpack'); // 执行结果: /* count: 3 count: 2 count: 1 */
class SyncLoopHook_MY { constructor() { this.hook = null; } // 订阅 tap(name, fn) { this.hook = fn; } // 发布 call() { let result; do { result = this.hook(...arguments); } while (result) } }
Async钩子
异步并行
(1) AsyncParallelHook
不关心监听函数的返回值。有三种注册/发布的模式,如下:
异步订阅 | 调用方法 |
---|---|
tap | callAsync |
tapAsync | callAsync |
tapPromise | promise |
const { AsyncParallelHook } = require("tapable"); let queue1 = new AsyncParallelHook(['name']); console.time('cost'); queue1.tap('1', function (name) { console.log(name, 1); }); queue1.tap('2', function (name) { console.log(name, 2); }); queue1.tap('3', function (name) { console.log(name, 3); }); queue1.callAsync('webpack', err => { console.timeEnd('cost'); }); // 执行结果 /* webpack 1 webpack 2 webpack 3 cost: 4.520ms */
let queue2 = new AsyncParallelHook(['name']); console.time('cost1'); queue2.tapAsync('1', function (name, cb) { setTimeout(() => { console.log(name, 1); cb(); }, 1000); }); queue2.tapAsync('2', function (name, cb) { setTimeout(() => { console.log(name, 2); cb(); }, 2000); }); queue2.tapAsync('3', function (name, cb) { setTimeout(() => { console.log(name, 3); cb(); }, 3000); }); queue2.callAsync('webpack', () => { console.log('over'); console.timeEnd('cost1'); }); // 执行结果 /* webpack 1 webpack 2 webpack 3 over time: 3004.411ms */
let queue3 = new AsyncParallelHook(['name']); console.time('cost3'); queue3.tapPromise('1', function (name, cb) { return new Promise(function (resolve, reject) { setTimeout(() => { console.log(name, 1); resolve(); }, 1000); }); }); queue3.tapPromise('1', function (name, cb) { return new Promise(function (resolve, reject) { setTimeout(() => { console.log(name, 2); resolve(); }, 2000); }); }); queue3.tapPromise('1', function (name, cb) { return new Promise(function (resolve, reject) { setTimeout(() => { console.log(name, 3); resolve(); }, 3000); }); }); queue3.promise('webpack') .then(() => { console.log('over'); console.timeEnd('cost3'); }, () => { console.log('error'); console.timeEnd('cost3'); }); /* webpack 1 webpack 2 webpack 3 over cost3: 3007.925ms */
异步串行
(1) AsyncSeriesHook
不关心callback()的参数。
const { AsyncSeriesHook } = require("tapable"); // tap let queue1 = new AsyncSeriesHook(['name']); console.time('cost1'); queue1.tap('1', function (name) { console.log(1); return "Wrong"; }); queue1.tap('2', function (name) { console.log(2); }); queue1.tap('3', function (name) { console.log(3); }); queue1.callAsync('zfpx', err => { console.log(err); console.timeEnd('cost1'); }); // 执行结果 /* 1 2 3 undefined cost1: 3.933ms */
let queue2 = new AsyncSeriesHook(['name']); console.time('cost2'); queue2.tapAsync('1', function (name, cb) { setTimeout(() => { console.log(name, 1); cb(); }, 1000); }); queue2.tapAsync('2', function (name, cb) { setTimeout(() => { console.log(name, 2); cb(); }, 2000); }); queue2.tapAsync('3', function (name, cb) { setTimeout(() => { console.log(name, 3); cb(); }, 3000); }); queue2.callAsync('webpack', (err) => { console.log(err); console.log('over'); console.timeEnd('cost2'); }); // 执行结果 /* webpack 1 webpack 2 webpack 3 undefined over cost2: 6019.621ms */
let queue3 = new AsyncSeriesHook(['name']); console.time('cost3'); queue3.tapPromise('1',function(name){ return new Promise(function(resolve){ setTimeout(function(){ console.log(name, 1); resolve(); },1000) }); }); queue3.tapPromise('2',function(name,callback){ return new Promise(function(resolve){ setTimeout(function(){ console.log(name, 2); resolve(); },2000) }); }); queue3.tapPromise('3',function(name,callback){ return new Promise(function(resolve){ setTimeout(function(){ console.log(name, 3); resolve(); },3000) }); }); queue3.promise('webapck').then(err=>{ console.log(err); console.timeEnd('cost3'); }); // 执行结果 /* webapck 1 webapck 2 webapck 3 undefined cost3: 6021.817ms */
class AsyncSeriesHook_MY { constructor() { this.hooks = []; } tapAsync(name, fn) { this.hooks.push(fn); } callAsync() { var slef = this; var args = Array.from(arguments); let done = args.pop(); let idx = 0; function next(err) { // 如果next的参数有值,就直接跳跃到 执行callAsync的回调函数 if (err) return done(err); let fn = slef.hooks[idx++]; fn ? fn(...args, next) : done(); } next(); } }
(2) AsyncSeriesBailHook
callback()的参数不为null,就会直接执行callAsync等触发函数绑定的回调函数。
const { AsyncSeriesBailHook } = require("tapable"); // tap let queue1 = new AsyncSeriesBailHook(['name']); console.time('cost1'); queue1.tap('1', function (name) { console.log(1); return "Wrong"; }); queue1.tap('2', function (name) { console.log(2); }); queue1.tap('3', function (name) { console.log(3); }); queue1.callAsync('webpack', err => { console.log(err); console.timeEnd('cost1'); }); // 执行结果: /* 1 null cost1: 3.979ms */
let queue2 = new AsyncSeriesBailHook(['name']); console.time('cost2'); queue2.tapAsync('1', function (name, callback) { setTimeout(function () { console.log(name, 1); callback(); }, 1000) }); queue2.tapAsync('2', function (name, callback) { setTimeout(function () { console.log(name, 2); callback('wrong'); }, 2000) }); queue2.tapAsync('3', function (name, callback) { setTimeout(function () { console.log(name, 3); callback(); }, 3000) }); queue2.callAsync('webpack', err => { console.log(err); console.log('over'); console.timeEnd('cost2'); }); // 执行结果 /* webpack 1 webpack 2 wrong over cost2: 3014.616ms */
let queue3 = new AsyncSeriesBailHook(['name']); console.time('cost3'); queue3.tapPromise('1', function (name) { return new Promise(function (resolve, reject) { setTimeout(function () { console.log(name, 1); resolve(); }, 1000) }); }); queue3.tapPromise('2', function (name, callback) { return new Promise(function (resolve, reject) { setTimeout(function () { console.log(name, 2); reject(); }, 2000) }); }); queue3.tapPromise('3', function (name, callback) { return new Promise(function (resolve) { setTimeout(function () { console.log(name, 3); resolve(); }, 3000) }); }); queue3.promise('webpack').then(err => { console.log(err); console.log('over'); console.timeEnd('cost3'); }, err => { console.log(err); console.log('error'); console.timeEnd('cost3'); }); // 执行结果: /* webpack 1 webpack 2 undefined error cost3: 3017.608ms */
(3) AsyncSeriesWaterfallHook
上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数
const { AsyncSeriesWaterfallHook } = require("tapable"); // tap let queue1 = new AsyncSeriesWaterfallHook(['name']); console.time('cost1'); queue1.tap('1', function (name) { console.log(name, 1); return 'lily' }); queue1.tap('2', function (data) { console.log(2, data); return 'Tom'; }); queue1.tap('3', function (data) { console.log(3, data); }); queue1.callAsync('webpack', err => { console.log(err); console.log('over'); console.timeEnd('cost1'); }); // 执行结果: /* webpack 1 2 'lily' 3 'Tom' null over cost1: 5.525ms */
let queue2 = new AsyncSeriesWaterfallHook(['name']); console.time('cost2'); queue2.tapAsync('1', function (name, callback) { setTimeout(function () { console.log('1: ', name); callback(null, 2); }, 1000) }); queue2.tapAsync('2', function (data, callback) { setTimeout(function () { console.log('2: ', data); callback(null, 3); }, 2000) }); queue2.tapAsync('3', function (data, callback) { setTimeout(function () { console.log('3: ', data); callback(null, 3); }, 3000) }); queue2.callAsync('webpack', err => { console.log(err); console.log('over'); console.timeEnd('cost2'); }); // 执行结果: /* 1: webpack 2: 2 3: 3 null over cost2: 6016.889ms */
let queue3 = new AsyncSeriesWaterfallHook(['name']); console.time('cost3'); queue3.tapPromise('1', function (name) { return new Promise(function (resolve, reject) { setTimeout(function () { console.log('1:', name); resolve('1'); }, 1000) }); }); queue3.tapPromise('2', function (data, callback) { return new Promise(function (resolve) { setTimeout(function () { console.log('2:', data); resolve('2'); }, 2000) }); }); queue3.tapPromise('3', function (data, callback) { return new Promise(function (resolve) { setTimeout(function () { console.log('3:', data); resolve('over'); }, 3000) }); }); queue3.promise('webpack').then(err => { console.log(err); console.timeEnd('cost3'); }, err => { console.log(err); console.timeEnd('cost3'); }); // 执行结果: /* 1: webpack 2: 1 3: 2 over cost3: 6016.703ms */
class AsyncSeriesWaterfallHook_MY { constructor() { this.hooks = []; } tapAsync(name, fn) { this.hooks.push(fn); } callAsync() { let self = this; var args = Array.from(arguments); let done = args.pop(); console.log(args); let idx = 0; let result = null; function next(err, data) { if (idx >= self.hooks.length) return done(); if (err) { return done(err); } let fn = self.hooks[idx++]; if (idx == 1) { fn(...args, next); } else { fn(data, next); } } next(); } }
Tapable事件流
webpack中的事件归纳如下,这些事件出现的顺序固定,但不一定每次打包所有事件都触发:
Type | Name | 이벤트 이름 |
---|---|---|
[C] | applyPluginsBailResult | entry-option |
[A] | 적용 플러그인 | 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|
applyPluginsAsyncWaterfall | before-resolve | |
applyPluginsWaterfall | factory | |
applyPluginsWaterfall | resolver | |
app lyPlugins | resolve | |
applyPlugins | resolve-step | |
applyPluginsParallelBailResult | file | |
applyPluginsParallelBailResult | 디렉토리 | |
applyPlugins | 해결 단계 | |
applyPluginsParallelBailResult | result | |
applyPluginsAsyncWaterfall | after-resolve | |
applyPlug insBailResult | create-module | |
applyPluginsWaterfall | module | |
applyPlugins | build-module | |
applyPlugins | normal-module-loader | |
applyPluginsBailResult | program | |
applyPluginsBailResult | statement | |
applyPluginsBailResult | CallExpression 평가 | |
applyPlug insBailResult | var data | |
applyPluginsBailResult | 식별자 평가 | |
applyPluginsBailResult | 식별자 평가 require | |
applyPluginsBailResult | call require | |
applyPluginsBailResult | 리터럴 평가 | |
applyPluginsBailResult | call require:amd:array | |
applyPluginsBailResult | 리터럴 평가 | |
applyPluginsBail 결과 | 호출 require:commonjs:item | |
applyPluginsBailResult | statement | |
applyPluginsBailResult | MemberExpression 평가 | |
applyPlugin sBailResult | 식별자 평가 console.log | |
applyPluginsBailResult | console.log를 호출 | |
applyPluginsBailResult | expression console.log | |
applyPluginsBailResult | 표현 콘솔 | |
플러그인 적용 | 성공 -module | |
applyPluginsAsyncWaterfall | before-resolve | |
applyPluginsWaterfall | factory | |
applyPlugins | build-module | |
applyPlugins | succeed-module | |
applyPlugins | seal | |
applyPlugins | optimize | |
플러그인 적용 | 최적화 - 모듈 | |
applyPlugins | after-optimize-modules | |
applyPlugins | optimize-chunks | |
applyPlugins | 최적화 후- 덩어리 | |
applyPluginsAsyncSeries | optimize-tree | |
applyPlugins | after-optimize-tree | |
applyPluginsBailResult | 기록해야 함 | [A] |
revive-modules | [A] | |
optimize-module-order | [A] | |
모듈 ID 이전 | [A] | |
optimize-module-ids | [A] | |
after-optimize-module-ids | [A] | |
기록 모듈 | [A] | |
revive-chunks | [A] | |
optimize-chunk-order | [A] | |
청크 이전의 ID | [A] | |
optimize-chunk-ids | [A] | |
after-optimize-chunk-ids | [A] | |
기록 덩어리 | ||
[A] | applyPlugins | before-hash |
[A] | applyPlugins | hash |
[A] | applyPlugins | h ash-for-chunk |
[A] | applyPlugins | chunk-hash |
[A] | applyPlugins | after-hash |
[A] | applyPlugins | 청크 자산 전 |
[B] | 적용플러그인폭포 | global-hash-paths |
[C] | applyPluginsBailResult | global-hash |
[B] | applyPluginsWaterfall | bootstrap |
[B] | applyPluginsWaterfall | local-vars |
[B] | applyPluginsWaterfall | require |
[B] | applyPluginsWaterfall | module-obj |
[B] | 적용 플러그인Waterfall | 모듈 필요 |
[B] | applyPluginsWaterfall | require-extensions |
[B] | applyPluginsWaterfall | asset-path |
[B] | applyPluginsWaterfall | startup |
[B] | applyPluginsWaterfall | 모듈- require |
[B] | applyPluginsWaterfall | render |
[B] | applyPluginsWaterfall | module |
[B] | applyPluginsWater fall | 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 | 추가 자산 |
[D] | applyPluginsAsyncSeries | 최적화 청크 자산 |
[A] | applyPlug ins | 최적화 후 청크 자산 |
[D] | applyPluginsAsyncSeries | optimize-assets |
[A] | applyPlugins | after-optimize-assets |
[D] | applyPluginsA syncSeries | 컴파일 후 |
[ C] | applyPluginsBailResult | 반출해야 함 |
[D] | applyPluginsAsyncSeries | emit |
[B] | applyPluginsWaterfall | asset- 경로 |
[D] | applyPluginsAsyncSeries | after-emit |
[A] | applyPlugins | done |
几个关键的事件对应打包的阶段:
了解以上事件,你可以很容易地写流个插件。
...未完待续
引用
상关教程推荐: 《Web pack入门视频教程》
위 내용은 웹팩에 대한 깊은 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!