超细致的webpack道理解读

webpack道明白读

本文抄自《深入浅出webpack》,发起想进修道理的手打一遍,操纵一遍,给他人讲一遍,然后就会了

在浏览前愿望您已有webpack相干的实践经验,不然读了也读不懂

本文浏览须要几分钟,明白须要本身着手操纵蛮长时候

0 设置文件

起首简朴看一下webpack设置文件(webpack.config.js):

var path = require('path');
var node_modules = path.resolve(__dirname, 'node_modules');
var pathToReact = path.resolve(node_modules, 'react/dist/react.min.js');

module.exports = {
  // 进口文件,是模块构建的动身点,同时每一个进口文件对应末了天生的一个 chunk。
  entry: {
    bundle: [
      'webpack/hot/dev-server',
      'webpack-dev-server/client?http://localhost:8080',
      path.resolve(__dirname, 'app/app.js')
    ]
  },
  // 文件途径指向(可加快打包历程)。
  resolve: {
    alias: {
      'react': pathToReact
    }
  },
  // 天生文件,是模块构建的尽头,包括输出文件与输出途径。
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: '[name].js'
  },
  // 这里设置了处置惩罚各模块的 loader ,包括 css 预处置惩罚 loader ,es6 编译 loader,图片处置惩罚 loader。
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel',
        query: {
          presets: ['es2015', 'react']
        }
      }
    ],
    noParse: [pathToReact]
  },
  // webpack 各插件对象,在 webpack 的事宜流中实行对应的要领。
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ]
};

1. 事变道理概述

1.1 基本观点

在相识webpack道理之前,须要控制以下几个中心观点

  • Entry: 进口,webpack构建第一步从entry最先
  • module:模块,在webpack中一个模块对应一个文件。webpack会从entry最先,递归找出统统依靠的模块
  • Chunk:代码块,一个chunk由多个模块组合而成,用于代码兼并与支解
  • Loader: 模块转换器,用于将模块的原内容依据需求转换成新内容
  • Plugin:拓展插件,在webpack构建流程中的特定时机遇播送对应的事宜,插件能够监听这些事宜的发作,在特定的机遇做对应的事变

1.2 流程概述

webpack从启动到终了顺次实行以下操纵:

graph TD
初始化参数 --> 最先编译 
最先编译 -->肯定进口 
肯定进口 --> 编译模块
编译模块 --> 完成编译模块
完成编译模块 --> 输出资本
输出资本 --> 输出完成

各个阶段实行的操纵以下:

  1. 初始化参数:从设置文件(默许webpack.config.js)和shell语句中读取与兼并参数,得出终究的参数
  2. 最先编译(compile):用上一步获得的参数初始化Comiler对象,加载统统设置的插件,经由过程实行对象的run要领最先实行编译
  3. 肯定进口:依据设置中的entry找出统统的进口文件
  4. 编译模块:从进口文件动身,挪用统统设置的Loader对模块举行翻译,再找出该模块依靠的模块,再递归本步骤直到统统进口依靠的文件都经由处置惩罚
  5. 完成编译模块:经由第四步以后,获得了每一个模块被翻译以后的终究内容以及他们之间的依靠关联
  6. 输出资本:依据进口和模块之间的依靠关联,组装成一个个包括多个模块的chunk,再将每一个chunk转换成一个零丁的文件到场输出列表中,这是能够修正输出内容的末了机遇
  7. 输出完成:在肯定好输出内容后,依据设置(webpack.config.js && shell)肯定输出的途径和文件名,将文件的内容写入文件体系中(fs)

在以上历程当中,webpack会在特定的时候点播送特定的事宜,插件监听事宜并实行响应的逻辑,而且插件能够挪用webpack供应的api转变webpack的运转效果

1.3 流程细节

webpack构建流程可分为以下三大阶段。

  1. 初始化:启动构建,读取与兼并设置参数,加载plugin,实例化Compiler
  2. 编译:从Entry动身,针对每一个Module串行挪用对应的Loader去翻译文件中的内容,再找到该Module依靠的Module,递归的举行编译处置惩罚
  3. 输出:将编译后的Module组合成Chunk,将Chunk转换成文件,输出到文件体系中

假如只实行一次,流程如上,但在开启监听形式下,流程以下图

