Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: egoist/tsup
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v7.0.0
Choose a base ref
...
head repository: egoist/tsup
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v7.1.0
Choose a head ref
  • 2 commits
  • 7 files changed
  • 2 contributors

Commits on Jun 25, 2023

  1. feat: Ensure matching declaration file exists for each output bundle …

    …format (#934)
    
    * Ensure dts files match all output formats
    
    * Update docs
    andrewbranch authored Jun 25, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    fb4c2b6 View commit details
  2. 1
    Copy the full SHA
    c151d53 View commit details
Showing with 87 additions and 45 deletions.
  1. +1 −1 docs/README.md
  2. +1 −24 src/esbuild/index.ts
  3. +1 −1 src/index.ts
  4. +1 −1 src/options.ts
  5. +23 −15 src/rollup.ts
  6. +28 −0 src/utils.ts
  7. +32 −3 test/index.test.ts
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
@@ -191,7 +191,7 @@ export default defineConfig({
tsup index.ts --dts
```

This will emit `./dist/index.js` and `./dist/index.d.ts`.
This will emit `./dist/index.js` and `./dist/index.d.ts`. When emitting multiple [bundle formats](#bundle-formats), one declaration file per bundle format is generated. This is required for consumers to get accurate type checking with TypeScript. Note that declaration files generated by any tool other than `tsc` are not guaranteed to be error-free, so it's a good idea to test the output with `tsc` or a tool like [@arethetypeswrong/cli](https://www.npmjs.com/package/@arethetypeswrong/cli) before publishing.

If you have multiple entry files, each entry will get a corresponding `.d.ts` file. So when you only want to generate declaration file for a single entry, use `--dts <entry>` format, e.g. `--dts src/index.ts`.

25 changes: 1 addition & 24 deletions src/esbuild/index.ts
Original file line number Diff line number Diff line change
@@ -14,35 +14,12 @@ import { externalPlugin } from './external'
import { postcssPlugin } from './postcss'
import { sveltePlugin } from './svelte'
import consola from 'consola'
import { truthy } from '../utils'
import { defaultOutExtension, truthy } from '../utils'
import { swcPlugin } from './swc'
import { nativeNodeModulesPlugin } from './native-node-module'
import { PluginContainer } from '../plugin'
import { OutExtensionFactory } from '../options'

const defaultOutExtension = ({
format,
pkgType,
}: {
format: Format
pkgType?: string
}): { js: string } => {
let jsExtension = '.js'
const isModule = pkgType === 'module'
if (isModule && format === 'cjs') {
jsExtension = '.cjs'
}
if (!isModule && format === 'esm') {
jsExtension = '.mjs'
}
if (format === 'iife') {
jsExtension = '.global.js'
}
return {
js: jsExtension,
}
}

const getOutputExtensionMap = (
options: NormalizedOptions,
format: Format,
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -238,7 +238,7 @@ export async function build(_options: Options) {
// .d.ts files are removed in the `dtsTask` instead
// `dtsTask` is a separate process, which might start before `mainTasks`
if (options.dts) {
extraPatterns.unshift('!**/*.d.ts')
extraPatterns.unshift('!**/*.d.{ts,cts,mts}')
}
await removeFiles(['**/*', ...extraPatterns], options.outDir)
logger.info('CLI', 'Cleaning output folder')
2 changes: 1 addition & 1 deletion src/options.ts
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ export type ContextForOutPathGeneration = {
pkgType?: string
}

export type OutExtensionObject = { js?: string }
export type OutExtensionObject = { js?: string, dts?: string }

export type OutExtensionFactory = (
ctx: ContextForOutPathGeneration
38 changes: 23 additions & 15 deletions src/rollup.ts
Original file line number Diff line number Diff line change
@@ -5,10 +5,10 @@ import ts from 'typescript'
import hashbangPlugin from 'rollup-plugin-hashbang'
import jsonPlugin from '@rollup/plugin-json'
import { handleError } from './errors'
import { removeFiles } from './utils'
import { defaultOutExtension, removeFiles } from './utils'
import { TsResolveOptions, tsResolvePlugin } from './rollup/ts-resolve'
import { createLogger, setSilent } from './log'
import { getProductionDeps } from './load'
import { getProductionDeps, loadPkg } from './load'
import path from 'path'
import { reportSize } from './lib/report-size'
import resolveFrom from 'resolve-from'
@@ -32,7 +32,7 @@ const dtsPlugin: typeof import('rollup-plugin-dts') = require('rollup-plugin-dts

type RollupConfig = {
inputConfig: InputOptions
outputConfig: OutputOptions
outputConfig: OutputOptions[]
}

const findLowestCommonAncestor = (filepaths: string[]) => {
@@ -111,13 +111,14 @@ const getRollupConfig = async (
}
}

const pkg = await loadPkg(process.cwd())
const deps = await getProductionDeps(process.cwd())

const tsupCleanPlugin: Plugin = {
name: 'tsup:clean',
async buildStart() {
if (options.clean) {
await removeFiles(['**/*.d.ts'], options.outDir)
await removeFiles(['**/*.d.{ts,mts,cts}'], options.outDir)
}
},
}
@@ -188,13 +189,19 @@ const getRollupConfig = async (
...(options.external || []),
],
},
outputConfig: {
dir: options.outDir || 'dist',
format: 'esm',
exports: 'named',
banner: dtsOptions.banner,
footer: dtsOptions.footer,
},
outputConfig: options.format.map((format) => {
const outputExtension =
options.outExtension?.({ format, options, pkgType: pkg.type }).dts ||
defaultOutExtension({ format, pkgType: pkg.type }).dts
return {
dir: options.outDir || 'dist',
format: 'esm',
exports: 'named',
banner: dtsOptions.banner,
footer: dtsOptions.footer,
entryFileNames: `[name]${outputExtension}`,
}
}),
}
}

@@ -207,15 +214,16 @@ async function runRollup(options: RollupConfig) {
}
logger.info('dts', 'Build start')
const bundle = await rollup(options.inputConfig)
const result = await bundle.write(options.outputConfig)
const results = await Promise.all(options.outputConfig.map(bundle.write))
const outputs = results.flatMap((result) => result.output)
logger.success('dts', `⚡️ Build success in ${getDuration()}`)
reportSize(
logger,
'dts',
result.output.reduce((res, info) => {
outputs.reduce((res, info) => {
const name = path.relative(
process.cwd(),
path.join(options.outputConfig.dir || '.', info.fileName)
path.join(options.outputConfig[0].dir || '.', info.fileName)
)
return {
...res,
@@ -231,7 +239,7 @@ async function runRollup(options: RollupConfig) {

async function watchRollup(options: {
inputConfig: InputOptions
outputConfig: OutputOptions
outputConfig: OutputOptions[]
}) {
const { watch } = await import('rollup')

28 changes: 28 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import fs from 'fs'
import glob from 'globby'
import resolveFrom from 'resolve-from'
import strip from 'strip-json-comments'
import { Format } from './options'

export type MaybePromise<T> = T | Promise<T>

@@ -129,3 +130,30 @@ export function jsoncParse(data: string) {
return {}
}
}

export function defaultOutExtension({
format,
pkgType,
}: {
format: Format
pkgType?: string
}): { js: string, dts: string } {
let jsExtension = '.js'
let dtsExtension = '.d.ts'
const isModule = pkgType === 'module'
if (isModule && format === 'cjs') {
jsExtension = '.cjs'
dtsExtension = '.d.cts'
}
if (!isModule && format === 'esm') {
jsExtension = '.mjs'
dtsExtension = '.d.mts'
}
if (format === 'iife') {
jsExtension = '.global.js'
}
return {
js: jsExtension,
dts: dtsExtension,
}
}
35 changes: 32 additions & 3 deletions test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1248,7 +1248,7 @@ test(`custom tsconfig should pass to dts plugin`, async () => {
}
`,
})
expect(outFiles).toEqual(['input.d.ts'])
expect(outFiles).toEqual(['input.d.mts'])
})

