前言
首屏优化的重要性不用多说,是所有面向大众产品的一个硬核指标。这里带大家梳理下常用的首屏优化策略
无论你是使用webpack打包,还是vite打包,本文都列举了相关示例,希望对大家有帮助😀😀😀
一、体积优化
1. 路由懒加载
原理:使用import()
函数语句,将路由组件打包成独立的模块。只有访问到对应路由时,才去加载对应的组件内容。这种方式可以有效地减少首次加载页面时需要加载的代码量,提高页面的加载速度。
示例:
const LoginPage = () => import('@/views/login/LoginPage.vue');
分析工具:
webpack使用webpack-bundle-analyzer进行分析;
vite使用rollup-plugin-visualizer进行分析;
效果图:
可以看到各个路由组件都被分别打包进不同的js文件。后续也可根据此分析图来进行代码优化。
2. npm包按需导入
在项目中只引入所需的特定模块或功能,而不是引入整个包。这样可以减小项目的体积,提高加载速度
示例:Element Plus自动按需导入
- 安装插件
yarn add unplugin-vue-components unplugin-auto-import -D
- 配置vite.config.ts
// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
// ...
plugins: [
// ...
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
3. 提取公共包
当一个npm包在多个路由页面中被引用,vite和webpack都会把公共包提取到一个公共的chunk(块)中。例如vite会把公共包打包进index-hash.js中,如下图所示:
现象:index-hash.js中包含了vue-i18n,vue-router,@vue等。在我的demo项目中,index-hash.js体积891KB,占比38.66%。在企业项目中,随着更多公共包的引入,体积只会越来越大。而且其中任何一个npm包进行了更新,文件的hash值就会改变,缓存失效,浏览器得重新从服务器下载资源
解决:webpack使用splitChunks,vite基于rollup使用manualChunks提取公共包
webpack:修改webpack.config.js,将echarts提取到名为echarts.vendor的js文件中
module.exports = {
//...
optimization: {
splitChunks: {
chunks: "all",
minSize: 30000,
name: false,
maxAsyncRequests: 5,
maxInitialRequests: 3,
cacheGroups: {
"echarts.vendor": {
name: "echarts.vendor",
priority: 40,
test: /[\/]node_modules[\/](echarts|zrender)[\/]/,
chunks: "all",
},
},
},
},
};
vite:修改vite.config.ts,将vue,pinia,vue-router提取到名为vue的js文件中
export default () => {
return defineConfig({
//...
build: {
rollupOptions: {
output: {
manualChunks: {
vue: ['vue', 'pinia', 'vue-router'],
},
},
},
},
});
};
二、图片优化
1. 压缩图片
-
使用在线压缩图片网站手动压缩图片:tinypng.com
-
在构建流程中加入压缩图片插件,例如 image-webpack-loader
2. 转为webp图片
webp是谷歌推出的新图片格式(2010),与PNG,JPG相比,体积更小
-
使用在线网站webP-converter等手动压缩图片:cloudconvert.com/png-to-webp
-
使用插件自动化生成,例如 image-min-webp
一张420K的png图片,转为webp后,体积仅为24K,大约提升了94%。如果图片本身就比较小,就没有转webp的必要了。
兼容性:除了IE浏览器不支持外,其他浏览器支持度都还可以。这年头不会还有要支持IE的项目吧(手动狗头🤣)
3. 使用字体图标代替图片
使用阿里字体图标库
4. 图片懒加载
-
将图片的src属性设置到占位符上,例如data-src
-
通过 IntersectionObserver 判断图片是否进入可视区域
-
进入可视区后,将占位符替换为真实的图片地址
或者直接使用第三方库,例如vue的vue-lazyload
、react的vanilla-lazyload
三、传输优化
1. 脚本延迟加载
defer
:异步加载,能保证执行顺序。在DOM解析之后,DOMContentLoaded触发之前执行
async
:异步加载,不能保证执行顺序。浏览器会尽快解析和执行
示例:在index.html中引入public文件夹下三个本地资源 echarts.min.js,china.js与go.js
//index.html
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script src="/static/echarts.min.js"></script>
<script src="/static/china.js"></script>
<script src="/static/go.js"></script>
</body>
使用Performance进行分析:
DOMContentLoaded用时439.13ms
现在给script标签增加异步加载标识,因为china.js依赖于echarts.min.js(需先加载完echarts.min.js),因此使用defer,go.js是个独立的第三方流程图模块,使用async
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script src="/static/echarts.min.js" defer></script>
<script src="/static/china.js" defer></script>
<script src="/static/go.js" async></script>
</body>
使用Performance进行分析:
DOMContentLoaded用时220.98ms,少了接近一半的时间,提升很明显
2. 资源预加载
preload
:预加载,预先下载本页面关键资源
prefetch
:预加载,在浏览器空闲时预先下载其他页面可能要用到的资源
优先级:preload > prefetch
示例:在index.html中引入element-plus的css资源
<head>
<link rel="stylesheet" href="//unpkg.com/element-plus@2.5.5/dist/index.css" />
</head>
使用Performance进行分析:
DOMContentLoaded用时354.69ms
现在给link标签增加预加载标识,因为element-plus的css资源在登录页就需要用到,因此使用preload。如果是其他页面的资源,可以使用prefetch。
<head>
<link rel="preload" href="//unpkg.com/element-plus@2.5.5/dist/index.css" as="style"/>
</head>
href:指定资源路径
as:指定资源类型
使用Performance进行分析:
DOMContentLoaded用时166.31ms,少了接近一半的时间,提升很明显
3. DNS预连接
DNS预连接在DNS预解析的基础上,提前与目标服务器建立TCP连接,从而进一步加速页面的加载速度。
场景:提前解析跨源资源地址
<link rel="preconnect" href="https://example.com" />
4. 使用HTTP2
在HTTP1中,浏览器对于同一域名的请求数量是有限制的。例如同时请求6条,更多的http请求只能排队。而HTTP2引入了多路复用的技术,在同一TCP连接上并发多个请求和响应,很好的解决了这个问题
开启也很简单,以Nginx为例,在443端口后面添加http2即可
// nginx.conf
listen 443 http2;
之后重启Nginx,在NetWork面板,可以看到接口的Protocol栏看到h2
5. 开启Gzip压缩
压缩文件体积,提高文件传输效率。gzip压缩通常能减少2/3的体积,例如echarts.min.js原741K,被压缩后只有184k。对于小文件,也能减少1/2的体积。
配置Nginx
http{
gzip on; # 是否开启gzip
gzip_min_length 1k;# 开始压缩的最小长度(再小就不要压缩了,意义不在)
gzip_buffers 4 16k; # 缓冲(压缩在内存中缓冲几块? 每块多大?)
gzip_http_version 1.1;# 开始压缩的http协议版本(可以不设置,目前几乎全是1.1协议)
gzip_comp_level 2;# 推荐6 压缩级别(级别越高,压的越小,越浪费CPU计算资源)
gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml;# 对哪些类型的文件用压缩 如txt,xml,html ,css
gzip_vary on; # 是否传输gzip压缩标志
gzip_proxied expired no-cache no-store private auth; #Nginx做为反向代理的时候启用 例如如果header中包含”Expires”头信息,启用压缩
gzip_disable "MSIE [1-6]\."; #正则匹配UA,配置禁用gzip条件。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
gzip_static on; #开启后会寻找以.gz结尾的文件,直接返回,不会占用cpu进行压缩,如果找不到则不进行压缩
}
尽管Nginx已配置为在响应时压缩并返回Gzip格式的数据,但压缩操作会消耗服务器CPU资源和时间。压缩级别越高,资源消耗越大。因此,我们通常会上传已压缩的gzip文件,以便服务器直接返回,从而节省资源。Nginx配置:gzip_static on
构建时生成gzip文件
webpack
- 安装
yarn add compression-webpack-plugin -D
- 修改webpack.prod.js
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
plugins: [new CompressionPlugin()],
};
vite:
- 安装
yarn add vite-plugin-compression -D
- 修改vite.config.ts
默认情况下插件在开发 (serve) 和生产 (build) 模式中都会调用,使用 apply 属性指明它们仅在 ‘build’ 或 ‘serve’ 模式时调用
这里打包生成 .gz 插件仅需在打包时使用
import viteCompression from 'vite-plugin-compression'
plugins: [
//...
{
...viteCompression(),
apply: 'build',
},
],
6. 使用CDN
CDN 加速原理:通过在网络不同节点部署缓存服务器,就近返回资源给用户,以提高网站的访问速度
示例:引入echarts
在index.html中使用cdn引入
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/4.4.0/echarts.min.js"></script>
使用CDN之后,还需配置externals(webpack)、external(vite),告诉打包工具忽略对某些库的打包,因为这些库已经通过CDN链接在浏览器中直接引用了
webpack
修改webpack.common.js,配置externals
module.exports = {
//...
//externals中的key是用于import,value表示在全局中访问到该对象
externals: {
'echarts': 'echarts'
},
};
vite
修改vite.config.ts,配置external
export default () => {
return defineConfig({
//...
build: {
rollupOptions: {
//...
external: ['echarts'],
},
},
});
};
7. 服务端渲染(SSR)
服务器会预先生成完整的HTML页面,包括接口请求数据,然后将这个已经渲染好的页面直接返回给客户端。这样用户在首次访问页面时,可以直接看到已经渲染好的内容,而不需要等待客户端JavaScript代码加载和执行完毕后再进行渲染
熟悉Vue的,使用Nuxt
熟悉React的,使用Next
四、交互优化
1. 骨架屏
例如antd的官网首页,就使用了骨架屏
2. loading动画
在JS没解析执行前,让用户能看到Loading动画,减轻等待焦虑。
示例:vue使用加载进度条
1.安装
yarn add nprogress
2.使用
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
//...省略其他路由配置相关代码
NProgress.configure({ showSpinner: false }); //关闭加载旋转器
//在路由全局前置钩子中开始
router.beforeEach((to) => {
NProgress.start();
});
//在路由全局后置钩子中结束
router.afterEach(() => {
NProgress.done();
});
例如对我的demo项目进行Performance分析,如下图所示:
FP(First Paint):首屏绘制为794.02ms
DCL(DOMContentLoaded):HTML文档加载完成为1.57s
加载进度条在1s内就出现在了视野区,提升了用户的交互体验
示例:React使用旋转动画
import { Spin } from 'antd';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<Suspense fallback={<Spin delay={1000} size="large" />}>
<RouterProvider router={router} />
</Suspense>,
);
结尾
文本涉及的内容较多,包含vite,webpack,rollup,vue,react等,可参考我以前的文章结合阅读:
近期文章推荐:
原文链接:https://juejin.cn/post/7336822951273725978 作者:敲代码的彭于晏