graph TD

  初始化-->编译;
  编译-->输出;
  输出-->文本发作变化
  文本发作变化-->编译

1.3.1初始化阶段

在初始化阶段会发作的事宜以下

事宜形貌
初始化参数从设置文件和shell语句中读取与兼并参数,得出终究的参数,这个历程还会实行设置文件中的插件实例化语句 new Plugin()
实例化Compiler实例化Compiler,传入上一步获得的参数,Compiler担任文件监听和启动编译。在Compiler实例中包括了完全的webpack设置,全局只要一个Compiler实例。
加载插件顺次挪用插件的apply要领,让插件能够监听后续的统统事宜节点。同时向插件中传入compiler实例的援用,以轻易插件经由过程compiler挪用webpack的api
environment最先运用Node.js作风的文件体系到compiler对象,以轻易后续的文件寻觅和读取
Entry-option读取设置的Entrys,为每一个Entry实例化一个对应的EntryPlugin,为背面该Entry的递归剖析事变做准备
After-plugins挪用完统统内置的和设置的插件的apply要领
After-resolvers依据设置初始化resolver,resolver担任在文件体系中寻觅指定途径的文件

#### 1.3.2 编译阶段 (事宜名全为小写)

事宜诠释
run启动一次编译
Watch-run在监听形式下启动编译,文件发作变化会从新编译
compile通知插件一次新的编译将要启动,同时会给插件带上compiler对象
compilation当webpack以开辟形式运转时,每当检测到文件的变化,便有一次新的compilation被建立。一个Compilation对象包括了当前的模块资本、编译天生资本、变化的文件等。compilation对象也供应了许多事宜回调给插件举行拓展
make一个新的compilation对象建立终了,即将从entry最先读取文件,依据文件范例和编译的loader对文件举行==编译==,编译完后再找出该文件依靠的文件,递归地编译和剖析
after-compile一次compilation实行完成
invalid当碰到毛病解触发改事宜,该事宜不会致使webpack退出

在编译阶段最重要的事宜是compilation,因为在compilation阶段挪用了Loader,完成了每一个模块的==转换==操纵。在compilation阶段又会发作许多小事宜,以下表

事宜诠释
build-module运用响应的Loader去转换一个模块
Normal-module-loader在运用loader转换完一个模块后,运用acorn剖析转换后的内容,输出对应的笼统语法树(AST),以轻易webpack对代码举行剖析
program从设置的进口模块最先,剖析其AST,当碰到require等导入其他模块的语句时,便将其到场依靠的模块列表中,同时关于新找出来的模块递归剖析,终究弄清楚统统模块的依靠关联
seal统统模块及依靠的模块都经由过程Loader转换完成,依据依靠关联天生Chunk

2.3 输出阶段

输出阶段会发作的事宜及诠释:

事宜诠释
should-emit统统须要输出的文件已天生,讯问插件有哪些文件须要输出,有哪些不须要输出
emit肯定好要输出哪些文件后,实行文件输出,==能够在这里猎取和修正输出的内容==
after-mit文件输出终了
done胜利完成一次完全的编译和输出流程
failed假如在编译和输出中涌现毛病,致使webpack退出,就会直接跳转到本步骤,插件能够在本事宜中猎取详细的毛病缘由

在输出阶段已获得了各个模块经由转化后的效果和其依靠关联,而且将响应的模块组合在一起构成一个个chunk.在输出阶段依据chunk的范例,运用对应的模板天生终究要输出的文件内容. |

//以下代码用来包括webpack运转历程当中的每一个阶段
//file:webpack.config.js

const path = require('path');
//插件监听事宜并实行响应的逻辑
class TestPlugin {
  constructor() {
    console.log('@plugin constructor');
  }

