Webpack优化实践,合理分包,降低白屏时间👆

2020-02-14

前言

webpack打包过程中,经常出现app.js一个文件好几兆的情况,这偏偏又是网页最先加载的文件,由于从上到下的执行顺序,前面的脚本在加载时会阻塞页面渲染,白屏时间由此而来。那么我们如何提高网页渲染速度呢?办法之一就是合理的分包策略

项目简述

源码戳GitHub,预览戳这里

一个简单的博客项目,项目本身并不大,但透过现象看本质,还是能说明一些问题的。

引用模块

Vue-Cli4(和3差不多),UI使用iview组件,除此之外还使用到了highlight.js用来高亮代码块,marked库转换markdown语法,jquery等外部库。在没有进行任何优化的情况下,分包情况如下图所示。

分包示意图

大大的vendors占据了90%的屏幕,项目总大小几乎等于vendors的大小,典型的头重脚轻。剩下的10%中几乎都是jquery,而我们自己的代码只占据了不到5%的体积。这还是生产环境构建的代码,大家有兴趣的话可以在服务器上跑一下开发环境,你就知道加载起来有多慢了,30s白屏时间不是吹的。

开始优化

配置externals

webpack的externals选项适用于以下情况,当我们使用了外部库,例如jquery这种会在全局创建一个命名空间用($)来存放相应的方法,vue(Vue),vue-router(VueRouter),element-ui(ELEMENT)等等,就是这个意思。

通常情况下我们只会使用一个库的某几个方法,全部打包到生产环境中显然不合适,这时候可以配置externals,让webpack在打包时忽略掉这些库,并自动在全局中挂载上相应的全局变量。具体的库你再通过CDN的方式引入即可,这样对应起来就可以将依赖抽离出来稍后再加载。例如:

/**
 * vue.config.js
 */
configureWebpack: config => {
    config.externals = {
        marked: 'marked',
        jquery: '&',
        // 这里要注意,键名就是npm包名,值就是库对应的全局变量
        // 如果有特殊符号最好用引号包起来,例如:
        'highlight.js': 'hljs'
    }
  }
}

下面我们看看效果:

externals效果

可以看到效果很明显,jquery不见了,highlight.js也不见了,marked也不见了。vendors从1m => 300kb,然后我们再html底部引入这些库即可:

html中引入对应CDN

由于highlight.js高亮代码的时候有点费时,所以我将这部分内容放到了WebWorker中实现,所以相应的CDN放到了woker中使用importScript引入,这里不再赘述。

效果还是很明显的。但是仍然有进步空间。

注意:放到页面底部可以加快页面渲染,前提是这些库并不是页面初始化就需要的。否则会导致初始化页面是出现变量未定义的错误。

由于全家桶vue,vue-router等页面一开始加载就需要使用了,所以即使换成CDN也是需要放到页面最上方,实际体验差不多,主要是还很麻烦,所以对于这类必须的依赖,我使用了另一种方式。

配置cacheGroups选项

先来看看脚手架内置的默认配置,项目根目录下运行vue ui进入可视化界面选择inspect任务执行:

ui管理界面

找到相关配置如下:

optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          name: 'chunk-vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          chunks: 'initial'
        },
        common: {
          name: 'chunk-common',
          minChunks: 2,
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    }
}

cacheGroups字面意思缓存组,其实就是定义分包股则,满足条件就将这些依赖提取到一个模块中,是splitChunks的关键配置。webpack就是通过这里判断如何拆分模块的。

上面可以看到,vendors利用test选项正则匹配,将node_modules下用到的所有依赖都打包到名为chunk-vendors的模块中。

你可能要问了,jquery也是node_modules里面的呀,为什么不在vendors中呢。因为我是在组件里引用的,所以就和组件打包到一起了。

假设超过两个组件中出现了以下引用语句:

import $ from 'jquery'

那么就会符合第二条规则common(minChunks为2),被提取出来,打包进名为chunk-common的文件中。至于同时满足好几条规则的情况,就使用priority来设置权重,决定听谁的。

还有很多其他的属性,这里就不展开了,感兴趣的可以去百度一下。 知道了这些之后就好办了。

