diff --git a/src/cli-main.ts b/src/cli-main.ts
index 070236e1..0022b859 100644
--- a/src/cli-main.ts
+++ b/src/cli-main.ts
@@ -42,6 +42,12 @@ export async function main(options: Options = {}) {
'--experimental-dts [entry]',
'Generate declaration file (experimental)',
)
+ .option(
+ '--experimental-dts-only',
+ 'Emit declaration files only (experimental)',
+ )
+ .alias('--experimentalDts-only')
+ .alias('--experimentalDtsOnly')
.option(
'--sourcemap [inline]',
'Generate external sourcemap, or inline source: --sourcemap inline',
@@ -133,6 +139,19 @@ export async function main(options: Options = {}) {
options.dts.only = true
}
}
+
+ if (flags.experimentalDts || flags.experimentalDtsOnly) {
+ options.experimentalDts = {}
+
+ if (typeof flags.experimentalDts === 'string') {
+ options.experimentalDts.entry = flags.experimentalDts
+ }
+
+ if (flags.experimentalDtsOnly) {
+ options.experimentalDts.only = true
+ }
+ }
+
if (flags.inject) {
const inject = ensureArray(flags.inject)
options.inject = inject
diff --git a/src/index.ts b/src/index.ts
index b0cdfa06..7b1f0a76 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -258,7 +258,7 @@ export async function build(_options: Options) {
}
const mainTasks = async () => {
- if (!options.dts?.only) {
+ if (!options.experimentalDts?.only && !options.dts?.only) {
let onSuccessProcess: ExecChild | undefined
let onSuccessCleanup: (() => any) | undefined | void
/** Files imported by the entry */
diff --git a/src/options.ts b/src/options.ts
index 827790d4..dfc09eba 100644
--- a/src/options.ts
+++ b/src/options.ts
@@ -40,6 +40,40 @@ export type DtsConfig = {
}
export type ExperimentalDtsConfig = {
+ /**
+ * When set to `true`, this option will emit only
+ * TypeScript declaration (`.d.ts`) files and skip generating the
+ * corresponding JavaScript files during the build process.
+ *
+ * @example
+ *
#### Generate only TypeScript declaration files
+ *
+ * ```ts
+ * import { defineConfig } from 'tsup'
+ *
+ * export default defineConfig({
+ * entry: { index: 'src/index.ts' },
+ * format: ['esm', 'cjs'],
+ * experimentalDts: { only: true },
+ * })
+ * ```
+ *
+ * @default false
+ *
+ * @since 8.5.0
+ *
+ * #### CLI Equivalent:
+ * You can use the following CLI commands to achieve the same result:
+ *
+ * ```bash
+ * tsup src/index.ts --experimental-dts-only
+ * # or
+ * tsup src/index.ts --experimentalDts-only
+ * # or
+ * tsup src/index.ts --experimentalDtsOnly
+ * ```
+ */
+ only?: boolean
entry?: InputOption
/**
* Overrides `compilerOptions`
@@ -258,7 +292,8 @@ export type Options = {
removeNodeProtocol?: boolean
}
-export interface NormalizedExperimentalDtsConfig {
+export interface NormalizedExperimentalDtsConfig extends ExperimentalDtsConfig {
+ only: boolean
entry: { [entryAlias: string]: string }
compilerOptions?: any
}
diff --git a/src/utils.ts b/src/utils.ts
index 5480f0f6..8a7b9c5a 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -325,6 +325,9 @@ const convertArrayEntriesToObjectEntries = (arrayOfEntries: string[]) => {
* patterns and converts the result into an entry object. If the input is
* already an object, it is returned as-is.
*
+ * @param entryPaths - The entry paths to resolve. Can be a string, an array of strings, or an object.
+ * @returns A {@linkcode Promise | promise} that resolves to the standardized entry paths in object format.
+ *
* @example
*
* ```ts
@@ -358,6 +361,7 @@ const resolveEntryPaths = async (entryPaths: InputOption) => {
* @param options - The options containing entry points and experimental DTS
* configuration.
* @param tsconfig - The loaded TypeScript configuration data.
+ * @returns A {@linkcode Promise | promise} that resolves to the normalized experimental DTS configuration, or `undefined` if no entry or experimental DTS option is provided.
*
* @internal
*/
@@ -378,6 +382,8 @@ export const resolveExperimentalDtsConfig = async (
: resolvedEntryPaths
const normalizedExperimentalDtsConfig: NormalizedExperimentalDtsConfig = {
+ only: options.experimentalDts?.only || false,
+
compilerOptions: {
...(tsconfig.data.compilerOptions || {}),
...(options.experimentalDts?.compilerOptions || {}),
@@ -390,8 +396,23 @@ export const resolveExperimentalDtsConfig = async (
}
/**
- * Resolves the initial experimental DTS configuration into a consistent
- * {@link NormalizedExperimentalDtsConfig} object.
+ * Resolves the initial experimental DTS configuration
+ * into a consistent
+ * {@linkcode NormalizedExperimentalDtsConfig | experimentalDts config object}.
+ *
+ * This function handles different types of
+ * {@linkcode NormalizedExperimentalDtsConfig | experimentalDts} inputs:
+ * - If {@linkcode experimentalDts} is a `boolean`:
+ * - if `true`, it returns a default object with an empty entry (`{ entry: {} }`).
+ * - if `false`, it returns `undefined`.
+ * - If {@linkcode experimentalDts} is a `string`, it treats the string as a glob pattern, resolving it to entry paths and returning an object with the `entry` property.
+ * - If {@linkcode experimentalDts} is already an object ({@linkcode NormalizedExperimentalDtsConfig}), it resolves {@linkcode NormalizedExperimentalDtsConfig.entry | the entry paths} (if necessary) and returns the object with the updated entries.
+ *
+ * The function focuses specifically on normalizing the **initial**
+ * {@linkcode NormalizedExperimentalDtsConfig | experimentalDts configuration}.
+ *
+ * @param experimentalDts - The {@linkcode NormalizedExperimentalDtsConfig | experimentalDts} value, which can be a `boolean`, `string`, `object`, or `undefined`.
+ * @returns A {@linkcode Promise | promise} that resolves to a normalized {@linkcode NormalizedExperimentalDtsConfig | experimentalDts config object}, or `undefined` if the input was `false` or `undefined`.
*
* @internal
*/
@@ -403,12 +424,14 @@ export const resolveInitialExperimentalDtsConfig = async (
}
if (typeof experimentalDts === 'boolean')
- return experimentalDts ? { entry: {} } : undefined
+ return experimentalDts ? { only: false, 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 {
+ only: false,
+
entry: convertArrayEntriesToObjectEntries(await glob(experimentalDts)),
}
}
@@ -416,6 +439,8 @@ export const resolveInitialExperimentalDtsConfig = async (
return {
...experimentalDts,
+ only: experimentalDts?.only || false,
+
entry:
experimentalDts?.entry == null
? {}
diff --git a/test/experimental-dts.test.ts b/test/experimental-dts.test.ts
index 825afbbf..e2d9926a 100644
--- a/test/experimental-dts.test.ts
+++ b/test/experimental-dts.test.ts
@@ -604,3 +604,106 @@ test('experimentalDts.entry can be a string of glob pattern', async ({
),
)
})
+
+test('experimentalDts.only works', async ({ expect, task }) => {
+ const { 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: { only: true },
+ } satisfies Options,
+ null,
+ 2,
+ )}`,
+ 'package.json': JSON.stringify(
+ {
+ name: 'experimental-dts-only-works',
+ 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.d.cts',
+ 'index.d.ts',
+ ])
+})
+
+test('experimental-dts-only cli option works', async ({ expect, task }) => {
+ const { 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,
+ format: ['esm', 'cjs'],
+ } satisfies Options,
+ null,
+ 2,
+ )}`,
+ 'package.json': JSON.stringify(
+ {
+ name: 'experimental-dts-only-cli-works',
+ 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: ['./src/index.ts'],
+ flags: ['--experimental-dts-only'],
+ },
+ )
+
+ expect(outFiles).toStrictEqual([
+ '_tsup-dts-rollup.d.cts',
+ '_tsup-dts-rollup.d.ts',
+ 'index.d.cts',
+ 'index.d.ts',
+ ])
+})