资讯专栏INFORMATION COLUMN

Webpack 3一些代码体积优化方案的小结

taowen / 872人阅读

摘要:表示生成一个懒加载的,只有当需要时才会被加载。主要是作用域提升,将所有模块放在同一个作用域当中,一方面能提高运行速度,另一方面也能降低文件体积。前提是你的代码是用模块写的。参考文章学习小结

前言

之前接手公司一个前端项目,开发了几个月后越来越难以忍受项目结构的混乱和打包体积的臃肿(脚手架和基本功能代码都是从公司的其他项目复制过来的),如果不立即进行重构,难以想象以后要怎么维护各个产品线。于是我自告奋勇承担了项目框架的优化任务,这里分享一下我在打包体积优化中所研究的成果,经过几轮的努力,成功的将我们这个 react+antd+immutable+rxjs的较大项目从打包后的9MB降低到了2.5MB,首屏加载(gzip)从600KB+降低到了200KB,并且基本上将稳定的第三方库,webpack runtime代码和业务代码完全分离,最低限度减少网站更新时用户需要加载的代码量。
废话不多说,下面详细说明我所做的每一个步骤。

1. 优化第三方库

项目里对库的使用较为混乱,有些库安装了但很少用或者根本没用,但是又在webpack中的vendor入口指定打包了进来,造成体积上的浪费,所以需要仔细评估每个库是否必要安装。
react v16对比react v15,加上react-dom,体积上降低了30%,因此果断升级。

2. moment.js

分析完stats.json后,发现的第一个问题就是moment很大,具体原因webpack是把所有的locale文件打包了进来。我们的项目不需要多语言,因此我们可以使用ContextReplacementPlugin插件来舍弃中文之外的其他语言文件:

new webpack.ContextReplacementPlugin(/moment[/]locale$/, /zh-cn/)

去除掉locale后,又发现了另一个问题:依赖分析显示,我的项目里打包了两份moment,一份es module版的,一份umd版的。经过一番排查后发现,使用import "moment"导入的会加载es module版的,这是webpack配置的mainFields决定的,但是在locale中的语言文件中,它会用相对路径导入umd版的moment,这就导致我的项目里出现了两份moment。为了统一版本,我们将moment设置为一个别名并指向umd版:

alias: {
  "moment$": path.resolve("node_modules/moment/moment"),
},
// $表示绝对匹配

另外还有一个库dayjs值得一提,其API基本与moment一致,但是体积仅为几KB,不知道antd会不会加入对dayjs的支持。

3. ECharts

项目之前是直接使用的完整版的echarts,并且没有将echarts组件抽取为公共chunk,结果导致每个异步加载的页面组件,只要用了echarts就会变得硕大无比。
解决方案:在echarts官网定制一份仅包含项目所需图表类型的阉割版,并且将echarts组件抽取为异步加载的chunk,这样就只需要加载一次。
关于如何将组件抽取为多带带的chunk,可以用import()语法,或者使用react-loadable这个库,它可以直接将react组件包装成异步组件,并在需要时才进行加载。

4. 抽取异步加载的chunk中的公共代码

上面的步骤抽取echarts就是指的抽取异步chunk中的公共代码,除了echarts之外还有很多大体积的公共代码,例如各种antd的组件以及其依赖的底层组件rc-components,这部分也是我们要提取出来的。我们没必要将每个antd组件包装为异步组件,这里只需要配置一下CommonsChunkPlugin就可以了:

new webpack.optimize.CommonsChunkPlugin({
  async: "async-vendor",
  deepChildren: true,
  minChunks: (module) => {
    return /node_modules/.test(module.context);
  },
}),

在没有将children设为true时,CommonsChunkPlugin会从入口文件(entry)提取公共代码,这时就不会对异步加载的chunk起作用。因此为了提取异步chunk的公共代码,我们设置deepChildrentruechildren指的是入口文件的直接子节点,deepChildren指的是全部子节点)。async表示生成一个懒加载的chunk,只有当需要时才会被加载。
上面只是将第三方库的公共代码提取了出来,如果希望把异步chunk当中自己的业务代码提取出来,则可以修改minChunks规则,或者再增加一个配置:

    new webpack.optimize.CommonsChunkPlugin({
      async: "async-biz",
      deepChildren: true,
      minChunks: 2,
    }),
5. 并非每个路由页面组件都需要异步加载

项目之前的做法是,每个路由对应的页面根组件都需要异步加载,这样做的结果是打包出了很多个chunk,而有一半的chunkgzip之前体积都不足5KB,浪费请求是一方面,更严重的是影响了首屏加载体积。
这是为什么?明明把每个页面都异步加载了,怎么会影响首屏体积呢?其实原因就是第三步中的async-vendor被首屏加载了,该chunk主要包含了antd组件,gzip之后约为120KB
对于用户来说,第一次打开我们的网站一定是到登录界面,此时需要完全加载我们的首屏代码,之后有了缓存,除了业务代码更新需要加载很小的chunk之外,理论上是不需要再下载任何代码的,因此我们需要针对登录界面进行首屏优化。
登录界面包含了登录、修改密码、申请账号等子路由,之前将这些都打包为异步chunk,由于这些界面需要async-vendor当中的某几个antd组件,因此首屏加载一定会包含async-vendor。拆分async-vendor是一种办法,但是还要分析到底用了哪些组件,改动业务代码后又要重新分析,显得很麻烦,最简单的做法就是取消登录相关路由的异步加载,将其打包到main当中,同时只需加载需要的antd组件,因此完全避免了加载async-vendor,首屏体积得到了大大降低。

6. 分离出webpack runtime代码

