相信大家对
webpack
并不陌生,用webpack
打包也是非常常见的事,但是webpack
在打包的时候有很多配置可以对打包就行优化。
最近正好在研究这方面的东西,所以在此记录一下,以便日后查看。
在进入正文之前先说一个“好玩”的东西,是一个对webpack打包之后的文件进行分析的工具——webpack-bundle-analyzer
虽然是在webpack
的配置文件中使用,但并不是webpack
官方提供的插件,需要使用npm
进行安装:
npm install webpack-bundle-analyzer --save-dev
首先在webpack
的配置文件中引入webpack-bundle-analyzer
,比如我的文件名是webpack.demo.config.js
:
// webpack.demo.config.jsvar BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
接着使用引入的BundleAnalyzerPlugin
:
// webpack.demo.config.jsnew BundleAnalyzerPlugin({analyzerMode: 'server',analyzerHost: '127.0.0.1',analyzerPort: 8888,reportFilename: 'report.html',defaultSizes: 'parsed',openAnalyzer: true,generateStatsFile: false,statsFilename: 'stats.json',statsOptions: null,logLevel: 'info'
})
可以看到里面的参数还是很多的:
analyzerMode
:表示已什么方式查看分析结果,可选参数为server
, static
or disabled
。
analyzerHost
:analyzerMode
为server
的时候生效,表示启动http服务的IP地址。
analyzerPort
:analyzerMode
为server
的时候生效,表示启动http服务的端口号。
reportFilename
:analyzerMode
为static
的时候生效,表示输出静态文件的文件名,输出路径output
路径相同。
defaultSizes
:表示默认显示的数据模式,可选参数为stat
, parsed
or gzip
。
openAnalyzer
:true|false
,表示是否在打包完成之后自动打开分析界面。
generateStatsFile
:true|false
,表示是否生成.json
文件。
statsFilename
:设置generateStatsFile
为true
的时候生成的.json
文件名。
statsOptions
:通过修改生成的.json
文件中的source:false
来排除模块的源码。
logLevel
:日志的显示方式,可选参数为info
, warn
, error
or silent
。
无论是通过http服务还是通过打开静态文件的方式效果都是一样的:
从上面就可以看出上面的数据有三种,分别是defaultSizes
的三个参数,由于我设置的是parsed
所以默认显示parsed
的大小。
简单解释一下
defaultSizes
三个参数的含义:
stat
:是指打包之前的文件大小
parsed
:是指打包之后的文件大小
gzip
:是指通过gzip
压缩之后的文件大小
如果默认设置了parsed
,但是我想看stat
时候的文件大小视图改怎么办呢?
当然可以在这个分析界面中去调整:
把最近看到的好东西拿出来分享一下,觉得使用这个东西一眼就能看出打包后的结构,每个模块分的很清晰,而且通过视图中每个模块占的面积大小很直观的能看出各个文件的体积比例,这对打包的分析很有帮助,并且使用简单,不需要进行什么复杂的配置。
接下来进入正题,就是这个webpack本身提供的插件 —— CommonsChunkPlugin
首先,介绍一下插件的使用格式;
然后,分析其中的每个参数的作用;
最后,举一个实际的“栗子”。
看到CommonsChunkPlugin
这个名字,大概明白这个插件是跟公共模块有关系的。没错,这个插件就是用来提取出代码中公共的部分,并且将他们打包到一个单独的文件里面,这样就避免了重复打包。同时,由于配置中有minChunks
(在文章后面会讲这个参数的作用)这个参数的存在可以满足一些其他的需求,不仅仅是提取公共部分这么简单了,而且一个文件中可以使用多次CommonsChunkPlugin
插件。
另外,还需要知道两个名词:chunk
和chunkName
。
chunk
:通过CommonsChunkPlugin
生成的一个文件就是一个chunk,由于可能多次使用插件,所以每次打完包可能会很多chunk
。
注:如果是多入口,每一个入口文件都是一个
chunk
。
chunkName
:上面说过每使用一次插件就会生成一个chunk
,那这些chunk
通过什么区分呢?答案就是chunkName
。
由于是webpack
提供的插件,所以就不需要安装了,直接在配置文件中引入,同样在此还是以webpack.demo.config.js
为配置文件:
// webpack.demo.config.jsvar CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin")/*
* 省略其他代码
**/new CommonsChunkPlugin({name: string, // ornames: string[],filename: string,minChunks: number|Infinity|function(module, count) -> boolean,chunks: string[],children: boolean,async: boolean|string,minSize: number,
})
or
当然,你也可以选择在头部一次引入webpack
,以后内置的插件都可以通过webpack
来使用,像这样:
// webpack.demo.config.jsvar webpack = require('webpack')/*
* 省略其他代码
**/new webpack.optimize.CommonsChunkPlugin({name: string, // ornames: string[],filename: string,minChunks: number|Infinity|function(module, count) -> boolean,chunks: string[],children: boolean,async: boolean|string,minSize: number,
})
name|names
:这里的name
就是上面所说的chunkName
,如果包括过个chunk
,那么就使用names
,以数组的形式传入chunkName
,如:
names: ['vendor','utils']
filename
:打包后的文件名,可以省略,如果省略默认文件名为name
的值。
minChunks
:定义提取公共部分的规则。
个人觉得这个
minChunks
是整个插件最屌的没有之一(自行忽略)。
如果值为数字,例:minChunks:2
,就说明提取文件中最少被引用2次的代码,
如果值为Infinity
,将会直接创建一个公共的chunk
,但是里面没有模块,
如果值为函数,将会提取出满足函数条件的代码,生成一个公共chunk
,所以可以在这里实现以下定制化的需求。函数会提供两个参数:module
和count
。
chunks
:定义从哪些chunk
中提取公共模块,如果省略,默认为入口chunk
。
children
:默认是false
,多个chunk
里面的模块可能会有相同的依赖关系,如果设置为true
,将其中相同的依赖提到父元素中(但是会影响初始加载的时间)。
async
:异步加载的附加公共chunk
, 当下载附加组块时,它会并行自动下载。
minSize
:创建公共chunk
之前,所有公共模块的的最小体积。
上面说了一堆理论,讲了一下基本结构,具体用法现在开始了!
下面是我一个Vue工程的项目结构:
现在我想要提取出node_modules
中以.js
结尾的文件和src
下以.vue
结尾的文件,将他们分别打到两个文件中。
先看一下入口和出口文件:
entry: {main: './examples/main.js'
},
output: {path: path.resolve(__dirname, '../demo'),filename: '[name].[chunkhash].js',
}
接着,按照上面说的,我首先引入CommonsChunkPlugin
:
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin")
接着使用这个插件:
new CommonsChunkPlugin({name: "list",minChunks: function (module, count) {return (module.resource &&/\.js$/.test(module.resource) &&module.resource.indexOf(path.join(__dirname, '../node_modules')) === 0)}
}),
new CommonsChunkPlugin({name: 'components',chunks: ['main'],minChunks: function (module, count) {return (module.resource &&/\.vue$/.test(module.resource) &&module.resource.indexOf(path.join(__dirname, '../src')) === 0)}
}),
new CommonsChunkPlugin({name: "manifest",chunks: ['list', 'components']
})
打完包之后的结果是这样的,确实单独打出了两个文件:
Asset Size Chunks Chunk Names
components.8ec10c04b75e68ee707a.js 479 kB 1 [emitted] [big] componentsmain.a33511b70c4ad0a4aead.js 738 kB 2 [emitted] [big] mainlist.b2de50f4150e217815ca.js 789 kB 3 [emitted] [big] listmanifest.9412c3435bf535f63115.js 6.07 kB 4 [emitted] manifest
打包生成的文件目录结构是这样的:
用webpack-bundle-analyzer
打开效果是这样的:
看着文档很简单,但是实际操作起来就有许多问题,比如:
- 打包之后Chunks
那一列数字是什么?
- 出口文件中的chunkhash
是什么?
- 生成manifest
又是什么?
-minChunks
函数中的参数到底有什么作用?
chunkhash
首先,chunkhash
既然叫hash
,所以是一种hash
值。当然,你也可以这么定义出口文件:
filename: '[name].[hash].js'
这里解释一下chunkhash与hash的区别:
- 对于打包生成的每个文件都有单独的hash值,这个hash值就叫做chunkhash;
- 而如果我们用[hash]
定义出口文件,打包之后的文件hash值是一致的,因为这个hash是整个的hash值,我把出口文件改了之后打包生成的文件放在下面,可以跟上面打包后的文件对比一下;
- 所以,chunkhash与hash的区别就是私有hash与全局hash的关系。
Asset Size Chunks Chunk Names
components.26ae71410d030a4f8f66.js 479 kB 1 [emitted] [big] componentsmain.26ae71410d030a4f8f66.js 738 kB 2 [emitted] [big] mainlist.26ae71410d030a4f8f66.js 789 kB 3 [emitted] [big] listmanifest.26ae71410d030a4f8f66.js 5.98 kB 4 [emitted] manifest
manifest
那么manifest
又是什么呢?其实就是webpack
的一个清单文件,manifest
的作用就是将你之前打包好的每个文件的chunkhash
利用webpack
的缓存机制保存起来,如果下次没有修给这个chunk
里面的文件,那你打包之后文件的chunkhash
是不变的,修改过的会生成新的chunkhash
。
接下来我修改上文中的其中一个.vue
文件,在重新打包的结果是这样的,可以跟之前的对比一下,看是不是其他chunkhash
没有变:
Asset Size Chunks Chunk Names
components.c2315d7bc104f905405c.js 479 kB 1 [emitted] [big] componentsmain.a33511b70c4ad0a4aead.js 738 kB 2 [emitted] [big] mainlist.b2de50f4150e217815ca.js 789 kB 3 [emitted] [big] listmanifest.e3e56878b56c560148b7.js 6.07 kB 4 [emitted] manifest
minChunks
当minChunks
为函数的时候,webpack
会提供两个参数:module
和 count
。其中,module
有两个属性module.context
和module.resource
:
- module.resource
:表示正在处理的文件位置(其实就是文件的路径)
- module.context
:表示文件所在的目录
- count
:表示文件被引用的次数
有兴趣可以把module.context和module.resource打印出来看一下。
对于count
的使用其实很简单,这里我还是想提一下,直接把官网的栗子拿过来说:
new CommonsChunkPlugin({name: "list",minChunks: function (module, count) {// 这里就是说,将文件路径中存在somelib并且被引用了3次的文件// 如果说没有前面的条件,完全可以直接设置minChunks:3return module.resource && (/somelib/).test(module.resource) && count === 3}
}),
到这里,上面写的CommonsChunkPlugin
配置基本就很清晰了,但是对于打包之后的Chunks
列数字问题,我在这里说一下,那些数字其实叫做Chunks.id
,是自动生成的,从0开始。这时候细心的人发现我上面的打包结果中并没有0,是从1开始的,这是为什么呢?答案是:其实是有0的,只不过打出来的文件与本文并没有关系,所以让我手动把第一行给删了,不要纠结,就是从0开始的,没毛病。
上面就CommonsChunkPlugin
的作用及配置进行了介绍,并且展示了一个小栗子,根据结果又分析出了很多东西。但是由于篇幅太长,配置里面的参数没有一一展示demo,如果想用可以自己再慢慢探索。
到这里文章就结束了,主要的目的还是为了记录和学习,顺便分享一下,以上都是亲自实践以及一些个人的理解,有兴趣的话也可以互相交流,交流是人类进步的阶梯。