Skip to content

Commit accd137

Browse files
committed
chore: use rolldown-plugin-dts for dts bundling
1 parent a7c0f81 commit accd137

File tree

3 files changed

+243
-78
lines changed

3 files changed

+243
-78
lines changed

packages/vite/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
"build-bundle": "rolldown --config rollup.config.ts",
7777
"build-types": "pnpm build-types-temp && pnpm build-types-roll && pnpm build-types-check",
7878
"build-types-temp": "tsc --emitDeclarationOnly --outDir temp -p src/node/tsconfig.build.json",
79-
"build-types-roll": "NODE_OPTIONS='--import tsx' rollup --config rollup.dts.config.ts && premove temp",
79+
"build-types-roll": "rolldown --config rollup.dts.config.ts && premove temp",
8080
"build-types-check": "tsc --project tsconfig.check.json",
8181
"typecheck": "tsc --noEmit && tsc --noEmit -p src/node",
8282
"lint": "eslint --cache --ext .ts src/**",
@@ -100,6 +100,7 @@
100100
"@babel/parser": "^7.27.2",
101101
"@jridgewell/trace-mapping": "^0.3.25",
102102
"@oxc-project/runtime": "^0.70.0",
103+
"@oxc-project/types": "^0.70.0",
103104
"@polka/compression": "^1.0.0-next.25",
104105
"@rollup/plugin-alias": "^5.1.1",
105106
"@rollup/plugin-commonjs": "^28.0.3",
@@ -141,7 +142,7 @@
141142
"postcss-modules": "^6.0.1",
142143
"resolve.exports": "^2.0.3",
143144
"rolldown": "^1.0.0-beta.9",
144-
"rollup-plugin-dts": "^6.2.1",
145+
"rolldown-plugin-dts": "^0.13.2",
145146
"rollup-plugin-license": "^3.6.0",
146147
"sass": "^1.88.0",
147148
"sass-embedded": "^1.88.0",

packages/vite/rollup.dts.config.ts

Lines changed: 166 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
import { readFileSync } from 'node:fs'
22
import { fileURLToPath } from 'node:url'
3-
import { findStaticImports } from 'mlly'
4-
import { defineConfig } from 'rollup'
5-
import type { Plugin, PluginContext, RenderedChunk } from 'rollup'
6-
import dts from 'rollup-plugin-dts'
7-
import { parse } from '@babel/parser'
3+
import { defineConfig } from 'rolldown'
4+
import type {
5+
OutputChunk,
6+
Plugin,
7+
PluginContext,
8+
RenderedChunk,
9+
} from 'rolldown'
10+
import { parseAst } from 'rolldown/parseAst'
11+
import { dts } from 'rolldown-plugin-dts'
12+
import { parse as parseWithBabel } from '@babel/parser'
813
import { walk } from 'estree-walker'
914
import MagicString from 'magic-string'
15+
import type {
16+
Directive,
17+
ModuleExportName,
18+
Program,
19+
Statement,
20+
} from '@oxc-project/types'
1021

1122
const depTypesDir = new URL('./src/types/', import.meta.url)
1223
const pkg = JSON.parse(
@@ -32,7 +43,7 @@ export default defineConfig({
3243
format: 'esm',
3344
},
3445
external,
35-
plugins: [patchTypes(), dts({ respectExternal: true })],
46+
plugins: [patchTypes(), dts({ dtsInput: true })],
3647
})
3748

