Skip to content

Commit 22ff43f

Browse files
dominikgsapphi-red
authored andcommitted
refactor: separate tsconfck caches per config in a weakmap
1 parent d4857c1 commit 22ff43f

File tree

3 files changed

+102
-69
lines changed

3 files changed

+102
-69
lines changed

packages/vite/src/node/optimizer/index.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,9 +1090,13 @@ export async function extractExportsData(
10901090
debug?.(
10911091
`Unable to parse: ${filePath}.\n Trying again with a ${loader} transform.`,
10921092
)
1093-
const transformed = await transformWithEsbuild(entryContent, filePath, {
1094-
loader,
1095-
})
1093+
const transformed = await transformWithEsbuild(
1094+
entryContent,
1095+
filePath,
1096+
{ loader },
1097+
undefined,
1098+
environment.config,
1099+
)
10961100
parseResult = parse(transformed.code)
10971101
usedJsxLoader = true
10981102
}

packages/vite/src/node/optimizer/scan.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,10 +299,10 @@ async function prepareEsbuildScanner(
299299
// Therefore, we use the closest tsconfig.json from the root to make it work in most cases.
300300
let tsconfigRaw = esbuildOptions.tsconfigRaw
301301
if (!tsconfigRaw && !esbuildOptions.tsconfig) {
302-
const tsconfigResult = await loadTsconfigJsonForFile(
302+
const { tsconfig } = await loadTsconfigJsonForFile(
303303
path.join(environment.config.root, '_dummy.js'),
304304
)
305-
if (tsconfigResult.compilerOptions?.experimentalDecorators) {
305+
if (tsconfig.compilerOptions?.experimentalDecorators) {
306306
tsconfigRaw = { compilerOptions: { experimentalDecorators: true } }
307307
}
308308
}

packages/vite/src/node/plugins/esbuild.ts

Lines changed: 93 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -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

445490
export 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

474501
export 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

Comments
 (0)