diff --git a/src/api-extractor.ts b/src/api-extractor.ts index 2d66a5031..495d153bd 100644 --- a/src/api-extractor.ts +++ b/src/api-extractor.ts @@ -88,10 +88,18 @@ async function rollupDtsFiles( exports: ExportDeclaration[], format: Format, ) { + if (!options.experimentalDts || !options.experimentalDts?.entry) { + return + } + + /** + * `.tsup/declaration` directory + */ const declarationDir = ensureTempDeclarationDir() const outDir = options.outDir || 'dist' const pkg = await loadPkg(process.cwd()) const dtsExtension = defaultOutExtension({ format, pkgType: pkg.type }).dts + const tsconfig = options.tsconfig || 'tsconfig.json' let dtsInputFilePath = path.join( declarationDir, @@ -113,16 +121,40 @@ async function rollupDtsFiles( formatAggregationExports(exports, declarationDir), ) - rollupDtsFile( - dtsInputFilePath, - dtsOutputFilePath, - options.tsconfig || 'tsconfig.json', - ) + rollupDtsFile(dtsInputFilePath, dtsOutputFilePath, tsconfig) for (let [out, sourceFileName] of Object.entries( - options.experimentalDts!.entry, + options.experimentalDts.entry, )) { + /** + * Source file name (`src/index.ts`) + * + * @example + * + * ```ts + * import { defineConfig } from 'tsup' + * + * export default defineConfig({ + * entry: { index: 'src/index.ts' }, + * // Here `src/index.ts` is our `sourceFileName`. + * }) + * ``` + */ sourceFileName = toAbsolutePath(sourceFileName) + /** + * Output file name (`dist/index.d.ts`) + * + * @example + * + * ```ts + * import { defineConfig } from 'tsup' + * + * export default defineConfig({ + * entry: { index: 'src/index.ts' }, + * // Here `dist/index.d.ts` is our `outFileName`. + * }) + * ``` + */ const outFileName = path.join(outDir, out + dtsExtension) // Find all declarations that are exported from the current source file diff --git a/src/exports.ts b/src/exports.ts index cb803b4c9..5cd75188d 100644 --- a/src/exports.ts +++ b/src/exports.ts @@ -1,5 +1,5 @@ import path from 'node:path' -import { slash, trimDtsExtension, truthy } from './utils' +import { replaceDtsWithJsExtensions, slash, truthy } from './utils' export type ExportDeclaration = ModuleExport | NamedExport @@ -41,14 +41,14 @@ function formatAggregationExport( declaration: ExportDeclaration, declarationDirPath: string, ): string { - const dest = trimDtsExtension( + const dest = replaceDtsWithJsExtensions( `./${path.posix.normalize( slash(path.relative(declarationDirPath, declaration.destFileName)), )}`, ) if (declaration.kind === 'module') { - // No implemeted + // Not implemented return '' } else if (declaration.kind === 'named') { return [ @@ -72,7 +72,7 @@ export function formatDistributionExports( fromFilePath: string, toFilePath: string, ) { - let importPath = trimDtsExtension( + let importPath = replaceDtsWithJsExtensions( path.posix.relative( path.posix.dirname(path.posix.normalize(slash(fromFilePath))), path.posix.normalize(slash(toFilePath)), diff --git a/src/index.ts b/src/index.ts index c63967957..85a6ea041 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,8 +12,9 @@ import { type MaybePromise, debouncePromise, removeFiles, + resolveExperimentalDtsConfig, + resolveInitialExperimentalDtsConfig, slash, - toObjectEntry, } from './utils' import { createLogger, setSilent } from './log' import { runEsbuild } from './esbuild' @@ -92,20 +93,10 @@ const normalizeOptions = async ( : typeof _options.dts === 'string' ? { entry: _options.dts } : _options.dts, - experimentalDts: _options.experimentalDts - ? typeof _options.experimentalDts === 'boolean' - ? _options.experimentalDts - ? { entry: {} } - : undefined - : typeof _options.experimentalDts === 'string' - ? { - entry: toObjectEntry(_options.experimentalDts), - } - : { - ..._options.experimentalDts, - entry: toObjectEntry(_options.experimentalDts.entry || {}), - } - : undefined, + + experimentalDts: await resolveInitialExperimentalDtsConfig( + _options.experimentalDts, + ), } setSilent(options.silent) @@ -151,17 +142,14 @@ const normalizeOptions = async ( ...(options.dts.compilerOptions || {}), } } + if (options.experimentalDts) { - options.experimentalDts.compilerOptions = { - ...(tsconfig.data.compilerOptions || {}), - ...(options.experimentalDts.compilerOptions || {}), - } - options.experimentalDts.entry = toObjectEntry( - Object.keys(options.experimentalDts.entry).length > 0 - ? options.experimentalDts.entry - : options.entry, + options.experimentalDts = await resolveExperimentalDtsConfig( + options as NormalizedOptions, + tsconfig, ) } + if (!options.target) { options.target = tsconfig.data?.compilerOptions?.target?.toLowerCase() } @@ -252,7 +240,7 @@ export async function build(_options: Options) { worker.on('message', (data) => { if (data === 'error') { terminateWorker() - reject(new Error('error occured in dts build')) + reject(new Error('error occurred in dts build')) } else if (data === 'success') { terminateWorker() resolve() diff --git a/src/tsc.ts b/src/tsc.ts index ce4d4c99b..3b1aa387d 100644 --- a/src/tsc.ts +++ b/src/tsc.ts @@ -174,6 +174,7 @@ function emit(compilerOptions?: any, tsconfig?: string) { ...rawTsconfig.data, compilerOptions: { ...rawTsconfig.data?.compilerOptions, + ...compilerOptions, // Enable declaration emit and disable javascript emit noEmit: false, diff --git a/src/utils.ts b/src/utils.ts index 3e6294a2a..e69cd3ad7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,16 @@ import fs from 'node:fs' import path from 'node:path' import resolveFrom from 'resolve-from' +import type { InputOption } from 'rollup' import strip from 'strip-json-comments' import { glob } from 'tinyglobby' -import type { Entry, Format } from './options' +import type { + Entry, + Format, + NormalizedExperimentalDtsConfig, + NormalizedOptions, + Options, +} from './options' export type MaybePromise = T | Promise @@ -242,3 +249,176 @@ export function writeFileSync(filePath: string, content: string) { fs.mkdirSync(path.dirname(filePath), { recursive: true }) fs.writeFileSync(filePath, content) } + +/** + * Replaces TypeScript declaration file + * extensions (`.d.ts`, `.d.mts`, `.d.cts`) + * with their corresponding JavaScript variants (`.js`, `.mjs`, `.cjs`). + * + * @param dtsFilePath - The file path to be transformed. + * @returns The updated file path with the JavaScript extension. + * + * @internal + */ +export function replaceDtsWithJsExtensions(dtsFilePath: string) { + return dtsFilePath.replace( + /\.d\.(ts|mts|cts)$/, + (_, fileExtension: string) => { + switch (fileExtension) { + case 'ts': + return '.js' + case 'mts': + return '.mjs' + case 'cts': + return '.cjs' + default: + return '' + } + }, + ) +} + +/** + * Converts an array of {@link NormalizedOptions.entry | entry paths} + * into an object where the keys represent the output + * file names (without extensions) and the values + * represent the corresponding input file paths. + * + * @param arrayOfEntries - An array of file path entries as strings. + * @returns An object where the keys are the output file name and the values are the input file name. + * + * @example + * + * ```ts + * import { defineConfig } from 'tsup' + * + * export default defineConfig({ + * entry: ['src/index.ts', 'src/types.ts'], + * // Becomes `{ index: 'src/index.ts', types: 'src/types.ts' }` + * }) + * ``` + * + * @internal + */ +const convertArrayEntriesToObjectEntries = (arrayOfEntries: string[]) => { + const objectEntries = Object.fromEntries( + arrayOfEntries.map( + (entry) => + [ + path.posix.join( + ...entry + .split(path.posix.sep) + .slice(1, -1) + .concat(path.parse(entry).name), + ), + entry, + ] as const, + ), + ) + + return objectEntries +} + +/** + * Resolves and standardizes entry paths into an object format. If the provided + * entry is a string or an array of strings, it resolves any potential glob + * patterns amd converts the result into an entry object. If the input is + * already an object, it is returned as-is. + * + * @example + * + * ```ts + * import { defineConfig } from 'tsup' + * + * export default defineConfig({ + * entry: { index: 'src/index.ts' }, + * format: ['esm', 'cjs'], + * experimentalDts: { entry: 'src/**\/*.ts' }, + * // becomes experimentalDts: { entry: { index: 'src/index.ts', types: 'src/types.ts } } + * }) + * ``` + * + * @internal + */ +const resolveEntryPaths = async (entryPaths: InputOption) => { + const resolvedEntryPaths = + typeof entryPaths === 'string' || Array.isArray(entryPaths) + ? convertArrayEntriesToObjectEntries(await glob(entryPaths)) + : entryPaths + + return resolvedEntryPaths +} + +/** + * Resolves the + * {@link NormalizedExperimentalDtsConfig | experimental DTS config} by + * resolving entry paths and merging the provided TypeScript configuration + * options. + * + * @param options - The options containing entry points and experimental DTS + * configuration. + * @param tsconfig - The loaded TypeScript configuration data. + * + * @internal + */ +export const resolveExperimentalDtsConfig = async ( + options: NormalizedOptions, + tsconfig: any, +): Promise => { + const resolvedEntryPaths = await resolveEntryPaths( + options.experimentalDts?.entry || options.entry, + ) + + // Fallback to `options.entry` if we end up with an empty object. + const experimentalDtsObjectEntry = + Object.keys(resolvedEntryPaths).length === 0 + ? Array.isArray(options.entry) + ? convertArrayEntriesToObjectEntries(options.entry) + : options.entry + : resolvedEntryPaths + + const normalizedExperimentalDtsConfig: NormalizedExperimentalDtsConfig = { + compilerOptions: { + ...(tsconfig.data.compilerOptions || {}), + ...(options.experimentalDts?.compilerOptions || {}), + }, + + entry: experimentalDtsObjectEntry, + } + + return normalizedExperimentalDtsConfig +} + +/** + * Resolves the initial experimental DTS configuration into a consistent + * {@link NormalizedExperimentalDtsConfig} object. + * + * @internal + */ +export const resolveInitialExperimentalDtsConfig = async ( + experimentalDts: Options['experimentalDts'], +): Promise => { + if (experimentalDts == null) { + return + } + + if (typeof experimentalDts === 'boolean') + return experimentalDts ? { entry: {} } : undefined + + if (typeof experimentalDts === 'string') { + // Treats the string as a glob pattern, resolving it to entry paths and + // returning an object with the `entry` property. + return { + entry: convertArrayEntriesToObjectEntries(await glob(experimentalDts)), + } + } + + return { + ...experimentalDts, + + entry: + experimentalDts?.entry == null + ? {} + : await resolveEntryPaths(experimentalDts.entry), + } +} diff --git a/test/__snapshots__/dts.test.ts.snap b/test/__snapshots__/dts.test.ts.snap index af7f717a3..2de72a3ac 100644 --- a/test/__snapshots__/dts.test.ts.snap +++ b/test/__snapshots__/dts.test.ts.snap @@ -234,95 +234,95 @@ export { } // dist/index.d.mts ////////////////////////////////////////////////////////////////////// -export { VERSION } from './_tsup-dts-rollup'; -export { render_alias_1 as render } from './_tsup-dts-rollup'; -export { ClientRenderOptions_alias_1 as ClientRenderOptions } from './_tsup-dts-rollup'; -export { sharedFunction_alias_1 as sharedFunction } from './_tsup-dts-rollup'; -export { sharedType_alias_1 as sharedType } from './_tsup-dts-rollup'; +export { VERSION } from './_tsup-dts-rollup.mjs'; +export { render_alias_1 as render } from './_tsup-dts-rollup.mjs'; +export { ClientRenderOptions_alias_1 as ClientRenderOptions } from './_tsup-dts-rollup.mjs'; +export { sharedFunction_alias_1 as sharedFunction } from './_tsup-dts-rollup.mjs'; +export { sharedType_alias_1 as sharedType } from './_tsup-dts-rollup.mjs'; ////////////////////////////////////////////////////////////////////// // dist/index.d.ts ////////////////////////////////////////////////////////////////////// -export { VERSION } from './_tsup-dts-rollup'; -export { render_alias_1 as render } from './_tsup-dts-rollup'; -export { ClientRenderOptions_alias_1 as ClientRenderOptions } from './_tsup-dts-rollup'; -export { sharedFunction_alias_1 as sharedFunction } from './_tsup-dts-rollup'; -export { sharedType_alias_1 as sharedType } from './_tsup-dts-rollup'; +export { VERSION } from './_tsup-dts-rollup.js'; +export { render_alias_1 as render } from './_tsup-dts-rollup.js'; +export { ClientRenderOptions_alias_1 as ClientRenderOptions } from './_tsup-dts-rollup.js'; +export { sharedFunction_alias_1 as sharedFunction } from './_tsup-dts-rollup.js'; +export { sharedType_alias_1 as sharedType } from './_tsup-dts-rollup.js'; ////////////////////////////////////////////////////////////////////// // dist/my-lib-client.d.mts ////////////////////////////////////////////////////////////////////// -export { render } from './_tsup-dts-rollup'; -export { ClientRenderOptions } from './_tsup-dts-rollup'; -export { sharedFunction } from './_tsup-dts-rollup'; -export { sharedType } from './_tsup-dts-rollup'; +export { render } from './_tsup-dts-rollup.mjs'; +export { ClientRenderOptions } from './_tsup-dts-rollup.mjs'; +export { sharedFunction } from './_tsup-dts-rollup.mjs'; +export { sharedType } from './_tsup-dts-rollup.mjs'; ////////////////////////////////////////////////////////////////////// // dist/my-lib-client.d.ts ////////////////////////////////////////////////////////////////////// -export { render } from './_tsup-dts-rollup'; -export { ClientRenderOptions } from './_tsup-dts-rollup'; -export { sharedFunction } from './_tsup-dts-rollup'; -export { sharedType } from './_tsup-dts-rollup'; +export { render } from './_tsup-dts-rollup.js'; +export { ClientRenderOptions } from './_tsup-dts-rollup.js'; +export { sharedFunction } from './_tsup-dts-rollup.js'; +export { sharedType } from './_tsup-dts-rollup.js'; ////////////////////////////////////////////////////////////////////// // dist/server/index.d.mts ////////////////////////////////////////////////////////////////////// -export { render_alias_2 as render } from '../_tsup-dts-rollup'; -export { default_alias as default } from '../_tsup-dts-rollup'; -export { ServerRenderOptions } from '../_tsup-dts-rollup'; -export { serverConstant } from '../_tsup-dts-rollup'; -export { serverConstantAlias } from '../_tsup-dts-rollup'; -export { ServerClass } from '../_tsup-dts-rollup'; -export { ServerThirdPartyNamespace } from '../_tsup-dts-rollup'; -export { sharedFunction_alias_2 as sharedFunction } from '../_tsup-dts-rollup'; -export { sharedType_alias_2 as sharedType } from '../_tsup-dts-rollup'; -export { renderToPipeableStream } from '../_tsup-dts-rollup'; -export { renderToString } from '../_tsup-dts-rollup'; -export { renderToNodeStream } from '../_tsup-dts-rollup'; -export { renderToStaticMarkup } from '../_tsup-dts-rollup'; -export { renderToStaticNodeStream } from '../_tsup-dts-rollup'; -export { renderToReadableStream } from '../_tsup-dts-rollup'; -export { RenderToPipeableStreamOptions } from '../_tsup-dts-rollup'; -export { PipeableStream } from '../_tsup-dts-rollup'; -export { ServerOptions } from '../_tsup-dts-rollup'; -export { RenderToReadableStreamOptions } from '../_tsup-dts-rollup'; -export { ReactDOMServerReadableStream } from '../_tsup-dts-rollup'; -export { version } from '../_tsup-dts-rollup'; +export { render_alias_2 as render } from '../_tsup-dts-rollup.mjs'; +export { default_alias as default } from '../_tsup-dts-rollup.mjs'; +export { ServerRenderOptions } from '../_tsup-dts-rollup.mjs'; +export { serverConstant } from '../_tsup-dts-rollup.mjs'; +export { serverConstantAlias } from '../_tsup-dts-rollup.mjs'; +export { ServerClass } from '../_tsup-dts-rollup.mjs'; +export { ServerThirdPartyNamespace } from '../_tsup-dts-rollup.mjs'; +export { sharedFunction_alias_2 as sharedFunction } from '../_tsup-dts-rollup.mjs'; +export { sharedType_alias_2 as sharedType } from '../_tsup-dts-rollup.mjs'; +export { renderToPipeableStream } from '../_tsup-dts-rollup.mjs'; +export { renderToString } from '../_tsup-dts-rollup.mjs'; +export { renderToNodeStream } from '../_tsup-dts-rollup.mjs'; +export { renderToStaticMarkup } from '../_tsup-dts-rollup.mjs'; +export { renderToStaticNodeStream } from '../_tsup-dts-rollup.mjs'; +export { renderToReadableStream } from '../_tsup-dts-rollup.mjs'; +export { RenderToPipeableStreamOptions } from '../_tsup-dts-rollup.mjs'; +export { PipeableStream } from '../_tsup-dts-rollup.mjs'; +export { ServerOptions } from '../_tsup-dts-rollup.mjs'; +export { RenderToReadableStreamOptions } from '../_tsup-dts-rollup.mjs'; +export { ReactDOMServerReadableStream } from '../_tsup-dts-rollup.mjs'; +export { version } from '../_tsup-dts-rollup.mjs'; ////////////////////////////////////////////////////////////////////// // dist/server/index.d.ts ////////////////////////////////////////////////////////////////////// -export { render_alias_2 as render } from '../_tsup-dts-rollup'; -export { default_alias as default } from '../_tsup-dts-rollup'; -export { ServerRenderOptions } from '../_tsup-dts-rollup'; -export { serverConstant } from '../_tsup-dts-rollup'; -export { serverConstantAlias } from '../_tsup-dts-rollup'; -export { ServerClass } from '../_tsup-dts-rollup'; -export { ServerThirdPartyNamespace } from '../_tsup-dts-rollup'; -export { sharedFunction_alias_2 as sharedFunction } from '../_tsup-dts-rollup'; -export { sharedType_alias_2 as sharedType } from '../_tsup-dts-rollup'; -export { renderToPipeableStream } from '../_tsup-dts-rollup'; -export { renderToString } from '../_tsup-dts-rollup'; -export { renderToNodeStream } from '../_tsup-dts-rollup'; -export { renderToStaticMarkup } from '../_tsup-dts-rollup'; -export { renderToStaticNodeStream } from '../_tsup-dts-rollup'; -export { renderToReadableStream } from '../_tsup-dts-rollup'; -export { RenderToPipeableStreamOptions } from '../_tsup-dts-rollup'; -export { PipeableStream } from '../_tsup-dts-rollup'; -export { ServerOptions } from '../_tsup-dts-rollup'; -export { RenderToReadableStreamOptions } from '../_tsup-dts-rollup'; -export { ReactDOMServerReadableStream } from '../_tsup-dts-rollup'; -export { version } from '../_tsup-dts-rollup'; +export { render_alias_2 as render } from '../_tsup-dts-rollup.js'; +export { default_alias as default } from '../_tsup-dts-rollup.js'; +export { ServerRenderOptions } from '../_tsup-dts-rollup.js'; +export { serverConstant } from '../_tsup-dts-rollup.js'; +export { serverConstantAlias } from '../_tsup-dts-rollup.js'; +export { ServerClass } from '../_tsup-dts-rollup.js'; +export { ServerThirdPartyNamespace } from '../_tsup-dts-rollup.js'; +export { sharedFunction_alias_2 as sharedFunction } from '../_tsup-dts-rollup.js'; +export { sharedType_alias_2 as sharedType } from '../_tsup-dts-rollup.js'; +export { renderToPipeableStream } from '../_tsup-dts-rollup.js'; +export { renderToString } from '../_tsup-dts-rollup.js'; +export { renderToNodeStream } from '../_tsup-dts-rollup.js'; +export { renderToStaticMarkup } from '../_tsup-dts-rollup.js'; +export { renderToStaticNodeStream } from '../_tsup-dts-rollup.js'; +export { renderToReadableStream } from '../_tsup-dts-rollup.js'; +export { RenderToPipeableStreamOptions } from '../_tsup-dts-rollup.js'; +export { PipeableStream } from '../_tsup-dts-rollup.js'; +export { ServerOptions } from '../_tsup-dts-rollup.js'; +export { RenderToReadableStreamOptions } from '../_tsup-dts-rollup.js'; +export { ReactDOMServerReadableStream } from '../_tsup-dts-rollup.js'; +export { version } from '../_tsup-dts-rollup.js'; " `; diff --git a/test/dts.test.ts b/test/dts.test.ts index 174a44388..941db49d8 100644 --- a/test/dts.test.ts +++ b/test/dts.test.ts @@ -258,29 +258,29 @@ test('should emit declaration files with experimentalDts', async () => { export function sharedFunction(value: T): T | null { return value || null } - + type sharedType = { shared: boolean } - + export type { sharedType } `, 'src/server.ts': ` export * from './shared' /** - * Comment for server render function + * Comment for server render function */ export function render(options: ServerRenderOptions): string { return JSON.stringify(options) } - + export interface ServerRenderOptions { /** * Comment for ServerRenderOptions.stream - * + * * @public - * + * * @my_custom_tag */ stream: boolean @@ -298,7 +298,7 @@ test('should emit declaration files with experimentalDts', async () => { import * as ServerThirdPartyNamespace from 'react-dom'; export { ServerThirdPartyNamespace } - // Export a third party module + // Export a third party module export * from 'react-dom/server'; `, @@ -308,7 +308,7 @@ test('should emit declaration files with experimentalDts', async () => { export function render(options: ClientRenderOptions): string { return JSON.stringify(options) } - + export interface ClientRenderOptions { document: boolean } @@ -385,6 +385,13 @@ test('.d.ts files should be cleaned when --clean and --experimental-dts are prov const filesFoo = { 'package.json': `{ "name": "tsup-playground", "private": true }`, 'foo.ts': `export const foo = 1`, + 'tsconfig.json': JSON.stringify( + { + compilerOptions: { skipLibCheck: true }, + }, + null, + 2, + ), } const filesFooBar = { diff --git a/test/experimental-dts.test.ts b/test/experimental-dts.test.ts new file mode 100644 index 000000000..825afbbfc --- /dev/null +++ b/test/experimental-dts.test.ts @@ -0,0 +1,606 @@ +import { test } from 'vitest' +import type { Options } from '../src/index.js' +import { getTestName, run } from './utils.js' + +test.for([ + { moduleResolution: 'NodeNext', moduleKind: 'NodeNext' }, + { moduleResolution: 'Node16', moduleKind: 'Node16' }, + { moduleResolution: 'Bundler', moduleKind: 'ESNext' }, + { moduleResolution: 'Bundler', moduleKind: 'Preserve' }, + { moduleResolution: 'Node10', moduleKind: 'ESNext' }, + { moduleResolution: 'Node10', moduleKind: 'CommonJS' }, + { moduleResolution: 'Node', moduleKind: 'ESNext' }, + { moduleResolution: 'Node', moduleKind: 'CommonJS' }, +] as const)( + "experimentalDts works with TypeScript's $moduleResolution module resolution and module set to $moduleKind", + async ({ moduleResolution, moduleKind }, { expect, task }) => { + const { getFileContent, outFiles } = await run( + getTestName(), + { + 'src/types.ts': `export type Person = { name: string }`, + 'src/index.ts': `export const foo = [1, 2, 3]\nexport type { Person } from './types.js'`, + 'tsup.config.ts': `export default ${JSON.stringify( + { + name: task.name, + entry: { index: 'src/index.ts' }, + format: ['esm', 'cjs'], + experimentalDts: true, + } satisfies Options, + null, + 2, + )}`, + 'package.json': JSON.stringify( + { + name: 'testing-experimental-dts', + description: task.name, + type: 'module', + }, + null, + 2, + ), + 'tsconfig.json': JSON.stringify( + { + compilerOptions: { + module: moduleKind, + moduleResolution, + outDir: './dist', + rootDir: './src', + skipLibCheck: true, + strict: true, + }, + include: ['src'], + }, + null, + 2, + ), + }, + { + entry: [], + }, + ) + + expect(outFiles).toStrictEqual([ + '_tsup-dts-rollup.d.cts', + '_tsup-dts-rollup.d.ts', + 'index.cjs', + 'index.d.cts', + 'index.d.ts', + 'index.js', + ]) + + const indexDtsContent = [ + `export { foo } from './_tsup-dts-rollup.js';`, + `export { Person } from './_tsup-dts-rollup.js';\n`, + ].join('\n') + + expect(await getFileContent('dist/index.d.ts')).toStrictEqual( + indexDtsContent, + ) + + expect(await getFileContent('dist/index.d.cts')).toStrictEqual( + indexDtsContent.replaceAll( + `'./_tsup-dts-rollup.js'`, + `'./_tsup-dts-rollup.cjs'`, + ), + ) + }, +) + +test('experimentalDts works when `entry` is set to an array', async ({ + expect, + task, +}) => { + const { getFileContent, outFiles } = await run( + getTestName(), + { + 'src/types.ts': `export type Person = { name: string }`, + 'src/index.ts': `export const foo = [1, 2, 3]\nexport type { Person } from './types.js'`, + 'tsup.config.ts': `export default ${JSON.stringify( + { + name: task.name, + entry: ['src/index.ts'], + format: ['esm', 'cjs'], + experimentalDts: true, + } satisfies Options, + null, + 2, + )}`, + 'package.json': JSON.stringify( + { + name: 'testing-experimental-dts-entry-array', + description: task.name, + type: 'module', + }, + null, + 2, + ), + 'tsconfig.json': JSON.stringify( + { + compilerOptions: { + outDir: './dist', + rootDir: './src', + skipLibCheck: true, + strict: true, + }, + include: ['src'], + }, + null, + 2, + ), + }, + { + entry: [], + }, + ) + + expect(outFiles).toStrictEqual([ + '_tsup-dts-rollup.d.cts', + '_tsup-dts-rollup.d.ts', + 'index.cjs', + 'index.d.cts', + 'index.d.ts', + 'index.js', + ]) + + const indexDtsContent = [ + `export { foo } from './_tsup-dts-rollup.js';`, + `export { Person } from './_tsup-dts-rollup.js';\n`, + ].join('\n') + + expect(await getFileContent('dist/index.d.ts')).toStrictEqual(indexDtsContent) + + expect(await getFileContent('dist/index.d.cts')).toStrictEqual( + indexDtsContent.replaceAll( + `'./_tsup-dts-rollup.js'`, + `'./_tsup-dts-rollup.cjs'`, + ), + ) +}) + +test('experimentalDts works when `entry` is set to an array of globs', async ({ + expect, + task, +}) => { + const { getFileContent, outFiles } = await run( + getTestName(), + { + 'src/types.ts': `export type Person = { name: string }`, + 'src/index.ts': `export const foo = [1, 2, 3]\nexport type { Person } from './types.js'`, + 'tsup.config.ts': `export default ${JSON.stringify( + { + name: task.name, + entry: ['src/**/*.ts'], + format: ['esm', 'cjs'], + experimentalDts: true, + } satisfies Options, + null, + 2, + )}`, + 'package.json': JSON.stringify( + { + name: 'entry-array-of-globs', + description: task.name, + type: 'module', + }, + null, + 2, + ), + 'tsconfig.json': JSON.stringify( + { + compilerOptions: { + outDir: './dist', + rootDir: './src', + skipLibCheck: true, + strict: true, + }, + include: ['src'], + }, + null, + 2, + ), + }, + { + entry: [], + }, + ) + + expect(outFiles).toStrictEqual([ + '_tsup-dts-rollup.d.cts', + '_tsup-dts-rollup.d.ts', + 'index.cjs', + 'index.d.cts', + 'index.d.ts', + 'index.js', + 'types.cjs', + 'types.d.cts', + 'types.d.ts', + 'types.js', + ]) + + const indexDtsContent = [ + `export { foo } from './_tsup-dts-rollup.js';`, + `export { Person } from './_tsup-dts-rollup.js';\n`, + ].join('\n') + + const typesDtsContent = `export { Person_alias_1 as Person } from './_tsup-dts-rollup.js';\n` + + expect(await getFileContent('dist/index.d.ts')).toStrictEqual(indexDtsContent) + + expect(await getFileContent('dist/index.d.cts')).toStrictEqual( + indexDtsContent.replaceAll( + `'./_tsup-dts-rollup.js'`, + `'./_tsup-dts-rollup.cjs'`, + ), + ) + + expect(await getFileContent('dist/types.d.ts')).toStrictEqual(typesDtsContent) + + expect(await getFileContent('dist/types.d.cts')).toStrictEqual( + typesDtsContent.replaceAll( + `'./_tsup-dts-rollup.js'`, + `'./_tsup-dts-rollup.cjs'`, + ), + ) +}) + +test('experimentalDts.entry can work independent from `options.entry`', async ({ + expect, + task, +}) => { + const { getFileContent, outFiles } = await run( + getTestName(), + { + 'src/types.ts': `export type Person = { name: string }`, + 'src/index.ts': `export const foo = [1, 2, 3]\nexport type { Person } from './types.js'`, + 'tsup.config.ts': `export default ${JSON.stringify( + { + name: task.name, + entry: ['src/**/*.ts'], + format: ['esm', 'cjs'], + experimentalDts: { entry: { index: 'src/index.ts' } }, + } satisfies Options, + null, + 2, + )}`, + 'package.json': JSON.stringify( + { + name: 'testing-experimental-dts-entry-can-work-independent', + description: task.name, + type: 'module', + }, + null, + 2, + ), + 'tsconfig.json': JSON.stringify( + { + compilerOptions: { + outDir: './dist', + rootDir: './src', + skipLibCheck: true, + strict: true, + }, + include: ['src'], + }, + null, + 2, + ), + }, + { + entry: [], + }, + ) + + expect(outFiles).toStrictEqual([ + '_tsup-dts-rollup.d.cts', + '_tsup-dts-rollup.d.ts', + 'index.cjs', + 'index.d.cts', + 'index.d.ts', + 'index.js', + 'types.cjs', + 'types.js', + ]) + + const indexDtsContent = [ + `export { foo } from './_tsup-dts-rollup.js';`, + `export { Person } from './_tsup-dts-rollup.js';\n`, + ].join('\n') + + expect(await getFileContent('dist/index.d.ts')).toStrictEqual(indexDtsContent) + + expect(await getFileContent('dist/index.d.cts')).toStrictEqual( + indexDtsContent.replaceAll( + `'./_tsup-dts-rollup.js'`, + `'./_tsup-dts-rollup.cjs'`, + ), + ) +}) + +test('experimentalDts.entry can be an array of globs', async ({ + expect, + task, +}) => { + const { getFileContent, outFiles } = await run( + getTestName(), + { + 'src/types.ts': `export type Person = { name: string }`, + 'src/index.ts': `export const foo = [1, 2, 3]\nexport type { Person } from './types.js'`, + 'tsup.config.ts': `export default ${JSON.stringify( + { + name: task.name, + entry: { index: 'src/index.ts' }, + format: ['esm', 'cjs'], + experimentalDts: { entry: ['src/**/*.ts'] }, + } satisfies Options, + null, + 2, + )}`, + 'package.json': JSON.stringify( + { + name: 'testing-experimental-dts-entry-array-of-globs', + description: task.name, + type: 'module', + }, + null, + 2, + ), + 'tsconfig.json': JSON.stringify( + { + compilerOptions: { + outDir: './dist', + rootDir: './src', + skipLibCheck: true, + strict: true, + }, + include: ['src'], + }, + null, + 2, + ), + }, + { + entry: [], + }, + ) + + expect(outFiles).toStrictEqual([ + '_tsup-dts-rollup.d.cts', + '_tsup-dts-rollup.d.ts', + 'index.cjs', + 'index.d.cts', + 'index.d.ts', + 'index.js', + 'types.d.cts', + 'types.d.ts', + ]) + + const indexDtsContent = [ + `export { foo } from './_tsup-dts-rollup.js';`, + `export { Person } from './_tsup-dts-rollup.js';\n`, + ].join('\n') + + expect(await getFileContent('dist/index.d.ts')).toStrictEqual(indexDtsContent) + + expect(await getFileContent('dist/index.d.cts')).toStrictEqual( + indexDtsContent.replaceAll( + `'./_tsup-dts-rollup.js'`, + `'./_tsup-dts-rollup.cjs'`, + ), + ) +}) + +test('experimentalDts can be a string', async ({ expect, task }) => { + const { getFileContent, outFiles } = await run( + getTestName(), + { + 'src/types.ts': `export type Person = { name: string }`, + 'src/index.ts': `export const foo = [1, 2, 3]\nexport type { Person } from './types.js'`, + 'tsup.config.ts': `export default ${JSON.stringify( + { + name: task.name, + entry: ['src/**/*.ts'], + format: ['esm', 'cjs'], + experimentalDts: 'src/index.ts', + } satisfies Options, + null, + 2, + )}`, + 'package.json': JSON.stringify( + { + name: 'testing-experimental-dts-can-be-a-string', + description: task.name, + type: 'module', + }, + null, + 2, + ), + 'tsconfig.json': JSON.stringify( + { + compilerOptions: { + outDir: './dist', + rootDir: './src', + skipLibCheck: true, + strict: true, + }, + include: ['src'], + }, + null, + 2, + ), + }, + { + entry: [], + }, + ) + + expect(outFiles).toStrictEqual([ + '_tsup-dts-rollup.d.cts', + '_tsup-dts-rollup.d.ts', + 'index.cjs', + 'index.d.cts', + 'index.d.ts', + 'index.js', + 'types.cjs', + 'types.js', + ]) + + const indexDtsContent = [ + `export { foo } from './_tsup-dts-rollup.js';`, + `export { Person } from './_tsup-dts-rollup.js';\n`, + ].join('\n') + + expect(await getFileContent('dist/index.d.ts')).toStrictEqual(indexDtsContent) + + expect(await getFileContent('dist/index.d.cts')).toStrictEqual( + indexDtsContent.replaceAll( + `'./_tsup-dts-rollup.js'`, + `'./_tsup-dts-rollup.cjs'`, + ), + ) +}) + +test('experimentalDts can be a string of glob pattern', async ({ + expect, + task, +}) => { + const { getFileContent, outFiles } = await run( + getTestName(), + { + 'src/types.ts': `export type Person = { name: string }`, + 'src/index.ts': `export const foo = [1, 2, 3]\nexport type { Person } from './types.js'`, + 'tsup.config.ts': `export default ${JSON.stringify( + { + name: task.name, + entry: { index: 'src/index.ts' }, + format: ['esm', 'cjs'], + experimentalDts: 'src/**/*.ts', + } satisfies Options, + null, + 2, + )}`, + 'package.json': JSON.stringify( + { + name: 'testing-experimental-dts-can-be-a-string-of-glob-pattern', + description: task.name, + type: 'module', + }, + null, + 2, + ), + 'tsconfig.json': JSON.stringify( + { + compilerOptions: { + outDir: './dist', + rootDir: './src', + skipLibCheck: true, + strict: true, + }, + include: ['src'], + }, + null, + 2, + ), + }, + { + entry: [], + }, + ) + + expect(outFiles).toStrictEqual([ + '_tsup-dts-rollup.d.cts', + '_tsup-dts-rollup.d.ts', + 'index.cjs', + 'index.d.cts', + 'index.d.ts', + 'index.js', + 'types.d.cts', + 'types.d.ts', + ]) + + const indexDtsContent = [ + `export { foo } from './_tsup-dts-rollup.js';`, + `export { Person } from './_tsup-dts-rollup.js';\n`, + ].join('\n') + + expect(await getFileContent('dist/index.d.ts')).toStrictEqual(indexDtsContent) + + expect(await getFileContent('dist/index.d.cts')).toStrictEqual( + indexDtsContent.replaceAll( + `'./_tsup-dts-rollup.js'`, + `'./_tsup-dts-rollup.cjs'`, + ), + ) +}) + +test('experimentalDts.entry can be a string of glob pattern', async ({ + expect, + task, +}) => { + const { getFileContent, outFiles } = await run( + getTestName(), + { + 'src/types.ts': `export type Person = { name: string }`, + 'src/index.ts': `export const foo = [1, 2, 3]\nexport type { Person } from './types.js'`, + 'tsup.config.ts': `export default ${JSON.stringify( + { + name: task.name, + entry: { index: 'src/index.ts' }, + format: ['esm', 'cjs'], + experimentalDts: { entry: 'src/**/*.ts' }, + } satisfies Options, + null, + 2, + )}`, + 'package.json': JSON.stringify( + { + name: 'testing-experimental-dts-entry-can-be-a-string-of-glob-pattern', + description: task.name, + type: 'module', + }, + null, + 2, + ), + 'tsconfig.json': JSON.stringify( + { + compilerOptions: { + outDir: './dist', + rootDir: './src', + skipLibCheck: true, + strict: true, + }, + include: ['src'], + }, + null, + 2, + ), + }, + { + entry: [], + }, + ) + + expect(outFiles).toStrictEqual([ + '_tsup-dts-rollup.d.cts', + '_tsup-dts-rollup.d.ts', + 'index.cjs', + 'index.d.cts', + 'index.d.ts', + 'index.js', + 'types.d.cts', + 'types.d.ts', + ]) + + const indexDtsContent = [ + `export { foo } from './_tsup-dts-rollup.js';`, + `export { Person } from './_tsup-dts-rollup.js';\n`, + ].join('\n') + + expect(await getFileContent('dist/index.d.ts')).toStrictEqual(indexDtsContent) + + expect(await getFileContent('dist/index.d.cts')).toStrictEqual( + indexDtsContent.replaceAll( + `'./_tsup-dts-rollup.js'`, + `'./_tsup-dts-rollup.cjs'`, + ), + ) +})