test(`should generate export {} when there are no exports in source file`, async () => {
@@ -1268,8 +1268,8 @@ test(`should generate export {} when there are no exports in source file`, async
}
`,
})
expect(outFiles).toEqual(['input.d.ts', 'input.mjs'])
expect(await getFileContent('dist/input.d.ts')).toContain('export { }')
expect(outFiles).toEqual(['input.d.mts', 'input.mjs'])
expect(await getFileContent('dist/input.d.mts')).toContain('export { }')
})

test('custom inject style function', async () => {
@@ -1335,3 +1335,32 @@ test('should load postcss esm config', async () => {
expect(outFiles).toEqual(['input.cjs', 'input.css'])
expect(await getFileContent('dist/input.css')).toContain('color: blue;')
})

test('should emit a declaration file per format', async () => {
const { outFiles } = await run(getTestName(), {
'input.ts': `export default 'foo'`,
'tsup.config.ts': `
export default {
entry: ['src/input.ts'],
format: ['esm', 'cjs'],
dts: true
}`,
});
expect(outFiles).toEqual(['input.d.mts', 'input.d.ts', 'input.js', 'input.mjs'])
});

test('should emit a declaration file per format (type: module)', async () => {
const { outFiles } = await run(getTestName(), {
'input.ts': `export default 'foo'`,
'package.json': `{
"type": "module"
}`,
'tsup.config.ts': `
export default {
entry: ['src/input.ts'],
format: ['esm', 'cjs'],
dts: true
}`,
});
expect(outFiles).toEqual(['input.cjs', 'input.d.cts', 'input.d.ts', 'input.js'])
});