3849
// Taken from https://stackoverflow.com/a/36328890
@@ -47,22 +58,50 @@ const identifierWithTrailingDollarRE = /\b(\w+)\$\d+\b/g
4758
*/
4859
const identifierReplacements: Record<string, Record<string, string>> = {
4960
rollup: {
50-
Plugin$1: 'rollup.Plugin',
51-
PluginContext$1: 'rollup.PluginContext',
52-
MinimalPluginContext$1: 'rollup.MinimalPluginContext',
53-
TransformResult$1: 'rollup.TransformResult',
61+
Plugin$2: 'Rollup.Plugin',
62+
TransformResult$1: 'Rollup.TransformResult',
5463
},
5564
esbuild: {
5665
TransformResult$2: 'esbuild_TransformResult',
5766
TransformOptions$1: 'esbuild_TransformOptions',
5867
BuildOptions$1: 'esbuild_BuildOptions',
5968
},
69+
'node:http': {
70+
// https://github.com/rolldown/rolldown/issues/4324
71+
http$1: 'http_1',
72+
http$2: 'http_2',
73+
http$3: 'http_3',
74+
Server$1: 'http.Server',
75+
IncomingMessage$1: 'http.IncomingMessage',
76+
},
6077
'node:https': {
61-
Server$1: 'HttpsServer',
62-
ServerOptions$1: 'HttpsServerOptions',
78+
Server$2: 'HttpsServer',
79+
ServerOptions$2: 'HttpsServerOptions',
80+
},
81+
'vite/module-runner': {
82+
FetchResult$1: 'moduleRunner_FetchResult',
83+
},
84+
'../../types/hmrPayload.js': {
85+
CustomPayload$1: 'hmrPayload_CustomPayload',
86+
HotPayload$1: 'hmrPayload_HotPayload',
87+
},
88+
'../../types/customEvent.js': {
89+
InferCustomEventPayload$1: 'hmrPayload_InferCustomEventPayload',
90+
},
91+
'../../types/internal/lightningcssOptions.js': {
92+
LightningCSSOptions$1: 'lightningcssOptions_LightningCSSOptions',
6393
},
6494
}
6595

