webpack原理解析 webpack原理解析

webpack原理解析

webpack打包原理解析

一开始,对webpack打包原理很不熟悉,看了不少资料,但是讲的都不是很清楚,现在来梳理一遍。

所有资源统一入口

这个是什么意思呢?就是webpack是通过js来获取和操作其他文件资源的,比如webpack想处理less,但是它并不会直接从本地的文件夹中直接通过路径去读取css文件,而且通过执行入口js文件,如果入口文件中,或者入口文件相关联的js文件中含有 require(xx.less) 这个less文件,那么它就会通过对应的loader去处理这个less文件

打包中的文件管理

重点来了: webpack是如何进行资源的打包的呢?

总结:

  • 每个文件都是一个资源,可以用require导入js
  • 每个入口文件会把自己所依赖(即require)的资源全部打包在一起,一个资源多次引用的话,只会打包一份
  • 对于多个入口的情况,其实就是分别独立的执行单个入口情况,每个入口文件不相干(可用CommonsChunkPlugin优化)

js单文件入口的情况

img1.gif

比如整个应用的入口为 entry.js

entry.js引用 util1.js util2.js, 同时util1.js又引用了util2.js

关键问题是: 它打包的会不会将 util2.js打包两份?

其实不会的,webpack打包的原理为,在入口文件中,对每个require资源文件进行配置一个id, 也就是说,对于同一个资源,就算是require多次的话,它的id也是一样的,所以无论在多少个文件中require,它都只会打包一分

对于和上面一致拓扑图,打包后的js代码为img2.gif

注: /* 1 */ 代码打包模块为 id 为 1

通过上面的图片我们看到,

  • entry.js 入口文件对应的id为 1
  • util1.js的id为 2
  • util2.js的id为 3

entry.js引用了 2 和 3, util1.js引用了 3,说明了entry和util1引用的是同一份,util2.js不会重复打包

css单文件入口的情况

上面分析了js的情况,其实css也是一个道理。它同样也会为每个css资源分配一个id, 每个资源同样也只会导入一次。

img3.gif

可以看到, entry.js 和 util1.js共同引用了 style2.less,那么它们作用的结果是怎么样的呢?

img4.gif

可以看到 entry.js 和 util1.js使用的是同一个 css资源

注意: /* 1 */ 注释表示id为1的模块

听说 ExtractTextPlugin 导出css的插件比较火, 让我们来看看它到底帮我们干了啥?配置好相关引用后

newExtractTextPlugin('[name].[chunkhash].css')
      
      

运行,在static下面生成了 一个.css 一个.js

生成的css代码为


      
      
  1. .style2 {
  2. color: green;
  3. }
  4. .style3 {
  5. color: blue;
  6. }
  7. .style1 {
  8. color: red;
  9. }

实际就是把三个css导出到一个css文件,而且同一个入口中,多次引用同一个css,实际只会打包一份

生成的.js为

img5.gif

可以看到 /* 2 */ 实际就是 util1.js模块,还是引用 id为3的模块 即css2, 但是我们看 3 模块的定义,为空函数, 它给出的解释是 removed by extract-text-webpack-plugin 实际就是newExtractTextPlugin把相关的css全部移出去了

多文件入口的情况

讲完了单入口,还需讲讲多入口,很多时候我们项目是需要多入口

img6.gif

修改webpack config

img7.gif

可以看到 输出为两个js文件

先对 entry.js 对应的输出文件进行分析

img8.gif

其实可以看到, util1.js util2.js的内容都打包进 对应的js里面去了

在对 entry2.js 输出的文件进行分析

img9.gif

可以看到,把entry2.js依赖的 util2.js也打包进去了

所以多 入口 实际就是 分别执行多个单入口,彼此之间不影响

问题来了,多入口对应的css呢? 还是上面的原则,css也是分别打包的,对于每个入口所依赖的css全部打包,输出就是这个入口对应的css

最后讨论 CommonsChunkPlugin

之前提到过,每个入口文件,都会独立打包自己依赖的模块,那就会造成很多重复打包的模块,有没有一种方法能把多个入口文件中,共同依赖的部分给独立出来呢? 肯定是有的 CommonsChunkPlugin

这个插件使用非常简单,它原理就是把多个入口共同的依赖都给定义成 一个新入口

