08- Vite中的性能优化

324 阅读5分钟

一、页面的性能指标

1. 首屏渲染时: fcp(first content paint)

1. 懒加载
2.http优化: 协商缓存 强缓存

强缓存:服务端会给响应头中添加expires设置过期时间 在这个时间内是不会重新请求而是从缓存中取

协商缓存:是否使用缓存要和后端商量一下,当服务端打上协商缓存的标记后,下次发请求时会发协商请求给服务端如果不需要就返回304拿缓存

2. JavaScript逻辑

1. 副作用清除

组件是会频繁的挂载和卸载:如果我们在某个组件中有定时器,不清除下次挂载等于开启两个线程

// react hook
const [timer,setTimer] = useState(null);

useEffect(()=>{
    setTimer(setTimeout(()=>{
        return () => cleatTimeout(timer)
    }))
})

// 对作用域的一个控制
const arr = [1 ,2]
// 由近到远
for(let i= 0 , len=arr.length;i<=len;i ++){
    
}
2. 浏览器渲染
故事背景

传统的javascript 动画是通过定时器 setTimeout 或者 setInterval 实现的。但是定时器动画一直存在两个问题

  1. 动画的循时间环间隔不好确定,设置长了动画显得不够平滑流畅,设置短了浏览器的重绘频率会达到瓶颈,推荐的最佳循环间隔是16.7ms(大多数电脑的显示器刷新频率是60Hz,1000ms/60)
  2. 定时器第二个时间参数只是指定了多久后将动画任务添加到浏览器的UI线程队列中,如果UI线程处于忙碌状态,那么动画不会立刻执行。为了解决这些问题,H5 中加入了 requestAnimationFrame以及requestIdleCallback
requestAnimationFrame
  1. 特点
  1. 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
  2. 在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回流,这当然就意味着更少的 CPU、GPU 和内存使用量
  3. requestAnimationFrame 是由浏览器专门为动画提供的 API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了 CPU 开销requestAnimationFrame的回调会在每一帧确定执行,属于高优先级任务
  1. 使用
let myReq;
let i = 0;
function step(timestamp) {
    console.log(i++);
    myReq = window.requestAnimationFrame(step);
}
window.requestAnimationFrame(step);

document.onclick = function(){
    window.cancelAnimationFrame(myReq);    // 专属清除方式
}
requestIdleCallback
  1. 特点

为了在渲染空闲时间执行优先级不高的操作,以避免阻塞渲染。

当前帧的运行时间小于刷新频率(16.6ms),函数fn才会执行。否则,就推迟到下一帧,以此类推。如果timeout时间内都没有时间,fn则会强制执行。

  1. 使用
// 函数fn接受deadline对象作为参数,deadline对象有两个属性:timeRemaining和didTimeout。

// timeRemaining() 返回当前帧还剩余的毫秒数。
// didTimeout 指定的时间是否过期。
function lowWork(deadline) {
  while (
    (deadline.timeRemaining() > 0 || deadline.didTimeout) &&
    taskList.length > 0
  ) {
    doWork();
  }
  if (taskList.length > 0) {
    requestIdleCallback(lowWork);
  }
}
requestIdleCallback(lowWork, 2000);
参考
3. css方面的优化

尽量避免太过于深的css嵌套

关注继承属性: 能继承就不要重复写

二、构建优化:vite(rollup) webpack

  1. 优化体积

    压缩 treeshaking 图片资源压缩 cdn加载 分包 ....

1. 分包策略
浏览器的缓存策略

名字没有变化,那么他就不会重新去拿

hash: 只要有内容变化hash就会变 文件名不一样就会重新请求

故事背景

分包就是吧不容易改变的文件,进行单独的打包处理

步骤大纲(简化版)
  1. 创建新目录,编写一个公用工具模块
  2. 编写两个入口 ts 文件,它们都引用了上面的工具模块
  3. 安装 loadsh 并在上面其中一个模块中调用
  4. 编写 vite 配置,处理多入口,
  5. 打包并观察结果
具体步骤(简化版)
  1. 创建新目录,编写以下文件

    公共模块:src/common/index.ts

    import { cloneDeep } from 'lodash'
    cloneDeep([])
    

    页面入口1:src/index.ts

    import { forEach } from 'lodash'
    import './common/index'
    
    const mainArr = []
    
    forEach(mainArr, el => {
    	window.console.log(el)
    })
    

    页面入口2:src/product.ts

    import { forEach } from 'lodash'
    import './common/index'
    
    const mainArr = []
    
    forEach(mainArr, () => {
    	window.console.log('12')
    })
    
    

    配置 vite

import { defineConfig } from 'vite'
import path from 'path'

export default defineConfig(() => {
	return {
		build: {
			minify: false,
			rollupOptions: {
				input: {
					main: path.resolve(__dirname, './index.html'),
					product: path.resolve(__dirname, './product.html')
				},
				output: {
					manualChunks: (id: string) => {
						if (id.includes('node_modules')) {
							return 'vendor'
						}
					}
				}
			}
		}
	}
})

打包并观察结果 自己试试

2. gzip压缩
具体步骤
  1. 下载依赖

npm i vite-plugin-compression -D

  1. 在文件vite.config.ts中配置
// 静态资源压缩
import viteCompression from 'vite-plugin-compression';
export default defineConfig({
  plugins: [
    viteCompression({
      verbose: true,
      disable: false, // 不禁用压缩
      deleteOriginFile: false, // 压缩后是否删除原文件
      threshold: 10240, // 压缩前最小文件大小
      algorithm: 'gzip', // 压缩算法
      ext: '.gz' // 文件类型
    })
  ],
});
后端配合
  1. 服务端读取gzip文件 返回时响应头加上content-encoding == > gzip
3. 动态导入
1. webpack和vite 构建原理的差别
> webpack: 是将所有的文件打包之后才会构建开发服务器
>
> vite:  是直接将入口文件 index.html 抛出去 如果引用了main.js  通过浏览器 esmodule的规范 去请求开发服务器 所以 vite是按需加载的

按需加载和动态导入有异曲同工之妙

动态导入是es6的新特性在此之前webpack也有自己的import()实现 使用动态导入按需加载

2. 具体步骤
  1. 在路由文件中

    import("@/component/index.vue") //返回的是一个promise
    
3. 实现原理
function import (path){
    return new Promise((resolv)=>{
        // vite 用的是es原生的动态导入 是浏览器自己执行的
      //e函数创建了promise.all 创建了一个script标签 src只想home这个文件
      webpack__require.e().then(() =>{
			const result = await webpack__require(path)
		})
    })
}

当没有进入过某个页面中把这个组件放入script标签里面去 但是布塞茹body里面
当进入这个页面才添加到body
4. cdn加速vite-plugin-cdn-import - npm (npmjs.com)

cdn:content delivery network 内容分发网络

将包用连接的形式加入到项目里

具体步骤

yarn add vite-plugin-cdn-import -D

import importToCDN from 'vite-plugin-cdn-import'
export default defineConfig({
  plugins: [
    importToCDN({
       name: 'lodash',
       var: '_',
       path: `umd/react-dom.production.min.js`,
    })
  ],
});

对应

import _ from "lodash";  name 和 var