96+
// type names that are declared
97+
const ignoreConfusingTypeNames = [
98+
'Plugin$1',
99+
'PluginContext$1',
100+
'MinimalPluginContext$1',
101+
'ServerOptions$1',
102+
'TransformPluginContext$1',
103+
]
104+
66105
/**
67106
* Patch the types files before passing to dts plugin
68107
* 1. Resolve `dep-types/*` and `types/*` imports
@@ -74,47 +113,102 @@ const identifierReplacements: Record<string, Record<string, string>> = {
74113
function patchTypes(): Plugin {
75114
return {
76115
name: 'patch-types',
77-
resolveId(id) {
78-
// Dep types should be bundled
79-
if (id.startsWith('dep-types/')) {
80-
const fileUrl = new URL(
81-
`./${id.slice('dep-types/'.length)}.d.ts`,
82-
depTypesDir,
83-
)
84-
return fileURLToPath(fileUrl)
85-
}
86-
// Ambient types are unbundled and externalized
87-
if (id.startsWith('types/')) {
88-
return {
89-
id: '../../' + (id.endsWith('.js') ? id : id + '.js'),
90-
external: true,
116+
resolveId: {
117+
order: 'pre',
118+
handler(id) {
119+
// Dep types should be bundled
120+
if (id.startsWith('dep-types/')) {
121+
const fileUrl = new URL(
122+
`./${id.slice('dep-types/'.length)}.d.ts`,
123+
depTypesDir,
124+
)
125+
return fileURLToPath(fileUrl)
91126
}
92-
}
127+
// Ambient types are unbundled and externalized
128+
if (id.startsWith('types/')) {
129+
return {
130+
id: '../../' + (id.endsWith('.js') ? id : id + '.js'),
131+
external: true,
132+
}
133+
}
134+
},
93135
},
94-
renderChunk(code, chunk) {
95-
if (
96-
chunk.fileName.startsWith('module-runner') ||
97-
// index and moduleRunner have a common chunk "moduleRunnerTransport"
98-
chunk.fileName.startsWith('moduleRunnerTransport') ||
99-
chunk.fileName.startsWith('types.d-')
100-
) {
101-
validateRunnerChunk.call(this, chunk)
102-
} else {
103-
validateChunkImports.call(this, chunk)
104-
code = replaceConfusingTypeNames.call(this, code, chunk)
105-
code = stripInternalTypes.call(this, code, chunk)
106-
code = cleanUnnecessaryComments(code)
136+
generateBundle(_opts, bundle) {
137+
for (const chunk of Object.values(bundle)) {
138+
if (chunk.type !== 'chunk') continue
139+
140+
const ast = parseAst(chunk.code, { lang: 'ts', sourceType: 'module' })
141+
const importBindings = getAllImportBindings(ast)
142+
if (
143+
chunk.fileName.startsWith('module-runner') ||
144+
// index and moduleRunner have a common chunk "moduleRunnerTransport"
145+
chunk.fileName.startsWith('moduleRunnerTransport') ||
146+
chunk.fileName.startsWith('types.d-')
147+
) {
148+
validateRunnerChunk.call(this, chunk, importBindings)
149+
} else {
150+
validateChunkImports.call(this, chunk, importBindings)
151+
replaceConfusingTypeNames.call(this, chunk, importBindings)
152+
stripInternalTypes.call(this, chunk)
153+
cleanUnnecessaryComments(chunk)
154+
}
107155
}
108-
return code
109156
},
110157
}
111158
}
112159

160+
function stringifyModuleExportName(node: ModuleExportName): string {
161+
if (node.type === 'Identifier') {
162+
return node.name
163+
}
164+
return node.value
165+
}
166+
167+
type ImportBindings = { id: string; bindings: string[]; locals: string[] }
168+
169+
function getImportBindings(
170+
node: Directive | Statement,
171+
): ImportBindings | undefined {
172+
if (node.type === 'ImportDeclaration') {
173+
return {
174+
id: node.source.value,
175+
bindings: node.specifiers.map((s) =>
176+
s.type === 'ImportDefaultSpecifier'
177+
? 'default'
178+
: s.type === 'ImportNamespaceSpecifier'
179+
? '*'
180+
: stringifyModuleExportName(s.imported),
181+
),
182+
locals: node.specifiers.map((s) => s.local.name),
183+
}
184+
}
185+
if (node.type === 'ExportNamedDeclaration') {
186+
if (!node.source) return undefined
187+
return {
188+
id: node.source.value,
189+
bindings: node.specifiers.map((s) => stringifyModuleExportName(s.local)),
190+
locals: [],
191+
}
192+
}
193+
if (node.type === 'ExportAllDeclaration') {
194+
if (!node.source) return undefined
195+
return { id: node.source.value, bindings: ['*'], locals: [] }
196+
}
197+
}
198+
199+
function getAllImportBindings(ast: Program): ImportBindings[] {
200+
return ast.body.flatMap((node) => getImportBindings(node) ?? [])
201+
}
202+
113203
/**
114204
* Runner chunk should only import local dependencies to stay lightweight
115205
*/
116-
function validateRunnerChunk(this: PluginContext, chunk: RenderedChunk) {
117-
for (const [id, bindings] of Object.entries(chunk.importedBindings)) {
206+
function validateRunnerChunk(
207+
this: PluginContext,
208+
chunk: RenderedChunk,
209+
importBindings: ImportBindings[],
210+
) {
211+
for (const { id, bindings } of importBindings) {
118212
if (
119213
!id.startsWith('./') &&
120214
!id.startsWith('../') &&
@@ -133,9 +227,13 @@ function validateRunnerChunk(this: PluginContext, chunk: RenderedChunk) {
133227
/**
134228
* Validate that chunk imports do not import dev deps
135229
*/
136-
function validateChunkImports(this: PluginContext, chunk: RenderedChunk) {
230+
function validateChunkImports(
231+
this: PluginContext,
232+
chunk: RenderedChunk,
233+
importBindings: ImportBindings[],
234+
) {
137235
const deps = Object.keys(pkg.dependencies)
138-
for (const [id, bindings] of Object.entries(chunk.importedBindings)) {
236+
for (const { id, bindings } of importBindings) {
139237
if (
140238
!id.startsWith('./') &&
141239
!id.startsWith('../') &&
@@ -163,17 +261,13 @@ function validateChunkImports(this: PluginContext, chunk: RenderedChunk) {
163261
*/
164262
function replaceConfusingTypeNames(
165263
this: PluginContext,
166-
code: string,
167-
chunk: RenderedChunk,
264+
chunk: OutputChunk,
265+
importBindings: ImportBindings[],
168266
) {
169-
const imports = findStaticImports(code)
170-
171267
for (const modName in identifierReplacements) {
172-
const imp = imports.find(
173-
(imp) => imp.specifier === modName && imp.imports.includes('{'),
174-
)
268+
const imp = importBindings.filter((imp) => imp.id === modName)
175269
// Validate that `identifierReplacements` is not outdated if there's no match
176-
if (!imp) {
270+
if (imp.length === 0) {
177271
this.warn(
178272
`${chunk.fileName} does not import "${modName}" for replacement`,
179273
)
@@ -184,7 +278,7 @@ function replaceConfusingTypeNames(
184278
const replacements = identifierReplacements[modName]
185279
for (const id in replacements) {
186280
// Validate that `identifierReplacements` is not outdated if there's no match
187-
if (!imp.imports.includes(id)) {
281+
if (!imp.some((i) => i.locals.includes(id))) {
188282
this.warn(
189283
`${chunk.fileName} does not import "${id}" from "${modName}" for replacement`,
190284
)
@@ -198,17 +292,23 @@ function replaceConfusingTypeNames(
198292
// named import cannot be replaced with `Foo as Namespace.Foo`, so we
199293
// pre-emptively remove the whole named import
200294
if (betterId.includes('.')) {
201-
code = code.replace(
295+
chunk.code = chunk.code.replace(
202296
new RegExp(`\\b\\w+\\b as ${regexEscapedId},?\\s?`),
203297
'',
204298
)
205299
}
206-
code = code.replace(new RegExp(`\\b${regexEscapedId}\\b`, 'g'), betterId)
300+
chunk.code = chunk.code.replace(
301+
new RegExp(`\\b${regexEscapedId}\\b`, 'g'),
302+
betterId,
303+
)
207304
}
208305
}
209306

210307
const unreplacedIds = unique(
211-
Array.from(code.matchAll(identifierWithTrailingDollarRE), (m) => m[0]),
308+
Array.from(
309+
chunk.code.matchAll(identifierWithTrailingDollarRE),
310+
(m) => m[0],
311+
).filter((id) => !ignoreConfusingTypeNames.includes(id)),
212312
)
213313
if (unreplacedIds.length) {
214314
const unreplacedStr = unreplacedIds.map((id) => `\n- ${id}`).join('')
@@ -217,23 +317,18 @@ function replaceConfusingTypeNames(
217317
)
218318
process.exitCode = 1
219319
}
220-
221-
return code
222320
}
223321

224322
/**
225323
* While we already enable `compilerOptions.stripInternal`, some internal comments
226324
* like internal parameters are still not stripped by TypeScript, so we run another
227325
* pass here.
228326
*/
229-
function stripInternalTypes(
230-
this: PluginContext,
231-
code: string,
232-
chunk: RenderedChunk,
233-
) {
234-
if (code.includes('@internal')) {
235-
const s = new MagicString(code)
236-
const ast = parse(code, {
327+
function stripInternalTypes(this: PluginContext, chunk: OutputChunk) {
328+
if (chunk.code.includes('@internal')) {
329+
const s = new MagicString(chunk.code)
330+
// need to parse with babel to get the comments
331+
const ast = parseWithBabel(chunk.code, {
237332
plugins: ['typescript'],
238333
sourceType: 'module',
239334
})
@@ -246,15 +341,13 @@ function stripInternalTypes(
246341
},
247342
})
248343

249-
code = s.toString()
344+
chunk.code = s.toString()
250345

251-
if (code.includes('@internal')) {
346+
if (chunk.code.includes('@internal')) {
252347
this.warn(`${chunk.fileName} has unhandled @internal declarations`)
253348
process.exitCode = 1
254349
}
255350
}
256-
257-
return code
258351
}
259352

260353
/**
@@ -283,8 +376,8 @@ function removeInternal(s: MagicString, node: any): boolean {
283376
return false
284377
}
285378

286-
function cleanUnnecessaryComments(code: string) {
287-
return code
379+
function cleanUnnecessaryComments(chunk: OutputChunk) {
380+
chunk.code = chunk.code
288381
.replace(multilineCommentsRE, (m) => {
289382
return licenseCommentsRE.test(m) ? '' : m
290383
})

0 commit comments

Comments
 (0)