Maison > interface Web > js tutoriel > Une introduction approfondie aux principes du webpack (avec des exemples)

Une introduction approfondie aux principes du webpack (avec des exemples)

不言
Libérer: 2019-01-15 10:29:13
avant
3396 Les gens l'ont consulté
Cet article vous apporte une introduction approfondie aux principes du webpack (avec des exemples). Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Cet article est copié de "Webpack en termes simples". Il est recommandé à ceux qui veulent apprendre les principes de le taper une fois, de l'utiliser une fois et de l'expliquer aux autres, puis ils le feront. sachez-le.

J'espère que vous l'avez avant de lire Expérience pratique liée au webpack, sinon vous ne la comprendrez pas après l'avoir lu

Il faut quelques minutes pour lire cet article, et il. prend beaucoup de temps pour le comprendre par vous-même

0 Fichier de configuration

Tout d'abord, jetez un bref coup d'œil au fichier de configuration du 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()
  ]
};
Copier après la connexion

1. Aperçu du principe de fonctionnement

1.1 Concepts de base

Avant de comprendre les principes de webpack, vous devez maîtriser les concepts de base suivants

  • Entrée : Entrée, la première étape de la construction du webpack commence par entrée

  • module : module Dans webpack, un module correspond. à un fichier. Webpack partira de l'entrée et trouvera récursivement tous les modules dépendants

  • Chunk : un bloc de code est composé de plusieurs modules et est utilisé pour la fusion et le fractionnement du code

  • <.>
  • Loader : convertisseur de module, utilisé pour convertir le contenu original du module en nouveau contenu selon les besoins

  • Plugin : plug-in d'extension, dans le processus de construction du webpack Événements correspondants sera diffusé à des heures spécifiques, et les plug-ins peuvent surveiller l'occurrence de ces événements et faire les choses correspondantes à des heures spécifiques

1.2 Présentation du processus

webpack du démarrage au end Effectuer les opérations suivantes dans l'ordre :

graph TD
初始化参数 --> 开始编译 
开始编译 -->确定入口 
确定入口 --> 编译模块
编译模块 --> 完成编译模块
完成编译模块 --> 输出资源
输出资源 --> 输出完成
Copier après la connexion
Les opérations effectuées à chaque étape sont les suivantes :

  1. Paramètres d'initialisation : à partir du fichier de configuration (webpack. config.js) et les instructions shell Lisez et fusionnez les paramètres pour obtenir les paramètres finaux

  2. Démarrez la compilation : initialisez l'objet Comiler avec les paramètres obtenus à l'étape précédente, chargez tous les plug-ins configurés. ins, et exécute l'objet La méthode run démarre la compilation

  3. Déterminer l'entrée : rechercher tous les fichiers d'entrée en fonction de l'entrée dans la configuration

  4. Compilez le module : commencez à partir du fichier d'entrée, appelez tous les Loaders configurés pour traduire le module, puis recherchez les modules dont dépend le module, puis répétez cette étape jusqu'à ce que tous les fichiers dépendants de l'entrée aient été traités

  5. Compiler le module : Après Après la quatrième étape, le contenu final de chaque module après traduction et les dépendances entre eux sont obtenus

  6. Ressources de sortie : Selon les dépendances entre l'entrée et les modules, ils sont assemblés en Contient des morceaux de plusieurs modules un par un, puis convertissent chaque morceau en un fichier séparé et l'ajoutent à la liste de sortie. C'est la dernière chance de modifier le contenu de sortie

  7. La sortie est terminée : dans Après avoir déterminé le contenu de la sortie, déterminez le chemin de sortie et le nom du fichier en fonction de la configuration (webpack.config.js && shell), et écrivez le contenu du fichier dans le système de fichiers (fs)
  8. dans Dans le processus ci-dessus, webpack diffusera des événements spécifiques à des moments précis. Le plug-in écoute les événements et exécute la logique correspondante, ainsi que le système de fichiers (fs). Le plug-in peut appeler l'API fournie par webpack pour modifier les résultats d'exécution du webpack

1.3 Détails du processus

Le processus de construction du webpack peut être divisé en trois étapes suivantes.

    Initialisation : démarrer le build, lire et fusionner les paramètres de configuration, charger le plugin, instancier le compilateur
  1. Compilation : à partir de l'entrée, pour chaque Chaque Le module appelle en série le chargeur correspondant pour traduire le contenu du fichier, puis trouve le module dont dépend le module et le compile de manière récursive
  2. Sortie : combinez les modules compilés en morceaux, convertissez le morceau dans un fichier et l'envoyer vers le système de fichiers
  3. S'il n'est exécuté qu'une seule fois, le processus est comme ci-dessus, mais lorsque le mode de surveillance est activé, le processus est comme suit

1.3.1 Phase d'initialisation
graph TD

  初始化-->编译;
  编译-->输出;
  输出-->文本发生变化
  文本发生变化-->编译
Copier après la connexion

Les événements qui se produiront pendant la phase d'initialisation sont les suivants

