ホームページ  >  記事  >  ウェブフロントエンド  >  Webpack の原則の詳細な紹介 (例付き)

Webpack の原則の詳細な紹介 (例付き)

不言
不言転載
2019-01-15 10:29:133360ブラウズ
この記事では、webpack の原理について詳しく説明します (例を示します)。必要な方は参考にしていただければ幸いです。

この記事は「Webpack in a Simple and Simple Language」からコピーしたものです。原則を学びたい人は、一度入力して、一度操作して、一度他の人に説明することをお勧めします。

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: Entry、Webpack 構築の最初のステップは、Entry から始まります

  • module: module、in webpack aモジュールはファイルに対応します。 webpack はエントリから開始し、すべての依存モジュールを再帰的に検索します

  • チャンク: コード ブロック、チャンクは複数のモジュールで構成され、コードのマージと分割に使用されます

  • Loader: モジュール コンバーター。必要に応じて、モジュールの元のコンテンツを新しいコンテンツに変換するために使用されます。

  • Plugin: Webpack ビルド プロセス内の拡張プラグイン。対応するイベントは、特定の時間にブロードキャストされ、プラグインはこれらのイベントの発生を監視し、特定の時間に対応する処理を実行できます

##1.2 プロセスの概要

webpack の起動から終了までの実行次の操作を順番に実行します:

graph TD
初始化参数 --> 开始编译 
开始编译 -->确定入口 
确定入口 --> 编译模块
编译模块 --> 完成编译模块
完成编译模块 --> 输出资源
输出资源 --> 输出完成
各段階で実行される操作は次のとおりです:

  1. 初期化パラメーター: 構成ファイルから読み取ります (デフォルトは webpack.config.js ) とシェル ステートメント パラメータを取得およびマージして、最終パラメータを取得します。

  2. コンパイル (コンパイル) を開始します。前の手順で取得したパラメータを使用して Comiler オブジェクトを初期化し、設定されているすべてのプラグインをロードします。 ins を指定し、オブジェクトの run メソッドを実行します。 コンパイルを開始します。

  3. #エントリを決定します。構成内のエントリに従って、すべてのエントリ ファイルを検索します。
  4. Compile module: エントリー ファイルから開始して、すべての構成済みローダーを呼び出してモジュールを変換し、そのモジュールが依存するモジュールを見つけて、すべてのエントリー依存ファイルが処理されるまでこのステップを繰り返します
  5. コンパイル モジュールを完了します: 4 番目のステップ以降 このステップの後、各モジュールの最終的に翻訳されたコンテンツとモジュール間の依存関係が取得されます。エントリとモジュール間の依存関係は、複数のモジュールのチャンクを含む個別のモジュールにアセンブルされ、各チャンクが個別のファイルに変換されて出力リストに追加されます。これは、出力コンテンツを変更する最後の機会です#。

  6. 出力完了:確認後 内容を出力後、設定(webpack.config.js && シェル)に従って出力パスとファイル名を決定し、ファイルの内容を書き込みますファイル システム (fs)
  7. #上記のプロセスでは、webpack は特定の時点で特定のイベントをブロードキャストし、プラグインはイベントをリッスンして対応するロジックを実行します。プラグインは、webpack が提供する API を呼び出して、webpack の実行結果を変更できます。
  8. ##1.3 プロセスの詳細
  9. webpack のビルド プロセスは、次の 3 つの段階に分けることができます。

初期化: ビルドの開始、構成パラメーターの読み取りとマージ、プラグインのロード、コンパイラーのインスタンス化

コンパイル: エントリーから開始し、それぞれについてモジュールは、対応するローダーをシリアルに呼び出してファイル内のコンテンツを変換し、モジュールが依存するモジュールを見つけて再帰的にコンパイルします。

  1. 出力: コンパイルされたモジュールをチャンクに結合します。 Chunk をファイルに変換してファイルシステムに出力

  2. 1 回だけ実行する場合は上記の処理ですが、リスニングモードがオンの場合は、次のとおりです

    graph TD
    
      初始化-->编译;
      编译-->输出;
      输出-->文本发生变化
      文本发生变化-->编译
  3. 1.3 .1 初期化フェーズ
  4. 初期化フェーズ中に発生するイベントは次のとおりです

Event

説明

