likes
comments
collection
share

性能优化实践 - 优化资源加载速度

作者站长头像
站长
· 阅读数 35

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第 3 篇文章,点击查看活动详情

一、背景与目标

背景:商城移动端,首屏加载过慢,需要做性能优化。

目标:减少白屏时间,提高页面加载速度,提升用户体验。

⭐本文将重点放在对资源加载速度的优化

二、优化思路

性能优化实践 - 优化资源加载速度

考虑到 webpack5 的一些新特性可以带来减少资源体积等作用,所以决定将项目中使用的 webpack4 升级到 webpack5

Webpack 5 发布 (2020-10-10) | webpack 中文文档

这个版本的重点在于以下几点。

  • 尝试用持久性缓存来提高构建性能。
  • 尝试用更好的算法和默认值来改进长期缓存。
  • 尝试用更好的 Tree Shaking 和代码生成来改善包大小。
  • 尝试改善与网络平台的兼容性。
  • 尝试在不引入任何破坏性变化的情况下,清理那些在实现 v4 功能时处于奇怪状态的内部结构。
  • 试图通过现在引入突破性的变化来为未来的功能做准备,使其能够尽可能长时间地保持在 v5 版本上。

⭐由于优化步骤是先升级 webpack,然后再进行相关优化,所以下文的组织方式也与之对应。

三、webpack 升级过程

升级指南

如何升级

方式一:手动修改 package.json 依赖包的版本号


方式二:通过工具升级,然后根据控制台报错解决问题

根据控制台报错去完善升级,会让自己处于很被动的状态,并且也会有一些奇怪的 bug 产生;


方式三:重新写 webpack.config.js

  • 删除 package.jsondevDependencies 的相关 loaderplugin 依赖包
  • webpackwebpack-cli 升级到最新稳定版
  • entryoutput 等路径相关的得参考以前的
  • 在重写配置的过程再去安装相应的 loaderplugin

会对 webpack 的掌握程度有一定要求,还需要检查以前的配置,哪些需要使用,哪些已经别的替代,哪些需要废弃

问题1-Error: Unknown option '--colors'

npm run build

性能优化实践 - 优化资源加载速度

原因

npx webpack --help

【webpack 5.x.x】

性能优化实践 - 优化资源加载速度

【webpack 4.x.x】

性能优化实践 - 优化资源加载速度

解决

package.json 中 --colors 替换为 --color

问题2-process is not defined

bootstrap:27 Uncaught ReferenceError: process is not defined

解决

new webpack.ProvidePlugin({ process: 'process/browser' }),

问题3-Autoprefixer

性能优化实践 - 优化资源加载速度

解决:去掉相关注释

.test {
  /* ! autoprefixer: off */
  ...
  /* ! autoprefixer: off */
}

问题4-CSS 写法

性能优化实践 - 优化资源加载速度

解决:按照提示

text-decoration-skip: ink; -> text-decoration-skip-ink: auto;

问题5-webpack.HotModuleReplacementPlugin

HotModuleReplacementPlugin | webpack 中文文档

webpack.HotModuleReplacementPlugin 是否需要配置,与 devServer.hot 对比

性能优化实践 - 优化资源加载速度 DevServer | webpack 中文文档

看一下 webpack-dev-server 源码

if (this.options.hot) {
  const HMRPluginExists = compiler.options.plugins.find(
    (p) => p.constructor === webpack.HotModuleReplacementPlugin
  );

  if (HMRPluginExists) {
    this.logger.warn(
      `"hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration.`
    );
  } else {
    // Apply the HMR plugin
    const plugin = new webpack.HotModuleReplacementPlugin();

    plugin.apply(compiler);
  }
}

说明如果配置了 devServer.hot: true 就不需要配置 webpack.HotModuleReplacementPlugin

四、优化过程

减小资源体积

css 压缩

  • css-minimizer-webpack-plugin (webpack5 推荐)

js 压缩

  • terser-webpack-plugin

Tree Shaking

官方文档:Tree Shaking | webpack 中文文档

  • Webpack 已经默认开启了这个功能,无需其他配置,但是使用这个会有条件

Tree Shaking 触发条件:

  1. 通过解构的方式获取方法,可以触发 Tree Shaking
  2. 调用的 npm 包必须使用 ESM
  3. 同一文件的 Tree Shaking 有触发条件,条件就是 mode=production
  4. 一定要注意使用解构来加载模块
// import { a } from 'xxx.js'
export function a(){}

// 引用 default 的没办法做 treeshaking
export default {
  a(){},
  b(){},
}

webpack 4.x 与 webpack 5.x Tree Shaking 的差异


图片压缩

开发如果项目中引用了较多图片,那么图片体积会比较大,将来请求速度比较慢,可以对图片进行任缩,减少图片体积。

