egoist / tsup

The simplest and fastest way to bundle your TypeScript libraries.
https://tsup.egoist.dev
MIT License
9.01k stars 217 forks source link

feat: add experimental dts rollup using @microsoft/api-extractor #983

Closed ocavue closed 10 months ago

ocavue commented 1 year ago

This PR introduces a new --experimental-dts flag to generate declaration files. This flag is intended to eventually replace the current --dts flag. As it's experimental, we can release it in a minor version without disrupting existing users. Once we have sufficient confidence in its functionality, we can deprecate the --dts flag and make --experimental-dts the default behavior in a major version.

Motivation

The --dts flag currently uses rollup-plugin-dts to generate declaration files. However, this plugin is no longer actively maintained^1 and has known issues.

The --experimental-dts flag aims to improve upon this by using @microsoft/api-extractor to generate declaration files.

Limitations

One limitation of @microsoft/api-extractor is that it doesn't support multiple entry files. For instance, react-dom has multiple entry files, including react-dom/client, react-dom/server, etc. Although there are plans^2 to support multiple entry files, progress has been slow. Therefore, I have implemented a workaround, which we'll discuss in the next section.

Implementation

Let's take an example, we have a library with the following structure:

my-lib/
├── src/
│   ├── client.ts
│   ├── server.ts
│   └── shared.ts
├── package.json
└── tsconfig.json

This library has two entry files, client.ts and server.ts. They both import shared.ts.

The first step is to generate declaration files using TypeScript compiler. The source code of this part is in src/tsc.ts. It will generate declaration files for the whole project, just like what tsc does. The generated declaration files will be put in a temporary directory .tsup. Here is the file structure after this step:

my-lib/
├── .tsup/
│   ├── .gitignore
│   └── declaration/
│       ├── client.d.ts
│       ├── server.d.ts
│       └── shared.d.ts
└── ...

We cannot run api-extractor separately for each entry file, because it will generate duplicated declaration for exported types in shared.ts. So we need to give it a single entry file. In src/tsc.ts, we use TypeScript to find all exported types for each entry file, and later we will generate a merged declaration file for them. Here is the file structure after this step:

my-lib/
├── .tsup/
│   ├── .gitignore
│   └── declaration/
│       ├── _tsup-dts-aggregation.d.ts
│       ├── client.d.ts
│       ├── server.d.ts
│       └── shared.d.ts
└── ...

The .gitignore file is used to ignore all files in .tsup directory, so users don't need to modify their .gitignore file. I tried to put it under node_modules/.tsup but it seems that api-extractor will ignore declaration files under node_modules.

In _tsup-dts-aggregation.d.ts, it will simply re-export all exported types from client.d.ts, server.d.ts. Here is the content of _tsup-dts-aggregation.d.ts:

export { render } from './client'
export { ClientRenderOptions } from './client'
export { render as render_alias_1 } from './server'
export { ServerRenderOptions } from './server'
export { default as default_alias } from './server'

Note that if there are multiple exported types with the same name, it will generate a unique name for them.

The next step will run api-extractor to generate declaration files. Api-extractor will read _tsup-dts-aggregation.d.ts and rollup all exported types into a single declaration file _tsup-dts-rollup.d.ts. Here is the file structure after this step:

my-lib/
├── dist/
│   └── _tsup-dts-rollup.d.ts
├── .tsup/
│   ├── .gitignore
│   └── declaration/
│       ├── _tsup-dts-aggregation.d.ts
│       ├── client.d.ts
│       ├── server.d.ts
│       └── shared.d.ts
└── ...

For the last step, we will create one declaration file for each entry file under the dist/, and re-export needed exported types from _tsup-dts-rollup.d.ts. Here is the final file structure and content:

my-lib/
├── dist/
│   ├── client.d.ts
│   ├── server.d.ts
│   └── _tsup-dts-rollup.d.ts
└── ...
// client.d.ts
export { render } from './_tsup-dts-rollup'
export { ClientRenderOptions } from './_tsup-dts-rollup'
// server.d.ts
export { render_alias_1 as render } from './_tsup-dts-rollup'
export { ServerRenderOptions } from './_tsup-dts-rollup'
export { default_alias as default } from './_tsup-dts-rollup'

Future Work

codesandbox[bot] commented 1 year ago

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders
Open Preview

vercel[bot] commented 1 year ago

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
tsup ✅ Ready (Inspect) Visit Preview 💬 Add feedback Nov 17, 2023 0:25am
socket-security[bot] commented 1 year ago

New dependencies detected. Learn more about Socket for GitHub ↗︎

Packages Version New capabilities Transitives Size Publisher
@microsoft/api-extractor 7.38.3 eval, environment +2 3.53 MB odspnpm
egoist commented 1 year ago

I think @microsoft/api-extractor can be a peer dependecy and use localRequire to load it on demand.

bang9 commented 11 months ago

I am amazed to discover this. We are using the api-extractor with the exact same logic to generate .d.ts files for our SDK build.

One issue I encountered was that, to ensure proper auto-import behavior in various IDEs, we needed to employ a little trick. For example, to support auto-import of types existing in sub-entry .d.ts files in VSCode, we had to pre-import all the sub-entry .d.ts files from the main entry .d.ts file.

For reference: https://github.com/sendbird/sendbird-chat-sdk-javascript/blob/main/index.d.ts#L1-L5

toteto commented 11 months ago

This is amazing work and looks promising! Any updates on the state of it?

mechanical-turk commented 10 months ago

This would be very cool. I'd love to use tsup for building declaration maps as well as everything else. "Go to definition" taking you to the source instead of the d.ts files would be a great devx improvement

github-actions[bot] commented 10 months ago

:tada: This PR is included in version 8.0.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

timocov commented 9 months ago

by using @microsoft/api-extractor to generate declaration files.

@ocavue out of curiosity if you considered dts-bundle-generator as an alternative and if so, what was your opinion and pros/cons that leaned you towards api-extractor? Thanks!

10Derozan commented 6 months ago

by using @microsoft/api-extractor to generate declaration files.

@ocavue out of curiosity if you considered dts-bundle-generator as an alternative and if so, what was your opinion and pros/cons that leaned you towards api-extractor? Thanks!

I want to know it, too. Any updates? Thanks.