From 77d40dc7630d76f14121c3a3f12ecd4b8fb38b7c Mon Sep 17 00:00:00 2001 From: vben Date: Sun, 2 Jun 2024 23:50:58 +0800 Subject: [PATCH] perf: Improve the global loading display --- apps/antd-view/src/main.ts | 33 ++++++++++++++++++- apps/antd-view/vite.config.mts | 2 +- internal/node-utils/src/index.ts | 1 + .../vite-config/src/config/application.ts | 10 +++--- internal/vite-config/src/config/index.ts | 19 +++++++---- internal/vite-config/src/config/library.ts | 2 +- .../src/plugins/extra-app-config.ts | 16 ++++----- internal/vite-config/src/plugins/index.ts | 4 +-- .../src/plugins/inject-app-loading/index.ts | 13 +++----- .../inject-app-loading/loading-antd.html | 14 ++++++-- .../plugins/inject-app-loading/loading.html | 13 ++++++-- internal/vite-config/src/typing.ts | 21 ++++++------ .../forward/preferences/src/preferences.ts | 14 +++++--- .../shared/chche/src/storage-manager.ts | 6 ++-- 14 files changed, 111 insertions(+), 57 deletions(-) diff --git a/apps/antd-view/src/main.ts b/apps/antd-view/src/main.ts index 0841a8bb..d15a01ab 100644 --- a/apps/antd-view/src/main.ts +++ b/apps/antd-view/src/main.ts @@ -17,7 +17,38 @@ async function initApplication() { overrides: overridesPreferences, }); - import('./bootstrap').then((m) => m.bootstrap(namespace)); + // 启动应用并挂载 + // vue应用主要逻辑及视图 + const { bootstrap } = await import('./bootstrap'); + await bootstrap(namespace); + + // 移除并销毁loading + destoryAppLoading(); +} + +/** + * 移除并销毁loading + * 放在这里是而不是放在 index.html 的app标签内,主要是因为这样比较不会生硬,渲染过快可能会有闪烁 + * 通过先添加css动画隐藏,在动画结束后在移除loading节点来改善体验 + */ +function destoryAppLoading() { + // 全局搜索文件 loading.html, 找到对应的节点 + const loadingElement = document.querySelector('#__app-loading__'); + if (loadingElement) { + loadingElement.classList.add('hidden'); + const injectLoadingElements = document.querySelectorAll( + '[data-app-loading^="inject"]', + ); + // 过渡动画结束后移除loading节点 + loadingElement.addEventListener( + 'transitionend', + () => { + loadingElement.remove(); + injectLoadingElements.forEach((el) => el?.remove()); + }, + { once: true }, + ); + } } initApplication(); diff --git a/apps/antd-view/vite.config.mts b/apps/antd-view/vite.config.mts index 783296bb..39453cdd 100644 --- a/apps/antd-view/vite.config.mts +++ b/apps/antd-view/vite.config.mts @@ -1,7 +1,7 @@ import { defineConfig } from '@vben/vite-config'; export default defineConfig({ - appcation: { + application: { compress: false, compressTypes: ['brotli', 'gzip'], importmap: false, diff --git a/internal/node-utils/src/index.ts b/internal/node-utils/src/index.ts index dad62297..46d7e161 100644 --- a/internal/node-utils/src/index.ts +++ b/internal/node-utils/src/index.ts @@ -13,6 +13,7 @@ export { toPosixPath } from './path'; export { prettierFormat } from './prettier'; export type { Package } from '@manypkg/get-packages'; export { consola } from 'consola'; +export { nanoid } from 'nanoid'; export { readPackageJSON } from 'pkg-types'; export { rimraf } from 'rimraf'; export { $, chalk as colors, fs, spinner } from 'zx'; diff --git a/internal/vite-config/src/config/application.ts b/internal/vite-config/src/config/application.ts index 4f7f8a1e..770a8370 100644 --- a/internal/vite-config/src/config/application.ts +++ b/internal/vite-config/src/config/application.ts @@ -7,11 +7,11 @@ import { defineConfig, loadEnv, mergeConfig } from 'vite'; import { getApplicationConditionPlugins } from '../plugins'; import { getCommonConfig } from './common'; -import type { DefineAppcationOptions } from '../typing'; +import type { DefineApplicationOptions } from '../typing'; -function defineApplicationConfig(options: DefineAppcationOptions = {}) { +function defineApplicationConfig(options: DefineApplicationOptions = {}) { return defineConfig(async ({ command, mode }) => { - const { appcation = {}, vite = {} } = options; + const { application = {}, vite = {} } = options; const root = process.cwd(); const isBuild = command === 'build'; const env = loadEnv(mode, root); @@ -29,11 +29,10 @@ function defineApplicationConfig(options: DefineAppcationOptions = {}) { mock: true, mode, turboConsole: false, - ...appcation, + ...application, }); const applicationConfig: UserConfig = { - // }, build: { rollupOptions: { output: { @@ -44,7 +43,6 @@ function defineApplicationConfig(options: DefineAppcationOptions = {}) { }, target: 'es2015', }, - // }, esbuild: { drop: isBuild ? [ diff --git a/internal/vite-config/src/config/index.ts b/internal/vite-config/src/config/index.ts index 36d61f6f..547ceffa 100644 --- a/internal/vite-config/src/config/index.ts +++ b/internal/vite-config/src/config/index.ts @@ -1,7 +1,6 @@ +import { existsSync } from 'node:fs'; import { join } from 'node:path'; -import { fs } from '@vben/node-utils'; - import { defineApplicationConfig } from './application'; import { defineLibraryConfig } from './library'; @@ -18,13 +17,19 @@ function defineConfig(options: DefineConfig = {}) { // 根据包是否存在 index.html,自动判断类型 if (type === 'auto') { const htmlPath = join(process.cwd(), 'index.html'); - projectType = fs.existsSync(htmlPath) ? 'appcation' : 'library'; + projectType = existsSync(htmlPath) ? 'application' : 'library'; } - if (projectType === 'appcation') { - return defineApplicationConfig(defineOptions); - } else if (projectType === 'library') { - return defineLibraryConfig(defineOptions); + switch (projectType) { + case 'application': { + return defineApplicationConfig(defineOptions); + } + case 'library': { + return defineLibraryConfig(defineOptions); + } + default: { + throw new Error(`Unsupported project type: ${projectType}`); + } } } diff --git a/internal/vite-config/src/config/library.ts b/internal/vite-config/src/config/library.ts index 96ff63b5..18e74d01 100644 --- a/internal/vite-config/src/config/library.ts +++ b/internal/vite-config/src/config/library.ts @@ -33,7 +33,7 @@ function defineLibraryConfig(options: DefineLibraryOptions = {}) { build: { lib: { entry: 'src/index.ts', - fileName: () => 'index.mjs', + fileName: 'index.mjs', formats: ['es'], }, rollupOptions: { diff --git a/internal/vite-config/src/plugins/extra-app-config.ts b/internal/vite-config/src/plugins/extra-app-config.ts index 7e19d003..acea1464 100644 --- a/internal/vite-config/src/plugins/extra-app-config.ts +++ b/internal/vite-config/src/plugins/extra-app-config.ts @@ -35,7 +35,7 @@ async function viteExtraAppConfigPlugin({ return { async configResolved(config) { - publicPath = config.base; + publicPath = ensureTrailingSlash(config.base); source = await getConfigSource(); }, async generateBundle() { @@ -59,21 +59,13 @@ async function viteExtraAppConfigPlugin({ }, name: 'vite:extra-app-config', async transformIndexHtml(html) { - publicPath = publicPath.endsWith('/') ? publicPath : `${publicPath}/`; const hash = `v=${version}-${generatorContentHash(source, 8)}`; const appConfigSrc = `${publicPath}${GLOBAL_CONFIG_FILE_NAME}?${hash}`; return { html, - tags: [ - { - attrs: { - src: appConfigSrc, - }, - tag: 'script', - }, - ], + tags: [{ attrs: { src: appConfigSrc }, tag: 'script' }], }; }, }; @@ -94,4 +86,8 @@ async function getConfigSource() { return source; } +function ensureTrailingSlash(path: string) { + return path.endsWith('/') ? path : `${path}/`; +} + export { viteExtraAppConfigPlugin }; diff --git a/internal/vite-config/src/plugins/index.ts b/internal/vite-config/src/plugins/index.ts index f6908623..553f7262 100644 --- a/internal/vite-config/src/plugins/index.ts +++ b/internal/vite-config/src/plugins/index.ts @@ -20,7 +20,7 @@ import { viteImportMapPlugin } from './importmap'; import { viteInjectAppLoadingPlugin } from './inject-app-loading'; import type { - AppcationPluginOptions, + ApplicationPluginOptions, CommonPluginOptions, ConditionPlugin, LibraryPluginOptions, @@ -82,7 +82,7 @@ async function getCommonConditionPlugins( * 根据条件获取应用类型的vite插件 */ async function getApplicationConditionPlugins( - options: AppcationPluginOptions, + options: ApplicationPluginOptions, ): Promise { // 单独取,否则commonOptions拿不到 const isBuild = options.isBuild; diff --git a/internal/vite-config/src/plugins/inject-app-loading/index.ts b/internal/vite-config/src/plugins/inject-app-loading/index.ts index 47672853..f5d8f252 100644 --- a/internal/vite-config/src/plugins/inject-app-loading/index.ts +++ b/internal/vite-config/src/plugins/inject-app-loading/index.ts @@ -14,14 +14,14 @@ async function viteInjectAppLoadingPlugin( ): Promise { const loadingHtml = await getLoadingRawByHtmlTemplate(); const envRaw = isBuild ? 'prod' : 'dev'; - const cacheName = `'__${env.VITE_APP_NAMESPACE}-${envRaw}-theme__'`; + const cacheName = `'${env.VITE_APP_NAMESPACE}-${envRaw}-preferences-theme'`; // 获取缓存的主题 // 保证黑暗主题下,刷新页面时,loading也是黑暗主题 const injectScript = ` - `; @@ -34,11 +34,8 @@ async function viteInjectAppLoadingPlugin( name: 'vite:inject-app-loading', transformIndexHtml: { handler(html) { - const re = /(\s*)<\/div>/; - html = html.replace( - re, - `
${injectScript}${loadingHtml}
`, - ); + const re = //; + html = html.replace(re, `${injectScript}${loadingHtml}`); return html; }, order: 'pre', diff --git a/internal/vite-config/src/plugins/inject-app-loading/loading-antd.html b/internal/vite-config/src/plugins/inject-app-loading/loading-antd.html index e660765f..cf7e9676 100644 --- a/internal/vite-config/src/plugins/inject-app-loading/loading-antd.html +++ b/internal/vite-config/src/plugins/inject-app-loading/loading-antd.html @@ -1,4 +1,4 @@ - -
+
<%= VITE_GLOB_APP_TITLE %>
diff --git a/internal/vite-config/src/plugins/inject-app-loading/loading.html b/internal/vite-config/src/plugins/inject-app-loading/loading.html index faaa9f7a..a947cd82 100644 --- a/internal/vite-config/src/plugins/inject-app-loading/loading.html +++ b/internal/vite-config/src/plugins/inject-app-loading/loading.html @@ -1,4 +1,4 @@ - -
+
<%= VITE_GLOB_APP_TITLE %>
diff --git a/internal/vite-config/src/typing.ts b/internal/vite-config/src/typing.ts index 42b05316..a37e5237 100644 --- a/internal/vite-config/src/typing.ts +++ b/internal/vite-config/src/typing.ts @@ -4,7 +4,7 @@ import type { PluginOptions } from 'vite-plugin-dts'; import viteTurboConsolePlugin from 'unplugin-turbo-console/vite'; -export interface IImportMap { +interface IImportMap { imports?: Record; scopes?: { [scope: string]: Record; @@ -40,7 +40,7 @@ interface CommonPluginOptions { /** 是否开启devtools */ devtools?: boolean; /** 环境变量 */ - env: Record; + env?: Record; /** 是否构建模式 */ isBuild?: boolean; /** 构建模式 */ @@ -49,7 +49,7 @@ interface CommonPluginOptions { visualizer?: PluginVisualizerOptions | boolean; } -interface AppcationPluginOptions extends CommonPluginOptions { +interface ApplicationPluginOptions extends CommonPluginOptions { /** 开启 gzip 压缩 */ compress?: boolean; /** 压缩类型 */ @@ -80,12 +80,12 @@ interface LibraryPluginOptions extends CommonPluginOptions { injectLibCss?: boolean; } -interface AppcationOptions extends AppcationPluginOptions {} +interface ApplicationOptions extends ApplicationPluginOptions {} interface LibraryOptions extends LibraryPluginOptions {} -interface DefineAppcationOptions { - appcation?: AppcationOptions; +interface DefineApplicationOptions { + application?: ApplicationOptions; vite?: UserConfig; } @@ -95,17 +95,18 @@ interface DefineLibraryOptions { } type DefineConfig = { - type?: 'appcation' | 'auto' | 'library'; -} & DefineAppcationOptions & + type?: 'application' | 'auto' | 'library'; +} & DefineApplicationOptions & DefineLibraryOptions; export type { - AppcationPluginOptions, + ApplicationPluginOptions, CommonPluginOptions, ConditionPlugin, - DefineAppcationOptions, + DefineApplicationOptions, DefineConfig, DefineLibraryOptions, + IImportMap, ImportmapPluginOptions, LibraryPluginOptions, }; diff --git a/packages/@vben-core/forward/preferences/src/preferences.ts b/packages/@vben-core/forward/preferences/src/preferences.ts index aa247adf..b835bee7 100644 --- a/packages/@vben-core/forward/preferences/src/preferences.ts +++ b/packages/@vben-core/forward/preferences/src/preferences.ts @@ -21,6 +21,8 @@ import { defaultPreferences } from './config'; import type { Preferences } from './types'; const STORAGE_KEY = 'preferences'; +const STORAGE_KEY_LOCALE = `${STORAGE_KEY}-locale`; +const STORAGE_KEY_THEME = `${STORAGE_KEY}-theme`; interface initialOptions { namespace: string; @@ -36,7 +38,7 @@ function isDarkTheme(theme: string) { } class PreferenceManager { - private cache: StorageManager | null = null; + private cache: StorageManager | null = null; private flattenedState: Flatten; private initialPreferences: Preferences = defaultPreferences; private isInitialized: boolean = false; @@ -60,6 +62,8 @@ class PreferenceManager { */ private _savePreferences(preference: Preferences) { this.cache?.setItem(STORAGE_KEY, preference); + this.cache?.setItem(STORAGE_KEY_LOCALE, preference.app.locale); + this.cache?.setItem(STORAGE_KEY_THEME, preference.app.themeMode); } /** @@ -89,7 +93,7 @@ class PreferenceManager { * 从缓存中加载偏好设置。如果缓存中没有找到对应的偏好设置,则返回默认偏好设置。 */ private loadCachedPreferences() { - return this.cache?.getItem(STORAGE_KEY); + return this.cache?.getItem(STORAGE_KEY); } /** @@ -231,8 +235,8 @@ class PreferenceManager { /** * 覆盖偏好设置 - * @param overrides - 要覆盖的偏好设置 - * @param namespace - 命名空间 + * overrides 要覆盖的偏好设置 + * namespace 命名空间 */ public async initPreferences({ namespace, overrides }: initialOptions) { // 是否初始化过 @@ -273,6 +277,8 @@ class PreferenceManager { this.savePreferences(this.state); // 从存储中移除偏好设置项 this.cache?.removeItem(STORAGE_KEY); + this.cache?.removeItem(STORAGE_KEY_THEME); + this.cache?.removeItem(STORAGE_KEY_LOCALE); } /** diff --git a/packages/@vben-core/shared/chche/src/storage-manager.ts b/packages/@vben-core/shared/chche/src/storage-manager.ts index e2732e49..d3bfcb41 100644 --- a/packages/@vben-core/shared/chche/src/storage-manager.ts +++ b/packages/@vben-core/shared/chche/src/storage-manager.ts @@ -10,7 +10,7 @@ interface StorageItem { value: T; } -class StorageManager { +class StorageManager { private prefix: string; private storage: Storage; @@ -67,7 +67,7 @@ class StorageManager { * @param defaultValue 当项不存在或已过期时返回的默认值 * @returns 值,如果项已过期或解析错误则返回默认值 */ - getItem(key: string, defaultValue: T | null = null): T | null { + getItem(key: string, defaultValue: T | null = null): T | null { const fullKey = this.getFullKey(key); const itemStr = this.storage.getItem(fullKey); if (!itemStr) { @@ -103,7 +103,7 @@ class StorageManager { * @param value 值 * @param ttl 存活时间(毫秒) */ - setItem(key: string, value: T, ttl?: number): void { + setItem(key: string, value: T, ttl?: number): void { const fullKey = this.getFullKey(key); const expiry = ttl ? Date.now() + ttl : undefined; const item: StorageItem = { expiry, value };