likes
comments
collection
share

基于GoView的低代码大屏: 如何做预览页面的性能优化前言 最近, 低代码大屏项目面临一个很严峻的问题, 其预览页面,

作者站长头像
站长
· 阅读数 108

前言

最近, 低代码大屏项目面临一个很严峻的问题, 其预览页面, 与基于vue2的原生页面相比, 大屏预览页面首次加载时间约是原生页面的2~3倍.

这意味着, 原生的vue2页面, 首屏加载时间约1秒左右, 而大屏页面首屏加载却需要2~3秒.

首屏加载慢这一点非常致命, 倘若不能解决该问题, 那么低代码大屏产品将不复存在.

通过研究GoView的代码库, 我花了约2天时间, 成功解决了首屏加载慢的问题, 下面我将为大家细述我的解决方案.

新建预览页面路由

GoView低代码大屏的预览页面和设计页面, 共用一个main.tsApp.vue文件, 而main.ts导入了很多预览页面无需用到的代码.

针对这种情况, 我准备给预览页面单独开一个html页面入口.

src目录下新建preview.tsPreview.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.tsrollupOptions添加如下配置

    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

基于GoView的低代码大屏: 如何做预览页面的性能优化前言 最近, 低代码大屏项目面临一个很严峻的问题, 其预览页面,

可看到预览页面已显示, 至此完成预览页面的单独配置

只加载使用到的组件

通过查看预览页面的网络请求, 我发现它加载了整个项目内的所有组件库, 追踪溯源, 发现如下罪魁祸首代码

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, 因为forEachmap等数组函数本身并不是一个异步方法, 它们皆为同步而生.

若需要在循环迭代内使用异步函数, 请用for of替代

尾声

再次打开预览页面, 发现首屏加载时间已缩短至1s左右, 这是在禁用缓存的情况下. 有时, 首屏加载甚至比原vue2页面还要快一些!

至此, 成功地解决了预览页面的性能问题, 感兴趣的小伙伴们欢迎在下方留言、点赞或关注哦~

转载自:https://juejin.cn/post/7418131622393348131
评论
请登录