Skip to content
霞露小伙 — HfWang
On this page

项目性能优化

总结篇

重点

  • 开发阶段开启缓存,比如针对一些性能开销比较大的 loader 使用 cache-loader 进行缓存
  • 使用路由懒加载 + 异步组件(vue defineAsyncComponent 和 react lazy)
  • 第三方组件库按需导入,第三方包如果有 esm 格式的更换 esm 格式的包 (lodash -> lodash-es, echarts),moment 更换为 dayjs
  • js 文件使用 terserWebpackPlugin 进行压缩
  • css 文件使用 mini-css-exctrat-plugin 提取成单独的文件,在使用 css-minimizer-webpack-plugin 插件进行压缩
  • 小图片使用 base64 减少 http 请求,大图片使用 webp 格式进行无损压缩
  • 开启多线程打包
  • 第三方包使用 dllPlugin 进行打包,这部分代码基本是不变的,可以使用强缓存,有效期设置为 1 年,避免后续对第三方的打包
  • 针对首屏优化还可以使用骨架屏

webpack 相关的优化

loader 指定范围,减少检索范围

javascript
const path = require('path')
module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.js[x]?$/,
        use: ['babel-loader'],
        include: [path.resolve(__dirname, 'src')],
      },
    ],
  },
}

使用 cache-loader 对于一些性能开销大的 loader 进行缓存

javascript
// pnpm add cache-loader -D
module.exports = {
  //...

  module: {
    //我的项目中,babel-loader耗时比较长,所以我给它配置了`cache-loader`
    rules: [
      {
        test: /\.jsx?$/,
        use: ['cache-loader', 'babel-loader'],
      },
    ],
  },
}

开启电脑多核处理 happypack

javascript
// pnpm add happypack -D
const Happypack = require('happypack')
module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.js[x]?$/,
        use: 'Happypack/loader?id=js',
        include: [path.resolve(__dirname, 'src')],
      },
      {
        test: /\.css$/,
        use: 'Happypack/loader?id=css',
        include: [path.resolve(__dirname, 'src'), path.resolve(__dirname, 'node_modules', 'bootstrap', 'dist')],
      },
    ],
  },
  plugins: [
    new Happypack({
      id: 'js', //和rule中的id=js对应
      //将之前 rule 中的 loader 在此配置
      use: ['babel-loader'], //必须是数组
    }),
    new Happypack({
      id: 'css', //和rule中的id=css对应
      use: ['style-loader', 'css-loader', 'postcss-loader'],
    }),
  ],
}

js 多进程压缩

webpack 5 默认使用 TerserWebpackPlugin, 即使默认就开启了多进程和缓存

中间缓存 HardSourceWebpackPlugin

首次构建时间没有太大变化,但是第二次开始,构建时间大约可以节约 80%。

javascript
// pnpm add hard-source-webpack-plugin -D
//webpack.config.js
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
module.exports = {
  //...
  plugins: [new HardSourceWebpackPlugin()],
}

url-loader 图片压缩 base64

javascript
{
    test: /.(jpg|jpeg|webp|png|svg|gif|woff|woff2|eot|ttf|otf)$/,
    exclude: /(node_modules|bower_components)/,
    use: [
        {
            loader: 'url-loader',
            options: {
                limit: 8192,
                name: '[path][name].[ext]',
            },
        },
    ],
}

开启 gzip 压缩

javascript
// pnpm add compression-webpack-plugin -D
// webpack.prod.config.js
const CompressionPlugin = require('compression-webpack-plugin')
const productionGzipExtensions = ['js', 'css']

module.exports = {
  // ...
  plugins: [
    new CompressionPlugin({
      filename: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
      threshold: 10240,
      minRatio: 0.8,
    }),
  ],
}

第三方包单独拆分

dllPlugin 只适用于 webpack3, webpack4 之后对打包的优化效果已经不在需要这个插件了,vue-cli 和 cra 在升级 webpack4 之后都移除了这个插件

DllPLugin 只有第一次打包会打包第三方包,后续打包只会打包业务代码

javascript
// DLLPlugin
// webpack.dll.config.js
const path = require('path')
const { DllPlugin } = require('webpack')

const pathResolve = (url) => path.resolve(__dirname, url)

