Skip to content

Commit a7206b1

Browse files
committed
Fix outExtension for dts files
1 parent 92ee842 commit a7206b1

11 files changed

Lines changed: 199 additions & 20 deletions

File tree

docs/README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,7 @@ When you use legacy TypeScript decorator by enabling `emitDecoratorMetadata` in
615615
decorators. In this case, you can give extra swc configuration in the `tsup.config.ts` file.
616616

617617
For example, if you have to define `useDefineForClassFields`, you can do that as follows:
618+
618619
```ts
619620
import { defineConfig } from 'tsup'
620621

@@ -626,10 +627,10 @@ export default defineConfig({
626627
swc: {
627628
jsc: {
628629
transform: {
629-
useDefineForClassFields: true
630-
}
631-
}
632-
}
630+
useDefineForClassFields: true,
631+
},
632+
},
633+
},
633634
})
634635
```
635636

@@ -648,9 +649,9 @@ Note: some SWC options cannot be configured:
648649
"keepClassNames": true,
649650
"target": "es2022"
650651
}
651-
```
652+
```
652653

653-
You can also define a custom `.swcrc` configuration file. Just set `swcrc` to `true`
654+
You can also define a custom `.swcrc` configuration file. Just set `swcrc` to `true`
654655
in `tsup.config.ts` to allow SWC plugin to discover automatically your custom swc config file.
655656

656657
```ts
@@ -662,8 +663,8 @@ export default defineConfig({
662663
sourcemap: true,
663664
clean: true,
664665
swc: {
665-
swcrc: true
666-
}
666+
swcrc: true,
667+
},
667668
})
668669
```
669670

