基于GoView的低代码大屏: 如何做预览页面的性能优化前言 最近, 低代码大屏项目面临一个很严峻的问题, 其预览页面,
前言
最近, 低代码大屏项目面临一个很严峻的问题, 其预览页面, 与基于vue2的原生页面相比, 大屏预览页面首次加载时间约是原生页面的2~3倍.
这意味着, 原生的vue2页面, 首屏加载时间约1秒左右, 而大屏页面首屏加载却需要2~3秒.
首屏加载慢这一点非常致命, 倘若不能解决该问题, 那么低代码大屏产品将不复存在.
通过研究GoView的代码库, 我花了约2天时间, 成功解决了首屏加载慢的问题, 下面我将为大家细述我的解决方案.
新建预览页面路由
GoView低代码大屏的预览页面和设计页面, 共用一个main.ts
和App.vue
文件, 而main.ts
导入了很多预览页面无需用到的代码.
针对这种情况, 我准备给预览页面单独开一个html
页面入口.
src目录下新建preview.ts
和Preview.vue
preview.ts
代码如下所示:
import { createApp } from 'vue';
import { setupNaive } from '@/plugins/naive'
import { initFunction } from '@/plugins/initFunction'
import router, { setupRouter } from '@/router/preview'
import { GoAppProvider } from '@/components/GoAppProvider/index'
import { setupStore } from '@/store'
import Preview from './Preview.vue';
async function appInit() {
const goAppProvider = createApp(GoAppProvider)
const app = createApp(Preview)
setupNaive(app)
setupStore(app)
goAppProvider.mount('#appProvider', true)
// 挂载路由
setupRouter(app)
// 路由准备就绪后挂载APP实例
await router.isReady()
app.mount('#app', true)
window['$vue'] = app
}
appInit().then(() => {
initFunction()
})
Preview.vue
代码如下所示:
<template>
<n-config-provider :theme="darkTheme" :locale="zhCN" :date-locale="dateZhCN">
<go-app-provider>
<router-view></router-view>
</go-app-provider>
</n-config-provider>
</template>
<script lang="ts" setup>
import { dateZhCN, zhCN, NConfigProvider } from 'naive-ui'
import { GoAppProvider } from '@/components/GoAppProvider'
import { useDarkThemeHook } from '@/hooks/useTheme.hook'
// 暗黑主题
const darkTheme = useDarkThemeHook()
</script>
注意: 不能在
Preview.vue
里直接引入预览页面组件, 因为该预览页面组件引入chartEditStore.ts
文件, 而在该文件内, 它执行了一段代码const chartHistoryStore = useChartHistoryStore()
, 此时, pinia尚未挂载到app
上, 导致抛出getActivePinia() was called but there was no active Pinia
, 为了解决该问题, 我创建了动态路由
src的router文件夹内创建preview.ts
preview.ts
的动态路由代码如下所示:
import type { App } from 'vue'
import { createRouter, RouteRecordRaw, createWebHashHistory } from 'vue-router'
import { createRouterGuards } from './router-guards'
import { ReloadRoute } from '@/router/base'
const RootRoute: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Preview',
component: () => import('@/views/newPreview/index.vue'),
}
]
export const constantRouter: any[] = [...RootRoute, ReloadRoute];
const router = createRouter({
history: createWebHashHistory(''),
routes: constantRouter,
strict: true,
})
export function setupRouter(app: App) {
app.use(router);
// 创建路由守卫
createRouterGuards(router)
}
export default router
根目录下创建preview.html
preview.html
代码如下
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta
name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"
/>
<link rel="icon" href="./favicon.ico" />
<title>预览</title>
<style>
* {
margin: 0;
}
</style>
</head>
<body>
<div id="appProvider" style="display: none"></div>
<div id="app">
</div>
<script type="module" src="/src/preview.ts"></script>
</body>
<script>
</script>
</html>
修改vite.config.ts
在vite.config.ts
的rollupOptions
添加如下配置
build: {
// target: 'es2015',
outDir: OUTPUT_DIR,
// minify: 'terser', // 如果需要用terser混淆,可打开这两行
// terserOptions: terserOptions,
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
preview: resolve(__dirname, 'preview.html'),
},
...rollupOptions
},
...
}
访问preview.html
可看到预览页面已显示, 至此完成预览页面的单独配置
只加载使用到的组件
通过查看预览页面的网络请求, 我发现它加载了整个项目内的所有组件库, 追踪溯源, 发现如下罪魁祸首代码
let configModules: Record<string, { default: string }>
let configPropsModules: Record<string, { default: string }>
let indexModules: Record<string, { default: string }>
let imagesModules: Record<string, { default: string }>
configModules = import.meta.glob('./components/**/config.vue', {
// 在模块加载时立即执行
eager: true
})
configPropsModules = import.meta.glob('./components/**/config.ts', {
// 在模块加载时立即执行
eager: true
})
indexModules = import.meta.glob('./components/**/index.vue', {
eager: true
})
imagesModules = import.meta.glob('../assets/images/chart/**', {
eager: true
})
...
/**
* * 获取展示组件
* @param {ConfigType} dropData 配置项
*/
export const fetchChartComponent = (dropData: ConfigType) => {
const { key } = dropData
return fetchComponent(key, FetchComFlagType.VIEW)?.default
}
/**
* * 获取配置组件
* @param {ConfigType} dropData 配置项
*/
export const fetchConfigComponent = (dropData: ConfigType) => {
const { key } = dropData
return fetchComponent(key, FetchComFlagType.CONFIG)?.default
}
import.meta.glob('./components/**/index.vue')
会遍历components
文件夹下的所有index.vue
组件, 有的index.vue
内甚至会引入数据量较大的json文件, 这些文件在生产环境时, 会通通打包到index.[hash].js里, 这是绝对不可取的!
在packages文件夹下新建preview.ts
preview.ts
代码如下
import { ConfigType } from '@/packages/index.d'
export const dynamicFetchChartComponent = async (dropData: ConfigType) => {
const { key, package: packageName, category } = dropData
const module = await import(`./components/${packageName}/${category}/${key}/index.vue`)
return module?.default
}
export const dynamicFetchConfigPropsComponent = async (dropData: ConfigType) => {
const { key, package: packageName, category } = dropData
const module = await import(`./components/${packageName}/${category}/${key}/config.ts`)
return module.option
}
更改预览页面的组件加载机制
更改预览页面的useComInstall.hook.ts
代码, 完整代码如下所示
import { ref } from 'vue'
import { ChartEditStorageType } from '../index.d'
import { CreateComponentType, CreateComponentGroupType } from '@/packages/index.d'
import { dynamicFetchChartComponent, dynamicFetchConfigPropsComponent } from '@/packages/preview'
export const useComInstall = (localStorageInfo: ChartEditStorageType) => {
const show = ref(false)
// 注册组件(一开始无法获取window['$vue'])
const intervalTiming = setInterval(async () => {
if (window['$vue'].component) {
clearInterval(intervalTiming)
const intComponent = async (target: CreateComponentType) => {
if (!window['$vue'].component(target.chartConfig.chartKey)) {
const targetComp = await dynamicFetchChartComponent(target.chartConfig)
window['$vue'].component(target.chartConfig.chartKey, targetComp)
}
}
for (const e of localStorageInfo.componentList) {
const options = await dynamicFetchConfigPropsComponent(e.chartConfig);
const storeOptions = e.option;
e.option = Object.assign({}, options, storeOptions);
await intComponent(e as CreateComponentType);
}
show.value = true
}
}, 200)
return {
show
}
}
避雷!! 千万别在
forEach
里面用async
, 因为forEach
、map
等数组函数本身并不是一个异步方法, 它们皆为同步而生.若需要在循环迭代内使用异步函数, 请用
for of
替代
尾声
再次打开预览页面, 发现首屏加载时间已缩短至1s
左右, 这是在禁用缓存的情况下. 有时, 首屏加载甚至比原vue2页面还要快一些!
至此, 成功地解决了预览页面的性能问题, 感兴趣的小伙伴们欢迎在下方留言、点赞或关注哦~
转载自:https://juejin.cn/post/7418131622393348131