模块太多太少都不好,不能那么极端。集中在一起能减少请求数量,分开能加快下载速度,但同时,前者增加了下载时间,后者增加了连接建立释放所花费的时间。这其中的利弊还需根据具体情况来衡量。

一般情况下,越早使用的模块越先加载。该项目中我将依赖分为:

  1. vue全家桶
  2. UI组件
  3. 其他依赖

配置如下:

/**
 * vue.config.js
 */
configureWebpack: config => {
    Object.assign(config.optimization.splitChunks.cacheGroups, {
      'view-design': { // 分离组件库
        name: true, // name 为true会自动命名
        test: /[\\/]view-design[\\/]/,
        priority: 10,
        chunks: 'initial'
      },
      vue: { // 分离vue全家桶
        name: true,
        test: /[\\/]vue(.+?)[\\/]/,
        priority: 5,
        chunks: 'initial'
      }
    })
  }
}

其他依赖的处理规则就是预定义的vendors和common,我没有改动。

最终结果

数据

其实到这里,就差不多了。分包的意义在于提升传输效率,而不是压缩体积。

其他

按需引入

例如lodash,moment等组件库,都配有相应webpack的插件按需引入,也可以适当使用externals。一些组件库,例如element-ui,iview等,都可以利用babel-plugin-import进行按需引入,官方文档都有对应说明。按需引入后就无需再使用externals了,个人感觉就不必要了。

关于CDN

由于同一域名下的并发请求是收到浏览器的限制的,有的是4个有的是6个,不同浏览器似乎有差异,所以引入很多个CDN的时候,可以考虑使用不同的源站,这里推荐两个:

  1. 前端静态资源库
  2. staticfile CDN

你说BootCDN?怎么说呢,起初对bootCDN印象挺好的,界面我喜欢,后来发现CDN不稳定,经常会无法访问,我之前还以为是项目的问题,排查之后发现是CDN没有引入成功的问题。也不是说不能用,写个小demo还是可以的

关于DllPlugin

这块不感兴趣的可以跳过

最近我使用这个来完成类似于上述cacheGroups的功能,实际上我是先了解的DllPlugin后知道的cacheGroup。由于配置稍微有点繁琐,我钻研懂了之后觉得自己可厉害了,可后来才知道这个插件已经过时了。怎么说呢,有点失落吧,毕竟钻研了半天。

但是仔细想想也是,webpack3的时候通过这个插件可以将常用但是又不会经常变动的库打包后直接引入html中,例如vue,vue-router,vuex等直接一锅端了。但是后来发现他打包的时候是全部引入的,如果遇到某个组件库,就会打包一大坨。

起初这个插件是用来加快构建速度的,因为静态资源不用每次都编译。但是由于需要额外写一份webpack配置,且配置相对繁琐了些。虽然有自动化插件,但也抵不过时代的更迭。如今webpack5都快来了,webpack的构建速度完全ok,再加上分包的话可以使用cacheGroups,就没有使用它的必要了。

什么?既然过时了为什么还写这些?因为我不爽呀🤬,我难受,心理落差,哈哈,我要记录下来。

最后

现在这年头,不会webpack都不好意思说自己是搞前端的。最近写了很多demo,过程中也遇到了很多问题,也正是这些问题,让我对webpack的认识又更进一步。一直想写一篇文章总结一下,可是遇到的问题又很零碎,不太像是能写一篇文章的样子,终于让我逮到这个机会。

看完觉得不错的话,记得点个赞奥,感谢😄

相关资源

  1. 前端静态资源库
  2. staticfile CDN
  3. BootCDN
  4. GitHub源码
  5. 前端优化:使用dll抽取第三方js(DllPlugin DllReferencePlugin)

原文链接:juejin.im

上一篇:使用jsencrypt加密和解密进行不对称加密
下一篇:上次送女神死亡芭比粉口红后,我痛定思痛
相关教程
关注微信

扫码加入 JavaScript 社区

相关文章

首次访问,需要验证
微信扫码,关注即可
(仅需验证一次)

欢迎加入 JavaScript 社区

号内回复关键字:

回到顶部