webpack在客户端运行时会首先加载webpack相关的代码,例如require函数等,这部分代码会随着每次修改业务代码后发生变化,原因是这里面会包含chunk id等容易变化的信息。如果不抽取出来将会被打包在vendor当中,导致vendor每次都要被用户重新加载,vendor也失去了它的意义。分离的配置很简单:

    new webpack.optimize.CommonsChunkPlugin({
      name: "manifest",
      minChunks: Infinity,
    }),

minChunks: Infinity表示创建一个什么都没有的chunk,因为不会有任何模块被无限次引用过,这样webpack runtime代码就会被CommonsChunkPlugin放入这个最后的chunk当中。

7. webpack内部优化

这部分内容很简单,就两个插件的使用,HashedModuleIdsPluginModuleConcatenationPlugin
默认情况下,webpack会为每个模块用数字做为ID,这样会导致同一个模块在添加删除其他模块后,ID会发生变化,不利于缓存。为了解决这个问题,有两种选择:NamedModulesPluginHashedModuleIdsPlugin,前者会用模块的文件路径作为模块名,后者会对路径进行md5处理,降低了文件体积,相比较而言,应该开发时选择前者,生产环境选择后者。
ModuleConcatenationPlugin主要是作用域提升,将所有模块放在同一个作用域当中,一方面能提高运行速度,另一方面也能降低文件体积。前提是你的代码是用es模块写的。

8. babel-polyfill

polyfill也是体积很大的一部分,但是又不得不加载,关于这部分的优化可以参考这篇文章,ES6和Babel你不知道的事儿。还有一种方法是使用polyfill.io,这个解决思路个人觉得很不错,但是还不敢在生产环境用,先观望观望。

总结

以上内容是我这些天找资料研究的结果,总的来说打包体积算是得到了有效控制,关于chunk的打包配置如下:

entry: {
    main: path.join(process.cwd(), "src/index.js"),
    vendor: [
      "babel-polyfill", "immutable", "moment", "react", "react-dom" ...
    ],
},
output: {
    filename: "[name].[chunkhash].js",
    chunkFilename: "[name].[chunkhash].chunk.js",
},
plugins: [
    new webpack.HashedModuleIdsPlugin(),
    
    new webpack.optimize.ModuleConcatenationPlugin(),
    
    new webpack.optimize.CommonsChunkPlugin({
      async: "async-vendor",
      deepChildren: true,
      minChunks: (module) => {
        return /node_modules/.test(module.context);
      },
    }),
    
    new webpack.optimize.CommonsChunkPlugin({
      name: "vendor",
    }),
    
    new webpack.optimize.CommonsChunkPlugin({
      name: "manifest",
      minChunks: Infinity,
    }),
]

webpack 4已经出了,再也没有CommonsChunkPlugin了,取而代之的是SplitChunksPlugin,看来又要研究新的东西了。。。

参考文章:
CommonsChunkPlugin学习小结

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95754.html

相关文章

  • webpack 构建性能优化策略小结

    摘要:但是,随者工程开发的复杂程度和代码规模不断地增加,暴露出来的各种性能问题也愈发明显,极大的影响着开发过程中的体验。对应的资源也可以直接由页面外链载入,有效地减小了资源包的体积。 背景 如今前端工程化的概念早已经深入人心,选择一款合适的编译和资源管理工具已经成为了所有前端工程中的标配,而在诸多的构建工具中,webpack以其丰富的功能和灵活的配置而深受业内吹捧,逐步取代了grunt和gu...

    hiYoHoo 评论0 收藏0
  • 深入理解 Webpack 打包分块(上)

    摘要:而一个哈希字符串就是根据文件内容产生的签名,每当文件内容发生更改时,哈希串也就发生了更改,文件名也就随之更改。很显然这不是我们需要的,如果文件内容发生了更改,的打包文件的哈希应该发生变化,但是不应该。前言 随着前端代码需要处理的业务越来越繁重,我们不得不面临的一个问题是前端的代码体积也变得越来越庞大。这造成无论是在调式还是在上线时都需要花长时间等待编译完成,并且用户也不得不花额外的时间和带宽...

    Rocko 评论0 收藏0
  • 用纯 DOM 方式结合 Puppeteer 自动生成网页骨架屏

    摘要:可以通过的提供的直接控制模拟大部分用户操作来进行或者作为爬虫访问页面来收集数据。   骨架屏是在页面数据尚未加载完成前先给用户展示出页面的大致结构,直到请求数据返回后再显示真正的页面内容;随着单页应用( SPA )的越来越流行,单页应用的用户体验也越来越得到前端开发者的关注;为了优化用户体验,在数据到达用户之前,往往会在页面上加上 loading 的效果,而现在,越来越多的场景倾向于使...

    BlackHole1 评论0 收藏0
  • 浅谈webpack4.0 性能优化

    摘要:中在性能优化所做的努力,也大抵围绕着这两个大方向展开。因此,将依赖模块从业务代码中分离是性能优化重要的一环。大型库是否可以通过定制功能的方式减少体积。这又违背了性能优化的基础。接下来可以抓住一些细节做更细的优化。中,为默认启动这一优化。 前言:在现实项目中,我们可能很少需要从头开始去配置一个webpack 项目,特别是webpack4.0发布以后,零配置启动一个项目成为一种标配。正因为...

    leanxi 评论0 收藏0
  • 浅探前端图片优化

    摘要:性能优化是前端开发必不可少的一环,而图片优化又是性能优化中必不可少的一环,但不知道有多少开发者在网页的开发过程中会注意图片的使用,图片使用不当可能会导致网页加载卡顿网页加载速度慢等问题,这篇文章将会将我以往对图片的处理做个总结。 性能优化是前端开发必不可少的一环,而图片优化又是性能优化中必不可少的一环,但不知道有多少开发者在网页的开发过程中会注意图片的使用,图片使用不当可能会导致网页加...

    CocoaChina 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<