前端项目优化-2. 性能优化-不同节点的优化

性能优化的方向

说起性能优化,就得从 《从浏览器输入URL到页面渲染完成经历了哪些过程?》 这个问题。

基本总结下来就是分为两块 两部分: 加载层面渲染层面

加载层面

就是项目文件的网络加载过程

加载层面就是网络在加载文件,核心要点就是快速加载

主要优化方向:

网络快

  1. DNS 策略选择:
  • 使用可靠的DNS服务提供商,避免 用户所在区域因为区域问题 导致 DNS 查询失败
  • 使用 dns-prefetch/ preconnect,将域名解析的 IP 地址缓存到本地。
1
2
3
4
<!-- 三方资源 DNS 预解析: -->
<link rel="dns-prefetch" href="//example.com">
<!-- 预链接,预先解析DNS 同时 还将进行 TCP 握手和建立传输层协议 -->
<link rel="preconnect" href="http://example.com">
  1. 使用CDN 加速,将文件放在离用户更近的地方。优先 使用 CDN 预热
  2. 使用HTTP2.0,并行加载多个资源,有效减少加载时间 「通过多路复用、头部压缩等技术,可以显著提高网络性能」
  3. 避免重定向
  4. 减少请求数量,避免进入等待队列「 此处需要权衡取舍,避免单个文件过大 导致加载缓慢」:图片转化成base64、精灵图
  5. 提高服务器性能
    • 配置更高的服务器、使用SSD硬盘、增加服务器带宽
    • 负载均衡:将用户请求分配到多台服务器,减少单台服务器的负载,Nginx。

文件小 [可能会导致HTTP请求过多, 注意平衡]

树摇-Tree Shaking
Webpack 5
  • 默认支持 Tree Shaking,并在生产模式下自动启用。
  • 使用 ES Module 语法(importexport),而不是 CommonJSrequiremodule.exports,以充分利用 Webpack 的静态分析功能。
  • package.json中设置 sideEffects 属性来标记模块是否存在副作用,这样可以更精确地优化。
Vite

Vite 基于 Rollup,默认支持 Tree Shaking,无需额外配置。

按需加载

使用的时候才加载 如: 路由懒加载、组件懒加载、 暂时不需要的文件动态导入

  1. 动态导入组件、路由懒加载: 使用 ES6 的() => import() 语法动态导入路由组件。
1
2
3
4
5
6
const LazyComponent = () => import( /* webpackChunkName: "lazy-component" */ './components/LazyComponent.vue');
export default {
components: {
LazyComponent
}
};
  1. import().then动态加载 模块: 任何你觉得比较占体积的包或者文件 都可以进行 动态导入。
1
2
3
import('./store/modules/largeModule').then(module => {
// 使用 module 中的内容
});
  1. vue3 中的 动态组件 使用 defineAsyncComponent 进行异步组件加载

支持指定加载中组件和错误处理组件,以及相关的延时和超时设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
import {
defineAsyncComponent
} from 'vue';
import LoadingComponent from './LoadingComponent.vue';
import ErrorComponent from './ErrorComponent.vue';

const AsyncComponent = defineAsyncComponent({
loader: () => import('./MyComponent.vue'),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000,
});
  1. Vue3 中的异步组件:<Suspense>组件 包裹异步组件,提供加载中和错误处理的功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>

<script>
import { defineAsyncComponent } from 'vue';

const AsyncComponent = defineAsyncComponent(() => import('./MyComponent.vue'));

export default {
components: {
AsyncComponent,
},
};
</script>
  1. 借助 Aiosx 进行 对非必要 文件 进行动态加载, 如 多语言模块等
代码分割
Webpack 的 SplitChunks

Webpack 提供了代码分割的功能,可以自动将代码拆分为多个块,按需加载这些块

1
2
3
4
5
6
7
8
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
vite中进行代码分割
  1. 基于 Rollup 的 manualChunks
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
// 手动指定分块
'vue-vendor': ['vue', 'vue-router'],
'element-plus': ['element-plus'],
'lodash-es': ['lodash-es'],
}
}
}
}
});
  1. 基于 viteplugin-chunk-split
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// vite.config.ts
import {
defineConfig
} from 'vite';
import vue from '@vitejs/plugin-vue';
import {
chunkSplitPlugin
} from 'vite-plugin-chunk-split';

export default defineConfig({
plugins: [
vue(),
chunkSplitPlugin({
customSplitting: {
'vue-vendor': ['vue', 'vue-router'],
'element-plus': ['element-plus'],
// 支持正则表达式。src 中 components 下的所有文件会被打包为 `components-util` 的 chunk 中
'components-util': [/src\/components/]
}
})
],
build: {
rollupOptions: {
output: {
chunkFileNames: 'assets/[name].[hash].js',
entryFileNames: 'assets/[name].[hash].js',
assetFileNames: 'assets/[ext]/[name].[hash].[ext]',
}
}
}
});
动态垫片

