From 7a1fc4c3a5e45d7c492b58b8eb2631aa9307051c Mon Sep 17 00:00:00 2001 From: Jinjiang Date: Wed, 13 Nov 2024 15:28:44 +0800 Subject: [PATCH] feat: add a feature option to support custom component id generator (#461) --- packages/plugin-vue/README.md | 17 ++++++++++++ packages/plugin-vue/src/index.ts | 17 ++++++++++++ .../plugin-vue/src/utils/descriptorCache.ts | 27 +++++++++++++++++-- playground/vue-custom-id/Main.vue | 7 +++++ .../__tests__/vue-custom-id.spec.ts | 8 ++++++ playground/vue-custom-id/components/Foo.vue | 9 +++++++ playground/vue-custom-id/index.html | 7 +++++ playground/vue-custom-id/package.json | 17 ++++++++++++ playground/vue-custom-id/vite.config.ts | 21 +++++++++++++++ pnpm-lock.yaml | 10 +++++++ 10 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 playground/vue-custom-id/Main.vue create mode 100644 playground/vue-custom-id/__tests__/vue-custom-id.spec.ts create mode 100644 playground/vue-custom-id/components/Foo.vue create mode 100644 playground/vue-custom-id/index.html create mode 100644 playground/vue-custom-id/package.json create mode 100644 playground/vue-custom-id/vite.config.ts diff --git a/packages/plugin-vue/README.md b/packages/plugin-vue/README.md index 57656766..b8289c94 100644 --- a/packages/plugin-vue/README.md +++ b/packages/plugin-vue/README.md @@ -58,6 +58,23 @@ export interface Options { * - **default:** `false` */ prodHydrationMismatchDetails?: boolean + /** + * Customize the component ID generation strategy. + * - `'filepath'`: hash the file path (relative to the project root) + * - `'filepath-source'`: hash the file path and the source code + * - `function`: custom function that takes the file path, source code, + * whether in production mode, and the default hash function as arguments + * - **default:** `'filepath'` in development, `'filepath-source'` in production + */ + componentIdGenerator?: + | 'filepath' + | 'filepath-source' + | (( + filepath: string, + source: string, + isProduction: boolean | undefined, + getHash: (text: string) => string, + ) => string) } // `script`, `template` and `style` are lower-level compiler options diff --git a/packages/plugin-vue/src/index.ts b/packages/plugin-vue/src/index.ts index c11a8efd..7fd40903 100644 --- a/packages/plugin-vue/src/index.ts +++ b/packages/plugin-vue/src/index.ts @@ -138,6 +138,23 @@ export interface Options { * - **default:** `false` */ prodHydrationMismatchDetails?: boolean + /** + * Customize the component ID generation strategy. + * - `'filepath'`: hash the file path (relative to the project root) + * - `'filepath-source'`: hash the file path and the source code + * - `function`: custom function that takes the file path, source code, + * whether in production mode, and the default hash function as arguments + * - **default:** `'filepath'` in development, `'filepath-source'` in production + */ + componentIdGenerator?: + | 'filepath' + | 'filepath-source' + | (( + filepath: string, + source: string, + isProduction: boolean | undefined, + getHash: (text: string) => string, + ) => string) } /** diff --git a/packages/plugin-vue/src/utils/descriptorCache.ts b/packages/plugin-vue/src/utils/descriptorCache.ts index 5a9e7282..044a6010 100644 --- a/packages/plugin-vue/src/utils/descriptorCache.ts +++ b/packages/plugin-vue/src/utils/descriptorCache.ts @@ -22,7 +22,14 @@ const prevCache = new Map() export function createDescriptor( filename: string, source: string, - { root, isProduction, sourceMap, compiler, template }: ResolvedOptions, + { + root, + isProduction, + sourceMap, + compiler, + template, + features, + }: ResolvedOptions, hmr = false, ): SFCParseResult { const { descriptor, errors } = compiler.parse(source, { @@ -34,7 +41,23 @@ export function createDescriptor( // ensure the path is normalized in a way that is consistent inside // project (relative to root) and on different systems. const normalizedPath = normalizePath(path.relative(root, filename)) - descriptor.id = getHash(normalizedPath + (isProduction ? source : '')) + + const componentIdGenerator = features?.componentIdGenerator + if (componentIdGenerator === 'filepath') { + descriptor.id = getHash(normalizedPath) + } else if (componentIdGenerator === 'filepath-source') { + descriptor.id = getHash(normalizedPath + source) + } else if (typeof componentIdGenerator === 'function') { + descriptor.id = componentIdGenerator( + normalizedPath, + source, + isProduction, + getHash, + ) + } else { + descriptor.id = getHash(normalizedPath + (isProduction ? source : '')) + } + ;(hmr ? hmrCache : cache).set(filename, descriptor) return { descriptor, errors } } diff --git a/playground/vue-custom-id/Main.vue b/playground/vue-custom-id/Main.vue new file mode 100644 index 00000000..37adc77d --- /dev/null +++ b/playground/vue-custom-id/Main.vue @@ -0,0 +1,7 @@ + + + diff --git a/playground/vue-custom-id/__tests__/vue-custom-id.spec.ts b/playground/vue-custom-id/__tests__/vue-custom-id.spec.ts new file mode 100644 index 00000000..9fd1bc19 --- /dev/null +++ b/playground/vue-custom-id/__tests__/vue-custom-id.spec.ts @@ -0,0 +1,8 @@ +import { expect, test } from 'vitest' +import { page } from '~utils' + +test('should render', async () => { + expect(await page.innerHTML('div')).toMatch( + '

Foo

', + ) +}) diff --git a/playground/vue-custom-id/components/Foo.vue b/playground/vue-custom-id/components/Foo.vue new file mode 100644 index 00000000..bf695b54 --- /dev/null +++ b/playground/vue-custom-id/components/Foo.vue @@ -0,0 +1,9 @@ + + + diff --git a/playground/vue-custom-id/index.html b/playground/vue-custom-id/index.html new file mode 100644 index 00000000..13de0b72 --- /dev/null +++ b/playground/vue-custom-id/index.html @@ -0,0 +1,7 @@ +
+ diff --git a/playground/vue-custom-id/package.json b/playground/vue-custom-id/package.json new file mode 100644 index 00000000..aad14496 --- /dev/null +++ b/playground/vue-custom-id/package.json @@ -0,0 +1,17 @@ +{ + "name": "@vitejs/test-vue-custom-id", + "private": true, + "version": "0.0.0", + "scripts": { + "dev": "vite", + "build": "vite build", + "debug": "node --inspect-brk vite", + "preview": "vite preview" + }, + "dependencies": { + "vue": "catalog:" + }, + "devDependencies": { + "@vitejs/plugin-vue": "workspace:*" + } +} diff --git a/playground/vue-custom-id/vite.config.ts b/playground/vue-custom-id/vite.config.ts new file mode 100644 index 00000000..ef0fba87 --- /dev/null +++ b/playground/vue-custom-id/vite.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vite' +import vuePlugin from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [ + vuePlugin({ + features: { + componentIdGenerator: (filename) => { + return filename + .replace(/\.\w+$/, '') + .replace(/[^a-z0-9]/gi, '-') + .toLowerCase() + }, + }, + }), + ], + build: { + // to make tests faster + minify: false, + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88c4e4b7..a28b911f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -276,6 +276,16 @@ importers: specifier: workspace:* version: link:../../packages/plugin-vue + playground/vue-custom-id: + dependencies: + vue: + specifier: 'catalog:' + version: 3.5.12(typescript@5.6.3) + devDependencies: + '@vitejs/plugin-vue': + specifier: workspace:* + version: link:../../packages/plugin-vue + playground/vue-jsx: dependencies: vue: