searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Webpack拆包

2022-12-19 06:02:59
173
0

       通过webpack实现前端项目整体模块化的优势固然很明显,但是它同样存在一些弊端,那就是我们项目中的所有代码最终都被打包到了一起,如果我们应用非常复杂,模块非常多的话,我们的打包结果就会特别的大。接下来将以开发中的一个项目作为例子,从问题的发现、分析以及解决的过程来分享一下webpack实现打包优化的使用心得。

1 问题的提出

      在我们的前端项目中,左侧菜单对应的是各个路由,路由使用import动态引入,并对单个路由进行分包,实现按需加载。

       通过配置动态路由,当我们使用到指定路由时才会去加载相对应的chunk文件,这就是webpack分包实现的效果。但是在实际使用过程中,我们发现一个问题:在生产环境中,我们发现设备管理的路由对应的chunk文件device-chunk体积过大,由于大体积导致chunk的下载时间耗时在网络环境不理想的情况下会长大5~6秒,这将对用户的体验造成巨大的影响。

      针对上述问题,我们使用webpack-bundle-analyzer来对项目的打包情况进行分析:

考虑到网络请求的性能消耗与用户能够拥有良好的体验,通常一个包的大小最好为不超过500KB。但是我们可以看到,项目中多个包已经超标,js/device这个包甚至高达3.56MB,这里迫切需要我们对webpack分包进行优化。我们分析造成该现象的原因主要有两点:

  1. 初始化未用到的第三方包没有按需引入,导致体积过大(excel只有设备导入导出时才会使用到,对于设备管理页的首次渲染没有任何意义);
  2. 多个组件引用到hls.js即播放器包,被重复打包,造成包体积冗余。

在我们的项目中其实已经用到了按需加载和splitChunkPlugins,根据上面的原因分析,我们初步制定了优化思路,可以利用以下两点对device-chunk进行分包优化:

  1. 充分利用按需加载以解决不必要包的关联(import());
  2. 合理配置分包策略以解决重复打包的问题(splitChunkPlugins)

2 webpack分包原理

       接下来我们将一起了解下webpack的分包原理,并在了解原理的过程中实现以上两点优化。

2.1 利用import()实现按需加载

      在 ES2020提案 中引入了 import() 函数,支持动态加载模块。不过不同于 CommonJs中 require()的同步加载,它是一个异步加载,返回一个 Promise 对象。其实,require()对module的引入是模块对象的浅拷贝,而import()是对模块对象的引用,故而可以依据import()进行静态分析,实现开发场景中的条件加载 (if 语句中)和 动态加载(需要的时候加载)。

      webpack 中,import() 语法可用作代码拆分。import() 的模块以及它引用的所有子模块,会分离到一个单独的 chunk 中。一般我们会借助 import() 对路由页面进行动态加载,实现页面代码的拆分和按需加载,在一定程度上提升应用的首屏渲染速度。常用到的场景有:路由的按需加载、组件懒加载、引入包时实现的按需加载等。

2.1.1 路由按需加载

2.1.2 组件懒加载

2.1.3 引入包的按需加载

2.2 webpack分包机制

      webpack以入口文件为起点有一条初始moduleChain,遇到动态引入import('xxxx')会新建一条moduleChain。分别对应的是动态引入的moduleChain(在后文中称为asyncModuleChain)、入口文件的初始moduleChain(在后文中称为initialModuleChain)。之后splitChunkPlugins的配置都是针对这两个分类去制定策略的。

2.3 splitChunkPlugins

      项目中已经使用到了splitChunkPlugins,但是会出现重复打包的问题,说明我们的配置并不正确,需要进一步的优化。以下为splitChunkPlugins常见的配置项以及各配置项所起到的作用描述:

上述配置项中,chunks(分割类型)、maxAsyncRequests(按需加载最大请求数)以及maxInitialRequests(入口文件最大请求数)是我们最常用到的,接下来将通过几个demo来阐述他们各自的作用。

2.3.1 chunksasync/initial/all

      首先描述下demo场景:我们有如下5个文件,其中index.js文件作为webpack打包的入口文件,它在引入antv、echarts两个包的同时,还动态引入了4个async.js文件;这四个async.js文件则都引入了excel.js和element-ui。

我们设置webpack配置如下,为了能够通过webpack-bundle-analyzer清晰的看到各个引入的扩展的打包情况,配置CashGroups将她们都各自独立出来。

      当chunks配置async模式时,async.js引入的包(excel.js和element-ui)被分割出来了,但是入口文件引入的包(antv和echarts)与入口文件其余代码被合成了一个包。

 

     当chunks配置initial模式时,入口文件引入的包(antv和echarts)被分割出来了,但是async.js引入的包(excel.js和element-ui)没有被分割出来,且出现了重复打包的情况。

     当chunks配置all模式时,入口文件引入的包(antv和echarts)与async.js引入的包(excel.js和element-ui)都被分割出来了。考虑到分包体积的合理性,一般情况下我们都会选择all模式作为项目的默认配置项。

2.3.2 maxInitialRequests/maxAsyncRequests

      在上个demo的基础上,我们为4个async.js文件再次引入一个hls.js扩展,,我们发现hls.js出现了重复打包的情况。

我们发现,在之前的配置中,maxAsyncRequests设置的是3,这意味着包括异步加载的文件只能分割出三个文件,而在包括本身async.js的情况下,加上excel.js、element-ui、hls.js共有四个包,由于配置的限制,hls.js不会被分割出去,会造成重复的打包。这也正是我们项目中出现重复打包的问题所在。当我们将maxAsyncRequests改为4时,重复打包的现象就消失了。同理,maxInitialRequests也是如此。

      在对项目各个文件的引入扩展的统计与设计策略是必要的,我们需要根据这些信息来配置合适的分包策略,因为其可能成为项目包体积冗余的潜在隐患。

3 总结

      问题1:初始化未用到的第三方包没有按需引入,导致体积过大(excel只有设备导入导出时才会使用到,对于设备管理页的首次渲染没有任何意义)

      解决:我们是使用引入包的按需加载去优化的,可以将excelJs从device-chunk中抽离出去,在使用到的时候再去下载

      问题2:多个组件引用到hls.js即播放器包,被重复打包,造成包内容冗余.

      解决:我们发现在引入hls.js时,当前路由chunk被分出去的包已经超过配置的maxAsyncRequests值,造成了hls.js没有被分割出去,继而造成了重复打包,项目总体积过度冗余。我们是通过调大maxAsyncRequests值从而解决了该问题。

      综上,我们发现了合理利用分包的好处,他可以帮助我们避免重复的打包,也可以通过按需加载来减小初始包体积,优化首屏时间。 但是平时的开发中也要根据实际问题去分析,精细的分包虽好,但是过度的分包反而会降低我们项目的性能,且不谈过度分包造成的构建速度过慢,实际生产中,我们每个包的下载都会消耗请求资源,如果一个包体积很小,那它被分出去所带来的提升远远没有请求消耗的影响大,那就得不偿失了,分包虽好,但需谨慎!