  apply(compiler) {
    console.log('@plugin apply');

    compiler.plugin('environment', (options) => {
      console.log('@environment');
    });

    compiler.plugin('after-environment', (options) => {
      console.log('@after-environment');
    });

    compiler.plugin('entry-option', (options) => {
      console.log('@entry-option');
    });

    compiler.plugin('after-plugins', (options) => {
      console.log('@after-plugins');
    });

    compiler.plugin('after-resolvers', (options) => {
      console.log('@after-resolvers');
    });

    compiler.plugin('before-run', (options, callback) => {
      console.log('@before-run');
      callback();
    });

    compiler.plugin('run', (options, callback) => {
      console.log('@run');
      callback();
    });

    compiler.plugin('watch-run', (options, callback) => {
      console.log('@watch-run');
      callback();
    });

    compiler.plugin('normal-module-factory', (options) => {
      console.log('@normal-module-factory');
    });

    compiler.plugin('context-module-factory', (options) => {
      console.log('@context-module-factory');
    });

    compiler.plugin('before-compile', (options, callback) => {
      console.log('@before-compile');
      callback();
    });

    compiler.plugin('compile', (options) => {
      console.log('@compile');
    });

    compiler.plugin('this-compilation', (options) => {
      console.log('@this-compilation');
    });

    compiler.plugin('compilation', (options) => {
      console.log('@compilation');
    });

    compiler.plugin('make', (options, callback) => {
      console.log('@make');
      callback();
    });

    compiler.plugin('compilation', (compilation) => {

      compilation.plugin('build-module', (options) => {
        console.log('@build-module');
      });

      compilation.plugin('normal-module-loader', (options) => {
        console.log('@normal-module-loader');
      });

      compilation.plugin('program', (options, callback) => {
        console.log('@program');
        callback();
      });

      compilation.plugin('seal', (options) => {
        console.log('@seal');
      });
    });

    compiler.plugin('after-compile', (options, callback) => {
      console.log('@after-compile');
      callback();
    });

    compiler.plugin('should-emit', (options) => {
      console.log('@should-emit');
    });

    compiler.plugin('emit', (options, callback) => {
      console.log('@emit');
      callback();
    });

    compiler.plugin('after-emit', (options, callback) => {
      console.log('@after-emit');
      callback();
    });

    compiler.plugin('done', (options) => {
      console.log('@done');
    });

    compiler.plugin('failed', (options, callback) => {
      console.log('@failed');
      callback();
    });

    compiler.plugin('invalid', (options) => {
      console.log('@invalid');
    });

  }
}
#在目次下实行
webpack
#输出以下内容
@plugin constructor
@plugin apply
@environment
@after-environment
@entry-option
@after-plugins
@after-resolvers
@before-run
@run
@normal-module-factory
@context-module-factory
@before-compile
@compile
@this-compilation
@compilation
@make
@build-module
@normal-module-loader
@build-module
@normal-module-loader
@seal
@after-compile
@should-emit
@emit
@after-emit
@done
Hash: 19ef3b418517e78b5286
Version: webpack 3.11.0
Time: 95ms
    Asset     Size  Chunks             Chunk Names
bundle.js  3.03 kB       0  [emitted]  main
   [0] ./main.js 44 bytes {0} [built]
   [1] ./show.js 114 bytes {0} [built]

2 输出文件剖析

2.1 举个栗子

下面经由过程 Webpack 构建一个采纳 CommonJS 模块化编写的项目,该项目有个网页会经由过程 JavaScript 在网页中显现 Hello,Webpack

运转构建前,先把要完成该功用的最基本的 JavaScript 文件和 HTML 建立好,须要以下文件:

页面进口文件 index.html

<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
<div id="app"></div>
<!--导入 Webpack 输出的 JavaScript 文件-->
<script src="./dist/bundle.js"></script>
</body>
</html>

JS 东西函数文件 show.js

// 操纵 DOM 元素,把 content 显现到网页上
function show(content) {
  window.document.getElementById('app').innerText = 'Hello,' + content;
}

// 经由过程 CommonJS 范例导出 show 函数
module.exports = show;

JS 实行进口文件 main.js

// 经由过程 CommonJS 范例导入 show 函数
const show = require('./show.js');
// 实行 show 函数
show('Webpack');

Webpack 在实行构建时默许会从项目根目次下的 webpack.config.js 文件读取设置,所以你还须要新建它,其内容以下:

const path = require('path');

module.exports = {
  // JavaScript 实行进口文件
  entry: './main.js',
  output: {
    // 把统统依靠的模块兼并输出到一个 bundle.js 文件
    filename: 'bundle.js',
    // 输出文件都放到 dist 目次下
    path: path.resolve(__dirname, './dist'),
  }
};

因为 Webpack 构建运转在 Node.js 环境下,所以该文件末了须要经由过程 CommonJS 范例导出一个形貌如何构建的 Object 对象。

|-- index.html
|-- main.js
|-- show.js
|-- webpack.config.js