#### 1.3.2 Phase de compilation (les noms d'événements sont tous en minuscules)

事件 解释
run 启动一次编译
Watch-run 在监听模式下启动编译,文件发生变化会重新编译
compile 告诉插件一次新的编译将要启动,同时会给插件带上compiler对象
compilation 当webpack以开发模式运行时,每当检测到文件的变化,便有一次新的compilation被创建。一个Compilation对象包含了当前的模块资源、编译生成资源、变化的文件等。compilation对象也提供了很多事件回调给插件进行拓展
make 一个新的compilation对象创建完毕,即将从entry开始读取文件,根据文件类型和编译的loader对文件进行==编译==,编译完后再找出该文件依赖的文件,递归地编译和解析
after-compile 一次compilation执行完成
invalid 当遇到错误会触发改事件,该事件不会导致webpack退出


L'événement le plus important dans la phase de compilation est la compilation, car Loader est appelé dans la phase de compilation pour terminer chaque Opération ==Conversion== du module. De nombreux petits événements se produiront lors de la phase de compilation, comme le montre le tableau suivant

事件 解释
build-module 使用相应的Loader去转换一个模块
Normal-module-loader 在使用loader转换完一个模块后,使用acorn解析转换后的内容,输出对应的抽象语法树(AST),以方便webpack对代码进行分析
program 从配置的入口模块开始,分析其AST,当遇到require等导入其他模块的语句时,便将其加入依赖的模块列表中,同时对于新找出来的模块递归分析,最终弄清楚所有模块的依赖关系
seal 所有模块及依赖的模块都通过Loader转换完成,根据依赖关系生成Chunk


2.3 Phase de sortie

Événements et explications qui se produiront lors de la phase de sortie :

事件 解释
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');
    });

  }
}
Copier après la connexion
#在目录下执行
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]
Copier après la connexion

2 输出文件分析

2.1 举个栗子

下面通过 Webpack 构建一个采用 CommonJS 模块化编写的项目,该项目有个网页会通过 JavaScript 在网页中显示 Hello,Webpack

运行构建前,先把要完成该功能的最基础的 JavaScript 文件和 HTML 建立好,需要如下文件:

页面入口文件 index.html


  <meta>


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

Copier après la connexion

JS 工具函数文件 show.js

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

// 通过 CommonJS 规范导出 show 函数
module.exports = show;
Copier après la connexion

JS 执行入口文件 main.js

// 通过 CommonJS 规范导入 show 函数
const show = require('./show.js');
// 执行 show 函数
show('Webpack');
Copier après la connexion

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'),
  }
};
Copier après la connexion

由于 Webpack 构建运行在 Node.js 环境下,所以该文件最后需要通过 CommonJS 规范导出一个描述如何构建的 Object 对象。

|-- index.html
|-- main.js
|-- show.js
|-- webpack.config.js
Copier après la connexion

一切文件就绪,在项目根目录下执行 webpack 命令运行 Webpack 构建,你会发现目录下多出一个 dist目录,里面有个 bundle.js 文件, bundle.js 文件是一个可执行的 JavaScript 文件,它包含页面所依赖的两个模块 main.jsshow.js 及内置的 webpackBootstrap 启动函数。 这时你用浏览器打开 index.html 网页将会看到 Hello,Webpack

2.2 bundle.js文件做了什么

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

首先看下bundle.js长什么样子:

Une introduction approfondie aux principes du webpack (avec des exemples)

注意:序号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;

  })
]);
Copier après la connexion

以上看上去复杂的代码其实是一个自执行函数(文件作为自执行函数的参数),可以简写如下:

(function(modules){
    //模拟require语句
    function __webpack_require__(){}
    //执行存放所有模块数组中的第0个模块(main.js)
    __webpack_require_[0]
})([/*存放所有模块的数组*/])
Copier après la connexion

bundles.js能直接在浏览器中运行的原因是,在输出的文件中通过__webpack_require__函数,定义了一个可以在浏览器中执行的加载函数(加载文件使用ajax实现),来模拟Node.js中的require语句。

原来一个个独立的模块文件被合并到了一个单独的 bundle.js 的原因在于浏览器不能像 Node.js 那样快速地去本地加载一个个模块文件,而必须通过网络请求去加载还未得到的文件。 如果模块数量很多,加载时间会很长,因此把所有模块都存放在了数组中,执行一次网络加载。

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

import show from './show';
show('Webpack');
Copier après la connexion

在目录下执行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;
}


})
]);
Copier après la connexion

参数不同的原因是es6的import和export模块被webpack编译处理过了,其实作用是一样的,接下来看一下在main.js中异步加载模块时,bundle.js是怎样的

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

main.js修改如下

import('./show').then(show=>{
    show('Webpack')
})
Copier après la connexion

构建成功后会生成两个文件

  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;
  }

})
]);
Copier après la connexion

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')
})


/***/ })
]);
Copier après la connexion

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:segmentfault.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal