From 7b46780af7ca6e973226b760e22acf106d8cc409 Mon Sep 17 00:00:00 2001 From: Vben Date: Mon, 12 Aug 2024 21:05:01 +0800 Subject: [PATCH] perf: optimize the diffPreferences logic and adjust the unit test (#4130) --- docs/src/commercial/community.md | 4 +- internal/vite-config/src/options.ts | 3 + internal/vite-config/src/plugins/importmap.ts | 7 +- .../src/plugins/inject-metadata.ts | 4 +- internal/vite-config/src/utils/env.ts | 33 ++++--- .../design/src/design-tokens/dark/index.css | 9 +- .../src/design-tokens/default/index.css | 8 +- .../@core/base/shared/src/utils/diff.test.ts | 75 +++++++-------- packages/@core/base/shared/src/utils/diff.ts | 96 +++++++++++++------ .../components/page/__tests__/page.test.ts | 18 +++- .../common-ui/src/components/page/page.vue | 19 +++- 11 files changed, 177 insertions(+), 99 deletions(-) diff --git a/docs/src/commercial/community.md b/docs/src/commercial/community.md index 35fc4054..b139f9f4 100644 --- a/docs/src/commercial/community.md +++ b/docs/src/commercial/community.md @@ -14,8 +14,8 @@ ::: -作者主要通过微信群提供帮助,如果你有问题,可以通过以下方式加入微信群: +作者主要通过微信群提供帮助,如果你有问题,可以通过以下方式加入微信群。 通过微信联系作者,注明加群来意: - + diff --git a/internal/vite-config/src/options.ts b/internal/vite-config/src/options.ts index b3d44624..d35441b5 100644 --- a/internal/vite-config/src/options.ts +++ b/internal/vite-config/src/options.ts @@ -25,6 +25,9 @@ const getDefaultPwaOptions = (name: string): Partial => ({ }, }); +/** + * importmap CDN 暂时不开启,因为有些包不支持,且网络不稳定 + */ const defaultImportmapOptions: ImportmapPluginOptions = { // 通过 Importmap CDN 方式引入, // 目前只有esm.sh源兼容性好一点,jspm.io对于 esm 入口要求高 diff --git a/internal/vite-config/src/plugins/importmap.ts b/internal/vite-config/src/plugins/importmap.ts index 600993d9..c6154c9e 100644 --- a/internal/vite-config/src/plugins/importmap.ts +++ b/internal/vite-config/src/plugins/importmap.ts @@ -80,7 +80,7 @@ async function viteImportMapPlugin( const firstLayerKeys = Object.keys(scopes); const inputMapScopes: string[] = []; firstLayerKeys.forEach((key) => { - inputMapScopes.push(...Object.keys(scopes[key])); + inputMapScopes.push(...Object.keys(scopes[key] || {})); }); const inputMapImports = Object.keys(imports); @@ -160,7 +160,10 @@ async function viteImportMapPlugin( options.defaultProvider || DEFAULT_PROVIDER, ); - const resultHtml = await injectShimsToHtml(html, esModuleShimsSrc); + const resultHtml = await injectShimsToHtml( + html, + esModuleShimsSrc || '', + ); html = await minify(resultHtml || html, { collapseWhitespace: true, minifyCSS: true, diff --git a/internal/vite-config/src/plugins/inject-metadata.ts b/internal/vite-config/src/plugins/inject-metadata.ts index 80d1db5a..2731412f 100644 --- a/internal/vite-config/src/plugins/inject-metadata.ts +++ b/internal/vite-config/src/plugins/inject-metadata.ts @@ -16,8 +16,8 @@ function resolvePackageVersion( async function resolveMonorepoDependencies() { const { packages } = await getPackages(); - const resultDevDependencies: Record = {}; - const resultDependencies: Record = {}; + const resultDevDependencies: Record = {}; + const resultDependencies: Record = {}; const pkgsMeta: Record = {}; for (const { packageJson } of packages) { diff --git a/internal/vite-config/src/utils/env.ts b/internal/vite-config/src/utils/env.ts index ea1cbf67..f9cdd4f5 100644 --- a/internal/vite-config/src/utils/env.ts +++ b/internal/vite-config/src/utils/env.ts @@ -6,6 +6,14 @@ import { fs } from '@vben/node-utils'; import dotenv from 'dotenv'; +const getBoolean = (value: string | undefined) => value === 'true'; + +const getString = (value: string | undefined, fallback: string) => + value ?? fallback; + +const getNumber = (value: string | undefined, fallback: number) => + Number(value) || fallback; + /** * 获取当前环境下生效的配置文件名 */ @@ -63,6 +71,7 @@ async function loadAndConvertEnv( } & Partial > { const envConfig = await loadEnv(match, confFiles); + const { VITE_APP_TITLE, VITE_BASE, @@ -74,22 +83,22 @@ async function loadAndConvertEnv( VITE_PWA, VITE_VISUALIZER, } = envConfig; - const compress = VITE_COMPRESS || ''; - const compressTypes = compress + + const compressTypes = (VITE_COMPRESS ?? '') .split(',') .filter((item) => item === 'brotli' || item === 'gzip'); return { - appTitle: VITE_APP_TITLE ?? 'Vben Admin', - base: VITE_BASE || '/', - compress: !!compress, - compressTypes: compressTypes as ('brotli' | 'gzip')[], - devtools: VITE_DEVTOOLS === 'true', - injectAppLoading: VITE_INJECT_APP_LOADING === 'true', - nitroMock: VITE_NITRO_MOCK === 'true', - port: Number(VITE_PORT) || 5173, - pwa: VITE_PWA === 'true', - visualizer: VITE_VISUALIZER === 'true', + appTitle: getString(VITE_APP_TITLE, 'Vben Admin'), + base: getString(VITE_BASE, '/'), + compress: compressTypes.length > 0, + compressTypes, + devtools: getBoolean(VITE_DEVTOOLS), + injectAppLoading: getBoolean(VITE_INJECT_APP_LOADING), + nitroMock: getBoolean(VITE_NITRO_MOCK), + port: getNumber(VITE_PORT, 5173), + pwa: getBoolean(VITE_PWA), + visualizer: getBoolean(VITE_VISUALIZER), }; } diff --git a/packages/@core/base/design/src/design-tokens/dark/index.css b/packages/@core/base/design/src/design-tokens/dark/index.css index 1dea6978..45e255f7 100644 --- a/packages/@core/base/design/src/design-tokens/dark/index.css +++ b/packages/@core/base/design/src/design-tokens/dark/index.css @@ -19,8 +19,13 @@ --popover-foreground: 210 40% 98%; /* Muted backgrounds such as , and */ - --muted: 220deg 6.82% 17.25%; - --muted-foreground: 215 20.2% 65.1%; + + /* --muted: 220deg 6.82% 17.25%; */ + + /* --muted-foreground: 215 20.2% 65.1%; */ + + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; /* 主题颜色 */ diff --git a/packages/@core/base/design/src/design-tokens/default/index.css b/packages/@core/base/design/src/design-tokens/default/index.css index bf334175..991ea890 100644 --- a/packages/@core/base/design/src/design-tokens/default/index.css +++ b/packages/@core/base/design/src/design-tokens/default/index.css @@ -19,8 +19,12 @@ --popover-foreground: 222.2 84% 4.9%; /* Muted backgrounds such as , and */ - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; + + /* --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; */ + + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; /* 主题颜色 */ diff --git a/packages/@core/base/shared/src/utils/diff.test.ts b/packages/@core/base/shared/src/utils/diff.test.ts index e2a8121d..c858a3bf 100644 --- a/packages/@core/base/shared/src/utils/diff.test.ts +++ b/packages/@core/base/shared/src/utils/diff.test.ts @@ -3,58 +3,51 @@ import { describe, expect, it } from 'vitest'; import { diff } from './diff'; describe('diff function', () => { - it('should correctly find differences in flat objects', () => { - const oldObj = { a: 1, b: 2, c: 3 }; - const newObj = { a: 1, b: 3, c: 3 }; - expect(diff(oldObj, newObj)).toEqual({ b: 3 }); + it('should return an empty object when comparing identical objects', () => { + const obj1 = { a: 1, b: { c: 2 } }; + const obj2 = { a: 1, b: { c: 2 } }; + expect(diff(obj1, obj2)).toEqual(undefined); }); - it('should correctly handle nested objects', () => { - const oldObj = { a: { b: 1, c: 2 }, d: 3 }; - const newObj = { a: { b: 1, c: 3 }, d: 3 }; - expect(diff(oldObj, newObj)).toEqual({ a: { b: 1, c: 3 } }); + it('should detect simple changes in primitive values', () => { + const obj1 = { a: 1, b: 2 }; + const obj2 = { a: 1, b: 3 }; + expect(diff(obj1, obj2)).toEqual({ b: 3 }); }); - it('should correctly handle arrays`', () => { - const oldObj = { a: [1, 2, 3] }; - const newObj = { a: [1, 2, 4] }; - expect(diff(oldObj, newObj)).toEqual({ a: [1, 2, 4] }); + it('should detect nested object changes', () => { + const obj1 = { a: 1, b: { c: 2, d: 4 } }; + const obj2 = { a: 1, b: { c: 3, d: 4 } }; + expect(diff(obj1, obj2)).toEqual({ b: { c: 3 } }); }); - it('should correctly handle nested arrays', () => { - const oldObj = { - a: [ - [1, 2], - [3, 4], - ], - }; - const newObj = { - a: [ - [1, 2], - [3, 5], - ], - }; - expect(diff(oldObj, newObj)).toEqual({ - a: [ - [1, 2], - [3, 5], - ], - }); + it('should handle array changes', () => { + const obj1 = { a: [1, 2, 3], b: 2 }; + const obj2 = { a: [1, 2, 4], b: 2 }; + expect(diff(obj1, obj2)).toEqual({ a: [1, 2, 4] }); }); - it('should return null if objects are identical', () => { - const oldObj = { a: 1, b: 2, c: 3 }; - const newObj = { a: 1, b: 2, c: 3 }; - expect(diff(oldObj, newObj)).toBeNull(); + it('should handle added keys', () => { + const obj1 = { a: 1 }; + const obj2 = { a: 1, b: 2 }; + expect(diff(obj1, obj2)).toEqual({ b: 2 }); }); - it('should return differences between two objects excluding ignored fields', () => { - const oldObj = { a: 1, b: 2, c: 3, d: 6 }; - const newObj = { a: 2, b: 2, c: 4, d: 5 }; - const ignoreFields: (keyof typeof newObj)[] = ['a', 'd']; + it('should handle removed keys', () => { + const obj1 = { a: 1, b: 2 }; + const obj2 = { a: 1 }; + expect(diff(obj1, obj2)).toEqual(undefined); + }); - const result = diff(oldObj, newObj, ignoreFields); + it('should handle boolean value changes', () => { + const obj1 = { a: true, b: false }; + const obj2 = { a: true, b: true }; + expect(diff(obj1, obj2)).toEqual({ b: true }); + }); - expect(result).toEqual({ c: 4 }); + it('should handle null and undefined values', () => { + const obj1 = { a: null, b: undefined }; + const obj2: any = { a: 1, b: undefined }; + expect(diff(obj1, obj2)).toEqual({ a: 1 }); }); }); diff --git a/packages/@core/base/shared/src/utils/diff.ts b/packages/@core/base/shared/src/utils/diff.ts index feb1f7df..449214d7 100644 --- a/packages/@core/base/shared/src/utils/diff.ts +++ b/packages/@core/base/shared/src/utils/diff.ts @@ -1,4 +1,4 @@ -type Diff = T; +// type Diff = T; // 比较两个数组是否相等 @@ -19,40 +19,78 @@ function arraysEqual(a: T[], b: T[]): boolean { } // 深度对比两个值 -function deepEqual(oldVal: T, newVal: T): boolean { - if ( - typeof oldVal === 'object' && - oldVal !== null && - typeof newVal === 'object' && - newVal !== null - ) { - return Array.isArray(oldVal) && Array.isArray(newVal) - ? arraysEqual(oldVal, newVal) - : diff(oldVal as any, newVal as any) === null; - } else { - return oldVal === newVal; - } -} +// function deepEqual(oldVal: T, newVal: T): boolean { +// if ( +// typeof oldVal === 'object' && +// oldVal !== null && +// typeof newVal === 'object' && +// newVal !== null +// ) { +// return Array.isArray(oldVal) && Array.isArray(newVal) +// ? arraysEqual(oldVal, newVal) +// : diff(oldVal as any, newVal as any) === null; +// } else { +// return oldVal === newVal; +// } +// } -// 主要的 diff 函数 -function diff( - oldObj: T, - newObj: T, - ignoreFields: (keyof T)[] = [], -): { [K in keyof T]?: Diff } | null { - const difference: { [K in keyof T]?: Diff } = {}; +// // diff 函数 +// function diff( +// oldObj: T, +// newObj: T, +// ignoreFields: (keyof T)[] = [], +// ): { [K in keyof T]?: Diff } | null { +// const difference: { [K in keyof T]?: Diff } = {}; - for (const key in oldObj) { - if (ignoreFields.includes(key)) continue; - const oldValue = oldObj[key]; - const newValue = newObj[key]; +// for (const key in oldObj) { +// if (ignoreFields.includes(key)) continue; +// const oldValue = oldObj[key]; +// const newValue = newObj[key]; - if (!deepEqual(oldValue, newValue)) { - difference[key] = newValue; +// if (!deepEqual(oldValue, newValue)) { +// difference[key] = newValue; +// } +// } + +// return Object.keys(difference).length === 0 ? null : difference; +// } + +type DiffResult = Partial<{ + [K in keyof T]: T[K] extends object ? DiffResult : T[K]; +}>; + +function diff>(obj1: T, obj2: T): DiffResult { + function findDifferences(o1: any, o2: any): any { + if (Array.isArray(o1) && Array.isArray(o2)) { + if (!arraysEqual(o1, o2)) { + return o2; + } + return undefined; } + + if ( + typeof o1 === 'object' && + typeof o2 === 'object' && + o1 !== null && + o2 !== null + ) { + const diffResult: any = {}; + + const keys = new Set([...Object.keys(o1), ...Object.keys(o2)]); + keys.forEach((key) => { + const valueDiff = findDifferences(o1[key], o2[key]); + if (valueDiff !== undefined) { + diffResult[key] = valueDiff; + } + }); + + return Object.keys(diffResult).length > 0 ? diffResult : undefined; + } + + return o1 === o2 ? undefined : o2; } - return Object.keys(difference).length === 0 ? null : difference; + return findDifferences(obj1, obj2); } export { arraysEqual, diff }; diff --git a/packages/effects/common-ui/src/components/page/__tests__/page.test.ts b/packages/effects/common-ui/src/components/page/__tests__/page.test.ts index 0efb72d2..4169514a 100644 --- a/packages/effects/common-ui/src/components/page/__tests__/page.test.ts +++ b/packages/effects/common-ui/src/components/page/__tests__/page.test.ts @@ -58,6 +58,20 @@ describe('page.vue', () => { expect(contentDiv.classes()).toContain('custom-class'); }); + it('does not render title slot if title prop is provided', () => { + const wrapper = mount(Page, { + props: { + title: 'Test Title', + }, + slots: { + title: '

Title Slot Content

', + }, + }); + + expect(wrapper.text()).toContain('Title Slot Content'); + expect(wrapper.html()).not.toContain('Test Title'); + }); + it('does not render description slot if description prop is provided', () => { const wrapper = mount(Page, { props: { @@ -68,7 +82,7 @@ describe('page.vue', () => { }, }); - expect(wrapper.text()).toContain('Test Description'); - expect(wrapper.html()).not.toContain('Description Slot Content'); + expect(wrapper.text()).toContain('Description Slot Content'); + expect(wrapper.html()).not.toContain('Test Description'); }); }); diff --git a/packages/effects/common-ui/src/components/page/page.vue b/packages/effects/common-ui/src/components/page/page.vue index cdbefa23..d8564543 100644 --- a/packages/effects/common-ui/src/components/page/page.vue +++ b/packages/effects/common-ui/src/components/page/page.vue @@ -24,11 +24,20 @@ const props = withDefaults(defineProps(), { v-if="description || $slots.description || title" class="bg-card px-6 py-4" > -
- {{ title }} -
- - + +
+ {{ title }} +
+
+ + +

+ {{ description }} +

+