Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 53 additions & 52 deletions packages/vite/src/node/plugins/esbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
TransformResult,
} from 'esbuild'
import { transform } from 'esbuild'
// TODO: import type { FSWatcher } from 'chokidar'
import type { RawSourceMap } from '@ampproject/remapping'
import type { InternalModuleFormat, SourceMap } from 'rollup'
import type { TSConfckParseResult } from 'tsconfck'
Expand All @@ -17,8 +18,9 @@ import {
createFilter,
ensureWatchedFile,
generateCodeFrame,
normalizePath,
} from '../utils'
import type { ViteDevServer } from '../server'

import type { ResolvedConfig } from '../config'
import type { Plugin } from '../plugin'
import { cleanUrl } from '../../shared/utils'
Expand All @@ -41,10 +43,6 @@ export const defaultEsbuildSupported = {
'import-meta': true,
}

// TODO: rework to avoid caching the server for this module.
// If two servers are created in the same process, they will interfere with each other.
let server: ViteDevServer

export interface ESBuildOptions extends TransformOptions {
include?: string | RegExp | string[] | RegExp[]
exclude?: string | RegExp | string[] | RegExp[]
Expand Down Expand Up @@ -83,6 +81,8 @@ export async function transformWithEsbuild(
filename: string,
options?: TransformOptions,
inMap?: object,
root?: string,
watcher?: any, // TODO: module-runner bundling issue with FSWatcher,
): Promise<ESBuildTransformResult> {
let loader = options?.loader

Expand Down Expand Up @@ -123,7 +123,11 @@ export async function transformWithEsbuild(
]
const compilerOptionsForFile: TSCompilerOptions = {}
if (loader === 'ts' || loader === 'tsx') {
const loadedTsconfig = await loadTsconfigJsonForFile(filename)
const loadedTsconfig = await loadTsconfigJsonForFile(
filename,
root,
watcher,
)
const loadedCompilerOptions = loadedTsconfig.compilerOptions ?? {}

for (const field of meaningfulFields) {
Expand Down Expand Up @@ -253,16 +257,43 @@ export function esbuildPlugin(config: ResolvedConfig): Plugin {

return {
name: 'vite:esbuild',
configureServer(_server) {
server = _server
server.watcher
.on('add', reloadOnTsconfigChange)
.on('change', reloadOnTsconfigChange)
.on('unlink', reloadOnTsconfigChange)
},
buildEnd() {
// recycle serve to avoid preventing Node self-exit (#6815)
server = null as any
configureServer(server) {
// we need to watch all files to find changes to previously unknown tsconfig.json files
server.watcher.on('all', (event, file) => {
if (
(event === 'add' || event === 'change' || event === 'unlink') &&
file.slice(-5) === '.json'
) {
const filename = normalizePath(file)
if (
filename.slice(-14) === '/tsconfig.json' ||
tsconfckCache?.hasParseResult(filename)
) {
server.config.logger.info(
`changed tsconfig file detected: ${filename} - Clearing cache and forcing full-reload to ensure TypeScript is compiled with updated config values.`,
{ clear: server.config.clearScreen, timestamp: true },
)

// TODO: more finegrained invalidation than the nuclear option below

// clear module graph to remove code compiled with outdated config
for (const environment of Object.values(server.environments)) {
environment.moduleGraph.invalidateAll()
}

// reset tsconfck cache so that recompile works with up2date configs
tsconfckCache?.clear()

// reload environments
for (const environment of Object.values(server.environments)) {
environment.hot.send({
type: 'full-reload',
path: '*',
})
}
}
}
})
},
async transform(code, id) {
if (filter(id) || filter(cleanUrl(id))) {
Expand Down Expand Up @@ -452,6 +483,8 @@ let tsconfckCache: TSConfckCache<TSConfckParseResult> | undefined

export async function loadTsconfigJsonForFile(
filename: string,
root?: string,
watcher?: any, // TODO: module-runner issue with FSWatcher,
): Promise<TSConfigJSON> {
try {
if (!tsconfckCache) {
Expand All @@ -462,49 +495,17 @@ export async function loadTsconfigJsonForFile(
ignoreNodeModules: true,
})
// tsconfig could be out of root, make sure it is watched on dev
if (server && result.tsconfigFile) {
ensureWatchedFile(server.watcher, result.tsconfigFile, server.config.root)
if (root && watcher && result.tsconfigFile) {
ensureWatchedFile(watcher, result.tsconfigFile, root)
}
return result.tsconfig
} catch (e) {
if (e instanceof TSConfckParseError) {
// tsconfig could be out of root, make sure it is watched on dev
if (server && e.tsconfigFile) {
ensureWatchedFile(server.watcher, e.tsconfigFile, server.config.root)
if (root && watcher && e.tsconfigFile) {
ensureWatchedFile(watcher, e.tsconfigFile, root)
}
}
throw e
}
}

async function reloadOnTsconfigChange(changedFile: string) {
// server could be closed externally after a file change is detected
if (!server) return
// any tsconfig.json that's added in the workspace could be closer to a code file than a previously cached one
// any json file in the tsconfig cache could have been used to compile ts
if (
path.basename(changedFile) === 'tsconfig.json' ||
(changedFile.endsWith('.json') &&
tsconfckCache?.hasParseResult(changedFile))
) {
server.config.logger.info(
`changed tsconfig file detected: ${changedFile} - Clearing cache and forcing full-reload to ensure TypeScript is compiled with updated config values.`,
{ clear: server.config.clearScreen, timestamp: true },
)

// clear module graph to remove code compiled with outdated config
server.moduleGraph.invalidateAll()

// reset tsconfck so that recompile works with up2date configs
tsconfckCache?.clear()

// server may not be available if vite config is updated at the same time
if (server) {
// force full reload
server.hot.send({
type: 'full-reload',
path: '*',
})
}
}
}
Loading