ImageMinimizerWebpackPlugin

ImageMinimizerWebpackPlugin | webpack 中文文档

分析:

由于项目中图片大多是在线链接,引用过多本地静态图片才需要考虑是否需要压缩

所以不需要配置这个

Code Spliting

  • 将代码分割成多个 js 文件,使单个文件体积更小,并行加载 js 速度更快。
  • 通过 import 动态导入语法,实现按需加载。
介绍

SplitChunksPlugin | webpack 中文文档

开箱即用的 SplitChunksPlugin 对于大部分用户来说非常友好。

默认情况下,它只会影响到按需加载的 chunks,因为修改 initial chunks 会影响到项目的 HTML 文件中的脚本标签。

webpack 将根据以下条件自动拆分 chunks

  • 新的 chunk 可以被共享,或者模块来自于 node_modules 文件夹
  • 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积)
  • 当按需加载 chunks 时,并行请求的最大数量小于或等于 30
  • 当加载初始化页面时,并发请求的最大数量小于或等于 30

当尝试满足最后两个条件时,最好使用较大的 chunks。

module.exports = {
  //...
  optimization: {
    splitChunks: {
      // chunks 用以告诉 splitChunks 的作用对象,其可选值有 async、 initial 和 all。默认值是 async,也就是默认只选取异步加载的chunk进行代码拆分
      chunks: 'async',
      minSize: 20000,
      minRemainingSize: 0,
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      enforceSizeThreshold: 50000,
      cacheGroups: {
        defaultVendors: {
          test: /[\/]node_modules[\/]/,
          priority: -10,
          reuseExistingChunk: true,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};
实际应用

代码分割的原则:

optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          name: 'chunk-vendors',
          test: /[\/]node_modules[\/]/,
          priority: 10,
          chunks: 'initial'
        },
        echarts: {
          name: 'chunk-echarts',
          priority: 20,
          test: /[\/]node_modules[\/]_?echarts|zrender(.*)/
        },
        commons: {
          name: 'chunk-commons',
          minChunks: 3, // minimum common number
          priority: 5,
          reuseExistingChunk: true
        }
      }
    },
  },

配置说明:

配置 runtimeChunk

runtimeChunk 用于保存文件的 hash 值和它们与文件关系,文件体积就比较小,所以变化重新请求的代价也小。

runtimeChunk: {
  name: "runtime~single"
}

externals

背景

分析打包体积,发现 chunk-vendors 体积很大,说明第三方依赖包体积很大,可以将一些依赖包提取出来,通过 CDN 引入,不通过 webpack 打包。

实践

webpack 配置 externals

externals: {
    'react': 'React',
    'react-dom': 'ReactDOM',
    'react-router-dom': 'ReactRouterDOM',
    'react-router-redux':'ReactRouterRedux',
    'redux': 'Redux',
    'react-router': 'ReactRouter',
    'react-redux': 'ReactRedux',
    'redux-logger': 'reduxLogger',
    'redux-thunk': 'ReduxThunk',
    'prop-types': 'PropTypes',
    'classnames': 'classNames',
    'lodash': '_',
    'immutable': 'Immutable',
    '@babel/polyfill': '_babelPolyfill'
  },

静态资源走 oss

考虑单独写一篇文章

五、优化结果

由于优化还在持续进行,等后续任务完成后再补上优化结果、优化前后的对比。

六、踩坑

lodash-es

为什么考虑使用 loadsh-es

  • 由于 lodash 采用的是 commonjs 规范,每次打包会把全部文件打包进去,不能按需引入,所以考虑使用 lodash-es

出现问题

  • npm uninstall lodash && npm i-S lodash-es 之后,修改了项目中的相关引用方式,发现打包出来了的资源始终包含 lodashlodash-es,反而增加了文件的体积大小。

找到原因

  • 后来找到原因,发现 server 端引用了 lodash
  • 如果用 lodash-es,即使使用了 babel-node 也不行,因为 lodash-es 属于第三方包,babel-node 会默认 ignore node_modules,而 node 又只认识 commonjs,所以应用 lodash-es 会报错

最终的处理方式

  • 所以最终决定不使用 lodash-es,还是使用 lodash

七、总结

性能优化可以从优化资源加载速度、优化运行性能等方面着手,采用适当的方式进行优化。本文从优化资源加载速度方面,针对项目存在的性能问题采取了相应的手段进行处理。

优化是无止尽的,关注每个阶段的目标,逐步优化;

需要量化优化的效果。

八、后续安排

建立性能优化知识体系

进一步优化

前端

  1. 优化加载性能

    • 首页所加载的资源还可以拆分、体积还可以减小(合理、找到平衡)
  2. 优化运行性能

    • 渲染层面
  3. 优化开发体验

    • 提升构建速度

后端

  • 接口拆分(各司其职)

九、参考