💂 个人网站:【紫陌】【笔记分享网】
💅 想寻找共同学习交流、共同成长的伙伴,请点击【前端学习交流群】
文章最后有作者l联系方式(备注进群)
- webpack的性能优化较多,我们可以对其进行分类: - 优化一:打包后的结果,上线时的性能优化。(比如分包处理、减小包体积、CDN服务器等)- 优化二:优化打包速度,开发或者构建时优化打包速度。(比如exclude、cache-loader等)
- 大多数情况下,我们会更加侧重于优化一,这对于线上的产品影响更大。
- 在大多数情况下webpack都帮我们做好了该有的性能优化: - 比如配置mode为production或者development时,默认webpack的配置信息;- 但是我们也可以针对性的进行自己的项目优化;
1.性能优化 - 代码分离
- 代码分离(Code Splitting)是webpack一个非常重要的特性: - 它主要的目的是将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件;- 比如默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页 的加载速度;- 代码分离可以分出更小的bundle,以及控制资源加载优先级,提供代码的加载性能;
- Webpack中常用的代码分离有三种: - 入口起点:使用entry配置手动分离代码;- 防止重复:使用Entry Dependencies或者SplitChunksPlugin去重和分离代码;- 动态导入:通过模块的内联函数调用来分离代码;
1.1 入口起点优化
- 入口起点的含义非常简单,就是配置多入口:
- 比如配置一个index.js和main.js的入口;
- 他们分别有自己的代码逻辑;
- 多入口优化-Entry Dependencies(入口依赖)
- 假如我们的index.js和main.js都依赖两个库:axios - 如果我们单纯的进行入口分离,那么打包后的两个bunlde都有会有一份axios- 事实上我们可以对他们进行共享;
1.2 动态导入(dynamic import)
- 另外一个代码拆分的方式是动态导入时,webpack提供了两种实现动态导入的方式:- 第一种,使用ECMAScript中的 import() 语法来完成,也是目前推荐的方式;- 第二种,使用webpack遗留的 require.ensure,目前已经不推荐使用;
- 比如我们有一个模块 bar.js:- 该模块我们希望在代码运行过程中来加载它(比如判断一个条件成立时加载);- 因为我们并不确定这个模块中的代码一定会用到,所以最好拆分成一个独立的js文件;- 这样可以保证不用到该内容时,浏览器不需要加载和处理该文件的js代码;- 这个时候我们就可以使用动态导入;
- 注意:使用动态导入bar.js:- 在webpack中,通过动态导入获取到一个对象;- 真正导出的内容,在该对象的default属性中,所以我们需要做一个简单的解构;
- 动态导入的文件命名
因为动态导入通常是一定会打包成独立的文件的,所以并不会再cacheGroups中进行配置;
那么它的命名我们通常会在output中,通过 chunkFilename 属性来命名;
希望修改name的值,可以通过magic comments(魔法注释)的方式
1.3自定义分包-SplitChunks
- 第三种分包的模式是splitChunk,它底层是使用SplitChunksPlugin来实现的:- 因为该插件webpack已经默认安装和集成,所以我们并不需要单独安装和直接使用该插件;- 只需要提供SplitChunksPlugin相关的配置信息即可;
- Webpack提供了SplitChunksPlugin默认的配置,我们也可以手动来修改它的配置:
- 比如默认配置中,chunks仅仅针对于异步(async)请求,我们可以设置为initial或者all;
- SplitChunks自定义配置解析
常用配置解析
- Chunks:- 默认值是async- 另一个值是initial,表示对通过的代码进行处理- all表示对同步和异步代码都进行处理
- minSize:- 拆分包的大小, 至少为minSize;- 如果一个包拆分出来达不到minSize,那么这个包就不会拆分;
- maxSize:- 将大于maxSize的包,拆分为不小于minSize的包;
- cacheGroups:- 用于对拆分的包就行分组,比如一个lodash在拆分之后,并不会立即打包,而是会等到有没有其他符合规则的包一起来打包;- test属性:匹配符合规则的包;- name属性:拆分包的name属性;- filename属性:拆分包的名称,可以自己使用placeholder属性;
3. 解决注释的单独提取
在生产环境下默认webpack在进行分包时,有对包中的注释进行单独提取。
这个包提取是由另一个插件默认配置的原因(默认为true,改为false即可)
- optimization.chunkIds配置
optimization.chunkIds配置用于告知webpack模块的id采用什么算法生成
- 有三个比较常见的值:
- natural:按照数字的顺序使用id;
- named:development下的默认值,一个可读的名称的id;
- deterministic:确定性的,在不同的编译中不变的短数字id - 在webpack4中是没有这个值的;- 那个时候如果使用natural,那么在一些编译发生变化时,就会有问题;
最佳实践:
- 开发过程中,我们推荐使用named;
- 打包过程中,我们推荐使用deterministic;
- optimization.runtimeChunk配置
- 配置runtime相关的代码是否抽取到一个单独的chunk中: - runtime相关的代码指的是在运行环境中,对模块进行解析、加载、模块信息相关的代码;- 比如我们的component、bar两个通过import函数相关的代码加载,就是通过runtime代码完成的;
- 抽离出来后,有利于浏览器缓存的策略: - 比如我们修改了业务代码(main),那么runtime和component、bar的chunk是不需要重新加载的;- 比如我们修改了component、bar的代码,那么main中的代码是不需要重新加载的;
- 设置的值:
- true/multiple:针对每个入口打包一个runtime文件;
- single:打包一个runtime文件;
- 对象:name属性决定runtimeChunk的名称;
会打包出单独的js文件。单独模块
- Prefetch和Preload
webpack v4.6.0+ 增加了对预获取和预加载的支持。
- 在声明 import 时,使用下面这些内置指令,来告知浏览器:- prefetch(预获取):将来某些导航下可能需要的资源- preload(预加载):当前导航下可能需要资源
与 prefetch 指令相比,preload 指令有许多不同之处:
- preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
- preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
- preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
2.性能优化-CDN
CDN称之为内容分发网络(Content Delivery Network或Content Distribution Network,缩写:CDN), 它是指通过相互连接的网络系统,利用最靠近每个用户的服务器; 更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户; 来提供高性能、可扩展性及低成本的网络内容传递给用户;
- 在开发中,我们使用CDN主要是两种方式: - 方式一:打包的所有静态资源,放到CDN服务器, 用户所有资源都是通过CDN服务器加载的;- 方式二:一些第三方资源放到CDN服务器上;
1.1配置CDN服务器
可以直接修改publicPath,在打包时添加上自己的CDN地址;
1.2配置第三方库的CDN服务器
通常一些比较出名的开源框架都会将打包后的源码放到一些比较出名的、免费的CDN服务器上:
- 国际上使用比较多的是unpkg、JSDelivr、cdnjs;
- 国内也有一个比较好用的CDN是bootcdn;
项目中,我们如何去引入这些CDN呢?
- 第一,在打包的时候我们不再需要对类似于lodash或者axios这些库进行打包;
- 第二,在html模块中,我们需要自己加入对应的CDN服务器地址;
第一步,我们可以通过webpack配置,来排除一些库的打包:
第二步,在html模块中,加入CDN服务器地址:
3.性能优化-提取css文件
MiniCssExtractPlugin可以帮助我们将css提取到一个独立的css文件中,该插件需要在webpack4+才可以使用。
安装 mini-css-extract-plugin:
npm install mini-css-extract-plugin -D
配置rules和plugins:
注意:1.style-loader打包样式是放到标签内联上的,2. minicssExtractPlugin打包独立css文件然后link标签引入css。
4. 性能优化-打包文件命名(Hash,ContentHash,ChunkHash)
- 在我们给打包的文件进行命名的时候,会使用placeholder,placeholder中有几个属性比较相似: - hash、chunkhash、contenthash- hash本身是通过MD4的散列函数处理后,生成一个128位的hash值(32个十六进制);
- hash值的生成和整个项目有关系: - 比如我们现在有两个入口index.js和main.js;- 它们分别会输出到不同的bundle文件中,并且在文件名称中我们有使用hash;- 这个时候,如果修改了index.js文件中的内容,那么hash会发生变化;- 那就意味着两个文件的名称都会发生变化;
- chunkhash可以有效的解决上面的问题,它会根据不同的入口进行借来解析来生成hash值: - 比如我们修改了index.js,那么main.js的chunkhash是不会发生改变的;
- contenthash表示生成的文件hash名称,只和内容有关系: - 比如我们的index.js,引入了一个style.css,style.css有被抽取到一个独立的css文件中;- 这个css文件在命名时,如果我们使用的是chunkhash;- 那么当index.js文件的内容发生变化时,css文件的命名也会发生变化;- 这个时候我们可以使用contenthash;
推荐使用contenthash,可以让没改动的文件文件名一样,然后用户浏览器还能使用以前的缓存文件
5.性能优化-JS-CSS代码压缩
1.Terser介绍和安装
- 什么是Terser呢?- Terser是一个JavaScript的解释(Parser)、Mangler(绞肉机)/Compressor(压缩机)的工具集;- 早期我们会使用 uglify-js来压缩、丑化我们的JavaScript代码,但是目前已经不再维护,并且不支持ES6+的语法;- Terser是从 uglify-es fork 过来的,并且保留它原来的大部分API以及适配 uglify-es和uglify-js@3等;
- Terser可以帮助我们压缩、丑化我们的代码,让我们的bundle变得更小。
- Terser是一个独立的工具,它可以单独安装:
- 全局安装 npm install terser -g
- 局部安装 npm install terser -D
这里就不介绍单独使用了,主要讲在webpack使用
2.Terser在webpack压缩JS
- 真实开发中,我们不需要手动的通过terser来处理我们的代码,我们可以直接通过webpack来处理:- 在webpack中有一个minimizer属性,在production模式下,默认就是使用TerserPlugin来处理我们的代码的;- 如果我们对默认的配置不满意,也可以自己来创建TerserPlugin的实例,并且覆盖相关的配置;
- 首先,我们需要打开minimize,让其对我们的代码进行压缩(默认production模式下已经打开了)
- 其次,我们可以在minimizer创建一个TerserPlugin:- extractComments:默认值为true,表示会将注释抽取到一个单独的文件中; - 在开发中,我们不希望保留这个注释时,可以设置为false;- parallel:使用多进程并发运行提高构建的速度,默认值是true - 并发运行的默认数量: os.cpus().length - 1;- 我们也可以设置自己的个数,但是使用默认值即可;- terserOptions:设置我们的terser相关的配置 - compress:设置压缩相关的选项;- mangle:设置丑化相关的选项,可以直接设置为true;- toplevel:顶层变量是否进行转换;- keep_classnames:保留类的名称;- keep_fnames:保留函数的名称;
3.Terser在webpack压缩CSS
- CSS压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等;
- CSS的压缩我们可以使用另外一个插件:css-minimizer-webpack-plugin;
- css-minimizer-webpack-plugin是使用cssnano工具来优化、压缩CSS(也可以单独使用);
安装 css-minimizer-webpack-plugin:
npm install css-minimizer-webpack-plugin -D
在optimization.minimizer中配置:
6.性能优化-webpack实现Tree Shaking
- 什么是Tree Shaking呢?- Tree Shaking是一个术语,在计算机中表示消除死代码(dead_code);- 最早的想法起源于LISP,用于消除未调用的代码(纯函数无副作用,可以放心的消除,这也是为什么要求我们在进行函数式 编程时,尽量使用纯函数的原因之一);- 后来Tree Shaking也被应用于其他的语言,比如JavaScript、Dart;
- JavaScript的Tree Shaking:- 对JavaScript进行Tree Shaking是源自打包工具rollup(后面我们也会讲的构建工具);- 这是因为Tree Shaking依赖于ES Module的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系);- webpack2正式内置支持了ES2015模块,和检测未使用模块的能力;- 在webpack4正式扩展了这个能力,并且通过 package.json的 sideEffects属性作为标记,告知webpack在编译时,哪里文 件可以安全的删除掉;- webpack5中,也提供了对部分CommonJS的tree shaking的支持;> webpack/changelog-v5: Temporary repo for the changelog for webpack 5 (github.com)
1. JS实现Tree Shaking
- webpack实现Tree Shaking采用了两种不同的方案: - usedExports:通过标记某些函数是否被使用,之后通过Terser来进行优化的;- sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用;
- usedExports实现- 将mode设置为development模式:- 为了可以看到 usedExports带来的效果,我们需要设置为 development 模式- 因为在 production 模式下,webpack默认的一些优化会带来很大的影响。- 设置usedExports为true和false对比打包后的代码:- 在usedExports设置为true时,会有一段注释:unused harmony export mul;- 这段注释的意义是什么呢?告知Terser在优化时,可以删除掉这段代码;
- 这个时候,我们讲 minimize设置true:- usedExports设置为false时,mul函数没有被移除掉;- usedExports设置为true时,mul函数有被移除掉;
- usedExports实现Tree Shaking是结合Terser来完成的。
- sideEffects实现
- sideEffects用于告知webpack compiler哪些模块时有副作用的: - 副作用的意思是这里面的代码有执行一些特殊的任务,不能仅仅通过export来判断这段代码的意义;
- 在package.json中设置sideEffects的值:- 如果我们将sideEffects设置为false,就是告知webpack可以安全的删除未用到的exports;- 如果有一些我们希望保留,可以设置为数组;
- 比如我们有一个format.js、style.css文件: - 该文件在导入时没有使用任何的变量来接受;- 那么打包后的文件,不会保留format.js、style.css相关的任何代码;
- 在项目中对JavaScript的代码进行TreeShaking呢(生成环境)?(项目中最佳实践) - 在optimization中配置usedExports为true,来帮助Terser进行优化;- 在package.json中配置sideEffects,直接对模块进行优化;
2.CSS实现TreeShaking
- 上面我们学习的都是关于JavaScript的Tree Shaking,那么CSS也可以进行Tree Shaking操作- CSS的Tree Shaking需要借助于一些其他的插件;- 在早期的时候,我们会使用PurifyCss插件来完成CSS的tree shaking,但是目前该库已经不再维护了(最新更新也是在4年前 了);- 目前我们可以使用另外一个库来完成CSS的Tree Shaking:PurgeCSS,也是一个帮助我们删除未使用的CSS的工具;
- 安装PurgeCss的webpack插件:npm install purgecss-webpack-plugin -D
webpack配置PurgeCss
- paths:表示要检测哪些目录下的内容需要被分析,这里我们可以使用glob;
- 默认情况下,Purgecss会将我们的html标签的样式移除掉,如果我们希望保留,可以添加一个safelist的属性;
7.Scope Hoisting(作用域提升)
- Scope Hoisting从webpack3开始增加的一个新功能;
- 功能是对作用域进行提升,并且让webpack打包后的代码更小、运行更快;
- 默认情况下webpack打包会有很多的函数作用域,包括一些(比如最外层的)IIFE: - 无论是从最开始的代码运行,还是加载一个模块,都需要执行一系列的函数;- Scope Hoisting可以将函数合并到一个模块中来运行;
- 使用Scope Hoisting非常的简单,webpack已经内置了对应的模块: - 在production模式下,默认这个模块就会启用;- 在development模式下,我们需要自己来打开该模块;
8. 性能优化-Webpack对文件压缩
1.什么是HTTP压缩
- HTTP压缩是一种内置在 服务器 和 客户端 之间的,以改进传输速度和带宽利用率的方式;
- HTTP压缩的流程什么呢?
- 第一步:HTTP数据在服务器发送前就已经被压缩了;(可以在webpack中完成)
- 第二步:兼容的浏览器在向服务器发送请求时,会告知服务器自己支持哪些压缩格式;
- 第三步:服务器在浏览器支持的压缩格式下,直接返回对应的压缩后的文件,并且在响应头中告知浏览器;
2.目前的流行压缩格式
- 目前的压缩格式非常的多:
- compress – UNIX的“compress”程序的方法(历史性原因,不推荐大多数应用使用,应该使用gzip或deflate);
- deflate – 基于deflate算法(定义于RFC 1951)的压缩,使用zlib数据格式封装;
- gzip – GNU zip格式(定义于RFC 1952),是目前使用比较广泛的压缩算法;
- br – 一种新的开源压缩算法,专为HTTP内容的编码而设计;
3. Webpack配置文件压缩
- webpack中相当于是实现了HTTP压缩的第一步操作,我们可以使用CompressionPlugin。 - 第一步,安装CompressionPlugin: - npm install compression-webpack-plugin -D- 第二步,使用CompressionPlugin即可:
9. 性能优化-HTML代码压缩
- 我们之前使用了HtmlWebpackPlugin插件来生成HTML的模板,事实上它还有一些其他的配置:- inject:设置打包的资源插入的位置- true、 false 、body、head- cache:设置为true,只有当文件改变时,才会生成新的文件(默认值也是true)- minify:默认会使用一个插件html-minifier-terser
版权归原作者 紫陌~ 所有, 如有侵权,请联系我们删除。