为何我这里说是定义成新入口呢,因为这个名字不仅仅对应着js 而且对于着和它相关的css等,比如HtmlWebpackPlugin 中 就能体现出来,可以它的 chunks中 加入 common 新入口,它会自动把common的css也导入html

img10.gif

可以看到, 不仅仅js公共模块独立出来,连css同样也是,感受到了 webpack强大了吧

我们可以大概对 common.js index.xxxxx.js(entry.js对应的代码) index2.xxxx.js(entry2.js对应的代码)打包出来的代码分析一下

提前知识:

webpack的id 有两种 一种为 chunkid 一种为moduleId

  • 每个chunkid 对应的是一个js文件
  • 每个moduleid对应的是一个个js文件的内容的模块(一个js文件里面可以require多个资源,每 个资源分配一个moduleid)

为何要出来一个chunkid呢? 这个chunkid的作用就是,标记这个js文件是否已经加载过了


      
      
  1. /******/ // object to store loaded and loading chunks
  2. /******/ // "0" means "already loaded"
  3. /******/ // Array means "loading", array contains callbacks
  4. /******/ var installedChunks = {
  5. /******/ 2 : 0
  6. /******/ };

installedChunks 是记录一个chunkid是否已经加载过了

我们先从 common.js分析

common.js是公共分离出来的模块,所以按理来说,它应该是一个顶层的模块,实际上,确实如此

