From eed512d5b73bae830d70730e8505f6c8c5127fc6 Mon Sep 17 00:00:00 2001 From: IlyaL Date: Wed, 25 Jun 2025 22:30:51 +0800 Subject: [PATCH 1/3] feat: add components prefix Co-authored-by: winches --- src/core/declaration.ts | 16 +++++++++++++--- src/core/options.ts | 2 ++ src/types.ts | 5 +++++ test/__snapshots__/dts.test.ts.snap | 22 ++++++++++++++++++++++ test/dts.test.ts | 15 +++++++++++++++ 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/core/declaration.ts b/src/core/declaration.ts index 7259be71..13230ef6 100644 --- a/src/core/declaration.ts +++ b/src/core/declaration.ts @@ -38,6 +38,16 @@ export function parseDeclaration(code: string): DeclarationImports | undefined { return imports } +function addComponentPrefix(component: ComponentInfo, prefix?: string) { + if (!component.as || !prefix) + return component + + return { + ...component, + as: `${prefix}${component.as}`, + } +} + /** * Converts `ComponentInfo` to an array * @@ -59,9 +69,9 @@ function stringifyComponentInfo(filepath: string, { from: path, as: name, name: * * `{ name: "typeof import(path)[importName]", ... }` */ -export function stringifyComponentsInfo(filepath: string, components: ComponentInfo[], importPathTransform?: Options['importPathTransform']): Record { +export function stringifyComponentsInfo(filepath: string, components: ComponentInfo[], importPathTransform?: Options['importPathTransform'], prefix?: string): Record { return Object.fromEntries( - components.map(info => stringifyComponentInfo(filepath, info, importPathTransform)) + components.map(info => stringifyComponentInfo(filepath, addComponentPrefix(info, prefix), importPathTransform)) .filter(notNullish), ) } @@ -78,7 +88,7 @@ export function getDeclarationImports(ctx: Context, filepath: string): Declarati ...ctx.componentCustomMap, }), ...resolveTypeImports(ctx.options.types), - ], ctx.options.importPathTransform) + ], ctx.options.importPathTransform, ctx.options.prefix) const directive = stringifyComponentsInfo( filepath, diff --git a/src/core/options.ts b/src/core/options.ts index 58ba6239..0ea67fb0 100644 --- a/src/core/options.ts +++ b/src/core/options.ts @@ -21,6 +21,8 @@ export const defaultOptions: Omit, 'include' | 'exclude' | 'ex importPathTransform: v => v, allowOverrides: false, + + prefix: '', } function normalizeResolvers(resolvers: (ComponentResolver | ComponentResolver[])[]): ComponentResolverObject[] { diff --git a/src/types.ts b/src/types.ts index ac17d1e3..bce2a70c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -114,6 +114,11 @@ export interface Options { */ directoryAsNamespace?: boolean + /** + * Generate components with prefix. + */ + prefix?: string + /** * Collapse same prefixes (camel-sensitive) of folders and components * to prevent duplication inside namespaced component name. diff --git a/test/__snapshots__/dts.test.ts.snap b/test/__snapshots__/dts.test.ts.snap index c1d09dda..ba2e5764 100644 --- a/test/__snapshots__/dts.test.ts.snap +++ b/test/__snapshots__/dts.test.ts.snap @@ -36,6 +36,28 @@ declare module 'vue' { " `; +exports[`dts > generate components with prefix 1`] = ` +"/* eslint-disable */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +// biome-ignore lint: disable +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + CustomPrefixRouterLink: typeof import('vue-router')['RouterLink'] + CustomPrefixRouterView: typeof import('vue-router')['RouterView'] + CustomPrefixTestComp: typeof import('test/component/TestComp')['default'] + } + export interface GlobalDirectives { + vLoading: typeof import('test/directive/Loading')['default'] + } +} +" +`; + exports[`dts > getDeclaration 1`] = ` "/* eslint-disable */ // @ts-nocheck diff --git a/test/dts.test.ts b/test/dts.test.ts index 9ddf0f5d..45d14df2 100644 --- a/test/dts.test.ts +++ b/test/dts.test.ts @@ -189,4 +189,19 @@ declare module 'vue' { const imports = parseDeclaration(code) expect(imports).matchSnapshot() }) + + it.only('generate components with prefix', async () => { + const ctx = new Context({ + resolvers: resolver, + directives: true, + prefix: 'CustomPrefix', + }) + const code = ` +const _component_test_comp = _resolveComponent("test-comp") +const _directive_loading = _resolveDirective("loading")` + await ctx.transform(code, '') + + const declarations = getDeclaration(ctx, 'test.d.ts') + expect(declarations).toMatchSnapshot() + }) }) From 4751b4a720e878e75ad63e509125fe881e06120c Mon Sep 17 00:00:00 2001 From: IlyaL Date: Wed, 25 Jun 2025 22:40:36 +0800 Subject: [PATCH 2/3] fix: remove `.only` --- test/dts.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dts.test.ts b/test/dts.test.ts index 45d14df2..6d871263 100644 --- a/test/dts.test.ts +++ b/test/dts.test.ts @@ -190,7 +190,7 @@ declare module 'vue' { expect(imports).matchSnapshot() }) - it.only('generate components with prefix', async () => { + it('generate components with prefix', async () => { const ctx = new Context({ resolvers: resolver, directives: true, From 922958755cff4581e4438b8ca44b00d70fb615a3 Mon Sep 17 00:00:00 2001 From: IlyaL Date: Wed, 25 Jun 2025 23:22:48 +0800 Subject: [PATCH 3/3] feat: only add prefix for specific components --- src/core/declaration.ts | 13 ++++++------- test/__snapshots__/dts.test.ts.snap | 20 +++++++++++++++++--- test/dts.test.ts | 3 +++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/core/declaration.ts b/src/core/declaration.ts index 13230ef6..cc2e35fa 100644 --- a/src/core/declaration.ts +++ b/src/core/declaration.ts @@ -69,9 +69,9 @@ function stringifyComponentInfo(filepath: string, { from: path, as: name, name: * * `{ name: "typeof import(path)[importName]", ... }` */ -export function stringifyComponentsInfo(filepath: string, components: ComponentInfo[], importPathTransform?: Options['importPathTransform'], prefix?: string): Record { +export function stringifyComponentsInfo(filepath: string, components: ComponentInfo[], importPathTransform?: Options['importPathTransform']): Record { return Object.fromEntries( - components.map(info => stringifyComponentInfo(filepath, addComponentPrefix(info, prefix), importPathTransform)) + components.map(info => stringifyComponentInfo(filepath, info, importPathTransform)) .filter(notNullish), ) } @@ -82,13 +82,12 @@ export interface DeclarationImports { } export function getDeclarationImports(ctx: Context, filepath: string): DeclarationImports | undefined { + const prefixComponentNameMap = Object.values(ctx.componentNameMap).map(info => addComponentPrefix(info, ctx.options.prefix)) const component = stringifyComponentsInfo(filepath, [ - ...Object.values({ - ...ctx.componentNameMap, - ...ctx.componentCustomMap, - }), + ...Object.values(ctx.componentCustomMap), + ...prefixComponentNameMap, ...resolveTypeImports(ctx.options.types), - ], ctx.options.importPathTransform, ctx.options.prefix) + ], ctx.options.importPathTransform) const directive = stringifyComponentsInfo( filepath, diff --git a/test/__snapshots__/dts.test.ts.snap b/test/__snapshots__/dts.test.ts.snap index ba2e5764..1359bad0 100644 --- a/test/__snapshots__/dts.test.ts.snap +++ b/test/__snapshots__/dts.test.ts.snap @@ -47,9 +47,23 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { - CustomPrefixRouterLink: typeof import('vue-router')['RouterLink'] - CustomPrefixRouterView: typeof import('vue-router')['RouterView'] - CustomPrefixTestComp: typeof import('test/component/TestComp')['default'] + CustomPrefixAvatar: typeof import('./examples/vite-vue3/src/components/global/avatar.vue')['default'] + CustomPrefixBook: typeof import('./examples/vite-vue3/src/components/book/index.vue')['default'] + CustomPrefixButton: typeof import('./examples/vite-vue3/src/components/ui/button.vue')['default'] + CustomPrefixCheckbox: typeof import('./examples/vite-vue3/src/components/ui/nested/checkbox.vue')['default'] + CustomPrefixCollapseFolderAndComponentFromRoot: typeof import('./examples/vite-vue3/src/components/collapse/collapseFolder/CollapseFolderAndComponentFromRoot.vue')['default'] + CustomPrefixComponentA: typeof import('./examples/vite-vue3/src/components/ComponentA.vue')['default'] + CustomPrefixComponentAsync: typeof import('./examples/vite-vue3/src/components/ComponentAsync.vue')['default'] + CustomPrefixComponentB: typeof import('./examples/vite-vue3/src/components/ComponentB.vue')['default'] + CustomPrefixComponentC: typeof import('./examples/vite-vue3/src/components/component-c.vue')['default'] + CustomPrefixComponentD: typeof import('./examples/vite-vue3/src/components/ComponentD.vue')['default'] + CustomPrefixFolderAndComponentPartially: typeof import('./examples/vite-vue3/src/components/collapse/collapseFolder/FolderAndComponentPartially.vue')['default'] + CustomPrefixKebabCaseCollapseFile: typeof import('./examples/vite-vue3/src/components/kebab-case/kebab-case-collapse/KebabCaseCollapseFile.vue')['default'] + CustomPrefixKebabCaseFile: typeof import('./examples/vite-vue3/src/components/kebab-case/KebabCaseFile.vue')['default'] + CustomPrefixRecursive: typeof import('./examples/vite-vue3/src/components/Recursive.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + TestComp: typeof import('test/component/TestComp')['default'] } export interface GlobalDirectives { vLoading: typeof import('test/directive/Loading')['default'] diff --git a/test/dts.test.ts b/test/dts.test.ts index 6d871263..aa8715d1 100644 --- a/test/dts.test.ts +++ b/test/dts.test.ts @@ -5,6 +5,7 @@ import { describe, expect, it } from 'vitest' import { Context } from '../src/core/context' import { getDeclaration, parseDeclaration } from '../src/core/declaration' +const root = path.resolve(__dirname, '../examples/vite-vue3') const resolver: ComponentResolver[] = [ { type: 'component', @@ -195,7 +196,9 @@ declare module 'vue' { resolvers: resolver, directives: true, prefix: 'CustomPrefix', + dirs: ['src/components'], }) + ctx.setRoot(root) const code = ` const _component_test_comp = _resolveComponent("test-comp") const _directive_loading = _resolveDirective("loading")`