11import { readFileSync } from 'node:fs'
22import { 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'
813import { walk } from 'estree-walker'
914import MagicString from 'magic-string'
15+ import type {
16+ Directive ,
17+ ModuleExportName ,
18+ Program ,
19+ Statement ,
20+ } from '@oxc-project/types'
1021
1122const depTypesDir = new URL ( './src/types/' , import . meta. url )
1223const 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 */
4859const 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>> = {
74113function 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 */
164262function 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