写在前面
前端性能优化面试中可能会被问到,在实际项目运行过程中也是一个值得每一个前端工程师花时间研究的问题。比如经过自己的一番操作,将原本30s的首屏时间优化到秒加载,这将会是一件很酷的事情。基本的思路是减轻访问时服务器和浏览器加载的压力,减少请求次数,对打包体积进行优化,gzip压缩等。下面我将其分为自己动手和麻烦后端两种思路来进行。
查看当前项目打包完成后,各模块占用的体积大小
使用该插件之后,默认会在根目录生成打包体积报告文件,可以手动打开,也可以配置自动打开。
import vue from '@vitejs/plugin-vue';
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig({
plugins: [
vue(),
visualizer({
gzipSize: true,
brotliSize: true,
emitFile: false,
filename: "size.html", //分析图生成的文件名
open:true //如果存在本地服务端口,将在打包后自动展示
}),
]
})
自己动手
1、使用cdn
优点:
使用cdn的原理是利用网络缓存,如果之前用户访问过的网站使用过同一个cdn的服务,浏览器就能直接调用本地缓存。同时由于浏览器对同一个域名一次性请求数量有限制,使用cdn域名可以极大的避免这一限制。
缺点:
A. 根据当前几天我的研究,选择了使用cdn,比如在vue项目里面,你就只能选择所有的依赖都使用cdn引入,比如element-plus, vue-router等。如果自己的项目依赖很多,很有可能某些依赖找不到cdn资源,这样就会浪费大量的时间。而且免费的cdn有一些不是非常稳定,可能会遇到服务加载失败的情况。
B. 还有文章说可能会被收集站点信息,安全性是一个问题。
开启方式:
vite
import vue from '@vitejs/plugin-vue';
import { Plugin as importToCDN } from 'vite-plugin-cdn-import'
export default defineConfig({
plugins: [
vue(),
importToCDN({
modules: [
{
// 包名,一般是小写
name: 'vue',
// 包在你项目中的全局名称,注意大小写
var: 'Vue',
// 这里是cdn的引用地址,只访问域名可以进入cdn官网,后边是版本号,使用压缩版本
path: 'https://cdn.bootcdn.net/ajax/libs/vue/3.4.23/vue.runtime.global.min.js',
},
{
name: 'echarts',
var: 'echarts',
path: 'https://cdn.bootcdn.net/ajax/libs/echarts/5.4.3/echarts.min.js',
},
{
name: 'vue-router',
var: 'VueRouter',
path: 'https://unpkg.com/vue-router@4.3.1/dist/vue-router.global.prod.js',
},
{
name: 'vuex',
var: 'Vuex',
path: 'https://unpkg.com/vuex@4.0.2/dist/vuex.global.prod.js',
},
{
name: 'element-plus',
var: 'ElementPlus',
path: 'https://unpkg.com/element-plus@2.6.0/dist/index.full.min.js',
css: 'https://unpkg.com/element-plus@2.6.0/dist/index.css',
},
{
name: '@element-plus/icons-vue',
var: 'ElementPlusIconsVue',
path: 'https://unpkg.com/@element-plus/icons-vue@2.3.1',
},
]
})
]
})
2、懒加载(最有效的方案)
使用懒加载可以有效的减小打包生成后的index.js主文件的体积,对首屏时间有最直观的改进。但是要注意,开启懒加载的项目,打包的时候一定要对文件名增加hash值,方便浏览器判断版本号。之前的项目中就由于测试阶段经常更新前端包,测试人员长期打开浏览器没有关闭,更新前端包之后出现页面点击没反应的问题。页面比较少,资源不是特别占用带宽的项目可以不用开启路由懒加载。
- 路由懒加载
// vue里面开启方式很简单
// 定义路由的时候不要使用传动的import语句,而是使用import()函数
{
path: 'index',
name: '首页',
component: () => import(/* webpackChunkName: "dashboard" */ '@/pages/index.vue'),
},
- 模块懒加载(不是专业的前端,设计的项目架构会有很离谱的问题,虽然合理的路由设计就足够使用,但是防不住接手别人的"屎山")
// 基本思路是借用闭包,实现一个只执行一次的函数,在这个函数里面对相关逻辑进行处理
// 比如在路由拦截器里面,对目标模块的地址进行判断,如果地址里面包含某一个关键字,则运行函数
// 在mian.js或者其它文件里面定义installOnce函数
function installNutflow() {
let isInstalled = false
return async function () {
if (!isInstalled) {
const pluginName = await import('node_module地址')
app.use(pluginName);
isInstalled = true
}
}
}
export const installOnce = installNutflow()
// 路由拦截器里面
import { installOnce } from './main.js'
router.beforeEach( async (to, from, next)=>{
if(to.path.includes('targetAddress')) {
// 使用await语句确保资源被加载完毕之后再进页面
await installOnce()
}
})
3、图片懒加载(针对有大量图片的网站,如果进页面一次性加载完毕所有的图片,首屏等待时间会很长,而且用户可能也并不想看完全部内容)
最原生的方式是使用IntersectionObserver观察目标image,等到image标签进入用户视野之后再给img标签赋值src。在vue里面自定义指令v-lazy也是差不多的原理,这里不赘述。
<!-- data-src 是一个自定义属性,可以是data-*,通过元素的dataSet属性获取到 -->
<img class="test-img" data-src="http://test.com">
// 创建一个IntersectionObserver实例
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 图片进入视口时加载图片
const img = entry.target;
img.src = img.dataset.src;
// 停止观察该图片
observer.unobserve(img);
}
});
});
// 获取所有的图片元素
const imgs = document.querySelectorAll('img');
// 观察每一个图片元素
imgs.forEach(img => {
observer.observe(img);
});
4、合理的控制前端文件数量
有一个共识就是,减少浏览器发起请求的次数,是前端优化的一个重要手段。而我们在前端项目工程化实践中,一般都会单独抽离项目中使用到的api到一个单独的文件夹里面,在这个文件夹里面又针对不同的菜单,编写不同的api接口函数。在项目页面达到一定量级之后,关注浏览器的network控制台可以发现,每次刷新页面,浏览器会单独加载所有的api文件,网络请求选项卡就会有很长的一个list,这对性能优化肯定不好。我们需要做的就是,尽量减少拆分出来的api文件的数量,或者打包的时候将这些文件压缩到一起。减小资源加载压力。
// vite项目在defineConfig里面可以这样配置,这样就将所有的api文件打包到了一起
build: {
rollupOptions: {
// https://rollupjs.org/guide/en/#outputmanualchunks
output: {
manualChunks(id) {
if (id.includes('/src/api')) {
return 'api'
}
}
},
},
},
前后端配合
1、gzip压缩
使用gzip压缩,一方面可以有效的减小前端包体积,减小带宽传输压力;另一方面浏览器针对gzip压缩也有专门优化,渲染速度会很快。但是就是需要后端同事配合才行。
后端配置,有很多专业后端写的文章,建议参考他们的配置。
vite打包开启gzip:
import vue from '@vitejs/plugin-vue';
import compression from 'vite-plugin-compression'; // 这个插件可以开启gzip
export default defineConfig({
plugins: [
vue(),
compression({
ext: '.gz',
deleteOriginFile: false,
})
]
})