-
-
Notifications
You must be signed in to change notification settings - Fork 10.1k
Build: Replace rollup-plugin-dts with rolldown-plugin-dts + tsgo for ~16x faster d.ts generation #34329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Build: Replace rollup-plugin-dts with rolldown-plugin-dts + tsgo for ~16x faster d.ts generation #34329
Changes from all commits
db0ca83
74ba67b
f31b2b5
9eb66f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -205,5 +205,5 @@ | |
| ] | ||
| }, | ||
| "analytics": false, | ||
| "codexCacheBust": "2026-03-23T12:57:22Z" | ||
| "codexCacheBust": "2026-03-26T08:49:19Z" | ||
| } | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,109 +1,67 @@ | ||
| import { spawn } from 'child_process'; | ||
| import limit from 'p-limit'; | ||
| import { sep } from 'node:path'; | ||
|
|
||
| import { join, relative } from 'pathe'; | ||
| import picocolors from 'picocolors'; | ||
| import { rolldown } from 'rolldown'; | ||
| import { dts } from 'rolldown-plugin-dts'; | ||
|
|
||
| import { ROOT_DIRECTORY } from '../../utils/constants'; | ||
| import type { BuildEntries } from './entry-utils'; | ||
| import { getExternal } from './entry-utils'; | ||
|
|
||
| const DIR_CODE = join(import.meta.dirname, '..', '..', '..', 'code'); | ||
|
|
||
| const MAX_DTS_ATTEMPTS = 2; | ||
| const RETRY_DELAY_MS = 500; | ||
|
|
||
| export async function generateTypesFiles(cwd: string, data: BuildEntries) { | ||
| const DIR_CWD = cwd; | ||
| const DIR_REL = relative(DIR_CODE, DIR_CWD); | ||
| const DIR_REL = relative(DIR_CODE, cwd); | ||
|
|
||
| const dtsEntries = Object.values(data.entries) | ||
| .flat() | ||
| .filter((entry) => entry.dts !== false) | ||
| .map((e) => e.entryPoint); | ||
|
|
||
| // Spawn each entry in it's own separate process, because they are slow & synchronous | ||
| // ...this way we do not bog down the main process/esbuild and can run them in parallel | ||
| // we limit the number of concurrent processes to 3, because we don't want to overload the host machine | ||
| // by trial and error, 3 seems to be the sweet spot between perf and consistency | ||
| const limited = limit(5); | ||
| let processes: ReturnType<typeof spawn>[] = []; | ||
|
|
||
| await Promise.all( | ||
| dtsEntries.map(async (entryPoint) => { | ||
| return limited(async () => { | ||
| for (let attempt = 1; attempt <= MAX_DTS_ATTEMPTS; attempt++) { | ||
| let timer: ReturnType<typeof setTimeout> | undefined; | ||
| const dtsProcess = spawn( | ||
| `"${join(ROOT_DIRECTORY, 'node_modules', '.bin', 'jiti')}"`, | ||
| [`"${join(import.meta.dirname, 'dts-process.ts')}"`, `"${entryPoint}"`], | ||
| { | ||
| shell: true, | ||
| cwd: DIR_CWD, | ||
| stdio: ['ignore', 'inherit', 'pipe'], | ||
| } | ||
| ); | ||
| processes.push(dtsProcess); | ||
| if (dtsEntries.length === 0) { | ||
| return; | ||
| } | ||
|
|
||
| // Filter stderr to exclude messages containing "are imported from external module", which is an ignorable warning from rollup | ||
| dtsProcess.stderr?.on('data', (data) => { | ||
| const message = data.toString(); | ||
| if (!message.includes('are imported from external module')) { | ||
| process.stderr.write(data); | ||
| } | ||
| }); | ||
| const { typesExternal: external } = await getExternal(cwd); | ||
|
|
||
| await Promise.race([ | ||
| new Promise((resolve) => { | ||
| dtsProcess.on('exit', () => { | ||
| resolve(void 0); | ||
| }); | ||
| dtsProcess.on('error', () => { | ||
| resolve(void 0); | ||
| }); | ||
| dtsProcess.on('close', () => { | ||
| resolve(void 0); | ||
| }); | ||
| }), | ||
| new Promise((resolve) => { | ||
| timer = setTimeout(() => { | ||
| console.log('⌛ Timed out generating d.ts files for', entryPoint); | ||
| const externalFn = (id: string) => | ||
| external.some( | ||
| (dep: string) => | ||
| id === dep || | ||
| id.startsWith(`${dep}/`) || | ||
| id.includes(`${sep}node_modules${sep}${dep}${sep}`) | ||
| ); | ||
|
|
||
| dtsProcess.kill(408); // timed out | ||
| resolve(void 0); | ||
| }, 120000); | ||
| }), | ||
| ]); | ||
| // Build entry map: { 'client-logger/index': '/absolute/path/src/client-logger/index.ts', ... } | ||
| const entryMap: Record<string, string> = {}; | ||
| for (const entry of dtsEntries) { | ||
| // ./src/client-logger/index.ts -> client-logger/index | ||
| const name = entry.replace(/^\.\/src\//, '').replace(/\.tsx?$/, ''); | ||
| entryMap[name] = join(cwd, entry); | ||
| } | ||
|
|
||
| if (timer) { | ||
| clearTimeout(timer); | ||
| } | ||
| // Use rolldown + rolldown-plugin-dts with tsgo for fast d.ts generation. | ||
| // tsgo (Go-based TypeScript compiler) runs once for all entries (~7s), | ||
| // then rolldown bundles the declarations natively in Rust. | ||
| const out = await rolldown({ | ||
| input: entryMap, | ||
| external: externalFn, | ||
| plugins: [ | ||
| dts({ | ||
| cwd, | ||
| tsconfig: join(cwd, 'tsconfig.json'), | ||
| tsgo: true, | ||
| emitDtsOnly: true, | ||
| }), | ||
| ], | ||
| logLevel: 'warn', | ||
| }); | ||
|
|
||
| if (dtsProcess.exitCode !== 0) { | ||
| if (attempt < MAX_DTS_ATTEMPTS) { | ||
| // Race: parallel DTS can read a .d.ts another process is still writing → invalid. Retry + delay usually fixes (flake in core:compile:production since #33759). | ||
| console.warn( | ||
| `⚠️ DTS failed for ${picocolors.cyan(relative(cwd, entryPoint))}, retrying (${attempt}/${MAX_DTS_ATTEMPTS})...` | ||
| ); | ||
| processes = processes.filter((p) => p !== dtsProcess); | ||
| await new Promise((r) => setTimeout(r, RETRY_DELAY_MS)); | ||
| continue; | ||
| } | ||
| console.error( | ||
| '\n❌ Generating types for', | ||
| picocolors.cyan(relative(cwd, entryPoint)), | ||
| ' failed' | ||
| ); | ||
| // If any fail after all retries, kill all the other processes and exit (bail) | ||
| processes.forEach((p) => p.kill()); | ||
| processes = []; | ||
| process.exit(dtsProcess.exitCode || 1); | ||
| } | ||
| await out.write({ dir: join(cwd, 'dist'), format: 'es' }); | ||
|
|
||
| if (!process.env.CI) { | ||
| console.log('✅ Generated types for', picocolors.cyan(join(DIR_REL, entryPoint))); | ||
| } | ||
| break; | ||
| } | ||
| }); | ||
| }) | ||
| ); | ||
| if (!process.env.CI) { | ||
| for (const entry of dtsEntries) { | ||
| console.log('Generated types for', picocolors.cyan(join(DIR_REL, entry))); | ||
| } | ||
| } | ||
|
Comment on lines
+43
to
+66
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: The rolldown-plugin-dts plugin provides the following configuration options, grouped by category as documented in the official GitHub repository and npm README (latest version 0.22.5 as of March 2026). These enable customization of .d.ts file generation and bundling for Rolldown builds. ### General Options - cwd: Directory to search for tsconfig.json. - dtsInput: boolean, true if entry files are .d.ts (skips generating .d.ts for entry). - emitDtsOnly: boolean, emits only .d.ts files and removes other outputs (useful for CJS .d.ts in separate builds). - tsconfig: Path to tsconfig.json (default: 'tsconfig.json'), or false to ignore. - tsconfigRaw: Raw tsconfig.json object. - compilerOptions: Overrides for compilerOptions from tsconfig.json. - sourcemap: boolean, generates .d.ts.map declaration maps. - resolver: 'oxc' (default, faster) or 'tsc' (more compatible). - cjsDefault: Controls default export emission for CJS. - sideEffects: boolean (default: false), treats .d.ts as having side effects for tree-shaking. ### TSC Options (only when oxc and tsgo are false) - build: boolean, uses tsc --build (true) or plain tsc (false). - vue: boolean, uses vue-tsc for .d.ts generation. - parallel: boolean, launches separate process for parallel tsc/vue-tsc. - emitJs: boolean, emits .d.ts for .js files (auto-enabled if allowJs true). ### Oxc Options - oxc: boolean, uses Oxc for faster .d.ts generation (auto-enabled if isolatedDeclarations true). ### TypeScript Go (tsgo) Option - Experimental - tsgo: boolean | TsgoOptions. Enables experimental DTS generation using tsgo (requires Citations:
Flag: tsgo is experimental and not recommended for production. The rolldown configuration uses Consider either:
Error handling is correct—failures will propagate to the caller as expected. 🤖 Prompt for AI Agents |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: storybookjs/storybook
Length of output: 624
Package versions 0.121.0, 1.0.0-rc.12, and 0.23.0 do not exist on npm—this will cause installation to fail.
Three of the four new packages specify versions that are not published:
oxc-transform0.121.0: Latest available is 0.99.0 (off by ~22 versions)rolldown1.0.0-rc.12: Latest available is 1.0.0-rc.9 (off by 3 release candidates)rolldown-plugin-dts0.23.0: Latest available is 0.9.9 (off by ~2.3x)Update these to versions that actually exist on npm.
@typescript/native-preview^7.0.0-dev.20260325.1 is fine and will resolve correctly.🤖 Prompt for AI Agents