基本概念
在了解webpack
原理之前,咱们先看以下几个核心概念
Entry
入口文件,Webpack
执行构建的第一步将从Entry
开始,可抽象成输入
。Module
模块,webpack
中一个模块对应一个文件,Webpack
会从配置的Entry
开始递归
找出所有依赖的模块
。Chunk
代码块,一个Chunk
由多个module
组成,主要是用于代码的分割。Loader
模块转换器,用于把模块原内容按照需求转换成新内容
。Plugin
扩展插件,在Webpack
构建流程中的特定时机,会广播出对应的事件
,插件可以监听这些事件的发生
,在特定时机做对应的事情。
流程概括
初始化参数
:从配置文件和 Shell
语句中读取与合并参数,得出最终的参数;开始编译
:用上一步得到的参数初始化 Compiler
对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
确定入口
:根据配置中的entry
找出所有的入口文件;编译模块
:从入口文件出发,调用所有配置的Loader
对模块进行翻译
,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
;完成模块编译
:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
;输出资源
:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,
再把每个Chunk 转换成一个单独的文件
加入到输出列表,这步是可以修改输出内容的最后机会;输出完成
:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
。
流程细节
Webpack
的构建流程可以分为以下三大阶段
:
-
初始化
:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler
。 -
编译:从
Entry
发出,针对每个Module
串行调用对应的Loader
去翻译文件内容,再找到该Module
依赖的Module
,递归地进行编译处理
。 -
输出
:对编译后的Module 组合成 Chunk
,把Chunk 转换成文件,输出到文件系统
。
初始化阶段
编译阶段
在编译阶段中,最重要的是compilation
事件,因为在compilation
阶段调用了Loader
完成了每个模块的转换操作,在compilation
阶段又包括很多小的事件,它们分别是:
输出阶段
在输出阶段已经得到了各个模块经过转换后的结果和其依赖关系
,并且把相关模块组合在一起形成一个个Chunk
。 在输出阶段会根据Chunk 的类型
,使用对应的模版生成最终要要输出的文件内容。输出文件分析
虽然在前面的章节中你学会了如何使用Webpack
,也大致知道其工作原理,可是你想过-
Webpack
输出的bundle.js
是什么样子的吗? -
为什么原来一个个的模块文件被合并成了一个单独的文件?
-
为什么
bundle.js
能直接运行在浏览器中? 本节将解释清楚以上问题。先来看看由 安装与使用 中最简单的项目构建出的
bundle.js
文件内容,代码如下:<p data-height="565" data-theme-id="0" data-slug-hash="NMQzxz" data-default-tab="js" data-user="whjin" data-embed-version="2" data-pen-title="bundle.js" class="codepen"> See the Pen bundle.js by whjin (@whjin) on CodePen.</p> <script async src="static.codepen.io/ass...;></script>
以上看上去复杂的代码其实是一个立即执行函数,可以简写为如下:
function(modules) { // 模拟 require 语句 function __webpack_require__() { } // 执行存放所有模块数组中的第0个模块 __webpack_require__(0); })([/*存放所有模块的数组*/])
-
bundle.js
能直接运行在浏览器中的原因在于输出的文件中通过__webpack_require__
函数定义了一个可以在浏览器中执行的加载函数来模拟Node.js 中的 require
语句。 -
原来一个个独立的模块文件被合并到了一个单独的
bundle.js
的原因在于浏览器不能像Node.js
那样快速地去本地加载一个个模块文件,而必须通过网络请求去加载还未得到的文件。 如果模块数量很多,加载时间会很长,因此把所有模块都存放在了数组中,执行一次网络加载
。 -
如果仔细分析
__webpack_require__
函数的实现,你还有发现 Webpack 做了缓存优化: 执行加载过的模块不会再执行第二次,执行结果会缓存在内存中,当某个模块第二次被访问时会直接去内存中读取被缓存的返回值
。分割代码时的输出
例如把源码中的
main.js
修改为如下:// 异步加载 show.js import('./show').then((show) => { // 执行 show 函数 show('Webpack'); });
重新构建后会输出两个文件,分别是执行入口文件
bundle.js
和 异步加载文件0.bundle.js
。
其中0.bundle.js
内容如下:// 加载在本文件(0.bundle.js)中包含的模块 webpackJsonp( // 在其它文件中存放着的模块的 ID [0], // 本文件所包含的模块 [ // show.js 所对应的模块 (function (module, exports) { function show(content) { window.document.getElementById('app').innerText = 'Hello,' + content; } module.exports = show; }) ] );
bundle.js
内容如下:<p data-height="565" data-theme-id="0" data-slug-hash="yjmRyG" data-default-tab="js" data-user="whjin" data-embed-version="2" data-pen-title="bundle.js" class="codepen" > > See the Pen bundle.js by whjin (@whjin) on CodePen.</p><script async src="static.codepen.io/ass...;></script>
这里的
bundle.js
和上面所讲的bundle.js
非常相似,区别在于:-
多了一个
__webpack_require__.e
用于加载被分割出去的,需要异步加载的 Chunk 对应的文件; -
多了一个
webpackJsonp
函数用于从异步加载的文件中安装模块。在使用了
CommonsChunkPlugin
去提取公共代码时输出的文件和使用了异步加载时输出的文件是一样的,都会有__webpack_require__.e
和webpackJsonp
。 原因在于提取公共代码和异步加载本质上都是代码分割。
-
-
loader
和plugin
下一篇文章再续