思考
为什么要进行性能优化?它究竟有多重要?实际上,一个网站的性能直接影响着用户的留存率和转化率。这就使得性能优化能力成为前端开发所必须具备的重要技能。
可以将性能优化的策略分为两个主要的类别:
加载时优化
:这部分主要关注提升网站的加载速度,让用户能更快地获得内容。运行时优化
:这涉及到网站在用户与之交互期间的性能,包括响应速度和效率。
本文主要介绍加载时性能优化都有哪些方式
那么来思考一个问题:浏览器输入 URL 后回车发生了什么?
答:
- URL 解析
- DNS 域名解析
- 建立 TCP 连接
- 发送 HTTP 请求
- 服务器对请求进行处理并做出响应
- 浏览器解析渲染页面
- 断开 TCP 连接
从这一过程中,其实就可以挖出优化点,缩短请求的时间,从而去加快网站的访问速度,提升性能。
例如:
- DNS 解析优化,可以缩短浏览器访问 DNS 的时间
- 使用 HTTP2
- 减少 HTTP 请求数量
- 减少 HTTP 请求大小
- 服务器端渲染
- 静态资源使用 CDN
- 资源缓存,不重复加载相同的资源
通过以上几个优化点出发,展开实现性能优化的方式。
DNS 预解析
DNS 是域名和 IP 地址相互映射的一个分布式数据库。DNS 查询就是将域名转换成 IP 的过程,这个过程短的话 2ms 几乎无感,长则可能达到几秒钟。
当浏览器访问一个域名的时候,需要解析一次 DNS,获得对应域名的 IP 地址。在解析过程中,按照浏览器缓存
、系统缓存
、路由器缓存
、ISP(运营商)DNS缓存
、根域名服务器
、顶级域名服务器
、主域名服务器
的顺序,逐步读取缓存,直到拿到 IP 地址。
DNS Prefetch,即 DNS 预解析就是根据浏览器定义的规则,提前解析之后可能会用到的域名,使解析结果缓存到系统缓存中,缩短 DNS 解析时间,来提高网站的访问速度。
DNS 预解析主要分为两种:
-
自动解析
浏览器使用超链接的 href 属性来查找要预解析的主机名。当遇到 a 标签,浏览器会自动将 href 中的域名解析为 IP 地址,这个解析过程是与用户浏览网页并行处理的。但是为了确保安全性,在 HTTPS 页面中不会自动解析。
-
手动解析
在页面添加如下标记:
html
<link rel="dns-prefetch" href="//img.alicdn.com">
上面的 link 标签会让浏览器预取 "img.alicdn.com" 的解析。
希望在 HTTPS 页面开启自动解析功能时,添加如下标记:
html
<meta http-equiv="x-dns-prefetch-control" content="on">
希望在 HTTP 页面关闭自动解析功能时,添加如下标记:
js
<meta http-equiv="x-dns-prefetch-control" content="off">
注意:dns-prefetch 需慎用,多页面重复 DNS 预解析会增加重复 DNS 查询次数。
使用 HTTP2.0
HTTP2.0 在相比之前版本,性能上有很大的提升。比如:
- 多路复用
HTTP2.0 复用 TCP 连接。在一个连接里,客户端和服务端可以同时发送和接收多个请求和响应,而且不用按照顺序一一对应,这样就避免了"队头堵塞"。
上图中,可以看到第四步中 CSS、JS资源是同时发送到服务端。
-
二进制分帧
HTTP2.0 引入了二进制分帧的机制。在 HTTP2.0 中,通信数据被分割为更小的二进制帧(Frames),每个帧都有自己的标识符和优先级,并独立发送和接收。这种分帧的方式使得多个请求和响应可以并行在同一个连接上进行传输,而不再像 HTTP1.x 需要按照顺序一一对应。
-
首部压缩
HTTP2.0 在客户端和服务器端使用"首部表"来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送。
例如:下图中的两个请求,请求一发送了所有的头部字段,请求二则只需要发送差异数据,这样可以减少冗余数据,降低开销。
- 服务器推送
服务端可以在发送页面HTML时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应。
减少 HTTP 请求数量
HTTP 请求建立和释放需要时间。
HTTP 请求从建立到关闭一共经过以下步骤:
- 客户端连接到 Web 服务器
- 发送 HTTP 请求
- 服务器接受请求并返回 HTTP 响应
- 释放连接 TCP 链接
以上步骤都需要花费时间的,在网络差的情况下,花费的时间更长。如果页面的资源非常碎片化,每个 HTTP 请求只带回来 几K 甚至不到 1K 的数据(比如各种小图标)那性能是非常浪费的。
入口配置合并
通过在 Webpack 的配置文件中配置多个入口文件,Webpack 会将这些入口文件合并为一个输出文件。这种方式适用于需要合并多个文件为一个输出文件的场景。
js
const path = require('path')
module.exports = {
entry: ['./a.js', './b.js'],
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
},
}
适用于小型项目,由于内容不多,合并成单一文件对性能影响不大,并且可以降低 HTTP 请求的数量。而大型项目由于体积大,全部合并到一个文件中容易导致文件过大,影响页面加载速度。对于大型项目,通常建议采用代码分割和按需加载。
代码分割
代码分割允许将一个大的 JavaScript 文件拆分成多个小块,然后按需加载这些块,从而减少初始加载时需要下载的资源量。
至于如何拆分,方式因人而异,因项目而异。如下:
- 将业务代码和第三方库分别打包,使用代码分割来按需加载。例如:在路由切换时,只加载当前页面所需的代码块。
- 业务代码中的公共业务模块提取打包到一个模块,避免重复加载相同的业务代码模块。
- 第三方库过多,可以将它们按照一定大小进行切割成多个块,以减少单个块的大小。
js
optimization: {
splitChunks: {
minSize: 30, // 提取出 chunk 的最小大小
cacheGroups: {
default: {
name: 'common',
chunks: 'initial',
minChunks: 2, // 模块被引用 2 次以上的才抽离
priority: -20
},
vendors: { // 拆分第三方库
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'initial',
priority: -10
},
locallib: { // 拆分指定文件
test: /(src\/locallib\.js)$/,
name: 'locallib',
chunks: 'initial',
priority: -9
}
}
}
}
CSS Sprites 雪碧图
主要用于把一堆小图标整合在一张背景透明的大图上,通过设置对应的位置(background-position)来显示不同的图片,目的是大幅减轻服务器对图片的请求数量。
推荐将雪碧图的生成集成到前端自动化构建工具中,例如:webpack 中使用 webpack-spritesmith,或者 gulp 中使用 gulp.spritesmith。两者都是基于 spritesmith 库。
SVG 图片 / 字体图标
SVG 图片:基于矢量的图像格式,可以无损地缩放到任意大小而不失真。文件通常非常小,特别是对于简单的图形,有助于减少 HTTP 传输的数据量,加快渲染速度。
字体图标:将图标表现为字符,类似于字体,因此可以自由调整大小、颜色和效果。字体图标通常较小,因为它们是由一组矢量字形组成的,只需要加载一个字体文件。
推荐使用 webpack-iconfont-plugin-nodejs 自动将 svg 生成 iconfont 字体图标,并且支持热更新。
CSS in JS
首先需要思考一个问题 " CSS 加载会阻塞 DOM 树的解析和渲染吗?"
由上图(浏览器的渲染流程图)可知,DOM 解析和 CSS 解析是两个并行的进程,所以 CSS 加载不会阻塞 DOM 树的解析。
Render Tree 是依赖于 DOM Tree 和 CSSOM Tree 的,所以无论 DOM Tree 是否已经完成,它都必须等待到 CSSOM Tree 构建完成,即 CSS 加载完成(或 CSS 加载失败)后,才能开始渲染。
因此,CSS加载是会阻塞 DOM 树的渲染。
而 CSS in JS 是一种将 CSS 样式直接嵌入到 JavaScript 代码中的方法,实现更加模块化、组件化的样式管理。它可以减少 HTTP 请求数量,因为它将样式与组件打包在一起,避免了额外的样式文件请求。此外,一些 CSS in JS 库还会进行样式的合并与压缩,进一步减少样式的体积。
一些常见的 CSS in JS 的实现方式,例如:
styled-components、emotion、css-modules、JSS (JavaScript Style Sheets)
图片懒加载
- 原生 JavaScript 懒加载
- 监听 onscroll 事件,通过 getBoundingClientRect API 获取元素图片距离视口顶部的距离,配合当前可视区域的位置实现图片的懒加载。
- 通过 HTML5 的 IntersectionObserver API 监听元素的 isIntersecting 属性,判断元素是否在可视区内,能够实现比监听 onscroll 性能更佳的图片懒加载方案。
- 使用 loading 属性
HTML5 中的<img>
标签支持 loading 属性,可以设置为 "lazy",以告诉浏览器在页面加载时不要立即加载图片,而是等到滚动到可视区域时再加载。
html
<img src="image.jpg" alt="Image" loading="lazy">
除了 loading="lazy",HTML5 还新增了 decoding 属性,用于告诉浏览器使用何种方式解析图像数据,增强图片的用户体验。 它的可选取值如下:
sync
: 同步解码图像,保证与其他内容一起显示。async
: 异步解码图像,加快显示其他内容。auto
: 默认模式,表示不偏好解码模式。由浏览器决定哪种方式更适合用户。
如果不希望图片的渲染解码影响页面的其他内容的展示,可以使用 decoding="async" 选项。
html
<img src="xxx.png" decoding="async">
这样,浏览器便会异步解码图像,加快显示其他内容。这是图片优化方案中可选的一环。
- React / Vue 懒加载
在 React 和 Vue 等现代前端框架中,可以使用特定的组件或指令来实现图片懒加载。
例如:React 中的 react-lazyload 组件或 Vue 中的 vue-lazyload 指令。
数据缓存
-
Web Storage
LocalStorage:浏览器提供的本地持久化存储,用于在浏览器中长期保存数据。数据会一直保存在用户的浏览器中,除非被显式清除。
SessionStorage:浏览器提供的会话级别的临时存储,用于在单个会话期间保存数据。数据在会话结束后被清除。
-
浏览器缓存
强缓存:浏览器在一定时间内直接使用缓存的资源,不发送请求到服务器。可通过设置响应头中的 Cache-Control 和 Expires 字段来控制缓存策略。
Expires 是一个响应头字段,表示资源的过期时间,是一个具体的日期时间。浏览器会将这个时间与客户端当前时间进行比较,判断资源是否过期。
Cache-Control 是一个用于控制缓存行为的响应头字段。常见的取值有:
- public:表示响应可以被客户端和代理服务器缓存。
- private:表示响应只能被客户端缓存,中间的代理服务器不能缓存。
- no-cache:表示客户端缓存该资源,但在使用之前必须先与服务器确认是否是最新的。
max-age=<seconds>
:指定资源在缓存中的最大有效时间,单位为秒。
例如:前端页面响应头多了 cache-control 这个字段,且 60s 内都走本地缓存,不会去请求服务端。
协商缓存:浏览器发送请求到服务器,服务器根据请求头中的条件判断是否返回新的资源。可通过设置响应头中的 Last-Modified 和 ETag 字段来控制缓存验证。
工作流程如下:
-
浏览器发送请求到服务器,请求特定资源。
-
服务器接收到请求后,检查资源的缓存策略,并在响应头中设置缓存验证相关的字段,如 ETag 和 Last-Modified。
ETag(实体标签):服务器为资源生成的唯一标识符,通常是基于资源内容计算的哈希值。
Last-Modified(最后修改时间):资源的最后修改时间。
-
浏览器收到响应后,检查响应头中的缓存验证字段。如果响应头中包含了 ETag / Last-Modified,浏览器会将其保存下来。
-
当浏览器再次请求该资源时,会在请求头中包含缓存验证字段,用于告知服务器上一次请求时使用的标识和最后修改时间。
If-None-Match:浏览器将上次请求中服务器返回的 ETag 值放在该字段中。
If-Modified-Since:浏览器将上次请求中服务器返回的 Last-Modified 值放在该字段中。
-
服务器接收到带有缓存验证字段的请求后,进行缓存验证:
如果资源未发生变化(ETag 匹配或 Last-Modified 时间之后未修改),服务器返回状态码 304 Not Modified,响应头中不包含新的资源内容。
如果验证结果为资源已发生变化,服务器返回新的资源内容,并在响应头中包含新的 ETag 和 Last-Modified。
-
浏览器根据服务器的响应进行处理:
如果服务器返回 304 Not Modified,表示资源未发生变化,浏览器会使用缓存的资源。 如果服务器返回新的资源内容,浏览器会使用新的资源,并更新缓存。
通过协商缓存,可以在资源发生变化时及时更新缓存,减少不必要的网络传输和服务器负载。它与强缓存一起使用,可以提供更灵活和高效的缓存策略,以提高应用的性能和用户体验。
减少 HTTP 请求大小
代码压缩
- TerserPlugin:在生产模式中,Webpack 5 默认使用 TerserPlugin 对 JavaScript 进行压缩和优化。TerserPlugin 也支持压缩 ES6+ 的语法。
- HtmlWebpackPlugin :用于创建 HTML 文件以供 bundle 使用,并在设置
minify
选项时压缩 HTML。 - MiniCssExtractPlugin:用于将 CSS 从 JavaScript 文件中提取出来,生成单独的 CSS 文件。对于在生产环境中优化加载性能非常重要,因为浏览器可以并行加载 CSS 和 JavaScript。
- ImageMinimizerPlugin:用于优化和压缩图片资源。
大部分情况下,开发者只需要在 webpack 配置中设置
mode: 'production'
,webpack 就会自动选定大多数合适的优化选项,包括文件压缩。
Gzip 压缩
在 Webpack 中,可以通过在 webpack.config.js 中配置 CompressionPlugin 插件来开启 Gzip 压缩:
js
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
plugins: [
new CompressionPlugin({
algorithm: 'gzip'
})
]
};
在 Vite 中,可以通过在 vite.config.js 中设置 compress 参数来开启 Gzip 压缩:
js
export default {
server: {
compress: true
}
}
前端打包时配置 Gzip 压缩,在 Nginx 服务中也需开启 Gzip 压缩。原因在于:虽然 Nginx 开启 Gzip 能够在服务器层面压缩响应,而本地压缩可以根据项目的需求实现更加个性化的、文件级别的压缩,以达到更好的优化效果。
以下为 Nginx 配置:
conf
server {
listen 80;
server_name localhost;
location / {
try_files $uri $uri/ /index.html;
root C:/nginx-1.18.0/html/gzip/dist;
index index.html index.htm;
}
location /api/ {
proxy_pass http://localhost:6666/;
}
# 以下主要为 Gzip 配置
gzip on; # 开启 Gzip 压缩
gzip_min_length 4k; # 小于4k的文件不会被压缩,反之压缩。
gzip_buffers 16 8k; # 处理请求压缩的缓冲区数量和大小,例:8k为单位申请16倍内存空间;使用默认即可,不用修改。
gzip_http_version 1.1; # 早期版本 HTTP 不支持,指定默认兼容,无需修改。
gzip_comp_level 2; # gzip 压缩级别(1-9),理论上数字越大压缩的越好,也越占用 CPU 时间。实际上超过2再压缩,只能压缩一点点了。所以2就够用了。
# 压缩文件类型
gzip_types text/plain application/x-javascript application/javascript text/javascript text/css application/xml application/x-httpd-php image/jpeg image/gif image/png application/vnd.ms-fontobject font/x-woff font/ttf;
gzip_vary on; # 是否在 HTTP Header 中添加 Vary: Accept-Encoding,一般情况下建议开启。
}
WebP
WebP 格式,谷歌开发的一种旨在加快图片加载速度的图片格式。
它具有更优的图像数据压缩算法,在拥有肉眼无法识别差异的图像质量前提下,带来更小的图片体积,同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都非常优秀、稳定和统一。
适用场景:
- 网站图片比重大
如果你的网站 80% 甚至更多依赖图片资源,那么请使用 WebP,资源成本可以较少 50% 以上。 - 细节要求不高
图片起到占位目的,不需要毛孔级别,那么请大胆使用! - 视频首图
视频内容形式的网站,资源存储必是一大难题。如何做到稳准狠,速度必不可少,大量的视频占位图也便成为了重中之重。
对比 demo:isparta.github.io/compare-web...
动态引入
Webpack 和 Vite 都支持使用动态引入来提高前端性能。动态引入的主要作用是延迟加载,只在需要的时候才加载相关的模块,减少了首次加载时的负担。
在 React 中,可以使用 React.lazy 和 Suspense 实现组件的懒加载。
jsx
const MyComponent = React.lazy(() => import('./path/to/module'));
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
结合 Webpack Output 配置,通过设置 chunkFilename 属性,让动态引入的模块以指定的文件名格式进行打包,这样可以更好地利用缓存,因为同样的模块不会因为代码变化而被重新请求。
js
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
path: path.resolve(__dirname, '../dist'),
}
hash:跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改,并且全部文件都共用相同的hash值。
chunkhash:跟入口文件的构建有关,根据入口文件构建对应的chunk,生成每个chunk对应的hash;入口文件更改,对应chunk的hash值会更改。
contenthash:跟文件内容本身相关,根据文件内容创建出唯一hash,也就是说文件内容更改,hash就更改。
在 Vue 3 中的 defineAsyncComponent 函数,通过传入异步加载模块的路径来创建异步组件。这样组件的加载过程将会被延迟到当组件被使用时再进行。
ts
import { defineAsyncComponent } from 'vue';
const modules = import.meta.glob('./*/*.vue', { eager: true })
const components: Record<string, any> = {}
for (const path in modules) {
const key = path.replace(/(.*\/)*([^.]+).vue/gi, '$2')
components[key] = defineAsyncComponent(() => import(path))
}
为了使动态引入能够真正发挥作用,还需要配置 Vite 的按需动态导入功能。在 vite.config.js 文件中,可以通过设置 rollupOptions 参数来开启此功能。
js
export default {
rollupOptions: {
output: {
manualChunks: {
lodash: ['lodash']
},
},
},
};
上述例子中,即使你只是使用 import get from 'lodash/get' 形式引入,Rollup 也会将 lodash 的所有模块放到一个自定义 chunk 中。
服务端渲染(Server Side Rendering)
客服端渲染:获取 HTML 文件,根据需要下载 JavaScript / CSS 文件,运行文件,生成 DOM,再渲染。
服务端渲染:服务端返回 HTML 文件,客户端只需解析 HTML。
优点:更好的初始加载性能、搜索引擎优化以及用户体验
缺点:服务器压力较大、开发复杂性高、缓存利用率降低
例如:Next.js(基于 React 的 JavaScript 框架)、Nuxt.js(基于 Vue 的 JavaScript 框架)
合理使用 defer / asnyc
defer 和 async 都是 HTML 的 script 标签的属性,可以影响浏览器加载和执行外部 JavaScript 文件的方式。合理使用可以优化网站的加载和运行性能。
defer: JavaScript 文件的加载和执行可以推迟到整个 HTML 文档解析完成后。这意味着无论 script 标签在文档的哪一处,它都不会阻塞HTML的解析。
使用场景:
- 需要等待整个 DOM 加载完毕才能执行脚本。
- 假设你有两个脚本,脚本A依赖于脚本B的函数或变量,那么需要先执行脚本B,再执行脚本A。因为 defer 要求脚本按照它们在HTML文件中出现的顺序执行。
async: JavaScript 文件的下载会异步进行,一旦下载完成,浏览器就会暂停 HTML 的解析,去执行这个JavaScript 文件,然后再继续 HTML 的解析。
使用场景:
- 许多网站使用 Google Analytics 这样的第三方统计脚本来收集用户访问信息。这些脚本通常不依赖于其他脚本或 DOM 元素,可以立即执行且不会阻塞页面的其他部分加载。
- 如果你的网页上有个天气插件,它请求一个 API,接收数据并显示出来,这个天气插件与页面的其他部分之间没有交互。
defer vs async
从图中可以得出以下几点:
- defer 和 async 在下载时是一样的,都是异步的(相较 HTML 解析)。
- defer 会在 HTML 解析完成后执行的,async 则是下载完成后执行。
- defer 是按照加载顺序执行的,async 是哪个文件先加载完,哪个先执行。
静态资源使用 CDN
CDN(内容分发网络)是一种网络技术,主要通过在多个地点分发内容的副本,使用户可以就近获取所需的内容,从而解决网络拥塞,提高用户访问响应速度和命中率的问题。在性能优化中,常常将静态资源(如图片、CSS、JavaScript 文档等)放置在 CDN 上。
例如使用 html-webpack-plugin 插件生成 HTML,并且希望通过 CDN 加载一些来自 npm 的库(如React, Vue等),可通过以下方式处理静态资源
js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true,
}),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
],
externals: {
'jquery': 'jQuery',
// 对于 react
'react': 'React',
'react-dom': 'ReactDOM'
}
...
}
上述代码中利用 externals 不要将模块打包到输出的 js 文件中,通过 CDN 加载。
在 HTML 中引入 CDN 链接:
html
<script src="https://CDN链接.com/react.min.js"></script>
<script src="https://CDN链接.com/react-dom.min.js"></script>
<script src="https://CDN链接.com/jquery.min.js"></script>
骨架屏
骨架屏(Skeleton Screen)是一种优化页面加载的用户体验策略。它预先显示出页面的大体结构,虽然数据还未加载好,但是可以让用户知道数据正在加载,避免用户在面对白屏的时候产生等待的焦虑。
图片 srcset 属性
srcset 属性可以为同一图像提供多个资源文件,这些文件可以根据用户的设备特性(屏幕分辨率,视口大小等)来进行选择性加载。这意味着在小屏幕设备上无需加载大尺寸的图片,减少了不必要的带宽消耗,也加快了图片的加载速度。
html
<img srcset="foo-160.jpg 160w,
foo-320.jpg 320w,
foo-640.jpg 640w,
foo-1280.jpg 1280w"
sizes="(max-width: 440px) 100vw,
(max-width: 900px) 33vw,
254px"
src="foo-1280.jpg">
上述代码中,sizes 属性给出了三种屏幕条件,以及对应的图像显示宽度。宽度不超过 440 像素的设备,图像显示宽度为 100%;宽度 441 像素到 900 像素的设备,图像显示宽度为 33%;宽度 900 像素以上的设备,图像显示宽度为 254px。
第三步,浏览器根据当前设备的宽度,从 sizes 属性获得图像的显示宽度,然后从 srcset 属性找出最接近该宽度的图像,进行加载。
假定当前设备的屏幕宽度是 480px,浏览器从 sizes 属性查询得到,图片的显示宽度是 33vw(即33%),等于 160px。srcset 属性里面,正好有宽度等于 160px 的图片,于是加载 foo-160.jpg。
图像 URL 后面的像素密度描述符,格式是像素密度倍数 + 字母x 。1x 表示单倍像素密度,可以省略。浏览器根据当前设备的像素密度,选择需要加载的图像。
如果 srcset 属性都不满足条件,那么就加载 src 属性指定的默认图像。
sizes 属性必须与 srcset 属性搭配使用。单独使用 sizes 属性是无效的。
Web Worker
在页面加载时,利用Web Worker进行一些能够后台处理的任务,能够较大程度上提升用户体验,提高页面的响应速度和性能。
使用场景:大数据处理(图像、视频、音频)、运行复杂的算法和大量的运算、定时任务和倒计时
js
// 主线程中创建Web Worker
var worker = new Worker('worker.js');
// 向Worker发送消息
worker.postMessage('Hello Worker');
// 从Worker接收消息
worker.onmessage = function (event) {
console.log('Received message ' + event.data);
}
// 在worker.js中
onmessage = function (event) {
console.log('Received message ' + event.data);
// 向主线程发送消息
postMessage('Hello Main');
}
Webpack5 缓存(cache)
Webpack5 引入了持久化缓存功能,将构建结果保存到文件系统中。在下次编译时,对比每一个文件的内容哈希或时间戳。如果文件未发生变化,会跳过编译操作,直接使用缓存副本,这样可以减少重复的计算。
- 开启内存缓存
js
module.exports = {
//...
cache: {
type: 'memory'
}
};
内存缓存通常用于开发模式,将构建结果保存在内存中,可以极大地提升重复的构建速度,但是一旦进程结束,缓存就会被清空。
- 开启文件缓存
js
module.exports = {
//...
cache: {
type: 'filesystem', // filesystem 指定文件系统缓存
buildDependencies: {
// 将你的配置添加至缓存依赖,更改配置时,缓存将被移除。
config: [__filename]
}
}
};
文件系统缓存则是将构建产物保存在硬盘中,适用于生产模式。即使进程结束或重启,缓存也会被保留,可以用于提升首次构建速度
Webpack的缓存功能并不直接影响页面的加载速度,而是优化了构建过程,从而间接提高了页面加载时的性能。
具体来讲,当你对代码进行更改并重新构建项目时,Webpack会尝试使用缓存来提高构建速度。如果代码并未发生变化,Webpack会直接使用之前构建过程中缓存的结果,以节省重新计算的时间。这样,构建后的资源文件(例如 JS、CSS 文件)将更早的被生成并可以早一步被服务器采用,从而使用户在访问你的网页时,能够更快的加载页面。
然而,需要注意的是,这主要影响的是开发过程中的体验,例如在保存代码更改后,浏览器热更新的速度等。对于用户来说,他们访问的通常都是构建后的静态文件,因此,他们不太可能直接受益于Webpack的缓存功能。而在开发者角度来看,更快的编译和构建速度对于开发和部署来说,无疑是很大的提升。
参考文章:
万字长文:分享前端性能优化知识体系
Webpack 优化之玩转代码分割和公共代码提取
第199题:CSS 会阻塞 DOM 解析吗?
现代图片性能优化及体验优化指南 - 懒加载及异步图像解码方案
不废话,代码实践带你掌握强缓存、协商缓存!
WebP 方案分析与实践
响应式图像教程
现代图片性能优化及体验优化指南 - 响应式图片方案