Instantiate Compilerプラグインをロードします環境Entry-optionAfter-pluginsAfter-resolvers構成に従ってリゾルバーを初期化します。リゾルバーは、ファイル システム内で指定されたパスを持つファイルを検索します。
初期化パラメータ 設定ファイルとシェル ステートメントからパラメータを読み取ってマージし、最終的なパラメータを取得します。また、構成ファイル内のプラグインのインスタンス化ステートメント new も実行されます。Plugin()
Instantiate Compiler と、前の手順で取得したパラメーターを渡します。コンパイラは、ファイルの監視とコンパイルの開始を担当します。 Compiler インスタンスには完全な Webpack 構成が含まれており、Compiler インスタンスはグローバルに 1 つだけ存在します。
プラグインが後続のすべてのイベント ノードを監視できるように、プラグインの apply メソッドを順番に呼び出します。同時に、コンパイラ インスタンスへの参照をプラグインに渡し、プラグインがコンパイラを通じて Webpack の API を呼び出せるようにします
開始Node.js スタイル ファイル システムをコンパイラ オブジェクトに適用して、その後のファイル検索と読み取りを容易にする
構成されたエントリを読み取り、対応する EntryPlugin をインスタンス化します。各エントリ。後でエントリの再帰解析の準備をします。
すべての組み込みおよび構成済みプラグインの apply メソッドを呼び出します。
#

# 1.3.2 コンパイル フェーズ (イベント名はすべて小文字)#run一度コンパイルを開始しますWatch-runモニター モードでコンパイルを開始すると、ファイルが生成されます。変更は再コンパイルされますcompile 新しいコンパイルが開始されることをプラグインに伝え、同時にコンパイラ オブジェクトをプラグインに取り込みますcompilationwebpack が開発モードで実行されている場合、ファイルへの変更が検出されるたびに新しいコンパイルが作成されます。 Compilation オブジェクトには、現在のモジュール リソース、コンパイルされたリソース、変更されたファイルなどが含まれます。コンパイル オブジェクトは、プラグインが展開するための多くのイベント コールバックも提供します。make新しいコンパイル オブジェクトが作成された後、ファイルはエントリから読み取られます。コンパイル済みローダーを使用してファイルをコンパイルします。コンパイル後、ファイルが依存するファイルを見つけて、再帰的にコンパイルおよび解析します。コンパイル後 invalid
##イベント 説明
# #Oneコンパイルの実行が完了しました
エラーが発生した場合、変更イベントがトリガーされますが、このイベントによって webpack が終了することはありません。


##コンパイル段階で最も重要なイベントはコンパイルです。コンパイル中にローダーが呼び出されるからです。フェーズとモジュールの各完了 ==変換== 操作。コンパイル段階では、次の表に示すように、多くの小さなイベントが発生します。

イベント

説明build-module対応するローダーを使用してモジュールを変換しますNormal-module-loader使用後モジュールを変換するローダー 最後に、acorn を使用して変換されたコンテンツを解析し、対応する抽象構文ツリー (AST) を出力して、Webpack がコードを分析しやすくします#program構成されたエントリから モジュールが開始され、その AST が分析され、他のモジュールをインポートする require ステートメントが検出されると、依存モジュールのリストに追加されます。同時に、新しく見つかったモジュールが再帰的に分析され、最終的に特定されます。すべてのモジュールの依存関係sealすべてのモジュールと依存モジュールはローダーを通じて変換され、チャンクは依存関係に基づいて生成されます出力フェーズで発生するイベントと説明:


2.3 出力フェーズ

イベント

説明すべて出力する必要のあるファイルが生成されたら、プラグインにどのファイルを出力する必要があり、どのファイルを出力する必要がないかを問い合わせます。#after-mitファイル出力完了done正常に完了しましたコンパイルと出力のプロセスが完了しました失敗しましたコンパイルと出力中にエラーが発生した場合は、 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


  <meta>


<p></p>
<!--导入 Webpack 输出的 JavaScript 文件-->
<script></script>

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 {
    show('Webpack')
})


/***/ })
]);

##発行する必要がある
emit 決定後、どのファイルを出力するか、実行ファイル出力、 == はここで取得および変更できます。 出力内容==

以上がWebpack の原則の詳細な紹介 (例付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。