4 补充

      我们在通过按需加载减少了初始包的体积,从而优化了首屏时间,但是省去的这个部分时间在我们使用到分出去的包对应的功能时,还是要把这个包下载回来,相比于优化前,这部分时间的消耗就变长了,有点拆东墙补西墙的嫌疑,那么可以如何去优化呢?

      其实webpack已经为我们做出了优化:在空余时间去预获取/预加载模块,从而减少这部分时间的消耗。因为Vue CLI 也会自动注入 resource hint (preload/prefetch、manifest 和图标链接 (当用到 PWA 插件时) 以及构建过程中处理的 JavaScript 和 CSS 文件的资源链接。@vue/cli-service中引入了@vue/preload-webpack-plugin,默认情况下,一个 Vue CLI 应用会为所有初始化渲染需要的文件自动生成 preload 提示。这些提示会被@vue/preload-webpack-plugin 注入,并且可以通过 chainWebpack 的 config.plugin('preload') 进行修改和删除。

      可以看到,@vue/cli-service已经为我们的项目添加了各个包对应的link标签,在闲时才会去对各个包进行预加载。当我们需要使用到包对应的功能,比如下面额导出表格功能时,浏览器会从prefetch cashe中去加载对应的包,这里消耗的时间是极少的,上面提到的疑问也就迎刃而解啦。

 

0条评论
0 / 1000
xiaoren
1文章数
0粉丝数
xiaoren
1 文章 | 0 粉丝
Ta的热门文章查看更多
xiaoren
1文章数
0粉丝数
xiaoren
1 文章 | 0 粉丝
原创

Webpack拆包

2022-12-19 06:02:59
173
0

       通过webpack实现前端项目整体模块化的优势固然很明显,但是它同样存在一些弊端,那就是我们项目中的所有代码最终都被打包到了一起,如果我们应用非常复杂,模块非常多的话,我们的打包结果就会特别的大。接下来将以开发中的一个项目作为例子,从问题的发现、分析以及解决的过程来分享一下webpack实现打包优化的使用心得。

1 问题的提出

      在我们的前端项目中,左侧菜单对应的是各个路由,路由使用import动态引入,并对单个路由进行分包,实现按需加载。

       通过配置动态路由,当我们使用到指定路由时才会去加载相对应的chunk文件,这就是webpack分包实现的效果。但是在实际使用过程中,我们发现一个问题:在生产环境中,我们发现设备管理的路由对应的chunk文件device-chunk体积过大,由于大体积导致chunk的下载时间耗时在网络环境不理想的情况下会长大5~6秒,这将对用户的体验造成巨大的影响。

      针对上述问题,我们使用webpack-bundle-analyzer来对项目的打包情况进行分析:

考虑到网络请求的性能消耗与用户能够拥有良好的体验,通常一个包的大小最好为不超过500KB。但是我们可以看到,项目中多个包已经超标,js/device这个包甚至高达3.56MB,这里迫切需要我们对webpack分包进行优化。我们分析造成该现象的原因主要有两点:

  1. 初始化未用到的第三方包没有按需引入,导致体积过大(excel只有设备导入导出时才会使用到,对于设备管理页的首次渲染没有任何意义);
  2. 多个组件引用到hls.js即播放器包,被重复打包,造成包体积冗余。

在我们的项目中其实已经用到了按需加载和splitChunkPlugins,根据上面的原因分析,我们初步制定了优化思路,可以利用以下两点对device-chunk进行分包优化:

  1. 充分利用按需加载以解决不必要包的关联(import());
  2. 合理配置分包策略以解决重复打包的问题(splitChunkPlugins)

2 webpack分包原理

       接下来我们将一起了解下webpack的分包原理,并在了解原理的过程中实现以上两点优化。

2.1 利用import()实现按需加载

      在 ES2020提案 中引入了 import() 函数,支持动态加载模块。不过不同于 CommonJs中 require()的同步加载,它是一个异步加载,返回一个 Promise 对象。其实,require()对module的引入是模块对象的浅拷贝,而import()是对模块对象的引用,故而可以依据import()进行静态分析,实现开发场景中的条件加载 (if 语句中)和 动态加载(需要的时候加载)。

      webpack 中,import() 语法可用作代码拆分。import() 的模块以及它引用的所有子模块,会分离到一个单独的 chunk 中。一般我们会借助 import() 对路由页面进行动态加载,实现页面代码的拆分和按需加载,在一定程度上提升应用的首屏渲染速度。常用到的场景有:路由的按需加载、组件懒加载、引入包时实现的按需加载等。

2.1.1 路由按需加载

2.1.2 组件懒加载

2.1.3 引入包的按需加载

2.2 webpack分包机制

      webpack以入口文件为起点有一条初始moduleChain,遇到动态引入import('xxxx')会新建一条moduleChain。分别对应的是动态引入的moduleChain(在后文中称为asyncModuleChain)、入口文件的初始moduleChain(在后文中称为initialModuleChain)。之后splitChunkPlugins的配置都是针对这两个分类去制定策略的。

2.3 splitChunkPlugins

      项目中已经使用到了splitChunkPlugins,但是会出现重复打包的问题,说明我们的配置并不正确,需要进一步的优化。以下为splitChunkPlugins常见的配置项以及各配置项所起到的作用描述:

上述配置项中,chunks(分割类型)、maxAsyncRequests(按需加载最大请求数)以及maxInitialRequests(入口文件最大请求数)是我们最常用到的,接下来将通过几个demo来阐述他们各自的作用。

2.3.1 chunksasync/initial/all

      首先描述下demo场景:我们有如下5个文件,其中index.js文件作为webpack打包的入口文件,它在引入antv、echarts两个包的同时,还动态引入了4个async.js文件;这四个async.js文件则都引入了excel.js和element-ui。

我们设置webpack配置如下,为了能够通过webpack-bundle-analyzer清晰的看到各个引入的扩展的打包情况,配置CashGroups将她们都各自独立出来。

      当chunks配置async模式时,async.js引入的包(excel.js和element-ui)被分割出来了,但是入口文件引入的包(antv和echarts)与入口文件其余代码被合成了一个包。

 

     当chunks配置initial模式时,入口文件引入的包(antv和echarts)被分割出来了,但是async.js引入的包(excel.js和element-ui)没有被分割出来,且出现了重复打包的情况。

     当chunks配置all模式时,入口文件引入的包(antv和echarts)与async.js引入的包(excel.js和element-ui)都被分割出来了。考虑到分包体积的合理性,一般情况下我们都会选择all模式作为项目的默认配置项。

2.3.2 maxInitialRequests/maxAsyncRequests

      在上个demo的基础上,我们为4个async.js文件再次引入一个hls.js扩展,,我们发现hls.js出现了重复打包的情况。

我们发现,在之前的配置中,maxAsyncRequests设置的是3,这意味着包括异步加载的文件只能分割出三个文件,而在包括本身async.js的情况下,加上excel.js、element-ui、hls.js共有四个包,由于配置的限制,hls.js不会被分割出去,会造成重复的打包。这也正是我们项目中出现重复打包的问题所在。当我们将maxAsyncRequests改为4时,重复打包的现象就消失了。同理,maxInitialRequests也是如此。

      在对项目各个文件的引入扩展的统计与设计策略是必要的,我们需要根据这些信息来配置合适的分包策略,因为其可能成为项目包体积冗余的潜在隐患。

3 总结

      问题1:初始化未用到的第三方包没有按需引入,导致体积过大(excel只有设备导入导出时才会使用到,对于设备管理页的首次渲染没有任何意义)

      解决:我们是使用引入包的按需加载去优化的,可以将excelJs从device-chunk中抽离出去,在使用到的时候再去下载

      问题2:多个组件引用到hls.js即播放器包,被重复打包,造成包内容冗余.

      解决:我们发现在引入hls.js时,当前路由chunk被分出去的包已经超过配置的maxAsyncRequests值,造成了hls.js没有被分割出去,继而造成了重复打包,项目总体积过度冗余。我们是通过调大maxAsyncRequests值从而解决了该问题。

      综上,我们发现了合理利用分包的好处,他可以帮助我们避免重复的打包,也可以通过按需加载来减小初始包体积,优化首屏时间。 但是平时的开发中也要根据实际问题去分析,精细的分包虽好,但是过度的分包反而会降低我们项目的性能,且不谈过度分包造成的构建速度过慢,实际生产中,我们每个包的下载都会消耗请求资源,如果一个包体积很小,那它被分出去所带来的提升远远没有请求消耗的影响大,那就得不偿失了,分包虽好,但需谨慎!

4 补充

      我们在通过按需加载减少了初始包的体积,从而优化了首屏时间,但是省去的这个部分时间在我们使用到分出去的包对应的功能时,还是要把这个包下载回来,相比于优化前,这部分时间的消耗就变长了,有点拆东墙补西墙的嫌疑,那么可以如何去优化呢?

      其实webpack已经为我们做出了优化:在空余时间去预获取/预加载模块,从而减少这部分时间的消耗。因为Vue CLI 也会自动注入 resource hint (preload/prefetch、manifest 和图标链接 (当用到 PWA 插件时) 以及构建过程中处理的 JavaScript 和 CSS 文件的资源链接。@vue/cli-service中引入了@vue/preload-webpack-plugin,默认情况下,一个 Vue CLI 应用会为所有初始化渲染需要的文件自动生成 preload 提示。这些提示会被@vue/preload-webpack-plugin 注入,并且可以通过 chainWebpack 的 config.plugin('preload') 进行修改和删除。

      可以看到,@vue/cli-service已经为我们的项目添加了各个包对应的link标签,在闲时才会去对各个包进行预加载。当我们需要使用到包对应的功能,比如下面额导出表格功能时,浏览器会从prefetch cashe中去加载对应的包,这里消耗的时间是极少的,上面提到的疑问也就迎刃而解啦。

 

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0