通过垫片服务根据UserAgent返回当前浏览器代码垫片,好处是无需将繁重的代码垫片打包进去。
每次构建都配置@babel/preset-env和core-js根据某些需求将Polyfill打包进来,这无疑又为代码体积增加了贡献

压缩资源
服务开启 Gzip 压缩

对常用文本资源 开启Gzip:

  • HTML 文件:text/html(nginx 服务器默认就会压缩)、application/xhtml+xml
  • CSS 文件:text/css
  • JS 文件:application/x-javascript、application/javascript、text/javascript
  • JSON 文件(或者API请求结果):application/json、application/geo+json、application/ld+json application/manifest+json、application/x-web-app-manifest+json
  • XML 文件:application/xml、application/atom+xml、application/rdf+xml、application/rss+xml
  • SVG 文件:image/svg+xml;
  1. 前端借助构建工具,预先生成gz文件,缺点是构打包后构建的产物体积会变大,优点是不耗费服务器的性能
  • webpack安装 compression-webpack-plugin
  • Vite 使用vite-plugin-compression 来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const CompressionPlugin = require('compression-webpack-plugin')

configurewebpack: (config) => {
if (process.env.NODE ENV === 'production') {
// 为生产环境修改配置.
config.mode = 'production'
return {
plugins: [new CompressionPlugin({
test: /\.js$|\.html$|\.css///匹配文件名
threshold: 10240//对超过10k的数据进行压缩
deleteoriginalAssets: false //是否网除原文件
})]
}
}
  1. Nginx 服务器开启 Gzip 压缩
1
2
3
4
5
//Nginx 服务器配置文件中
http {
gzip on;
gzip_types text / plain text / css application / javascript;
}

具体可参考启用 Gzip 压缩

最小化代码

去除冗余字符(例如不必要的注释、空格符和换行符等),减少文件大小。

  • JavaScript 代码的最小化和压缩: Rollup.js 和 Webpack 中都得到了应用,以降低代码体积、减少下载时间。
  • CSS,在 Webpack 中,通常会利用 mini-css-extract-plugin 插件进行优化。
压缩字体
  1. TTF 字体更换为 WOFF2,体积减小 近60%
  2. 字体文件 取子集 按需压缩:借助三方工具
MP4 视频 优化

编码视频选择 “为 web 优化” 或 “为串流优化”的选项,将名称为 moov 的特殊 atom (包含 元数据信息)置于文件开头,让视频不必下载完成就可以播放,带来更好的用户体验

【译】优化 MP4 视频以便更快的网络串流

图像处理
  1. 使用图片压缩工具,将图片压缩到合适大小,减少请求次数。
  2. 选择适合的图片格式。图像格式及应用场景:
  • SVG:用于 icon 和 logo,包含几何图形,无论缩放如何都保持清晰。
  • JPEG:适用于摄影图片,通过有损和无损优化减小文件大小。(适用于非透明的 banner图)
  • PNG:适用于高分辨率图片,无损压缩,而 WebP 整体上更小。
  • WebP:适用于高分辨率图片,支持无损和有损压缩,文件大小更小。(注意兼容性问题)
  • Video:对于动画,建议使用 video 而不是 GIF,因为 GIF 有颜色限制且文件大小较大。
  1. 远端图片控制:借助图床压缩服务,进行图片尺寸裁剪、压缩 等操作。

性能优化——图片压缩、加载和格式选择

缓存类

前端性能优化(三)——浏览器九大缓存方法

利用http缓存
  1. 静态资源 优先 强缓存
  2. 同域公共资源提取,最大化利用 缓存共享

前端性能优化-开启 HTTP 缓存

利用indexDB 缓存

利用indexDB 缓存 进行 图片、视频、音频等资源的缓存。如:地图项目常用于缓存瓦片数据。

特点:

  • indexDB大小取决于你的硬盘,存储的数据量非常大。
  • 可以直接存储任何类型的数据,如 js任何类型的数据 、blob流。
  • 可以创建索引,提供高性能搜索功能。
  • 采用事务,保证数据的准确性和一致性。
ServiceWorker

Service Worker 是一种在浏览器后台运行的脚本,它可以拦截网络请求,缓存资源,提供离线访问等功能。 Service Worker 借助了 cacheStorage 进行缓存

通常借助 workbox-webpack-plugin 进行配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//webpack.config.js:
const WorkboxPlugin = require('workbox-webpack-plugin');

module.exports = {
// Other webpack config...

plugins: [
// Other plugins...

new WorkboxPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true,
runtimeCaching: [{
urlPattern: /.\/*/, // 需要缓存的路径
handler: 'StaleWhileRevalidate', // 缓存策略
options: {
cacheName: 'my-webcache',
expiration: {
maxEntries: 2000,
},
},
}],
})
]
};

或者直接使用 sw-precache-webpack-plugin`workbox-build` 进行构建。

调试示例

ServiceWorker 让你的网页拥抱服务端的能力
WorkBox 之底层逻辑Service Worker
使用 Service Worker 让首页秒开
前端更新部署后通知用户刷新

离线包

离线包个家实现方式各有不同,本质是要依赖 客户端完成。
相较于 ServiceWorker 方案,离线包 可以解决用户第一次访问 慢的问题。

货拉拉H5离线包原理与实践
离线包实现详解
Hybrid App 离线包方案实践

渲染层面

就是我们拿到文件后页面开始渲染并且交互的过程
渲染层面的配置就 落实到我们前端的具体技术细节

阻塞策略:

位置放

底部,让JS不阻塞HTML和CSS的解析
脚本与DOM/其它脚本的依赖关系很强:对 <script>设置defer

脚本与DOM/其它脚本的依赖关系不强:对 <script>设置async

CSS策略:

避免-多层的嵌套规则
避免-为ID选择器添加多余选择器
避免-使用通配选择器,只对目标节点声明规则

DOM策略:(减少回流重绘)

缓存DOM计算属性
避免频繁操作 DOM
使用display控制DOM显隐,将DOM离线化
修改DOM时把其包装成微任务
使用 transform 代替 top/left 修改位置

代码实践策略:

懒加载策略
  1. 图片-懒加载:非必要图片 延后加载

  2. 图片-渐进式处理:

  3. 代码业务懒加载(路由、模块、 多语言)

  4. 视频 利用 Video标签的Preload 延后加载

  • none: 表示视频不应预加载。
  • metadata: 表示仅获取视频元数据(例如长度)。
  • auto: 表示整个视频文件可以下载,即使用户预计不会使用它。
  • 空字符串: auto 值的同义词。
1
2
3
4
5
6
7
<video controls preload="none" poster="placeholder.jpg">
<source src="video.mp4" type="video/mp4">
<p>
Your browser doesn't support HTML video. Here is a
<a href="myVideo.mp4" download="myVideo.mp4">link to the video</a> instead.
</p>
</video>
预加载策略
  1. 借助进行 预加载、预渲染、预获取
1
2
3
4
5
6
7
8
<!-- 顾名思义,提前加载资源(未用到),首先要确定这个资源一定会在未来用到,然后提前加载,放入浏览器缓存中 -->
<link rel="prefetch" href="image.png">

<!-- 指定的预获取资源具有最高的优先级,在所有 prefetch 项之前进行 -->
<link rel="subresource" href="styles.css">

<!-- # Prerender 预先加载的资源文件,也就是说可以让浏览器提前加载指定页面的所有资源 -->
<link rel="prerender" href="http://example.com/index.html">
  1. 使用 js 预加载资源,为后面页面、模块使用做准备
对于 UI 改动,推荐使用requestAnimationFrame

浏览器会在下次重绘时调用该方法,相较于 setInterval 或 setTimeout,它能够更智能地在浏览器的帧渲染中进行优化。
使用 setInterval 或 setTimeout 有可能导致回调在帧的某个点运行,可能在帧的末尾,这通常导致错过一帧,从而导致界面卡顿。
而 requestAnimationFrame 可以确保回调在浏览器准备好进行下一次重绘时执行,使得动画效果更加流畅。

避免长任务,代码优化 (执行时间超过 50 毫秒的任务)
  1. 使用 requestIdleCallback 空闲时间处理任务, 是一种优化手段 可在 主线程 空闲时调度执行低优先级或后台任务,以提高页面的响应性
  2. 利用 Promise.then 、queueMicrotask 微任务 进行包装,延后任务处理
  3. scheduler.postTask 允许以更细粒度的方式调度任务,并且是一种帮助浏览器确定任务优先级的方法,确保低优先级任务可以释放main thread的机制。尽管目前大多数浏览器并不全面支持,但可在这里获取详细信息。
  4. 使用 Web Workers 处理耗时任务【web worker 可以让主线程另起新的线程来运行脚本】

相关推荐:
如何减少卡顿的代码级别详细文章
7种在 JavaScript 中分解长任务的技术
一文彻底了解Web Worker,十万条数据都是弟弟

首页处理

  1. loding
  2. 骨架屏
  3. SSR ( 需要注意 html体积,尽量只预先渲染 首屏内容)

(一文彻底说清楚SSR渲染)[https://mp.weixin.qq.com/s/6pnp8F6j8MSxP7iPp_YepA]