module.exports = {
  mode: 'development',
  entry: {
    utils: ['isarray', 'is-promise'],
  },
  output: {
    path: pathResolve('../dist'),
    filename: 'utils.dll.js',
    library: '_dll_utils',
  },
  plugins: [
    new DllPlugin({
      name: '_dll_utils',
      path: path.join(__dirname, '../dist', 'utils.manifest.json'),
    }),
  ],
}

//webbpack.config.js
const path = require('path')
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const pathResolve = (url) => path.resolve(__dirname, url)

module.exports = {
  mode: 'development',
  devtool: false,
  entry: pathResolve('../src/index.js'),
  output: {
    path: pathResolve('../dist'),
    filename: 'bundle.js',
  },
  plugins: [
    new DllReferencePlugin({
      manifest: require('../dist/utils.manifest.json'),
    }),
    new HtmlWebpackPlugin({
      template: pathResolve('../public/index.html'),
    }),
  ],
}

vue 相关的优化

路由懒加载

静态资源存放 cdn

图片使用 webp

组件库按需引入

异步组件


首屏优化

开启 http 缓存

使用骨架屏

script 标签使用 defer 、async


获取性能指标

performance api

performance

  • performance
    • eventCounts
      • size
    • memory: 显示此刻内存占用情况
      • jsHeapSizeLimit number 可使用的内存
      • totalJSHeapSize number 内存大小限制
      • usedJSHeapSize number JS对象占用的内存数
      • 注意: 如果 usedJSHeapSize > totalJSHeapSize, 那么极有可能是发生了内存泄漏
    • navigate: 显示页面的来源信息
      • redirectCount number 表示如果有重定向的话,页面通过几次重定向跳转而来,默认为0
      • type number 表示页面打开的方式
        • 0 正常进入
        • 1 通过window.reload()刷新的页面
        • 2 通过浏览器的前进后退按钮进入的页面
        • 255 非以上方式进入的页面。
    • timeOrigin: 时间戳 一系列时间点的基准点,精确到万分之一毫秒
    • timing: 一系列关键时间点,包含网络、解析等一系列的时间数据。
      • 。。。。太多了

一下信息都是 timing 里面的字段计算出来的

重要

  • 重定向耗时:redirectEnd - redirectStart
  • DNS查询耗时:domainLookupEnd - domainLookupStart
  • TCP链接耗时:connectEnd - connectStart
  • HTTP请求耗时:responseEnd - responseStart
  • 解析dom树耗时:domComplete - domInteractive
  • 白屏时间:responseStart - navigationStart
  • DOM ready时间:domContentLoadedEventEnd - navigationStart
  • onload时间:loadEventEnd - navigationStart

web-vitals

ts
import {
    onCLS,
    onFCP,
    onFID,
    onINP,
    onLCP,
    onTTFB,
    Metric
} from 'web-vitals'
import { performanceMapping } from './contants'

/**
 * @param {function?} reportInfoToServer 上报性能指标给到后端
 */
const getSystemPerformance = (
    reportInfoToServer: Function
) => {
    // 网站性能核心指标
    onLCP((e) => reportCallBack(e, reportInfoToServer)) // 用于判断页面视图是否大部分加载出来了,最好小于 2.5 秒
    onFID((e) => reportCallBack(e, reportInfoToServer)) // 用于判断页面处于可交互性的时机, 最好小于 100 豪秒
    onCLS((e) => reportCallBack(e, reportInfoToServer)) // 用于判断页面的视觉稳定性, 最好小于 0.1
    // 网站性能其他指标
    onINP((e) => reportCallBack(e, reportInfoToServer))
    onFCP((e) => reportCallBack(e, reportInfoToServer))
    onTTFB((e) => reportCallBack(e, reportInfoToServer))
}


/**
 * @param e 性能指标相关的数据
 * @param reportInfoToServer 上报函数
 */
const reportCallBack = (
    e: Metric, 
    reportInfoToServer?: Function
) => {
    console.log(`-------${performanceMapping[e.name] || e.name}-----`, e)
    reportInfoToServer && reportInfoToServer()
}

export default getSystemPerformance

本站中引用到的其他资料,如有侵权,请联系本人删除