From 32673307509175679a43bac2cf675fd2bd72075d Mon Sep 17 00:00:00 2001 From: Xingwang Liao Date: Thu, 8 May 2025 14:57:39 +0000 Subject: [PATCH 1/4] feat: enhance TypeScript declaration generation --- src/core/context.ts | 2 +- src/core/declaration.ts | 138 ++++++++++++++++++++-------- src/core/options.ts | 20 ++-- src/core/unplugin.ts | 3 +- src/types.ts | 10 +- test/__snapshots__/dts.test.ts.snap | 24 +++-- test/dts.test.ts | 21 +++-- 7 files changed, 148 insertions(+), 70 deletions(-) diff --git a/src/core/context.ts b/src/core/context.ts index 499a9a3b..b654acfb 100644 --- a/src/core/context.ts +++ b/src/core/context.ts @@ -301,7 +301,7 @@ export class Context { return debug.declaration('generating dts') - return writeDeclaration(this, this.options.dts, removeUnused) + return writeDeclaration(this, removeUnused) } generateDeclaration(removeUnused = !this._server): void { diff --git a/src/core/declaration.ts b/src/core/declaration.ts index 4fd2c168..2240bf20 100644 --- a/src/core/declaration.ts +++ b/src/core/declaration.ts @@ -1,4 +1,4 @@ -import type { ComponentInfo, Options } from '../types' +import type { ComponentInfo, DtsConfigure, DtsDeclarationType, Options } from '../types' import type { Context } from './context' import { existsSync } from 'node:fs' import { mkdir, readFile, writeFile as writeFile_ } from 'node:fs/promises' @@ -39,31 +39,48 @@ export function parseDeclaration(code: string): DeclarationImports | undefined { } /** - * Converts `ComponentInfo` to an array + * Converts `ComponentInfo` to an import info. * - * `[name, "typeof import(path)[importName]"]` + * `{name, entry: "typeof import(path)[importName]", filepath}` */ -function stringifyComponentInfo(filepath: string, { from: path, as: name, name: importName }: ComponentInfo, importPathTransform?: Options['importPathTransform']): [string, string] | undefined { +function stringifyComponentInfo(dts: DtsConfigure, info: ComponentInfo, declarationType: DtsDeclarationType, importPathTransform?: Options['importPathTransform']): Record<'name' | 'entry' | 'filepath', string> | undefined { + const { from: path, as: name, name: importName } = info + if (!name) return undefined - path = getTransformedPath(path, importPathTransform) - const related = isAbsolute(path) - ? `./${relative(dirname(filepath), path)}` - : path + + const filepath = dts(info, declarationType) + if (!filepath) + return undefined + + const transformedPath = getTransformedPath(path, importPathTransform) + const related = isAbsolute(transformedPath) + ? `./${relative(dirname(filepath), transformedPath)}` + : transformedPath const entry = `typeof import('${slash(related)}')['${importName || 'default'}']` - return [name, entry] + return { name, entry, filepath } } /** - * Converts array of `ComponentInfo` to an import map + * Converts array of `ComponentInfo` to a filepath grouped import map. * - * `{ name: "typeof import(path)[importName]", ... }` + * `{ filepath: { name: "typeof import(path)[importName]", ... } }` */ -export function stringifyComponentsInfo(filepath: string, components: ComponentInfo[], importPathTransform?: Options['importPathTransform']): Record { - return Object.fromEntries( - components.map(info => stringifyComponentInfo(filepath, info, importPathTransform)) - .filter(notNullish), - ) +export function stringifyComponentsInfo(dts: DtsConfigure, components: ComponentInfo[], declarationType: DtsDeclarationType, importPathTransform?: Options['importPathTransform']): Record> { + const stringified = components.map(info => stringifyComponentInfo(dts, info, declarationType, importPathTransform)).filter(notNullish) + + const filepathMap: Record> = {} + + for (const info of stringified) { + const { name, entry, filepath } = info + + if (!filepathMap[filepath]) + filepathMap[filepath] = {} + + filepathMap[filepath][name] = entry + } + + return filepathMap } export interface DeclarationImports { @@ -71,27 +88,55 @@ export interface DeclarationImports { directive: Record } -export function getDeclarationImports(ctx: Context, filepath: string): DeclarationImports | undefined { - const component = stringifyComponentsInfo(filepath, [ +export function getDeclarationImports(ctx: Context): Record | undefined { + if (!ctx.options.dts) + return undefined + + const componentMap = stringifyComponentsInfo(ctx.options.dts, [ ...Object.values({ ...ctx.componentNameMap, ...ctx.componentCustomMap, }), ...resolveTypeImports(ctx.options.types), - ], ctx.options.importPathTransform) + ], 'component', ctx.options.importPathTransform) - const directive = stringifyComponentsInfo( - filepath, + const directiveMap = stringifyComponentsInfo( + ctx.options.dts, Object.values(ctx.directiveCustomMap), + 'directive', ctx.options.importPathTransform, ) - if ( - (Object.keys(component).length + Object.keys(directive).length) === 0 - ) - return + const declarationMap: Record = {} + + for (const [filepath, component] of Object.entries(componentMap)) { + if (!declarationMap[filepath]) + declarationMap[filepath] = { component: {}, directive: {} } + + declarationMap[filepath].component = { + ...declarationMap[filepath].component, + ...component, + } + } - return { component, directive } + for (const [filepath, directive] of Object.entries(directiveMap)) { + if (!declarationMap[filepath]) + declarationMap[filepath] = { component: {}, directive: {} } + + declarationMap[filepath].directive = { + ...declarationMap[filepath].directive, + ...directive, + } + } + + for (const [filepath, { component, directive }] of Object.entries(declarationMap)) { + if ( + (Object.keys(component).length + Object.keys(directive).length) === 0 + ) + delete declarationMap[filepath] + } + + return declarationMap } export function stringifyDeclarationImports(imports: Record) { @@ -104,11 +149,7 @@ export function stringifyDeclarationImports(imports: Record) { }) } -export function getDeclaration(ctx: Context, filepath: string, originalImports?: DeclarationImports) { - const imports = getDeclarationImports(ctx, filepath) - if (!imports) - return - +function getDeclaration(imports: DeclarationImports, originalImports?: DeclarationImports): string { const declarations = { component: stringifyDeclarationImports({ ...originalImports?.component, ...imports.component }), directive: stringifyDeclarationImports({ ...originalImports?.directive, ...imports.directive }), @@ -140,21 +181,40 @@ declare module 'vue' {` return code } +export async function getDeclarations(ctx: Context, removeUnused: boolean): Promise | undefined> { + const importsMap = getDeclarationImports(ctx) + if (!importsMap || !Object.keys(importsMap).length) + return undefined + + const results = await Promise.all(Object.entries(importsMap).map(async ([filepath, imports]) => { + const originalContent = existsSync(filepath) ? await readFile(filepath, 'utf-8') : '' + const originalImports = removeUnused ? undefined : parseDeclaration(originalContent) + + const code = getDeclaration(imports, originalImports) + + if (code !== originalContent) { + return [filepath, code] + } + })) + + return Object.fromEntries(results.filter(notNullish)) +} + async function writeFile(filePath: string, content: string) { await mkdir(dirname(filePath), { recursive: true }) return await writeFile_(filePath, content, 'utf-8') } -export async function writeDeclaration(ctx: Context, filepath: string, removeUnused = false) { - const originalContent = existsSync(filepath) ? await readFile(filepath, 'utf-8') : '' - const originalImports = removeUnused ? undefined : parseDeclaration(originalContent) - - const code = getDeclaration(ctx, filepath, originalImports) - if (!code) +export async function writeDeclaration(ctx: Context, removeUnused = false) { + const declarations = await getDeclarations(ctx, removeUnused) + if (!declarations || !Object.keys(declarations).length) return - if (code !== originalContent) - await writeFile(filepath, code) + await Promise.all( + Object.entries(declarations).map(async ([filepath, code]) => { + return writeFile(filepath, code) + }), + ) } export async function writeComponentsJson(ctx: Context, _removeUnused = false) { diff --git a/src/core/options.ts b/src/core/options.ts index 58ba6239..ee0d2cee 100644 --- a/src/core/options.ts +++ b/src/core/options.ts @@ -1,4 +1,4 @@ -import type { ComponentResolver, ComponentResolverObject, Options, ResolvedOptions } from '../types' +import type { ComponentResolver, ComponentResolverObject, DtsConfigure, Options, ResolvedOptions } from '../types' import { join, resolve } from 'node:path' import { slash, toArray } from '@antfu/utils' import { getPackageInfoSync, isPackageExists } from 'local-pkg' @@ -21,6 +21,8 @@ export const defaultOptions: Omit, 'include' | 'exclude' | 'ex importPathTransform: v => v, allowOverrides: false, + sourcemap: true, + dumpComponentsInfo: false, } function normalizeResolvers(resolvers: (ComponentResolver | ComponentResolver[])[]): ComponentResolverObject[] { @@ -78,14 +80,16 @@ export function resolveOptions(options: Options, root: string): ResolvedOptions return false }) - resolved.dts = !resolved.dts + const originalDts = resolved.dts + + resolved.dts = !originalDts ? false - : resolve( - root, - typeof resolved.dts === 'string' - ? resolved.dts - : 'components.d.ts', - ) + : ((...args) => { + const res = typeof originalDts === 'function' ? originalDts(...args) : originalDts + if (!res) + return false + return resolve(root, typeof res === 'string' ? res : 'components.d.ts') + }) as DtsConfigure if (!resolved.types && resolved.dts) resolved.types = detectTypeImports() diff --git a/src/core/unplugin.ts b/src/core/unplugin.ts index 39616787..084d5256 100644 --- a/src/core/unplugin.ts +++ b/src/core/unplugin.ts @@ -66,8 +66,7 @@ export default createUnplugin((options = {}) => { if (ctx.options.dts) { ctx.searchGlob() - if (!existsSync(ctx.options.dts)) - ctx.generateDeclaration() + ctx.generateDeclaration() } if (ctx.options.dumpComponentsInfo && ctx.dumpComponentsInfoPath) { diff --git a/src/types.ts b/src/types.ts index ac17d1e3..8ba20dc9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -46,6 +46,10 @@ export type Transformer = (code: string, id: string, path: string, query: Record export type SupportedTransformer = 'vue3' | 'vue2' +export type DtsDeclarationType = 'component' | 'directive' + +export type DtsConfigure = (info: ComponentInfo, declarationType: DtsDeclarationType) => string | false + export interface PublicPluginAPI { /** * Resolves a component using the configured resolvers. @@ -163,13 +167,13 @@ export interface Options { /** * Generate TypeScript declaration for global components * - * Accept boolean or a path related to project root + * Accept boolean, a path related to project root or a function that returns boolean or a path. * * @see https://github.com/vuejs/core/pull/3399 * @see https://github.com/johnsoncodehk/volar#using * @default true */ - dts?: boolean | string + dts?: boolean | string | DtsConfigure /** * Do not emit warning on component overriding @@ -227,7 +231,7 @@ export type ResolvedOptions = Omit< resolvedDirs: string[] globs: string[] globsExclude: string[] - dts: string | false + dts: false | DtsConfigure root: string } diff --git a/test/__snapshots__/dts.test.ts.snap b/test/__snapshots__/dts.test.ts.snap index 75752379..51c69003 100644 --- a/test/__snapshots__/dts.test.ts.snap +++ b/test/__snapshots__/dts.test.ts.snap @@ -1,7 +1,8 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`dts > components only 1`] = ` -"/* eslint-disable */ +[ + "/* eslint-disable */ // @ts-nocheck // Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 @@ -16,11 +17,13 @@ declare module 'vue' { TestComp: typeof import('test/component/TestComp')['default'] } } -" +", +] `; exports[`dts > directive only 1`] = ` -"/* eslint-disable */ +[ + "/* eslint-disable */ // @ts-nocheck // Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 @@ -33,11 +36,13 @@ declare module 'vue' { vLoading: typeof import('test/directive/Loading')['default'] } } -" +", +] `; exports[`dts > getDeclaration 1`] = ` -"/* eslint-disable */ +[ + "/* eslint-disable */ // @ts-nocheck // Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 @@ -55,7 +60,8 @@ declare module 'vue' { vLoading: typeof import('test/directive/Loading')['default'] } } -" +", +] `; exports[`dts > parseDeclaration - has icon component like 1`] = ` @@ -96,7 +102,8 @@ exports[`dts > parseDeclaration 1`] = ` `; exports[`dts > vue 2.7 components only 1`] = ` -"/* eslint-disable */ +[ + "/* eslint-disable */ // @ts-nocheck // Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 @@ -111,7 +118,8 @@ declare module 'vue' { TestComp: typeof import('test/component/TestComp')['default'] } } -" +", +] `; exports[`dts > writeDeclaration - keep unused 1`] = ` diff --git a/test/dts.test.ts b/test/dts.test.ts index b26ccba4..edc689b7 100644 --- a/test/dts.test.ts +++ b/test/dts.test.ts @@ -3,7 +3,7 @@ import { readFile, writeFile } from 'node:fs/promises' import path from 'node:path' import { describe, expect, it } from 'vitest' import { Context } from '../src/core/context' -import { getDeclaration, parseDeclaration } from '../src/core/declaration' +import { getDeclarations, parseDeclaration } from '../src/core/declaration' const resolver: ComponentResolver[] = [ { @@ -27,8 +27,8 @@ 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() + const declarations = await getDeclarations(ctx, false) + expect(Object.values(declarations ?? {})).toMatchSnapshot() }) it('writeDeclaration', async () => { @@ -87,38 +87,41 @@ const _directive_loading = _resolveDirective("loading")` const ctx = new Context({ resolvers: resolver, directives: true, + dts: 'test.d.ts', }) const code = 'const _component_test_comp = _resolveComponent("test-comp")' await ctx.transform(code, '') - const declarations = getDeclaration(ctx, 'test.d.ts') - expect(declarations).toMatchSnapshot() + const declarations = await getDeclarations(ctx, false) + expect(Object.values(declarations ?? {})).toMatchSnapshot() }) it('vue 2.7 components only', async () => { const ctx = new Context({ resolvers: resolver, directives: true, + dts: 'test.d.ts', version: 2.7, }) const code = 'const _component_test_comp = _c("test-comp")' await ctx.transform(code, '') - const declarations = getDeclaration(ctx, 'test.d.ts') - expect(declarations).toMatchSnapshot() + const declarations = await getDeclarations(ctx, false) + expect(Object.values(declarations ?? {})).toMatchSnapshot() }) it('directive only', async () => { const ctx = new Context({ resolvers: resolver, directives: true, + dts: 'test.d.ts', types: [], }) const code = 'const _directive_loading = _resolveDirective("loading")' await ctx.transform(code, '') - const declarations = getDeclaration(ctx, 'test.d.ts') - expect(declarations).toMatchSnapshot() + const declarations = await getDeclarations(ctx, false) + expect(Object.values(declarations ?? {})).toMatchSnapshot() }) it('parseDeclaration', async () => { From b66b287d82654e89286d2a67e566628540488fd9 Mon Sep 17 00:00:00 2001 From: Xingwang Liao Date: Fri, 9 May 2025 01:59:49 +0000 Subject: [PATCH 2/4] feat: set paramter default value for getDeclarations --- src/core/declaration.ts | 2 +- test/dts.test.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/declaration.ts b/src/core/declaration.ts index 2240bf20..608ebdd4 100644 --- a/src/core/declaration.ts +++ b/src/core/declaration.ts @@ -181,7 +181,7 @@ declare module 'vue' {` return code } -export async function getDeclarations(ctx: Context, removeUnused: boolean): Promise | undefined> { +export async function getDeclarations(ctx: Context, removeUnused = false): Promise | undefined> { const importsMap = getDeclarationImports(ctx) if (!importsMap || !Object.keys(importsMap).length) return undefined diff --git a/test/dts.test.ts b/test/dts.test.ts index edc689b7..594a5327 100644 --- a/test/dts.test.ts +++ b/test/dts.test.ts @@ -27,7 +27,7 @@ const _component_test_comp = _resolveComponent("test-comp") const _directive_loading = _resolveDirective("loading")` await ctx.transform(code, '') - const declarations = await getDeclarations(ctx, false) + const declarations = await getDeclarations(ctx) expect(Object.values(declarations ?? {})).toMatchSnapshot() }) @@ -92,7 +92,7 @@ const _directive_loading = _resolveDirective("loading")` const code = 'const _component_test_comp = _resolveComponent("test-comp")' await ctx.transform(code, '') - const declarations = await getDeclarations(ctx, false) + const declarations = await getDeclarations(ctx) expect(Object.values(declarations ?? {})).toMatchSnapshot() }) @@ -106,7 +106,7 @@ const _directive_loading = _resolveDirective("loading")` const code = 'const _component_test_comp = _c("test-comp")' await ctx.transform(code, '') - const declarations = await getDeclarations(ctx, false) + const declarations = await getDeclarations(ctx) expect(Object.values(declarations ?? {})).toMatchSnapshot() }) @@ -120,7 +120,7 @@ const _directive_loading = _resolveDirective("loading")` const code = 'const _directive_loading = _resolveDirective("loading")' await ctx.transform(code, '') - const declarations = await getDeclarations(ctx, false) + const declarations = await getDeclarations(ctx) expect(Object.values(declarations ?? {})).toMatchSnapshot() }) From 035343c2471d64b2cf551eba060b6e021eeb225b Mon Sep 17 00:00:00 2001 From: Xingwang Liao Date: Fri, 9 May 2025 02:31:00 +0000 Subject: [PATCH 3/4] feat: add tests for getDeclaration and writeDeclaration with multiple files support --- test/__snapshots__/dts.test.ts.snap | 140 ++++++++++++++++++++++++++++ test/dts.test.ts | 128 ++++++++++++++++++++++++- 2 files changed, 266 insertions(+), 2 deletions(-) diff --git a/test/__snapshots__/dts.test.ts.snap b/test/__snapshots__/dts.test.ts.snap index 51c69003..60de08e1 100644 --- a/test/__snapshots__/dts.test.ts.snap +++ b/test/__snapshots__/dts.test.ts.snap @@ -40,6 +40,110 @@ declare module 'vue' { ] `; +exports[`dts > getDeclaration - filter 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 { + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + TestComp: typeof import('test/component/TestComp')['default'] + } +} +", +] +`; + +exports[`dts > getDeclaration - function expression 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 { + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + TestComp: typeof import('test/component/TestComp')['default'] + } + export interface ComponentCustomProperties { + vLoading: typeof import('test/directive/Loading')['default'] + } +} +", +] +`; + +exports[`dts > getDeclaration - multiple files 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 { + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + TestComp: typeof import('test/component/TestComp')['default'] + } +} +", + "/* 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 ComponentCustomProperties { + vLoading: typeof import('test/directive/Loading')['default'] + } +} +", +] +`; + +exports[`dts > getDeclaration - return absolute path 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 { + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + TestComp: typeof import('test/component/TestComp')['default'] + } + export interface ComponentCustomProperties { + vLoading: typeof import('test/directive/Loading')['default'] + } +} +", +] +`; + exports[`dts > getDeclaration 1`] = ` [ "/* eslint-disable */ @@ -147,6 +251,42 @@ declare module 'vue' { " `; +exports[`dts > writeDeclaration - multiple files 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 { + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + TestComp: typeof import('test/component/TestComp')['default'] + } +} +" +`; + +exports[`dts > writeDeclaration - multiple files 2`] = ` +"/* 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 ComponentCustomProperties { + vLoading: typeof import('test/directive/Loading')['default'] + } +} +" +`; + exports[`dts > writeDeclaration 1`] = ` "/* eslint-disable */ // @ts-nocheck diff --git a/test/dts.test.ts b/test/dts.test.ts index 594a5327..f8b78e36 100644 --- a/test/dts.test.ts +++ b/test/dts.test.ts @@ -1,7 +1,7 @@ -import type { ComponentResolver } from '../src' +import type { ComponentInfo, ComponentResolver, DtsDeclarationType } from '../src' import { readFile, writeFile } from 'node:fs/promises' import path from 'node:path' -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { Context } from '../src/core/context' import { getDeclarations, parseDeclaration } from '../src/core/declaration' @@ -31,6 +31,110 @@ const _directive_loading = _resolveDirective("loading")` expect(Object.values(declarations ?? {})).toMatchSnapshot() }) + it('getDeclaration - function expression', async () => { + const ctx = new Context({ + resolvers: resolver, + directives: true, + dts: () => 'test.d.ts', + }) + + const filepath = path.resolve(__dirname, '../test.d.ts') + + const code = ` +const _component_test_comp = _resolveComponent("test-comp") +const _directive_loading = _resolveDirective("loading")` + await ctx.transform(code, '') + + const declarations = await getDeclarations(ctx) + + expect(Object.keys(declarations ?? {})).toEqual([filepath]) + expect(Object.values(declarations ?? {})).toMatchSnapshot() + }) + + it('getDeclaration - return absolute path', async () => { + const filepath = path.resolve(__dirname, 'test.d.ts') + + const ctx = new Context({ + resolvers: resolver, + directives: true, + dts: () => filepath, + }) + + const code = ` +const _component_test_comp = _resolveComponent("test-comp") +const _directive_loading = _resolveDirective("loading")` + await ctx.transform(code, '') + + const declarations = await getDeclarations(ctx) + + expect(Object.keys(declarations ?? {})).toEqual([filepath]) + expect(Object.values(declarations ?? {})).toMatchSnapshot() + }) + + it('getDeclaration - return false', async () => { + const ctx = new Context({ + resolvers: resolver, + directives: true, + dts: () => false, + }) + + const code = ` +const _component_test_comp = _resolveComponent("test-comp") +const _directive_loading = _resolveDirective("loading")` + await ctx.transform(code, '') + const declarations = await getDeclarations(ctx) + expect(declarations).toBeUndefined() + }) + + it('getDeclaration - multiple files', async () => { + const fn = vi.fn().mockImplementation((_info: ComponentInfo, type: DtsDeclarationType) => { + return type === 'component' ? 'test.d.ts' : 'test2.d.ts' + }) + + const ctx = new Context({ + resolvers: resolver, + directives: true, + dts: fn, + }) + + const filepath = path.resolve(__dirname, '../test.d.ts') + const filepath2 = path.resolve(__dirname, '../test2.d.ts') + + const code = ` +const _component_test_comp = _resolveComponent("test-comp") +const _directive_loading = _resolveDirective("loading")` + + await ctx.transform(code, '') + + const declarations = await getDeclarations(ctx) + + expect(fn).toBeCalledTimes(4) + expect(fn).toBeCalledWith({ as: 'TestComp', from: 'test/component/TestComp' } satisfies ComponentInfo, 'component') + expect(fn).toBeCalledWith({ as: 'vLoading', from: 'test/directive/Loading' } satisfies ComponentInfo, 'directive') + expect(fn).toBeCalledWith({ from: 'vue-router', name: 'RouterView', as: 'RouterView' } satisfies ComponentInfo, 'component') + expect(fn).toBeCalledWith({ from: 'vue-router', name: 'RouterLink', as: 'RouterLink' } satisfies ComponentInfo, 'component') + + expect(Object.keys(declarations ?? {})).toEqual([filepath, filepath2]) + expect(Object.values(declarations ?? {})).toMatchSnapshot() + }) + + it('getDeclaration - filter', async () => { + const ctx = new Context({ + resolvers: resolver, + directives: true, + dts: (_, type) => type === 'component' ? 'test.d.ts' : false, + }) + + const code = ` +const _component_test_comp = _resolveComponent("test-comp") +const _directive_loading = _resolveDirective("loading")` + await ctx.transform(code, '') + + const declarations = await getDeclarations(ctx) + + expect(Object.values(declarations ?? {})).toMatchSnapshot() + }) + it('writeDeclaration', async () => { const filepath = path.resolve(__dirname, 'tmp/dts-test.d.ts') const ctx = new Context({ @@ -83,6 +187,26 @@ const _directive_loading = _resolveDirective("loading")` expect(contents).toContain('vSome') }) + it('writeDeclaration - multiple files', async () => { + const filepath = path.resolve(__dirname, 'tmp/dts-test.d.ts') + const filepath2 = path.resolve(__dirname, 'tmp/dts-test2.d.ts') + + const ctx = new Context({ + resolvers: resolver, + directives: true, + dts: (_, type) => (type === 'component' ? filepath : filepath2), + }) + + const code = ` +const _component_test_comp = _resolveComponent("test-comp") +const _directive_loading = _resolveDirective("loading")` + await ctx.transform(code, '') + await ctx._generateDeclaration() + + expect(await readFile(filepath, 'utf-8')).matchSnapshot() + expect(await readFile(filepath2, 'utf-8')).matchSnapshot() + }) + it('components only', async () => { const ctx = new Context({ resolvers: resolver, From 5f090190ebcca2b9296972f719cb30a784eb4150 Mon Sep 17 00:00:00 2001 From: Xingwang Liao Date: Fri, 9 May 2025 02:39:55 +0000 Subject: [PATCH 4/4] docs: update docs for new dts option --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cdf4dfe4..ec1c3ac3 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,22 @@ Once the setup is done, a `components.d.ts` will be generated and updates automa > **Make sure you also add `components.d.ts` to your `tsconfig.json` under `include`.** +We also provide a way to generate multiple `d.ts` files for components or directives. You can pass a function to `dts` option, which will be called with the component info and type. You can return a string or a boolean to indicate whether to generate it to a file or not. + +```ts +Components({ + dts: (componentInfo, type) => { + if (type === 'component') { + return 'components.d.ts' + } + else if (type === 'directive') { + return 'directives.d.ts' + } + return false + }, +}) +``` + ## Importing from UI Libraries We have several built-in resolvers for popular UI libraries like **Vuetify**, **Ant Design Vue**, and **Element Plus**, where you can enable them by: @@ -371,7 +387,7 @@ Components({ resolvers: [], // generate `components.d.ts` global declarations, - // also accepts a path for custom filename + // also accepts a path, a custom filename or a function that returns a path or a boolean // default: `true` if package typescript is installed dts: false, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy