11import { mkdir , readFile , writeFile } from 'node:fs/promises'
22import path from 'node:path'
33import { createFilter } from '@rollup/pluginutils'
4+ import MagicString from 'magic-string'
45import { parseAsync } from 'oxc-parser'
56import {
67 createUnplugin ,
@@ -15,6 +16,7 @@ import {
1516 tsTransform ,
1617 type TransformResult ,
1718} from './core/transformer'
19+ import type { TSESTree } from '@typescript-eslint/typescript-estree'
1820import type { PluginBuild } from 'esbuild'
1921import type { Plugin , PluginContext } from 'rollup'
2022
@@ -94,6 +96,41 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
9496 code : string ,
9597 id : string ,
9698 ) : Promise < undefined > {
99+ let program : TSESTree . Program | undefined
100+ try {
101+ program = JSON . parse (
102+ ( await parseAsync ( code , { sourceFilename : id } ) ) . program ,
103+ )
104+ } catch { }
105+
106+ if ( options . autoAddExts && program ) {
107+ const imports = program . body . filter (
108+ ( node ) =>
109+ node . type === 'ImportDeclaration' ||
110+ node . type === 'ExportAllDeclaration' ||
111+ node . type === 'ExportNamedDeclaration' ,
112+ )
113+ const s = new MagicString ( code )
114+ for ( const i of imports ) {
115+ if ( ! i . source || path . basename ( i . source . value ) . includes ( '.' ) ) {
116+ continue
117+ }
118+
119+ const resolved = await resolve ( context , i . source . value , id )
120+ if ( ! resolved || resolved . external ) continue
121+ if ( resolved . id . endsWith ( '.ts' ) ) {
122+ s . overwrite (
123+ // @ts -expect-error
124+ i . source . start ,
125+ // @ts -expect-error
126+ i . source . end ,
127+ JSON . stringify ( `${ i . source . value } .js` ) ,
128+ )
129+ }
130+ }
131+ code = s . toString ( )
132+ }
133+
97134 let result : TransformResult
98135 switch ( options . transformer ) {
99136 case 'oxc' :
@@ -120,25 +157,45 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
120157 }
121158 addOutput ( id , sourceText )
122159
123- let program : any
124- try {
125- program = JSON . parse (
126- ( await parseAsync ( code , { sourceFilename : id } ) ) . program ,
127- )
128- } catch {
129- return
130- }
131- const typeImports = program . body . filter ( ( node : any ) => {
132- if ( node . type !== 'ImportDeclaration' ) return false
133- if ( node . importKind === 'type' ) return true
134- return ( node . specifiers || [ ] ) . every (
135- ( spec : any ) =>
136- spec . type === 'ImportSpecifier' && spec . importKind === 'type' ,
137- )
138- } )
160+ if ( ! program ) return
161+ const typeImports = program . body . filter (
162+ (
163+ node ,
164+ ) : node is
165+ | TSESTree . ImportDeclaration
166+ | TSESTree . ExportNamedDeclaration
167+ | TSESTree . ExportAllDeclaration => {
168+ if ( node . type === 'ImportDeclaration' ) {
169+ if ( node . importKind === 'type' ) return true
170+ return (
171+ node . specifiers &&
172+ node . specifiers . every (
173+ ( spec ) =>
174+ spec . type === 'ImportSpecifier' && spec . importKind === 'type' ,
175+ )
176+ )
177+ }
178+ if (
179+ node . type === 'ExportNamedDeclaration' ||
180+ node . type === 'ExportAllDeclaration'
181+ ) {
182+ if ( node . exportKind === 'type' ) return true
183+ return (
184+ node . type === 'ExportNamedDeclaration' &&
185+ node . specifiers &&
186+ node . specifiers . every (
187+ ( spec ) =>
188+ spec . type === 'ExportSpecifier' && spec . exportKind === 'type' ,
189+ )
190+ )
191+ }
192+ return false
193+ } ,
194+ )
139195
140196 for ( const i of typeImports ) {
141- const resolved = await resolve ( context , i . source . value , id )
197+ if ( ! i . source ) continue
198+ const resolved = ( await resolve ( context , i . source . value , id ) ) ?. id
142199 if ( resolved && filter ( resolved ) && ! outputFiles [ stripExt ( resolved ) ] ) {
143200 let source : string
144201 try {
@@ -212,22 +269,23 @@ export const IsolatedDecl: UnpluginInstance<Options | undefined, false> =
212269 }
213270 } )
214271
215- const resolve = async (
272+ async function resolve (
216273 context : UnpluginBuildContext ,
217274 id : string ,
218275 importer : string ,
219- ) = > {
276+ ) : Promise < { id : string ; external : boolean } | undefined > {
220277 const nativeContext = context . getNativeBuildContext ?.( )
221278 if ( nativeContext ?. framework === 'esbuild' ) {
222- return (
223- await nativeContext . build . resolve ( id , {
224- importer,
225- resolveDir : path . dirname ( importer ) ,
226- kind : 'import-statement' ,
227- } )
228- ) . path
279+ const resolved = await nativeContext . build . resolve ( id , {
280+ importer,
281+ resolveDir : path . dirname ( importer ) ,
282+ kind : 'import-statement' ,
283+ } )
284+ return { id : resolved . path , external : resolved . external }
229285 }
230- return ( await ( context as PluginContext ) . resolve ( id , importer ) ) ?. id
286+ const resolved = await ( context as PluginContext ) . resolve ( id , importer )
287+ if ( ! resolved ) return
288+ return { id : resolved . id , external : ! ! resolved . external }
231289}
232290
233291function stripExt ( filename : string ) {
0 commit comments