Skip to content

Commit e5cdff7

Browse files
authoredMar 23, 2023
perf(esbuild): make tsconfck non-blocking (#12548)
1 parent 7eb52ec commit e5cdff7

File tree

2 files changed

+59
-39
lines changed

2 files changed

+59
-39
lines changed
 

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

+58-38
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type {
99
import { transform } from 'esbuild'
1010
import type { RawSourceMap } from '@ampproject/remapping'
1111
import type { InternalModuleFormat, SourceMap } from 'rollup'
12-
import type { TSConfckParseOptions, TSConfckParseResult } from 'tsconfck'
12+
import type { TSConfckParseOptions } from 'tsconfck'
1313
import { TSConfckParseError, findAll, parse } from 'tsconfck'
1414
import {
1515
cleanUrl,
@@ -18,11 +18,13 @@ import {
1818
createFilter,
1919
ensureWatchedFile,
2020
generateCodeFrame,
21+
timeFrom,
2122
} from '../utils'
2223
import type { ResolvedConfig, ViteDevServer } from '..'
2324
import type { Plugin } from '../plugin'
2425
import { searchForWorkspaceRoot } from '..'
2526

27+
const isDebug = process.env.DEBUG
2628
const debug = createDebugger('vite:esbuild')
2729

2830
const INJECT_HELPERS_IIFE_RE =
@@ -204,7 +206,8 @@ export async function transformWithEsbuild(
204206
}
205207
}
206208

207-
export function esbuildPlugin(options: ESBuildOptions): Plugin {
209+
export function esbuildPlugin(config: ResolvedConfig): Plugin {
210+
const options = config.esbuild as ESBuildOptions
208211
const { jsxInject, include, exclude, ...esbuildTransformOptions } = options
209212

210213
const filter = createFilter(include || /\.(m?ts|[jt]sx)$/, exclude || /\.js$/)
@@ -226,6 +229,8 @@ export function esbuildPlugin(options: ESBuildOptions): Plugin {
226229
keepNames: false,
227230
}
228231

232+
initTSConfck(config.root)
233+
229234
return {
230235
name: 'vite:esbuild',
231236
configureServer(_server) {
@@ -235,9 +240,6 @@ export function esbuildPlugin(options: ESBuildOptions): Plugin {
235240
.on('change', reloadOnTsconfigChange)
236241
.on('unlink', reloadOnTsconfigChange)
237242
},
238-
async configResolved(config) {
239-
await initTSConfck(config)
240-
},
241243
buildEnd() {
242244
// recycle serve to avoid preventing Node self-exit (#6815)
243245
server = null as any
@@ -281,11 +283,10 @@ const rollupToEsbuildFormatMap: Record<
281283
}
282284

283285
export const buildEsbuildPlugin = (config: ResolvedConfig): Plugin => {
286+
initTSConfck(config.root)
287+
284288
return {
285289
name: 'vite:esbuild-transpile',
286-
async configResolved(config) {
287-
await initTSConfck(config)
288-
},
289290
async renderChunk(code, chunk, opts) {
290291
// @ts-expect-error injected by @vitejs/plugin-legacy
291292
if (opts.__vite_skip_esbuild__) {
@@ -432,32 +433,51 @@ function prettifyMessage(m: Message, code: string): string {
432433
return res + `\n`
433434
}
434435

435-
const tsconfckParseOptions: TSConfckParseOptions = {
436-
cache: new Map<string, TSConfckParseResult>(),
437-
tsConfigPaths: undefined,
438-
root: undefined,
439-
resolveWithEmptyIfConfigNotFound: true,
436+
let tsconfckRoot: string | undefined
437+
let tsconfckParseOptions: TSConfckParseOptions | Promise<TSConfckParseOptions> =
438+
{ resolveWithEmptyIfConfigNotFound: true }
439+
440+
function initTSConfck(root: string, force = false) {
441+
// bail if already cached
442+
if (!force && root === tsconfckRoot) return
443+
444+
const workspaceRoot = searchForWorkspaceRoot(root)
445+
446+
tsconfckRoot = root
447+
tsconfckParseOptions = initTSConfckParseOptions(workspaceRoot)
448+
449+
// cached as the options value itself when promise is resolved
450+
tsconfckParseOptions.then((options) => {
451+
if (root === tsconfckRoot) {
452+
tsconfckParseOptions = options
453+
}
454+
})
440455
}
441456

442-
async function initTSConfck(config: ResolvedConfig) {
443-
const workspaceRoot = searchForWorkspaceRoot(config.root)
444-
debug(`init tsconfck (root: ${colors.cyan(workspaceRoot)})`)
445-
446-
tsconfckParseOptions.cache!.clear()
447-
tsconfckParseOptions.root = workspaceRoot
448-
tsconfckParseOptions.tsConfigPaths = new Set([
449-
...(await findAll(workspaceRoot, {
450-
skip: (dir) => dir === 'node_modules' || dir === '.git',
451-
})),
452-
])
453-
debug(`init tsconfck end`)
457+
async function initTSConfckParseOptions(workspaceRoot: string) {
458+
const start = isDebug ? performance.now() : 0
459+
460+
const options: TSConfckParseOptions = {
461+
cache: new Map(),
462+
root: workspaceRoot,
463+
tsConfigPaths: new Set(
464+
await findAll(workspaceRoot, {
465+
skip: (dir) => dir === 'node_modules' || dir === '.git',
466+
}),
467+
),
468+
resolveWithEmptyIfConfigNotFound: true,
469+
}
470+
471+
isDebug && debug(timeFrom(start), 'tsconfck init', colors.dim(workspaceRoot))
472+
473+
return options
454474
}
455475

456476
async function loadTsconfigJsonForFile(
457477
filename: string,
458478
): Promise<TSConfigJSON> {
459479
try {
460-
const result = await parse(filename, tsconfckParseOptions)
480+
const result = await parse(filename, await tsconfckParseOptions)
461481
// tsconfig could be out of root, make sure it is watched on dev
462482
if (server && result.tsconfigFile !== 'no_tsconfig_file_found') {
463483
ensureWatchedFile(server.watcher, result.tsconfigFile, server.config.root)
@@ -474,15 +494,15 @@ async function loadTsconfigJsonForFile(
474494
}
475495
}
476496

477-
function reloadOnTsconfigChange(changedFile: string) {
497+
async function reloadOnTsconfigChange(changedFile: string) {
478498
// server could be closed externally after a file change is detected
479499
if (!server) return
480500
// any tsconfig.json that's added in the workspace could be closer to a code file than a previously cached one
481501
// any json file in the tsconfig cache could have been used to compile ts
482502
if (
483503
path.basename(changedFile) === 'tsconfig.json' ||
484504
(changedFile.endsWith('.json') &&
485-
tsconfckParseOptions?.cache?.has(changedFile))
505+
(await tsconfckParseOptions)?.cache?.has(changedFile))
486506
) {
487507
server.config.logger.info(
488508
`changed tsconfig file detected: ${changedFile} - Clearing cache and forcing full-reload to ensure TypeScript is compiled with updated config values.`,
@@ -493,15 +513,15 @@ function reloadOnTsconfigChange(changedFile: string) {
493513
server.moduleGraph.invalidateAll()
494514

495515
// reset tsconfck so that recompile works with up2date configs
496-
initTSConfck(server.config).finally(() => {
497-
// server may not be available if vite config is updated at the same time
498-
if (server) {
499-
// force full reload
500-
server.ws.send({
501-
type: 'full-reload',
502-
path: '*',
503-
})
504-
}
505-
})
516+
initTSConfck(server.config.root, true)
517+
518+
// server may not be available if vite config is updated at the same time
519+
if (server) {
520+
// force full reload
521+
server.ws.send({
522+
type: 'full-reload',
523+
path: '*',
524+
})
525+
}
506526
}
507527
}

‎packages/vite/src/node/plugins/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export async function resolvePlugins(
7272
}),
7373
htmlInlineProxyPlugin(config),
7474
cssPlugin(config),
75-
config.esbuild !== false ? esbuildPlugin(config.esbuild) : null,
75+
config.esbuild !== false ? esbuildPlugin(config) : null,
7676
jsonPlugin(
7777
{
7878
namedExports: true,

0 commit comments

Comments
 (0)
Please sign in to comment.