Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: cached fs utils with shared trees #15294

Draft
wants to merge 26 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4d666ab
perf: cached fs utils
patak-dev Dec 7, 2023
aac3690
chore: merge main
patak-dev Dec 7, 2023
524a52a
fix: temporal guard for custom watchers
patak-dev Dec 7, 2023
c929f96
feat: add experimental server.fs.cacheChecks
patak-dev Dec 7, 2023
2fbf00a
feat: also enable fsUtils in createResolver
patak-dev Dec 7, 2023
2f25f9e
chore: update types
patak-dev Dec 7, 2023
a343c66
feat: optimize on add file or directory
patak-dev Dec 7, 2023
89df6d5
feat: optimize tryResolveRealFileWithExtensions
patak-dev Dec 8, 2023
d3d45fc
feat: avoid double normalizePath calls
patak-dev Dec 8, 2023
8ea355e
chore: update
patak-dev Dec 8, 2023
df42d68
chore: remove watch null and custom ignored guard
patak-dev Dec 8, 2023
93572c8
chore: lint
patak-dev Dec 8, 2023
c3d504b
chore: merge main
patak-dev Dec 8, 2023
cfb8693
feat: shared dirent caches
patak-dev Dec 8, 2023
42f689f
fix: only register valid active configs
patak-dev Dec 8, 2023
cc9c8e9
chore: merge main
patak-dev Jan 25, 2024
0104871
chore: update
patak-dev Jan 25, 2024
ab5bc15
chore: update
patak-dev Jan 25, 2024
93d0815
chore: merge main
patak-dev Jan 26, 2024
4b37e14
chore: merge main
patak-dev Jan 26, 2024
1ba78ab
fix: connectRootCacheToActiveRoots
patak-dev Jan 26, 2024
9aef77d
fix: use workspace root
patak-dev Jan 26, 2024
37dcc60
chore: simplify
patak-dev Jan 27, 2024
b3768f1
chore: test
patak-dev Jan 27, 2024
4c409f1
chore: enable back
patak-dev Jan 27, 2024
e11de80
fix: add file to cache servers sharing a root
patak-dev Jan 27, 2024
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
52 changes: 49 additions & 3 deletions packages/vite/src/node/fsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import path from 'node:path'
import type { FSWatcher } from 'dep-types/chokidar'
import type { ResolvedConfig } from './config'
import {
createDebugger,
isInNodeModules,
normalizePath,
safeRealpathSync,
tryStatSync,
} from './utils'
import { searchForWorkspaceRoot } from './server/searchRoot'

const debug = createDebugger('vite:fs')

export interface FsUtils {
existsSync: (path: string) => boolean
isDirectory: (path: string) => boolean
Expand Down Expand Up @@ -41,10 +44,24 @@ export const commonFsUtils: FsUtils = {
tryResolveRealFileOrType,
}

const activeResolvedConfigs = new Array<WeakRef<ResolvedConfig>>()
const registry = new FinalizationRegistry((fsUtils: FsUtils) => {
const i = activeResolvedConfigs.findIndex((r) => !r.deref())
activeResolvedConfigs.splice(i, 1)
})
function addActiveResolvedConfig(config: ResolvedConfig, fsUtils: FsUtils) {
activeResolvedConfigs.push(new WeakRef(config))
registry.register(config, fsUtils)
debug?.(
`registered FsUtils for config with root ${config.root}, active configs: ${activeResolvedConfigs.length}`,
)
}

const cachedFsUtilsMap = new WeakMap<ResolvedConfig, FsUtils>()
export function getFsUtils(config: ResolvedConfig): FsUtils {
let fsUtils = cachedFsUtilsMap.get(config)
if (!fsUtils) {
debug?.(`resolving FsUtils for ${config.root}`)
if (config.command !== 'serve' || !config.server.fs.cachedChecks) {
// cached fsUtils is only used in the dev server for now, and only when the watcher isn't configured
// we can support custom ignored patterns later
Expand All @@ -56,6 +73,7 @@ export function getFsUtils(config: ResolvedConfig): FsUtils {
fsUtils = commonFsUtils
} else {
fsUtils = createCachedFsUtils(config)
addActiveResolvedConfig(config, fsUtils)
}
cachedFsUtilsMap.set(config, fsUtils)
}
Expand Down Expand Up @@ -118,16 +136,41 @@ function ensureFileMaybeSymlinkIsResolved(
isSymlink === undefined ? 'error' : isSymlink ? 'symlink' : 'file'
}

interface CachedFsUtilsMeta {
root: string
rootCache: DirentCache
}
const cachedFsUtilsMeta = new WeakMap<ResolvedConfig, CachedFsUtilsMeta>()

function createSharedRootCache(root: string): DirentCache {
for (const otherConfigRef of activeResolvedConfigs) {
const otherConfig = otherConfigRef?.deref()
if (otherConfig) {
patak-dev marked this conversation as resolved.
Show resolved Hide resolved
const otherCachedFsUtilsMeta = cachedFsUtilsMeta.get(otherConfig)!
const { rootCache: otherRootCache, root: otherRoot } =
otherCachedFsUtilsMeta
if (root === otherRoot) {
debug?.(`FsUtils for ${root} sharing root cache with compatible cache`)
return otherRootCache
patak-dev marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

debug?.(`FsUtils for ${root} started as an new root cache`)
return { type: 'directory' as DirentCacheType } // dirents will be computed lazily
}

function pathUntilPart(root: string, parts: string[], i: number): string {
let p = root
for (let k = 0; k < i; k++) p += '/' + parts[k]
return p
}

export function createCachedFsUtils(config: ResolvedConfig): FsUtils {
function createCachedFsUtils(config: ResolvedConfig): FsUtils {
const root = normalizePath(searchForWorkspaceRoot(config.root))
const rootDirPath = `${root}/`
const rootCache: DirentCache = { type: 'directory' } // dirents will be computed lazily
const rootCache = createSharedRootCache(root)
cachedFsUtilsMeta.set(config, { root, rootCache })

const getDirentCacheSync = (parts: string[]): DirentCache | undefined => {
let direntCache: DirentCache = rootCache
Expand Down Expand Up @@ -210,7 +253,10 @@ export function createCachedFsUtils(config: ResolvedConfig): FsUtils {
direntCache.type === 'directory' &&
direntCache.dirents
) {
direntCache.dirents.set(path.basename(file), { type })
const fileBasename = path.basename(file)
if (!direntCache.dirents.has(fileBasename)) {
direntCache.dirents.set(fileBasename, { type })
}
}
}

Expand Down
Loading