react前端项目优化指南,webpack分割代码项目优化实战

前言:最近重构一个项目(基于umi2脚手架搭建的),打包上线后发现包非常大,决定将项目优化一下,打包后的dist文件

 

 可以看到打包后的dist文件有16M,然后部署上去发现首次打开蜗牛🐌般的速度,原因有一个公共依赖文件有7.6M之大,我giao,这怎么行呢!?

  如果浏览器选项勾选了不允许缓存,那么将导致每次打开页面或者刷新都将会几乎如同首次加载一样,加载这些文件,每次都这么慢,致命致命致致命。

这在项目部署上线,用户访问时候是非常致命的,下面我们开始针对这个进行优化:

一、压缩静态资源

先从静态资源入手:图片。看下截图文件ued给的大小居然一张图片2M多,这...,索性自己来

 

使用这个网址: https://tinypng.com/    这个网站可以在保持清晰度变化不大的情况下把图片大小压缩,把稍微比较大的图片上传压缩, 可以看到,左侧是原图大小,右侧是压缩后的代码大小

 

把文件替换了,使用压缩后的图片。

二、使用打包分析工具---去除不需要的依赖

webpack的打包分析工具,我项目用的是umi2脚手架,所以直接根据文档配置即可,在.env文件新增:ANALYZE=1,然后umi build打包,会自动打开打包后的分析图。

  Stat size :代表原始文件大小(文件没有经过任何处理的)

parsed size :解析后的文件大小,输出压缩过后的文件大小。(右键查看文件属性大小和这个大小一致)

gzipped:经过gizp压缩过后的代码大小(例如nginx可以开启gzip压缩),这里实际也是最终打开页面下载的文件大小是这个大。

把图上的包模块标注分析一下,红色的是目前以知的插件依赖,其他是引用的插件中有需要用到的插件:

 首先分析下:echart、G6关系图谱、超图(地图)、代码编辑器这几个都是我项目中首页加载不到的(依据你的项目情况而定),我们需要把他从vendors.async.js文件中抽出来。这样就可以在打开首页时候(第一次加载),避免引入不需要的插件代码。

 1、先从moment入手,时间插件上有很多国际语言,我们不需要用到,需要将它过滤掉:

 在.umirc.js文件加入webpack,umi里面使用的是 chainWebpack

 具体写法如下:

  chainWebpack(config) {
    //过滤掉momnet的那些不使用的国际化文件
    config.plugin('replace').use(require('webpack').ContextReplacementPlugin).tap(() => {
      return [/moment[/\\]locale$/, /zh-cn/];
    });
  },

接下来,umi  build打包测试看效果moment时间依赖包体积明显了500k左右:

 

 2、处理下echart,写法换成按需引入,打包时候也会按需打包

再次打包查看,dist文件大小,明显小了非常多(可能图片主要静态文件大小)!

 

 我们发现即使这么做了,虽然把项目总体减小了,但是加载时候还有需要加载一个超级大的主文件,这就是一个问题, 之后我们应该用包分析模块显示的大小来做参考调整分割。进行第三步现在~~~

 

三、使用webpack分割代码---把主文件的依赖分离出来

 以上步骤完成以后开始优化项目加载的具体操作,这里解释下为什么要这么做,因为我们通过分析发现没分割之前,浏览器打开页面时候下载的那个vendor.async.js文件非常大,并且里面的依赖不是我们每次都要用到,所以我们需要把这个文件拆分。

整理思路:拆分成什么样?我们需要把不是每次用到的依赖拆分出来,让他使用到的时候再根据那个页面按需要加载。

需要根据实际情况来定夺,实际目的只有一个,把主文件(里面的公共依赖文件用得比较少的)分割出来,多出引用的放在主文件里面(避免每次都要加载那部分的依赖代码文件),例如我项目中:

关系图谱只有一个页面有用到、echart只有一个页面用到、地图只有两个页面用到(一个前台一个后台),考虑到主文件太大因素,需要将这几个依赖拆分出来,等打开那几个特定页面时候按需要去加载这依赖代码。

开始拆分:使用webpck的 splitChunks