src/api-extractor.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@ async function rollupDtsFiles(
9898
const declarationDir = ensureTempDeclarationDir()
9999
const outDir = options.outDir || 'dist'
100100
const pkg = await loadPkg(process.cwd())
101-
const dtsExtension = defaultOutExtension({ format, pkgType: pkg.type }).dts
101+
102+
const dtsExtension =
103+
options.outputExtensionMap.get(format)?.dts ||
104+
defaultOutExtension({ format, pkgType: pkg.type }).dts
105+
102106
const tsconfig = options.tsconfig || 'tsconfig.json'
103107

104108
let dtsInputFilePath = path.join(

src/esbuild/swc.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import type { Logger } from '../log'
99

1010
export type SwcPluginConfig = { logger: Logger } & Options
1111

12-
export const swcPlugin = ({ logger, ...swcOptions }: SwcPluginConfig): Plugin => {
12+
export const swcPlugin = ({
13+
logger,
14+
...swcOptions
15+
}: SwcPluginConfig): Plugin => {
1316
return {
1417
name: 'swc',
1518

src/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
removeFiles,
1515
resolveExperimentalDtsConfig,
1616
resolveInitialExperimentalDtsConfig,
17+
resolveOutputExtensionMap,
1718
slash,
1819
} from './utils'
1920
import { createLogger, setSilent } from './log'
@@ -77,14 +78,17 @@ const normalizeOptions = async (
7778
...optionsOverride,
7879
}
7980

81+
const formats =
82+
typeof _options.format === 'string'
83+
? [_options.format]
84+
: _options.format || ['cjs']
85+
8086
const options: Partial<NormalizedOptions> = {
8187
outDir: 'dist',
8288
removeNodeProtocol: true,
8389
..._options,
84-
format:
85-
typeof _options.format === 'string'
86-
? [_options.format as Format]
87-
: _options.format || ['cjs'],
90+
format: formats,
91+
8892
dts:
8993
typeof _options.dts === 'boolean'
9094
? _options.dts
@@ -161,6 +165,10 @@ const normalizeOptions = async (
161165
options.target = 'node16'
162166
}
163167

168+
options.outputExtensionMap = await resolveOutputExtensionMap(
169+
options as NormalizedOptions,
170+
)
171+
164172
return options as NormalizedOptions
165173
}
166174

src/options.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,8 @@ export type Options = {
257257
* @default true
258258
*/
259259
removeNodeProtocol?: boolean
260-
261-
swc?: SwcPluginConfig;
260+
261+
swc?: SwcPluginConfig
262262
}
263263

264264
export interface NormalizedExperimentalDtsConfig {
@@ -275,5 +275,14 @@ export type NormalizedOptions = Omit<
275275
tsconfigResolvePaths: Record<string, string[]>
276276
tsconfigDecoratorMetadata?: boolean
277277
format: Format[]
278+
279+
/**
280+
* Custom file extension per each
281+
* {@linkcode Format | module format}.
282+
*
283+
* @since 8.6.0
284+
*/
285+
outputExtensionMap: Map<Format, OutExtensionObject>
286+
278287
swc?: SwcPluginConfig
279288
}

src/rollup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ const getRollupConfig = async (
138138
},
139139
outputConfig: options.format.map((format): OutputOptions => {
140140
const outputExtension =
141-
options.outExtension?.({ format, options, pkgType: pkg.type }).dts ||
141+
options.outputExtensionMap.get(format)?.dts ||
142142
defaultOutExtension({ format, pkgType: pkg.type }).dts
143143
return {
144144
dir: options.outDir || 'dist',

src/utils.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import resolveFrom from 'resolve-from'
44
import type { InputOption } from 'rollup'
55
import strip from 'strip-json-comments'
66
import { glob } from 'tinyglobby'
7+
import { loadPkg } from './load.js'
78
import type {
89
Entry,
910
Format,
@@ -422,3 +423,40 @@ export const resolveInitialExperimentalDtsConfig = async (
422423
: await resolveEntryPaths(experimentalDts.entry),
423424
}
424425
}
426+
427+
/**
428+
* Resolves the
429+
* {@linkcode NormalizedOptions.outputExtensionMap | output extension map}
430+
* for each specified {@linkcode Format | format}
431+
* in the provided {@linkcode options}.
432+
*
433+
* @param options - The normalized options containing format and output extension details.
434+
* @returns A {@linkcode Promise | promise} that resolves to a {@linkcode Map}, where each key is a {@linkcode Format | format} and each value is an object containing the resolved output extensions for both `js` and `dts` files.
435+
*
436+
* @internal
437+
*/
438+
export const resolveOutputExtensionMap = async (
439+
options: NormalizedOptions,
440+
): Promise<NormalizedOptions['outputExtensionMap']> => {
441+
const pkg = await loadPkg(process.cwd())
442+
443+
const formatOutExtension = new Map(
444+
options.format.map((format) => {
445+
const outputExtensions = options.outExtension?.({
446+
format,
447+
options,
448+
pkgType: pkg.type,
449+
})
450+
451+
return [
452+
format,
453+
{
454+
...defaultOutExtension({ format, pkgType: pkg.type }),
455+
...(outputExtensions || {}),
456+
},
457+
] as const
458+
}),
459+
)
460+
461+
return formatOutExtension
462+
}

test/dts.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,3 +480,58 @@ test('declaration files with multiple entrypoints #316', async () => {
480480
'dist/bar/index.d.ts',
481481
).toMatchSnapshot()
482482
})
483+
484+
test('custom dts output extension', async ({ expect, task }) => {
485+
const { outFiles } = await run(
486+
getTestName(),
487+
{
488+
'src/types.ts': `export type Person = { name: string }`,
489+
'src/index.ts': `export const foo = [1, 2, 3]\nexport type { Person } from './types'`,
490+
'tsup.config.ts': `export default {
491+
name: '${task.name}',
492+
entry: { index: 'src/index.ts' },
493+
dts: true,
494+
format: ['esm', 'cjs'],
495+
outExtension({ format }) {
496+
return {
497+
js: format === 'esm' ? '.cjs' : '.mjs',
498+
dts: format === 'esm' ? '.d.cts' : '.d.mts',
499+
}
500+
},
501+
}`,
502+
'package.json': JSON.stringify(
503+
{
504+
name: 'custom-dts-output-extension',
505+
description: task.name,
506+
type: 'module',
507+
},
508+
null,
509+
2,
510+
),
511+
'tsconfig.json': JSON.stringify(
512+
{
513+
compilerOptions: {
514+
outDir: './dist',
515+
rootDir: './src',
516+
moduleResolution: 'Bundler',
517+
module: 'ESNext',
518+
strict: true,
519+
skipLibCheck: true,
520+
},
521+
include: ['src'],
522+
},
523+
null,
524+
2,
525+
),
526+
},
527+
{
528+
entry: [],
529+
},
530+
)
531+
expect(outFiles).toStrictEqual([
532+
'index.cjs',
533+
'index.d.cts',
534+
'index.d.mts',
535+
'index.mjs',
536+
])
537+
})

test/experimental-dts.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,3 +604,64 @@ test('experimentalDts.entry can be a string of glob pattern', async ({
604604
),
605605
)
606606
})
607+
608+
test('custom outExtension works with experimentalDts', async ({
609+
expect,
610+
task,
611+
}) => {
612+
const { outFiles } = await run(
613+
getTestName(),
614+
{
615+
'src/types.ts': `export type Person = { name: string }`,
616+
'src/index.ts': `export const foo = [1, 2, 3]\nexport type { Person } from './types'`,
617+
'tsup.config.ts': `export default {
618+
name: '${task.name}',
619+
entry: { index: 'src/index.ts' },
620+
format: ['esm', 'cjs'],
621+
experimentalDts: true,
622+
outExtension({ format }) {
623+
return {
624+
js: format === 'cjs' ? '.cjs' : '.mjs',
625+
dts: format === 'cjs' ? '.d.cts' : '.d.mts',
626+
}
627+
},
628+
}`,
629+
'package.json': JSON.stringify(
630+
{
631+
name: 'custom-dts-output-extension-with-experimental-dts',
632+
description: task.name,
633+
type: 'module',
634+
},
635+
null,
636+
2,
637+
),
638+
'tsconfig.json': JSON.stringify(
639+
{
640+
compilerOptions: {
641+
outDir: './dist',
642+
rootDir: './src',
643+
moduleResolution: 'Bundler',
644+
module: 'ESNext',
645+
strict: true,
646+
skipLibCheck: true,
647+
},
648+
include: ['src'],
649+
},
650+
null,
651+
2,
652+
),
653+
},
654+
{
655+
entry: [],
656+
},
657+
)
658+
659+
expect(outFiles).toStrictEqual([
660+
'_tsup-dts-rollup.d.cts',
661+
'_tsup-dts-rollup.d.mts',
662+
'index.cjs',
663+
'index.d.cts',
664+
'index.d.mts',
665+
'index.mjs',
666+
])
667+
})

test/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ test('onSuccess: use a function from config file', async () => {
217217
await new Promise((resolve) => {
218218
setTimeout(() => {
219219
console.log('world')
220-
resolve('')
220+
resolve('')
221221
}, 1_000)
222222
})
223223
}
@@ -601,7 +601,7 @@ test('use rollup for treeshaking --format cjs', async () => {
601601
}`,
602602
'input.tsx': `
603603
import ReactSelect from 'react-select'
604-
604+
605605
export const Component = (props: {}) => {
606606
return <ReactSelect {...props} />
607607
};

0 commit comments

Comments
 (0)