diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e54a288a..e5dcaeae 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,29 +1,27 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.222.0/containers/javascript-node { - "name": "Node.js", - "build": { - "dockerfile": "Dockerfile", - // Update 'VARIANT' to pick a Node version: 16, 14, 12. - // Append -bullseye or -buster to pin to an OS version. - // Use -bullseye variants on local arm64/Apple Silicon. - "args": { "VARIANT": "16-bullseye" } - }, + "name": "Node.js", + "build": { + "dockerfile": "Dockerfile", + // Update 'VARIANT' to pick a Node version: 16, 14, 12. + // Append -bullseye or -buster to pin to an OS version. + // Use -bullseye variants on local arm64/Apple Silicon. + "args": { "VARIANT": "16-bullseye" } + }, - // Set *default* container specific settings.json values on container create. - "settings": {}, + // Set *default* container specific settings.json values on container create. + "settings": {}, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "dbaeumer.vscode-eslint" - ], + // Add the IDs of extensions you want installed when the container is created. + "extensions": ["dbaeumer.vscode-eslint"], - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], - // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "pnpm install", + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "pnpm install", - // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "node" + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "node" } diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 00000000..41ddf54a --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,37 @@ +name: Fix + +on: + push: + branches-ignore: + - 'main' + - 'dev' + +jobs: + format: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v2 + name: Install pnpm + + - uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: pnpm + + - run: pnpm i + + - name: Format + run: pnpm run format + + - name: Commit files and push + continue-on-error: true + if: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dev' }} + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add --all + git commit -m "chore(ci): [bot] format code" + git push diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..b8ba9a0e --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +pnpm-lock.yaml +.cache +dist diff --git a/.prettierrc b/.prettierrc index c3481a75..fd496a82 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,4 @@ { "singleQuote": true, "semi": false -} \ No newline at end of file +} diff --git a/README.md b/README.md index 7b984cf6..a6a6d444 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,6 @@ Head over to the [discussions](https://github.com/egoist/tsup/discussions) to sh ## Sponsors -

diff --git a/docs/README.md b/docs/README.md index f4922f2c..85146320 100644 --- a/docs/README.md +++ b/docs/README.md @@ -195,7 +195,7 @@ If you have multiple entry files, each entry will get a corresponding `.d.ts` fi Note that `--dts` does not resolve external (aka in `node_modules`) types used in the `.d.ts` file, if that's somehow a requirement, try the experimental `--dts-resolve` flag instead. -Since tsup version 7.4.0, you can also use `--experimental-dts` flag to generate declaration files. This flag use [@microsoft/api-extractor](https://www.npmjs.com/package/@microsoft/api-extractor) to generate declaration files, which is more reliable than the previous `--dts` flag. It's still experimental and we are looking for feedbacks. +Since tsup version 8.0.0, you can also use `--experimental-dts` flag to generate declaration files. This flag use [@microsoft/api-extractor](https://www.npmjs.com/package/@microsoft/api-extractor) to generate declaration files, which is more reliable than the previous `--dts` flag. It's still experimental and we are looking for feedbacks. To use `--experimental-dts`, you would need to install `@microsoft/api-extractor`, as it's a peer dependency of tsup: diff --git a/package.json b/package.json index f6145ba2..647b09d0 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "build": "tsup src/cli-*.ts src/index.ts src/rollup.ts --clean --splitting", "prepublishOnly": "npm run build", "test": "npm run build && npm run test-only", + "format": "prettier --write .", "test-only": "vitest run", "build-fast": "npm run build -- --no-dts" }, diff --git a/renovate.json b/renovate.json index f45d8f11..4f39080e 100644 --- a/renovate.json +++ b/renovate.json @@ -1,5 +1,3 @@ { - "extends": [ - "config:base" - ] + "extends": ["config:base"] } diff --git a/schema.json b/schema.json index 0402482e..a033bf76 100644 --- a/schema.json +++ b/schema.json @@ -24,7 +24,6 @@ } } ] - } } }, diff --git a/src/api-extractor.ts b/src/api-extractor.ts index d1121d0f..8e269e9e 100644 --- a/src/api-extractor.ts +++ b/src/api-extractor.ts @@ -17,6 +17,7 @@ import { defaultOutExtension, ensureTempDeclarationDir, getApiExtractor, + removeFiles, toAbsolutePath, writeFileSync, } from './utils' @@ -123,13 +124,24 @@ async function rollupDtsFiles( sourceFileName = toAbsolutePath(sourceFileName) const outFileName = path.join(outDir, out + dtsExtension) + // Find all declarations that are exported from the current source file + const currentExports = exports.filter( + (declaration) => declaration.sourceFileName === sourceFileName + ) + writeFileSync( outFileName, - formatDistributionExports(exports, outFileName, dtsOutputFilePath) + formatDistributionExports(currentExports, outFileName, dtsOutputFilePath) ) } } +function cleanDtsFiles(options: NormalizedOptions) { + if (options.clean) { + removeFiles(['**/*.d.{ts,mts,cts}'], options.outDir) + } +} + export async function runDtsRollup( options: NormalizedOptions, exports?: ExportDeclaration[] @@ -144,6 +156,7 @@ export async function runDtsRollup( if (!exports) { throw new Error('Unexpected internal error: dts exports is not define') } + cleanDtsFiles(options) for (const format of options.format) { await rollupDtsFiles(options, exports, format) } diff --git a/src/cli-main.ts b/src/cli-main.ts index 790723e1..58f9dad0 100644 --- a/src/cli-main.ts +++ b/src/cli-main.ts @@ -38,7 +38,10 @@ export async function main(options: Options = {}) { .option('--dts [entry]', 'Generate declaration file') .option('--dts-resolve', 'Resolve externals types used for d.ts files') .option('--dts-only', 'Emit declaration files only') - .option('--experimental-dts [entry]', 'Generate declaration file (experimental)') + .option( + '--experimental-dts [entry]', + 'Generate declaration file (experimental)' + ) .option( '--sourcemap [inline]', 'Generate external sourcemap, or inline source: --sourcemap inline' diff --git a/src/esbuild/node-protocol.ts b/src/esbuild/node-protocol.ts index 1bf66228..a35e0abd 100644 --- a/src/esbuild/node-protocol.ts +++ b/src/esbuild/node-protocol.ts @@ -5,17 +5,20 @@ import { Plugin } from 'esbuild' * https://nodejs.org/api/esm.html#node-imports */ export const nodeProtocolPlugin = (): Plugin => { - const nodeProtocol = 'node:' + const nodeProtocol = 'node:' - return { - name: 'node-protocol-plugin', - setup({ onResolve }) { - onResolve({ - filter: /^node:/, - }, ({ path }) => ({ - path: path.slice(nodeProtocol.length), - external: true - })) - } - } + return { + name: 'node-protocol-plugin', + setup({ onResolve }) { + onResolve( + { + filter: /^node:/, + }, + ({ path }) => ({ + path: path.slice(nodeProtocol.length), + external: true, + }) + ) + }, + } } diff --git a/src/exports.ts b/src/exports.ts index d265effe..c1fdf3e2 100644 --- a/src/exports.ts +++ b/src/exports.ts @@ -82,7 +82,7 @@ export function formatDistributionExports( if (!importPath.match(/^\.+\//)) { importPath = './' + importPath } - + let seen = { named: new Set(), module: new Set(), diff --git a/src/index.ts b/src/index.ts index 247de43e..6b2b1b2e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -297,7 +297,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) { + if (options.dts || options.experimentalDts) { extraPatterns.unshift('!**/*.d.{ts,cts,mts}') } await removeFiles(['**/*', ...extraPatterns], options.outDir) diff --git a/src/plugins/tree-shaking.ts b/src/plugins/tree-shaking.ts index f1c9a9ef..3826f1ce 100644 --- a/src/plugins/tree-shaking.ts +++ b/src/plugins/tree-shaking.ts @@ -11,7 +11,7 @@ export const treeShakingPlugin = ({ name, silent, }: { - treeshake?: TreeshakingStrategy, + treeshake?: TreeshakingStrategy name?: string silent?: boolean }): Plugin => { @@ -46,7 +46,7 @@ export const treeShakingPlugin = ({ format: this.format, file: 'out.js', sourcemap: !!this.options.sourcemap, - name + name, }) for (const file of result.output) { diff --git a/src/rollup.ts b/src/rollup.ts index 8691a388..a7dd0780 100644 --- a/src/rollup.ts +++ b/src/rollup.ts @@ -165,6 +165,7 @@ const getRollupConfig = async ( banner: dtsOptions.banner, footer: dtsOptions.footer, entryFileNames: `[name]${outputExtension}`, + chunkFileNames: `[name]-[hash]${outputExtension}`, plugins: [ format === 'cjs' && options.cjsInterop && fixCjsExport, ].filter(Boolean), diff --git a/test/__snapshots__/index.test.ts.snap b/test/__snapshots__/index.test.ts.snap index b3dd2ab5..80c49058 100644 --- a/test/__snapshots__/index.test.ts.snap +++ b/test/__snapshots__/index.test.ts.snap @@ -340,44 +340,22 @@ export { } // dist/index.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 { VERSION } 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 { renderToString } from './_tsup-dts-rollup'; -export { renderToNodeStream } from './_tsup-dts-rollup'; -export { renderToStaticMarkup } from './_tsup-dts-rollup'; -export { renderToStaticNodeStream } from './_tsup-dts-rollup'; -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'; ////////////////////////////////////////////////////////////////////// // dist/index.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 { VERSION } 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 { renderToString } from './_tsup-dts-rollup'; -export { renderToNodeStream } from './_tsup-dts-rollup'; -export { renderToStaticMarkup } from './_tsup-dts-rollup'; -export { renderToStaticNodeStream } from './_tsup-dts-rollup'; -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'; ////////////////////////////////////////////////////////////////////// @@ -388,18 +366,6 @@ 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 { VERSION } 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 { renderToString } from './_tsup-dts-rollup'; -export { renderToNodeStream } from './_tsup-dts-rollup'; -export { renderToStaticMarkup } from './_tsup-dts-rollup'; -export { renderToStaticNodeStream } from './_tsup-dts-rollup'; -export { version } from './_tsup-dts-rollup'; ////////////////////////////////////////////////////////////////////// @@ -410,35 +376,21 @@ 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 { VERSION } 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 { renderToString } from './_tsup-dts-rollup'; -export { renderToNodeStream } from './_tsup-dts-rollup'; -export { renderToStaticMarkup } from './_tsup-dts-rollup'; -export { renderToStaticNodeStream } from './_tsup-dts-rollup'; -export { version } from './_tsup-dts-rollup'; ////////////////////////////////////////////////////////////////////// // dist/server/index.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 { VERSION } from '../_tsup-dts-rollup'; +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 { renderToString } from '../_tsup-dts-rollup'; export { renderToNodeStream } from '../_tsup-dts-rollup'; export { renderToStaticMarkup } from '../_tsup-dts-rollup'; @@ -450,17 +402,15 @@ export { version } from '../_tsup-dts-rollup'; // dist/server/index.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 { VERSION } from '../_tsup-dts-rollup'; +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 { renderToString } from '../_tsup-dts-rollup'; export { renderToNodeStream } from '../_tsup-dts-rollup'; export { renderToStaticMarkup } from '../_tsup-dts-rollup'; diff --git a/test/index.test.ts b/test/index.test.ts index db07db34..71c946a2 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1398,6 +1398,93 @@ test('should emit a declaration file per format (type: module)', async () => { ]) }) +test('should emit dts chunks per format', async () => { + const { outFiles } = await run( + getTestName(), + { + 'src/input1.ts': ` + import type { InternalType } from './shared.js' + + export function getValue(value: InternalType) { + return value; + } + `, + 'src/input2.ts': ` + import type { InternalType } from './shared.js' + + export function getValue(value: InternalType) { + return value; + } + `, + 'src/shared.ts': `export type InternalType = 'foo'`, + 'tsup.config.ts': ` + export default { + entry: ['./src/input1.ts', './src/input2.ts'], + format: ['esm', 'cjs'], + dts: true + }`, + }, + { entry: [] } + ) + expect(outFiles).toEqual([ + 'input1.d.mts', + 'input1.d.ts', + 'input1.js', + 'input1.mjs', + 'input2.d.mts', + 'input2.d.ts', + 'input2.js', + 'input2.mjs', + 'shared-qBqaX8Tr.d.mts', + 'shared-qBqaX8Tr.d.ts', + ]) +}) + +test('should emit dts chunks per format (type: module)', async () => { + const { outFiles } = await run( + getTestName(), + { + 'src/input1.ts': ` + import type { InternalType } from './shared.js' + + export function getValue(value: InternalType) { + return value; + } + `, + 'src/input2.ts': ` + import type { InternalType } from './shared.js' + + export function getValue(value: InternalType) { + return value; + } + `, + 'src/shared.ts': `export type InternalType = 'foo'`, + 'tsup.config.ts': ` + export default { + entry: ['./src/input1.ts', './src/input2.ts'], + format: ['esm', 'cjs'], + dts: true + }`, + 'package.json': `{ + "type": "module" + }`, + }, + { entry: [] } + ) + expect(outFiles).toEqual([ + 'input1.cjs', + 'input1.d.cts', + 'input1.d.ts', + 'input1.js', + 'input2.cjs', + 'input2.d.cts', + 'input2.d.ts', + 'input2.js', + 'shared-qBqaX8Tr.d.cts', + 'shared-qBqaX8Tr.d.ts', + ]) +}) + test('should emit declaration files with experimentalDts', async () => { const files = { 'package.json': ` @@ -1546,3 +1633,85 @@ test('should emit declaration files with experimentalDts', async () => { ) expect(snapshots.sort().join('\n')).toMatchSnapshot() }) + +test('should only include exported declarations with experimentalDts', async () => { + const files = { + 'package.json': `{ "name": "tsup-playground", "private": true }`, + 'tsconfig.json': `{ "compilerOptions": { "skipLibCheck": true } }`, + 'tsup.config.ts': ` + export default { + entry: ['./src/entry1.ts', './src/entry2.ts'] + } + `, + 'src/shared.ts': ` + export const declare1 = 'declare1' + export const declare2 = 'declare2' + `, + 'src/entry1.ts': ` + export { declare1 } from './shared' + `, + 'src/entry2.ts': ` + export { declare2 } from './shared' + `, + } + const { getFileContent } = await run(getTestName(), files, { + entry: [], + flags: ['--experimental-dts'], + }) + + let entry1dts = await getFileContent('dist/entry1.d.ts') + let entry2dts = await getFileContent('dist/entry2.d.ts') + + expect(entry1dts).toContain('declare1') + expect(entry1dts).not.toContain('declare2') + + expect(entry2dts).toContain('declare2') + expect(entry2dts).not.toContain('declare1') +}) + +test('.d.ts files should be cleaned when --clean and --experimental-dts are provided', async () => { + const filesFoo = { + 'package.json': `{ "name": "tsup-playground", "private": true }`, + 'foo.ts': `export const foo = 1`, + } + + const filesFooBar = { + ...filesFoo, + 'bar.ts': `export const bar = 2`, + } + + // First run with both foo and bar + const result1 = await run(getTestName(), filesFooBar, { + entry: ['foo.ts', 'bar.ts'], + flags: ['--experimental-dts'], + }) + + expect(result1.outFiles).toContain('foo.d.ts') + expect(result1.outFiles).toContain('foo.js') + expect(result1.outFiles).toContain('bar.d.ts') + expect(result1.outFiles).toContain('bar.js') + + // Second run with only foo + const result2 = await run(getTestName(), filesFoo, { + entry: ['foo.ts'], + flags: ['--experimental-dts'], + }) + + // When --clean is not provided, the previous bar.* files should still exist + expect(result2.outFiles).toContain('foo.d.ts') + expect(result2.outFiles).toContain('foo.js') + expect(result2.outFiles).toContain('bar.d.ts') + expect(result2.outFiles).toContain('bar.js') + + // Third run with only foo and --clean + const result3 = await run(getTestName(), filesFoo, { + entry: ['foo.ts'], + flags: ['--experimental-dts', '--clean'], + }) + + // When --clean is provided, the previous bar.* files should be deleted + expect(result3.outFiles).toContain('foo.d.ts') + expect(result3.outFiles).toContain('foo.js') + expect(result3.outFiles).not.toContain('bar.d.ts') + expect(result3.outFiles).not.toContain('bar.js') +})