@@ -80,7 +80,7 @@ export async function transformWithEsbuild(
8080 filename : string ,
8181 options ?: TransformOptions ,
8282 inMap ?: object ,
83- root ?: string ,
83+ config ?: ResolvedConfig ,
8484 watcher ?: any , // TODO: module-runner bundling issue with FSWatcher,
8585) : Promise < ESBuildTransformResult > {
8686 let loader = options ?. loader
@@ -122,18 +122,29 @@ export async function transformWithEsbuild(
122122 ]
123123 const compilerOptionsForFile : TSCompilerOptions = { }
124124 if ( loader === 'ts' || loader === 'tsx' ) {
125- const loadedTsconfig = await loadTsconfigJsonForFile (
126- filename ,
127- root ,
128- watcher ,
129- )
130- const loadedCompilerOptions = loadedTsconfig . compilerOptions ?? { }
125+ try {
126+ const { tsconfig : loadedTsconfig , tsconfigFile } =
127+ await loadTsconfigJsonForFile ( filename , config )
128+ // tsconfig could be out of root, make sure it is watched on dev
129+ if ( watcher && tsconfigFile && config ) {
130+ ensureWatchedFile ( watcher , tsconfigFile , config . root )
131+ }
132+ const loadedCompilerOptions = loadedTsconfig . compilerOptions ?? { }
131133
132- for ( const field of meaningfulFields ) {
133- if ( field in loadedCompilerOptions ) {
134- // @ts -expect-error TypeScript can't tell they are of the same type
135- compilerOptionsForFile [ field ] = loadedCompilerOptions [ field ]
134+ for ( const field of meaningfulFields ) {
135+ if ( field in loadedCompilerOptions ) {
136+ // @ts -expect-error TypeScript can't tell they are of the same type
137+ compilerOptionsForFile [ field ] = loadedCompilerOptions [ field ]
138+ }
136139 }
140+ } catch ( e ) {
141+ if ( e instanceof TSConfckParseError ) {
142+ // tsconfig could be out of root, make sure it is watched on dev
143+ if ( watcher && e . tsconfigFile && config ) {
144+ ensureWatchedFile ( watcher , e . tsconfigFile , config . root )
145+ }
146+ }
147+ throw e
137148 }
138149 }
139150
@@ -254,11 +265,23 @@ export function esbuildPlugin(config: ResolvedConfig): Plugin {
254265 } ,
255266 }
256267
268+ let server : ViteDevServer
269+
257270 return {
258271 name : 'vite:esbuild' ,
272+ configureServer ( _server ) {
273+ server = _server
274+ } ,
259275 async transform ( code , id ) {
260276 if ( filter ( id ) || filter ( cleanUrl ( id ) ) ) {
261- const result = await transformWithEsbuild ( code , id , transformOptions )
277+ const result = await transformWithEsbuild (
278+ code ,
279+ id ,
280+ transformOptions ,
281+ undefined ,
282+ config ,
283+ server ?. watcher ,
284+ )
262285 if ( result . warnings . length ) {
263286 result . warnings . forEach ( ( m ) => {
264287 this . warn ( prettifyMessage ( m , code ) )
@@ -309,7 +332,13 @@ export const buildEsbuildPlugin = (config: ResolvedConfig): Plugin => {
309332 return null
310333 }
311334
312- const res = await transformWithEsbuild ( code , chunk . fileName , options )
335+ const res = await transformWithEsbuild (
336+ code ,
337+ chunk . fileName ,
338+ options ,
339+ undefined ,
340+ config ,
341+ )
313342
314343 if ( config . build . lib ) {
315344 // #7188, esbuild adds helpers out of the UMD and IIFE wrappers, and the
@@ -440,35 +469,33 @@ function prettifyMessage(m: Message, code: string): string {
440469 return res + `\n`
441470}
442471
443- let tsconfckCache : TSConfckCache < TSConfckParseResult > | undefined
472+ let globalTSConfckCache : TSConfckCache < TSConfckParseResult > | undefined
473+ const tsconfckCacheMap = new WeakMap <
474+ ResolvedConfig ,
475+ TSConfckCache < TSConfckParseResult >
476+ > ( )
477+
478+ function getTSConfckCache ( config ?: ResolvedConfig ) {
479+ if ( ! config ) {
480+ return ( globalTSConfckCache ??= new TSConfckCache < TSConfckParseResult > ( ) )
481+ }
482+ let cache = tsconfckCacheMap . get ( config )
483+ if ( ! cache ) {
484+ cache = new TSConfckCache < TSConfckParseResult > ( )
485+ tsconfckCacheMap . set ( config , cache )
486+ }
487+ return cache
488+ }
444489
445490export async function loadTsconfigJsonForFile (
446491 filename : string ,
447- root ?: string ,
448- watcher ?: any , // TODO: module-runner issue with FSWatcher,
449- ) : Promise < TSConfigJSON > {
450- try {
451- if ( ! tsconfckCache ) {
452- tsconfckCache = new TSConfckCache < TSConfckParseResult > ( )
453- }
454- const result = await parse ( filename , {
455- cache : tsconfckCache ,
456- ignoreNodeModules : true ,
457- } )
458- // tsconfig could be out of root, make sure it is watched on dev
459- if ( root && watcher && result . tsconfigFile ) {
460- ensureWatchedFile ( watcher , result . tsconfigFile , root )
461- }
462- return result . tsconfig
463- } catch ( e ) {
464- if ( e instanceof TSConfckParseError ) {
465- // tsconfig could be out of root, make sure it is watched on dev
466- if ( root && watcher && e . tsconfigFile ) {
467- ensureWatchedFile ( watcher , e . tsconfigFile , root )
468- }
469- }
470- throw e
471- }
492+ config ?: ResolvedConfig ,
493+ ) : Promise < { tsconfigFile : string ; tsconfig : TSConfigJSON } > {
494+ const { tsconfig, tsconfigFile } = await parse ( filename , {
495+ cache : getTSConfckCache ( config ) ,
496+ ignoreNodeModules : true ,
497+ } )
498+ return { tsconfigFile, tsconfig }
472499}
473500
474501export async function reloadOnTsconfigChange (
@@ -477,32 +504,34 @@ export async function reloadOnTsconfigChange(
477504) : Promise < void > {
478505 // any tsconfig.json that's added in the workspace could be closer to a code file than a previously cached one
479506 // any json file in the tsconfig cache could have been used to compile ts
480- if (
481- path . basename ( changedFile ) === 'tsconfig.json' ||
482- changedFile . endsWith ( '.json' ) /*
483- TODO: the tsconfckCache?.clear() line will make this fail if there are several servers
484- we may need a cache per server if we don't want all servers to share the reset
485- leaving it commented for now because it should still work
486- && tsconfckCache?.hasParseResult(changedFile)
487- */
488- ) {
489- server . config . logger . info (
490- `changed tsconfig file detected: ${ changedFile } - Clearing cache and forcing full-reload to ensure TypeScript is compiled with updated config values.` ,
491- { clear : server . config . clearScreen , timestamp : true } ,
492- )
493-
494- // clear module graph to remove code compiled with outdated config
495- for ( const environment of Object . values ( server . environments ) ) {
496- environment . moduleGraph . invalidateAll ( )
497- }
507+ if ( changedFile . endsWith ( '.json' ) ) {
508+ const cache = getTSConfckCache ( server . config )
509+ if (
510+ changedFile . endsWith ( '/tsconfig.json' ) ||
511+ cache . hasParseResult ( changedFile )
512+ ) {
513+ server . config . logger . info (
514+ `changed tsconfig file detected: ${ changedFile } - Clearing cache and forcing full-reload to ensure TypeScript is compiled with updated config values.` ,
515+ { clear : server . config . clearScreen , timestamp : true } ,
516+ )
498517
499- // reset tsconfck so that recompile works with up2date configs
500- tsconfckCache ?. clear ( )
518+ // TODO: more finegrained invalidation than the nuclear option below
519+
520+ // clear module graph to remove code compiled with outdated config
521+ for ( const environment of Object . values ( server . environments ) ) {
522+ environment . moduleGraph . invalidateAll ( )
523+ }
501524
502- // force full reload
503- server . ws . send ( {
504- type : 'full-reload' ,
505- path : '*' ,
506- } )
525+ // reset tsconfck cache so that recompile works with up2date configs
526+ cache . clear ( )
527+
528+ // reload environments
529+ for ( const environment of Object . values ( server . environments ) ) {
530+ environment . hot . send ( {
531+ type : 'full-reload' ,
532+ path : '*' ,
533+ } )
534+ }
535+ }
507536 }
508537}
0 commit comments