以下是webpack分割代码字段的解释说明:

      optimization: {
        minimize: true,
        splitChunks: {
          chunks: 'async',
          //all: 不管文件是动态还是非动态载入,统一将文件分离。当页面首次载入会引入所有的包
          //async: 将异步加载的文件分离,首次一般不引入,到需要异步引入的组件才会引入。
          //initial:将异步和非异步的文件分离,如果一个文件被异步引入也被非异步引入,那它会被打包两次(注意和all区别),用于分离页面首次需要加载的包。
          minSize: 30000,// 文件最小打包体积,单位byte,默认30000
          minChunks: 1, //最少引入的次数  2:引入两次及以上被打包
          automaticNameDelimiter: '.',// 打包分割符
          cacheGroups: { 
            vendors: { // 项目基本框架等
              chunks: 'all',
              test: /(react|react-dom|react-dom-router|babel-polyfill|react-redux)/,
              priority: 100,
              name: 'vendors',
            },
            echartsVenodr: { // 异步加载echarts包
              test: /echarts/,
              priority: 100, // 高于async-commons优先级
              name: 'echartsVenodr',
              chunks: 'all',
              minChunks: 5, //最少引入的次数  2:引入两次及以上被打包
            },
          },
        },
      },
chunks:有三个值:分别代表你想优化打包优化的类型,默认为async,截图看下这个意思
async:动态引入
initial:直接引入加载

 all:以上两者都进行打包分割优化

最终代码:
  chainWebpack(config) {
    //过滤掉momnet的那些不使用的国际化文件
    config.plugin('replace').use(require('webpack').ContextReplacementPlugin).tap(() => {
      return [/moment[/\\]locale$/, /zh-cn/];
    });
    config.merge({
      optimization: {
        minimize: true,
        splitChunks: {
          chunks: 'all',
          //all: 不管文件是动态还是非动态载入,统一将文件分离。当页面首次载入会引入所有的包
          //async: 将异步加载的文件分离,首次一般不引入,到需要异步引入的组件才会引入。
          //initial:将异步和非异步的文件分离,如果一个文件被异步引入也被非异步引入,那它会被打包两次(注意和all区别),用于分离页面首次需要加载的包。
          minSize: 30000,// 文件最小打包体积,单位byte,默认30000
          minChunks: 1, //最少引入的次数  2:引入两次及以上被打包
          automaticNameDelimiter: '.',// 打包分割符
          cacheGroups: {  //优先级高于外面配置,理解为先分割后合并
            vendors: { // 项目基本框架等
              chunks: 'all',
              test: /(react|react-dom|react-dom-router|babel-polyfill|react-redux)/,
              priority: 100,
              name: 'vendors',
            },
            echartsVenodr: { // 异步加载echarts包
              test: /echarts/,
              priority: 100, // 高于async-commons优先级
              name: 'echartsVenodr',
              chunks: 'async',
              // minChunks: 5, //最少引入的次数  2:引入两次及以上被打包
            },
            antvVenodr: { // 异步加载@antv包
              test: /@antv/,
              priority: 100, // 高于async-commons优先级
              name: 'antvVenodr',
              chunks: 'async',
            },
            'async-commons': { // 异步加载公共包、组件等
              chunks: 'async',
              minChunks: 2,
              name: 'async-commons',
              priority: 90,
            },
            commons: { // 其他同步加载公共包
              chunks: 'all',
              minChunks: 2,
              name: 'commons',
              priority: 80,
            },
          },
        },
      },
    });
  },

实际效果:可以看到echart包、g6、codemirror这几个插件被单独抽离出来成js文件,不会再没用到情况下加载引入。

 

补充:在较早之前的版本,我们知道webpack可以将js文件打包压缩成js.gz文件,使得文件体积大大减小,但是现在大部分前后端分离的场景都是将前端项目部署在nginx,nginx上有个功能能将js压缩成gizp格式传输给浏览器,这也是为什么现在大都打包成js格式就可以,因为nginx上只要开启压缩功能,他会检测是否有.gz文件,没有的话会自动将js文件压缩传输。

 

总结:

1、首先通过webpack手段优化项目,(开启按需加载前提),只能对打包的代码分割,总体积上基本不可有很大的改变(某些情况可能会导致dist总体积变大,因为分离公共依赖包,能把公共的东西从vendors.async.js文件提取出来,同时也会把这个依赖拿出来在放到另一个或者另两个js文件里面做到按需加载,好处是能使得某个页面时候不需要一次引入这么大的一个文件。)

2、通过修改代码优化:例如将插件通过Index.html内的script标签方式引入(外网环境可以直接用CDN方式),这样可以使得公共依赖包减少体积。

3、简单粗暴图片压缩(webpack也有其他的图片转换压缩,例如将小于多少kb的图片转换为base64格式)

4、性能优化:多写PureComponent组件而不是普通的Component组件,因为pure有自动判断shouldComponentUpdate这个组件生命周期的功能(也只能进行浅比较:数组、对象类型,因为存储堆栈,引用地址指向问题还是会频繁更新)。备注:如果有写hook组件,那么使用hook的memo,usememo,usecallback,是最佳方案!

 

posted @ 2021-03-10 22:37  月亮出来了  阅读(1740)  评论(0编辑  收藏  举报