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() ] };
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
graph TD 初始化参数 --> 开始编译 开始编译 -->确定入口 确定入口 --> 编译模块 编译模块 --> 完成编译模块 完成编译模块 --> 输出资源 输出资源 --> 输出完成
1.3 Détails du processus
graph TD 初始化-->编译; 编译-->输出; 输出-->文本发生变化 文本发生变化-->编译
#### 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 |
É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'); }); } }
#在目录下执行 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]
下面通过 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.js
和 show.js
及内置的 webpackBootstrap
启动函数。 这时你用浏览器打开 index.html
网页将会看到 Hello,Webpack
。
看之前记住:一个模块就是一个文件,
首先看下bundle.js长什么样子:
注意:序号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
,会发现:
生成的代码会有所不同,但是主要的区别是自执行函数的参数不同,也就是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是怎样的
main.js
修改如下
import('./show').then(show=>{ show('Webpack') })
构建成功后会生成两个文件
bundle.js 执行入口文件
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的区别在于:
多了一个__webpack_require__.e
,用于加载被分割出去的需要异步加载的chunk对应的文件
多了一个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') }) /***/ }) ]);
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!