统统文件停当,在项目根目次下实行 webpack 敕令运转 Webpack 构建,你会发明目次下多出一个 dist目次,内里有个 bundle.js 文件, bundle.js 文件是一个可实行的 JavaScript 文件,它包括页面所依靠的两个模块 main.jsshow.js 及内置的 webpackBootstrap 启动函数。 这时候你用浏览器翻开 index.html 网页将会看到 Hello,Webpack

2.2 bundle.js文件做了什么

看之前记着:一个模块就是一个文件,

起首看下bundle.js长什么模样:

《超细致的webpack道理解读》

注重:序号1处是个自实行函数,序号2作为自实行函数的参数传入

详细代码以下:(发起把以下代码放入编辑器中检察,最好让index.html实行下,弄清楚实行的递次)

(function(modules) { // webpackBootstrap
  // 1. 缓存模块
  var installedModules = {};
  // 2. 定义能够在浏览器运用的require函数
  function __webpack_require__(moduleId) {

    // 2.1搜检模块是不是在缓存里,在的话直接返回
    if(installedModules[moduleId]) {
      return installedModules[moduleId].exports;
    }
    // 2.2 模块不在缓存里,新建一个对象module=installModules[moduleId] {i:moduleId,l:模块是不是加载,exports:模块返回值}
    var module = installedModules[moduleId] = {
      i: moduleId,//第一次实行动0
      l: false,
      exports: {}
    };//第一次实行module:{i:0,l:false,exports:{}}
    // 2.3 实行传入的参数中对应id的模块 第一次实行数组中传入的第一个参数
          //modules[0].call({},{i:0,l:false,exports:{}},{},__webpack_require__函数)
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    // 2.4 将这个模块标记为已加载
    module.l = true;
    // 2.5 返回这个模块的导出值
    return module.exports;
  }
  // 3. webpack暴露属性 m c d n o p
  __webpack_require__.m = modules;
  __webpack_require__.c = installedModules;
  __webpack_require__.d = function(exports, name, getter) {
    if(!__webpack_require__.o(exports, name)) {
      Object.defineProperty(exports, name, {
        configurable: false,
        enumerable: true,
        get: getter
      });
    }
  };
  __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;
  };
  __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
  __webpack_require__.p = "";
  // 4. 实行reruire函数引入第一个模块(main.js对应的模块)
  return __webpack_require__(__webpack_require__.s = 0);
})
([ // 0. 传入参数,参数是个数组

  /* 第0个参数 main.js对应的文件*/
  (function(module, exports, __webpack_require__) {

    // 经由过程 CommonJS 范例导入 show 函数
    const show = __webpack_require__(1);//__webpack_require__(1)返回show
    // 实行 show 函数
    show('Webpack');

  }),
  /* 第1个参数 show.js对应的文件 */
  (function(module, exports) {

    // 操纵 DOM 元素,把 content 显现到网页上
    function show(content) {
      window.document.getElementById('app').innerText = 'Hello,' + content;
    }
    // 经由过程 CommonJS 范例导出 show 函数
    module.exports = show;

  })
]);

以上看上去庞杂的代码实际上是一个自实行函数(文件作为自实行函数的参数),能够简写以下:

(function(modules){
    //模仿require语句
    function __webpack_require__(){}
    //实行寄存统统模块数组中的第0个模块(main.js)
    __webpack_require_[0]
})([/*寄存统统模块的数组*/])

bundles.js能直接在浏览器中运转的缘由是,在输出的文件中经由过程__webpack_require__函数,定义了一个能够在浏览器中实行的加载函数(加载文件运用ajax完成),来模仿Node.js中的require语句。

本来一个个自力的模块文件被兼并到了一个零丁的 bundle.js 的缘由在于浏览器不能像 Node.js 那样疾速地去当地加载一个个模块文件,而必需经由过程收集请求去加载还未获得的文件。 假如模块数目许多,加载时候会很长,因而把统统模块都寄存在了数组中,实行一次收集加载。

修正main.js,改成import引入模块

import show from './show';
show('Webpack');

在目次下实行webpack,会发明:

  1. 天生的代码会有所差别,然则重要的区分是自实行函数的参数差别,也就是2.2代码的第二部份差别
([//自实行函数和上面雷同,参数差别
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__show__ = __webpack_require__(1);

Object(__WEBPACK_IMPORTED_MODULE_0__show__["a" /* default */])('Webpack');


}),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* harmony export (immutable) */ __webpack_exports__["a"] = show;
function show(content) {
  window.document.getElementById('app').innerText = 'Hello,' + content;
}


})
]);

参数差别的缘由是es6的import和export模块被webpack编译处置惩罚过了,实在作用是一样的,接下来看一下在main.js中异步加载模块时,bundle.js是如何的

2.3异步加载时,bundle.js代码剖析

main.js修正以下

import('./show').then(show=>{
    show('Webpack')
})

构建胜利后会天生两个文件

  1. bundle.js 实行进口文件
  2. 0.bundle.js 异步加载文件

个中0.bundle.js文件的内容以下:

webpackJsonp(/*在其他文件中寄存的模块的ID*/[0],[//本文件所包括的模块
/* 0 */,
/* 1 show.js对应的模块 */
(function(module, __webpack_exports__, __webpack_require__) {

  "use strict";
  Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
  /* harmony export (immutable) */ 
  __webpack_exports__["default"] = show;

  function show(content) {
    window.document.getElementById('app').innerText = 'Hello,' + content;
  }

})
]);

bundle.js文件的内容以下:

注重:bundle.js比上面的bundle.js的区分在于:

  1. 多了一个__webpack_require__.e,用于加载被支解出去的须要异步加载的chunk对应的文件
  2. 多了一个webpackJsonp函数,用于从异步加载的文件中装置模块
(function(modules) { // webpackBootstrap
    // install a JSONP callback for chunk loading
  var parentJsonpFunction = window["webpackJsonp"];
  // webpackJsonp用于从异步加载的文件中装置模块
  // 将webpackJsonp挂载到全局是为了轻易在其他文件中挪用
  /**
   * @param chunkIds 异步加载的模块中须要装置的模块对应的id
   * @param moreModules 异步加载的模块中须要装置模块列表
   * @param executeModules 异步加载的模块装置胜利后须要实行的模块对应的index
   */
    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()();
        }
    };
    // The module cache
    var installedModules = {};
    // objects to store loaded and loading chunks
    var installedChunks = {
        1: 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;
    }
    // This file contains only the entry chunk.
  // The chunk loading function for additional chunks
  /**
   * 用于加载被支解出去的须要异步加载的chunk对应的文件
   * @param chunkId 须要异步加载的chunk对应的id
   * @returns {Promise}
   */
    __webpack_require__.e = function requireEnsure(chunkId) {
      var installedChunkData = installedChunks[chunkId];
      if(installedChunkData === 0) {
        return new Promise(function(resolve) { resolve(); });
      }
      // a Promise means "currently loading".
      if(installedChunkData) {
        return installedChunkData[2];
      }
      // setup Promise in chunk cache
      var promise = new Promise(function(resolve, reject) {
        installedChunkData = installedChunks[chunkId] = [resolve, reject];
      });
      installedChunkData[2] = promise;
      // start chunk loading
      var head = document.getElementsByTagName('head')[0];
      var script = document.createElement('script');
      script.type = "text/javascript";
      script.charset = 'utf-8';
      script.async = true;
      script.timeout = 120000;
      if (__webpack_require__.nc) {
        script.setAttribute("nonce", __webpack_require__.nc);
      }
      script.src = __webpack_require__.p + "" + chunkId + ".bundle.js";
      var timeout = setTimeout(onScriptComplete, 120000);
      script.onerror = script.onload = onScriptComplete;
      function onScriptComplete() {
        // avoid mem leaks in IE.
        script.onerror = script.onload = null;
        clearTimeout(timeout);
        var chunk = installedChunks[chunkId];
        if(chunk !== 0) {
          if(chunk) {
            chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
          }
          installedChunks[chunkId] = undefined;
        }
      };
      head.appendChild(script);
      return promise;
    };
    // 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; };
    // Load entry module and return exports
    return __webpack_require__(__webpack_require__.s = 0);
})
/************************************************************************/
([//寄存没有经由异步加载的,跟着实行进口文件加载的模块
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

__webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 1)).then(show=>{
    show('Webpack')
})


/***/ })
]);
    原文作者:hyangteng
    原文地址: https://segmentfault.com/a/1190000017890529
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