看 common.js代码


      
      
  1. /******/ ( function ( modules ) { // webpackBootstrap
  2. /******/ // install a JSONP callback for chunk loading
  3. /******/ var parentJsonpFunction = window [ "webpackJsonp" ];
  4. /******/ window [ "webpackJsonp" ] = function webpackJsonpCallback ( chunkIds , moreModules ) {
  5. /******/ // add "moreModules" to the modules object,
  6. /******/ // then flag all "chunkIds" as loaded and fire callback

定义了一个 webpackJsonp 函数

看 index.xxxx.js代码


      
      
  1. webpackJsonp ([ 0 ],[
  2. /* 0 */
  3. /***/ function ( module , exports , __webpack_require__ ) {
  4. module . exports = __webpack_require__ ( 1 );
  5. /***/ },

使用 common.js中定义的函数 webpackJsonp, 所以在浏览器中加载的话,必须先加载 common.js

这个 webpackJsonp([0],[]) 第一个参数 [0] 是什么意思呢? 它就是 chunkid ,我们知道一个chunkidid对应一个 js 文件,它对应的js文件就是它的入口文件 entry.js, 那为何这里还要是一个数组

因为一个入口文件的话,可以依赖多个js文件,其他的 id 就是它所依赖的, 其实就是配置webpack的时候

那个入口js对应的 数组

index: ['./src/js/entry.js'], [0] 和 现在的id数组一一对应

在 index2.xxx.js中,


      
      
  1. webpackJsonp ([ 1 ],{
  2. /***/ 0 :
  3. /***/ function ( module , exports , __webpack_require__ ) {
  4. module . exports = __webpack_require__ ( 8 );

[1] 就是 webpack.config配置的数组 js 对应的 thunkid

index2: ['./src/js/entry2.js']

即 entry2.js 就是 thunkid 1

上面说完了thunkid, 下面就说 moduleid了, common.js为顶层js文件,通过调用webpackJsonp对其他文件进行处理,

common.js中


      
      
  1. /******/ var parentJsonpFunction = window [ "webpackJsonp" ];
  2. /******/ window [ "webpackJsonp" ] = function webpackJsonpCallback ( chunkIds , moreModules ) {
  3. /******/ // add "moreModules" to the modules object,
  4. /******/ // then flag all "chunkIds" as loaded and fire callback
  5. /******/ for ( moduleId in moreModules ) {
  6. /******/ modules [ moduleId ] = moreModules [ moduleId ];
  7. /******/ }

webpackJsonpCallback(chunkIds, moreModules)定义为 chunkIds, moreModules,chunkIds 说了,就是入口文件对应的 js 文件的 thunkid, 那么这个moreModules是什么呢? 就是一个js文件中,一个个依赖的和 导出本身的 module

可以看index2.xxxx.js


      
      
  1. webpackJsonp ([ 1 ],{
  2. /***/ 0 :
  3. /***/ function ( module , exports , __webpack_require__ ) {
  4. module . exports = __webpack_require__ ( 8 );
  5. /***/ },
  6. /***/ 8 :
  7. /***/ function ( module , exports , __webpack_require__ ) {
  8. var util2 = __webpack_require__ ( 3 )
  9. var css1 = __webpack_require__ ( 4 )
  10. /***/ }
  11. });

这里 moreModules 为一个对象,key 为 moduleid, value 就是一个个 module定义

比如 function(module, exports, __webpack_require__) { module.exports = __webpack_require__(8);}

最重要的来了, 这里的话, utils2 是公共的module,它被定义在commong.js中,

common.js


      
      
  1. /* 0 */ ,
  2. /* 1 */ ,
  3. /* 2 */ ,
  4. /* 3 */
  5. /***/ function ( module , exports ) {
  6. module . exports = { "name" : "util2.js" }
  7. /***/ },
  8. /* 4 */

可以看到, index.xxx.js 的 调用 和 index2.xxx.js的调用都是一样的,都是下面这句

var util2 = __webpack_require__(3)

它会寻找 moduleid 为3的模块,然后返回

如何寻找呢 看看 webpack_require 定义


      
      
  1. /******/ function __webpack_require__ ( moduleId ) {
  2. /******/ // Check if module is in cache
  3. /******/ if ( installedModules [ moduleId ])
  4. /******/ return installedModules [ moduleId ]. exports ;
  5. /******/ // Create a new module (and put it into the cache)
  6. /******/ var module = installedModules [ moduleId ] = {
  7. /******/ exports : {},
  8. /******/ id : moduleId ,
  9. /******/ loaded : false
  10. /******/ };

if(installedModules[moduleId]) 如果缓存中存在的话,就直接返回。

由于 index.xxx.js 和 index2.xxx.js都是返回的同一个 id 的模块,所以实际上 它们使用的是同一个对象

这和nodejs里面的require是一样的,所以整个项目中,require公共模块中的资源的话,实际返回的都是同一个对象

webpack打包原理解析

一开始,对webpack打包原理很不熟悉,看了不少资料,但是讲的都不是很清楚,现在来梳理一遍。

所有资源统一入口

这个是什么意思呢?就是webpack是通过js来获取和操作其他文件资源的,比如webpack想处理less,但是它并不会直接从本地的文件夹中直接通过路径去读取css文件,而且通过执行入口js文件,如果入口文件中,或者入口文件相关联的js文件中含有 require(xx.less) 这个less文件,那么它就会通过对应的loader去处理这个less文件

打包中的文件管理

重点来了: webpack是如何进行资源的打包的呢?

总结:

  • 每个文件都是一个资源,可以用require导入js
  • 每个入口文件会把自己所依赖(即require)的资源全部打包在一起,一个资源多次引用的话,只会打包一份
  • 对于多个入口的情况,其实就是分别独立的执行单个入口情况,每个入口文件不相干(可用CommonsChunkPlugin优化)

js单文件入口的情况

img1.gif

比如整个应用的入口为 entry.js

entry.js引用 util1.js util2.js, 同时util1.js又引用了util2.js

关键问题是: 它打包的会不会将 util2.js打包两份?

其实不会的,webpack打包的原理为,在入口文件中,对每个require资源文件进行配置一个id, 也就是说,对于同一个资源,就算是require多次的话,它的id也是一样的,所以无论在多少个文件中require,它都只会打包一分

对于和上面一致拓扑图,打包后的js代码为img2.gif

注: /* 1 */ 代码打包模块为 id 为 1

通过上面的图片我们看到,

  • entry.js 入口文件对应的id为 1
  • util1.js的id为 2
  • util2.js的id为 3

entry.js引用了 2 和 3, util1.js引用了 3,说明了entry和util1引用的是同一份,util2.js不会重复打包

css单文件入口的情况

上面分析了js的情况,其实css也是一个道理。它同样也会为每个css资源分配一个id, 每个资源同样也只会导入一次。

img3.gif

可以看到, entry.js 和 util1.js共同引用了 style2.less,那么它们作用的结果是怎么样的呢?

img4.gif

可以看到 entry.js 和 util1.js使用的是同一个 css资源

注意: /* 1 */ 注释表示id为1的模块

听说 ExtractTextPlugin 导出css的插件比较火, 让我们来看看它到底帮我们干了啥?配置好相关引用后

newExtractTextPlugin('[name].[chunkhash].css')
  
  

运行,在static下面生成了 一个.css 一个.js

生成的css代码为


  
  
  1. .style2 {
  2. color: green;
  3. }
  4. .style3 {
  5. color: blue;
  6. }
  7. .style1 {
  8. color: red;
  9. }

实际就是把三个css导出到一个css文件,而且同一个入口中,多次引用同一个css,实际只会打包一份

生成的.js为

img5.gif

可以看到 /* 2 */ 实际就是 util1.js模块,还是引用 id为3的模块 即css2, 但是我们看 3 模块的定义,为空函数, 它给出的解释是 removed by extract-text-webpack-plugin 实际就是newExtractTextPlugin把相关的css全部移出去了

多文件入口的情况

讲完了单入口,还需讲讲多入口,很多时候我们项目是需要多入口

img6.gif

修改webpack config

img7.gif

可以看到 输出为两个js文件

先对 entry.js 对应的输出文件进行分析

img8.gif

其实可以看到, util1.js util2.js的内容都打包进 对应的js里面去了

在对 entry2.js 输出的文件进行分析

img9.gif

可以看到,把entry2.js依赖的 util2.js也打包进去了

所以多 入口 实际就是 分别执行多个单入口,彼此之间不影响

问题来了,多入口对应的css呢? 还是上面的原则,css也是分别打包的,对于每个入口所依赖的css全部打包,输出就是这个入口对应的css

最后讨论 CommonsChunkPlugin

之前提到过,每个入口文件,都会独立打包自己依赖的模块,那就会造成很多重复打包的模块,有没有一种方法能把多个入口文件中,共同依赖的部分给独立出来呢? 肯定是有的 CommonsChunkPlugin

这个插件使用非常简单,它原理就是把多个入口共同的依赖都给定义成 一个新入口

为何我这里说是定义成新入口呢,因为这个名字不仅仅对应着js 而且对于着和它相关的css等,比如HtmlWebpackPlugin 中 就能体现出来,可以它的 chunks中 加入 common 新入口,它会自动把common的css也导入html

img10.gif

可以看到, 不仅仅js公共模块独立出来,连css同样也是,感受到了 webpack强大了吧

我们可以大概对 common.js index.xxxxx.js(entry.js对应的代码) index2.xxxx.js(entry2.js对应的代码)打包出来的代码分析一下

提前知识:

webpack的id 有两种 一种为 chunkid 一种为moduleId

  • 每个chunkid 对应的是一个js文件
  • 每个moduleid对应的是一个个js文件的内容的模块(一个js文件里面可以require多个资源,每 个资源分配一个moduleid)

为何要出来一个chunkid呢? 这个chunkid的作用就是,标记这个js文件是否已经加载过了


  
  
  1. /******/ // object to store loaded and loading chunks
  2. /******/ // "0" means "already loaded"
  3. /******/ // Array means "loading", array contains callbacks
  4. /******/ var installedChunks = {
  5. /******/ 2 : 0
  6. /******/ };

installedChunks 是记录一个chunkid是否已经加载过了

我们先从 common.js分析

common.js是公共分离出来的模块,所以按理来说,它应该是一个顶层的模块,实际上,确实如此

看 common.js代码


  
  
  1. /******/ ( function ( modules ) { // webpackBootstrap
  2. /******/ // install a JSONP callback for chunk loading
  3. /******/ var parentJsonpFunction = window [ "webpackJsonp" ];
  4. /******/ window [ "webpackJsonp" ] = function webpackJsonpCallback ( chunkIds , moreModules ) {
  5. /******/ // add "moreModules" to the modules object,
  6. /******/ // then flag all "chunkIds" as loaded and fire callback

定义了一个 webpackJsonp 函数

看 index.xxxx.js代码


  
  
  1. webpackJsonp ([ 0 ],[
  2. /* 0 */
  3. /***/ function ( module , exports , __webpack_require__ ) {
  4. module . exports = __webpack_require__ ( 1 );
  5. /***/ },

使用 common.js中定义的函数 webpackJsonp, 所以在浏览器中加载的话,必须先加载 common.js

这个 webpackJsonp([0],[]) 第一个参数 [0] 是什么意思呢? 它就是 chunkid ,我们知道一个chunkidid对应一个 js 文件,它对应的js文件就是它的入口文件 entry.js, 那为何这里还要是一个数组

因为一个入口文件的话,可以依赖多个js文件,其他的 id 就是它所依赖的, 其实就是配置webpack的时候

那个入口js对应的 数组

index: ['./src/js/entry.js'], [0] 和 现在的id数组一一对应

在 index2.xxx.js中,


  
  
  1. webpackJsonp ([ 1 ],{
  2. /***/ 0 :
  3. /***/ function ( module , exports , __webpack_require__ ) {
  4. module . exports = __webpack_require__ ( 8 );

[1] 就是 webpack.config配置的数组 js 对应的 thunkid

index2: ['./src/js/entry2.js']

即 entry2.js 就是 thunkid 1

上面说完了thunkid, 下面就说 moduleid了, common.js为顶层js文件,通过调用webpackJsonp对其他文件进行处理,

common.js中


  
  
  1. /******/ var parentJsonpFunction = window [ "webpackJsonp" ];
  2. /******/ window [ "webpackJsonp" ] = function webpackJsonpCallback ( chunkIds , moreModules ) {
  3. /******/ // add "moreModules" to the modules object,
  4. /******/ // then flag all "chunkIds" as loaded and fire callback
  5. /******/ for ( moduleId in moreModules ) {
  6. /******/ modules [ moduleId ] = moreModules [ moduleId ];
  7. /******/ }

webpackJsonpCallback(chunkIds, moreModules)定义为 chunkIds, moreModules,chunkIds 说了,就是入口文件对应的 js 文件的 thunkid, 那么这个moreModules是什么呢? 就是一个js文件中,一个个依赖的和 导出本身的 module

可以看index2.xxxx.js


  
  
  1. webpackJsonp ([ 1 ],{
  2. /***/ 0 :
  3. /***/ function ( module , exports , __webpack_require__ ) {
  4. module . exports = __webpack_require__ ( 8 );
  5. /***/ },
  6. /***/ 8 :
  7. /***/ function ( module , exports , __webpack_require__ ) {
  8. var util2 = __webpack_require__ ( 3 )
  9. var css1 = __webpack_require__ ( 4 )
  10. /***/ }
  11. });

这里 moreModules 为一个对象,key 为 moduleid, value 就是一个个 module定义

比如 function(module, exports, __webpack_require__) { module.exports = __webpack_require__(8);}

最重要的来了, 这里的话, utils2 是公共的module,它被定义在commong.js中,

common.js


  
  
  1. /* 0 */ ,
  2. /* 1 */ ,
  3. /* 2 */ ,
  4. /* 3 */
  5. /***/ function ( module , exports ) {
  6. module . exports = { "name" : "util2.js" }
  7. /***/ },
  8. /* 4 */

可以看到, index.xxx.js 的 调用 和 index2.xxx.js的调用都是一样的,都是下面这句

var util2 = __webpack_require__(3)

它会寻找 moduleid 为3的模块,然后返回

如何寻找呢 看看 webpack_require 定义


  
  
  1. /******/ function __webpack_require__ ( moduleId ) {
  2. /******/ // Check if module is in cache
  3. /******/ if ( installedModules [ moduleId ])
  4. /******/ return installedModules [ moduleId ]. exports ;
  5. /******/ // Create a new module (and put it into the cache)
  6. /******/ var module = installedModules [ moduleId ] = {
  7. /******/ exports : {},
  8. /******/ id : moduleId ,
  9. /******/ loaded : false
  10. /******/ };

if(installedModules[moduleId]) 如果缓存中存在的话,就直接返回。

由于 index.xxx.js 和 index2.xxx.js都是返回的同一个 id 的模块,所以实际上 它们使用的是同一个对象

这和nodejs里面的require是一样的,所以整个项目中,require公共模块中的资源的话,实际返回的都是同一个对象

猜你喜欢

转载自blog.csdn.net/weixin_43739375/article/details/88676583