diff --git a/src/core/declaration.ts b/src/core/declaration.ts index 24b8319e..c073c9b1 100644 --- a/src/core/declaration.ts +++ b/src/core/declaration.ts @@ -1,11 +1,11 @@ import { dirname, isAbsolute, relative } from 'path' import { existsSync } from 'fs' import { mkdir, readFile, writeFile as writeFile_ } from 'fs/promises' -import { notNullish, slash } from '@antfu/utils' +import { isString, notNullish, slash } from '@antfu/utils' import type { ComponentInfo } from '../../dist' -import type { Options } from '../types' +import type { CompPrefix, Options } from '../types' import type { Context } from './context' -import { getTransformedPath } from './utils' +import { capitalize, getTransformedPath } from './utils' import { resolveTypeImports } from './type-imports/detect' const multilineCommentsRE = /\/\*.*?\*\//gms @@ -72,6 +72,9 @@ export interface DeclarationImports { } export function getDeclarationImports(ctx: Context, filepath: string): DeclarationImports | undefined { + const compPrefix = ctx.options.compPrefix + compPrefix && Object.keys(ctx.componentNameMap).length && addPrefixToComp(compPrefix) + const component = stringifyComponentsInfo(filepath, [ ...Object.values({ ...ctx.componentNameMap, @@ -92,6 +95,32 @@ export function getDeclarationImports(ctx: Context, filepath: string): Declarati return return { component, directive } + + function addPrefixToComp(compPrefix: CompPrefix) { + if (isString(compPrefix)) { + Object.keys(ctx.componentNameMap).forEach((comp) => { + const ctxCompMapValue = ctx.componentNameMap[comp] + ctx.componentNameMap[comp] = { ...ctxCompMapValue, as: `${capitalize(compPrefix)}${comp}` } + }) + } + else { + const { prefix, include = ctx.options.dirs, exclude = null } = compPrefix + const resultList = Object.entries(ctx.componentNameMap) + .flatMap(([comp, compInfo]) => include.filter((includePath) => { + if (exclude) { + return exclude.some(excludePath => ( + !compInfo.from.includes(excludePath) && compInfo.from.includes(includePath) + )) + } + + return compInfo.from.includes(includePath) + }).map(() => [comp, compInfo])) + + resultList.forEach(([comp, ctxCompMapValue]) => { + ctx.componentNameMap[comp as string] = { ...(ctxCompMapValue as ComponentInfo), as: `${capitalize(prefix)}${comp}` } + }) + } + } } export function stringifyDeclarationImports(imports: Record) { diff --git a/src/core/options.ts b/src/core/options.ts index bce8e5de..04ebbfdc 100644 --- a/src/core/options.ts +++ b/src/core/options.ts @@ -4,7 +4,7 @@ import { getPackageInfoSync, isPackageExists } from 'local-pkg' import type { ComponentResolver, ComponentResolverObject, Options, ResolvedOptions } from '../types' import { detectTypeImports } from './type-imports/detect' -export const defaultOptions: Omit, 'include' | 'exclude' | 'transformer' | 'globs' | 'directives' | 'types' | 'version'> = { +export const defaultOptions: Omit, 'include' | 'exclude' | 'transformer' | 'globs' | 'directives' | 'types' | 'version' | 'compPrefix'> = { dirs: 'src/components', extensions: 'vue', deep: true, @@ -75,6 +75,8 @@ export function resolveOptions(options: Options, root: string): ResolvedOptions : !resolved.resolvers.some(i => i.type === 'directive') ? false : resolved.version >= 3 + + resolved.compPrefix = options.compPrefix return resolved } diff --git a/src/types.ts b/src/types.ts index c6c2299a..c8441782 100644 --- a/src/types.ts +++ b/src/types.ts @@ -46,6 +46,12 @@ export type Transformer = (code: string, id: string, path: string, query: Record export type SupportedTransformer = 'vue3' | 'vue2' +export type CompPrefix = string | { + prefix: string + include?: string[] + exclude?: string[] +} + export interface PublicPluginAPI { /** * Resolves a component using the configured resolvers. @@ -176,11 +182,20 @@ export interface Options { * Vue version of project. It will detect automatically if not specified. */ version?: 2 | 2.7 | 3 + + /** + * Generate components with prefix. + * + * when set the typeof compPrefix is `string` it will add prefix with all dirs components. + * + * or you can use `include` / `exclude` to filter Default `include` value is `dirs`. + */ + compPrefix?: CompPrefix } export type ResolvedOptions = Omit< Required, -'resolvers' | 'extensions' | 'dirs' | 'globalComponentsDeclaration' +'resolvers' | 'extensions' | 'dirs' | 'globalComponentsDeclaration' | 'compPrefix' > & { resolvers: ComponentResolverObject[] extensions: string[] @@ -189,6 +204,7 @@ Required, globs: string[] dts: string | false root: string + compPrefix?: CompPrefix } export type ComponentsImportMap = Record diff --git a/test/__snapshots__/dts.test.ts.snap b/test/__snapshots__/dts.test.ts.snap index 99dc4c5f..c45ff522 100644 --- a/test/__snapshots__/dts.test.ts.snap +++ b/test/__snapshots__/dts.test.ts.snap @@ -38,6 +38,70 @@ declare module '@vue/runtime-core' { " `; +exports[`dts > generate components with prefix - object 1`] = ` +"/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +import '@vue/runtime-core' + +export {} + +declare module '@vue/runtime-core' { + export interface GlobalComponents { + Book: typeof import('./../examples/vite-vue3/src/components/book/index.vue')['default'] + CollapseFolderAndComponentFromRoot: typeof import('./../examples/vite-vue3/src/components/collapse/collapseFolder/CollapseFolderAndComponentFromRoot.vue')['default'] + ComponentA: typeof import('./../examples/vite-vue3/src/components/ComponentA.vue')['default'] + ComponentAsync: typeof import('./../examples/vite-vue3/src/components/ComponentAsync.vue')['default'] + ComponentB: typeof import('./../examples/vite-vue3/src/components/ComponentB.vue')['default'] + ComponentC: typeof import('./../examples/vite-vue3/src/components/component-c.vue')['default'] + ComponentD: typeof import('./../examples/vite-vue3/src/components/ComponentD.vue')['default'] + ElAvatar: typeof import('./../examples/vite-vue3/src/components/global/avatar.vue')['default'] + ElButton: typeof import('./../examples/vite-vue3/src/components/ui/button.vue')['default'] + ElCheckbox: typeof import('./../examples/vite-vue3/src/components/ui/nested/checkbox.vue')['default'] + FolderAndComponentPartially: typeof import('./../examples/vite-vue3/src/components/collapse/collapseFolder/FolderAndComponentPartially.vue')['default'] + Recursive: 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'] + } +} +" +`; + +exports[`dts > generate components with prefix - string 1`] = ` +"/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +import '@vue/runtime-core' + +export {} + +declare module '@vue/runtime-core' { + export interface GlobalComponents { + ElAvatar: typeof import('./../examples/vite-vue3/src/components/global/avatar.vue')['default'] + ElBook: typeof import('./../examples/vite-vue3/src/components/book/index.vue')['default'] + ElButton: typeof import('./../examples/vite-vue3/src/components/ui/button.vue')['default'] + ElCheckbox: typeof import('./../examples/vite-vue3/src/components/ui/nested/checkbox.vue')['default'] + ElCollapseFolderAndComponentFromRoot: typeof import('./../examples/vite-vue3/src/components/collapse/collapseFolder/CollapseFolderAndComponentFromRoot.vue')['default'] + ElComponentA: typeof import('./../examples/vite-vue3/src/components/ComponentA.vue')['default'] + ElComponentAsync: typeof import('./../examples/vite-vue3/src/components/ComponentAsync.vue')['default'] + ElComponentB: typeof import('./../examples/vite-vue3/src/components/ComponentB.vue')['default'] + ElComponentC: typeof import('./../examples/vite-vue3/src/components/component-c.vue')['default'] + ElComponentD: typeof import('./../examples/vite-vue3/src/components/ComponentD.vue')['default'] + ElFolderAndComponentPartially: typeof import('./../examples/vite-vue3/src/components/collapse/collapseFolder/FolderAndComponentPartially.vue')['default'] + ElRecursive: 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'] + } +} +" +`; + exports[`dts > getDeclaration 1`] = ` "/* eslint-disable */ /* prettier-ignore */ diff --git a/test/dts.test.ts b/test/dts.test.ts index 2bb67aaa..e43af642 100644 --- a/test/dts.test.ts +++ b/test/dts.test.ts @@ -1,10 +1,12 @@ import { readFile, writeFile } from 'fs/promises' import path from 'path' +import { resolve } from 'pathe' import { describe, expect, test } from 'vitest' import type { ComponentResolver } from '../src' import { Context } from '../src/core/context' import { getDeclaration, parseDeclaration } from '../src/core/declaration' +const root = resolve(__dirname, '../examples/vite-vue3') const resolver: ComponentResolver[] = [ { type: 'component', @@ -195,4 +197,38 @@ declare module '@vue/runtime-core' { const imports = parseDeclaration(code) expect(imports).matchSnapshot() }) + + test('generate components with prefix - object', async () => { + const ctx = new Context({ + resolvers: resolver, + directives: true, + compPrefix: { + prefix: 'El', + include: ['src/components/global', 'src/components/ui'], + exclude: ['src/components/collapse'], + }, + dirs: ['src/components'], + }) + ctx.setRoot(root) + const code = 'const _component_test_comp = _resolveComponent("test-comp")' + await ctx.transform(code, '') + + const declarations = getDeclaration(ctx, 'test.d.ts') + expect(declarations).toMatchSnapshot() + }) + + test('generate components with prefix - string', async () => { + const ctx = new Context({ + resolvers: resolver, + directives: true, + compPrefix: 'El', + dirs: ['src/components'], + }) + ctx.setRoot(root) + const code = 'const _component_test_comp = _resolveComponent("test-comp")' + await ctx.transform(code, '') + + const declarations = getDeclaration(ctx, 'test.d.ts') + expect(declarations).toMatchSnapshot() + }) })