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

fix: move package.json cache into ResolvedConfig #5388

Merged
merged 3 commits into from
Nov 19, 2021
Merged
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { DepOptimizationMetadata } from './optimizer'
import { scanImports } from './optimizer/scan'
import { assetImportMetaUrlPlugin } from './plugins/assetImportMetaUrl'
import { loadFallbackPlugin } from './plugins/loadFallback'
import { watchPackageDataPlugin } from './packages'

export interface BuildOptions {
/**
Expand Down Expand Up @@ -348,6 +349,7 @@ export function resolveBuildPlugins(config: ResolvedConfig): {
const options = config.build
return {
pre: [
watchPackageDataPlugin(config),
buildHtmlPlugin(config),
commonjsPlugin(options.commonjsOptions),
dataURIPlugin(),
Expand Down
4 changes: 4 additions & 0 deletions packages/vite/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
import aliasPlugin from '@rollup/plugin-alias'
import { build } from 'esbuild'
import { performance } from 'perf_hooks'
import { PackageCache } from './packages'

const debug = createDebugger('vite:config')

Expand Down Expand Up @@ -239,6 +240,8 @@ export type ResolvedConfig = Readonly<
logger: Logger
createResolver: (options?: Partial<InternalResolveOptions>) => ResolveFn
optimizeDeps: Omit<DepOptimizationOptions, 'keepNames'>
/** @internal */
packageCache: PackageCache
}
>

Expand Down Expand Up @@ -458,6 +461,7 @@ export async function resolveConfig(
return DEFAULT_ASSETS_RE.test(file) || assetsFilter(file)
},
logger,
packageCache: new Map(),
createResolver,
optimizeDeps: {
...config.optimizeDeps,
Expand Down
10 changes: 4 additions & 6 deletions packages/vite/src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export { optimizeDeps } from './optimizer'
export { send } from './server/send'
export { createLogger, printHttpServerUrls } from './logger'
export { transformWithEsbuild } from './plugins/esbuild'
export { resolvePackageData, resolvePackageEntry } from './plugins/resolve'
export { resolvePackageEntry } from './plugins/resolve'
export { resolvePackageData } from './packages'
export { normalizePath } from './utils'

// additional types
Expand Down Expand Up @@ -34,6 +35,7 @@ export type {
DepOptimizationOptions
} from './optimizer'
export type { Plugin } from './plugin'
export type { PackageCache, PackageData } from './packages'
export type {
Logger,
LogOptions,
Expand All @@ -60,11 +62,7 @@ export type { JsonOptions } from './plugins/json'
export type { TransformOptions as EsbuildTransformOptions } from 'esbuild'
export type { ESBuildOptions, ESBuildTransformResult } from './plugins/esbuild'
export type { Manifest, ManifestChunk } from './plugins/manifest'
export type {
PackageData,
ResolveOptions,
InternalResolveOptions
} from './plugins/resolve'
export type { ResolveOptions, InternalResolveOptions } from './plugins/resolve'
export type { WebSocketServer } from './server/ws'
export type { PluginContainer } from './server/pluginContainer'
export type { ModuleGraph, ModuleNode, ResolvedUrl } from './server/moduleGraph'
Expand Down
164 changes: 164 additions & 0 deletions packages/vite/src/node/packages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import fs from 'fs'
import path from 'path'
import { createFilter } from '@rollup/pluginutils'
import { createDebugger, resolveFrom } from './utils'
import { ResolvedConfig } from './config'
import { Plugin } from './plugin'

const isDebug = process.env.DEBUG
const debug = createDebugger('vite:resolve-details', {
onlyWhenFocused: true
})

/** Cache for package.json resolution and package.json contents */
export type PackageCache = Map<string, PackageData>

export interface PackageData {
dir: string
hasSideEffects: (id: string) => boolean | 'no-treeshake'
webResolvedImports: Record<string, string | undefined>
nodeResolvedImports: Record<string, string | undefined>
setResolvedCache: (key: string, entry: string, targetWeb: boolean) => void
getResolvedCache: (key: string, targetWeb: boolean) => string | undefined
data: {
[field: string]: any
version: string
main: string
module: string
browser: string | Record<string, string | false>
exports: string | Record<string, any> | string[]
dependencies: Record<string, string>
}
}

export function invalidatePackageData(
packageCache: PackageCache,
pkgPath: string
): void {
packageCache.delete(pkgPath)
const pkgDir = path.dirname(pkgPath)
packageCache.forEach((pkg, cacheKey) => {
if (pkg.dir === pkgDir) {
packageCache.delete(cacheKey)
}
})
}

export function resolvePackageData(
id: string,
basedir: string,
preserveSymlinks = false,
packageCache?: PackageCache
): PackageData | null {
let pkg: PackageData | undefined
let cacheKey: string | undefined
if (packageCache) {
cacheKey = `${id}&${basedir}&${preserveSymlinks}`
if ((pkg = packageCache.get(cacheKey))) {
return pkg
}
}
let pkgPath: string | undefined
try {
pkgPath = resolveFrom(`${id}/package.json`, basedir, preserveSymlinks)
pkg = loadPackageData(pkgPath, true, packageCache)
if (packageCache) {
packageCache.set(cacheKey!, pkg)
}
return pkg
} catch (e) {
if (e instanceof SyntaxError) {
isDebug && debug(`Parsing failed: ${pkgPath}`)
}
// Ignore error for missing package.json
else if (e.code !== 'MODULE_NOT_FOUND') {
throw e
}
}
return null
}

export function loadPackageData(
pkgPath: string,
preserveSymlinks?: boolean,
packageCache?: PackageCache
): PackageData {
if (!preserveSymlinks) {
pkgPath = fs.realpathSync.native(pkgPath)
}

let cached: PackageData | undefined
if ((cached = packageCache?.get(pkgPath))) {
return cached
}

const data = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
const pkgDir = path.dirname(pkgPath)
const { sideEffects } = data
let hasSideEffects: (id: string) => boolean
if (typeof sideEffects === 'boolean') {
hasSideEffects = () => sideEffects
} else if (Array.isArray(sideEffects)) {
hasSideEffects = createFilter(sideEffects, null, { resolve: pkgDir })
} else {
hasSideEffects = () => true
}

const pkg: PackageData = {
dir: pkgDir,
data,
hasSideEffects,
webResolvedImports: {},
nodeResolvedImports: {},
setResolvedCache(key: string, entry: string, targetWeb: boolean) {
if (targetWeb) {
pkg.webResolvedImports[key] = entry
} else {
pkg.nodeResolvedImports[key] = entry
}
},
getResolvedCache(key: string, targetWeb: boolean) {
if (targetWeb) {
return pkg.webResolvedImports[key]
} else {
return pkg.nodeResolvedImports[key]
}
}
}

packageCache?.set(pkgPath, pkg)
return pkg
}

export function watchPackageDataPlugin(config: ResolvedConfig): Plugin {
const watchQueue = new Set<string>()
let watchFile = (id: string) => {
watchQueue.add(id)
}

const { packageCache } = config
const setPackageData = packageCache.set.bind(packageCache)
packageCache.set = (id, pkg) => {
if (id.endsWith('.json')) {
watchFile(id)
}
return setPackageData(id, pkg)
}

return {
name: 'vite:watch-package-data',
buildStart() {
watchFile = this.addWatchFile
watchQueue.forEach(watchFile)
watchQueue.clear()
},
buildEnd() {
watchFile = (id) => watchQueue.add(id)
},
watchChange(id) {
if (id.endsWith('/package.json')) {
invalidatePackageData(packageCache, id)
}
}
}
}
1 change: 1 addition & 0 deletions packages/vite/src/node/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export async function resolvePlugins(
root: config.root,
isProduction: config.isProduction,
isBuild,
packageCache: config.packageCache,
ssrConfig: config.ssr,
asSrc: true
}),
Expand Down
97 changes: 16 additions & 81 deletions packages/vite/src/node/plugins/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,14 @@ import {
getTsSrcPath
} from '../utils'
import { ViteDevServer, SSROptions } from '..'
import { createFilter } from '@rollup/pluginutils'
import { PartialResolvedId } from 'rollup'
import { resolve as _resolveExports } from 'resolve.exports'
import {
loadPackageData,
PackageCache,
PackageData,
resolvePackageData
} from '../packages'

// special id for paths marked with browser: false
// https://github.com/defunctzombie/package-browser-field-spec#ignore-a-module
Expand All @@ -56,6 +61,7 @@ export interface InternalResolveOptions extends ResolveOptions {
isBuild: boolean
isProduction: boolean
ssrConfig?: SSROptions
packageCache?: PackageCache
/**
* src code mode also attempts the following:
* - resolving /xxx as URLs
Expand Down Expand Up @@ -415,11 +421,15 @@ function tryResolveFile(
} else if (tryIndex) {
if (!skipPackageJson) {
const pkgPath = file + '/package.json'
if (fs.existsSync(pkgPath)) {
try {
// path points to a node package
const pkg = loadPackageData(pkgPath)
const pkg = loadPackageData(pkgPath, options.preserveSymlinks)
const resolved = resolvePackageEntry(file, pkg, targetWeb, options)
return resolved
} catch (e) {
if (e.code !== 'ENOENT') {
throw e
}
}
}
const index = tryFsResolve(file + '/index', options)
Expand Down Expand Up @@ -457,7 +467,7 @@ export function tryNodeResolve(
server?: ViteDevServer,
ssr?: boolean
): PartialResolvedId | undefined {
const { root, dedupe, isBuild } = options
const { root, dedupe, isBuild, preserveSymlinks, packageCache } = options

// split id by last '>' for nested selected packages, for example:
// 'foo > bar > baz' => 'foo > bar' & 'baz'
Expand Down Expand Up @@ -508,12 +518,12 @@ export function tryNodeResolve(

// nested node module, step-by-step resolve to the basedir of the nestedPath
if (nestedRoot) {
basedir = nestedResolveFrom(nestedRoot, basedir, options.preserveSymlinks)
basedir = nestedResolveFrom(nestedRoot, basedir, preserveSymlinks)
}

let pkg: PackageData | undefined
const pkgId = possiblePkgIds.reverse().find((pkgId) => {
pkg = resolvePackageData(pkgId, basedir, options.preserveSymlinks)
pkg = resolvePackageData(pkgId, basedir, preserveSymlinks, packageCache)!
return pkg
})!

Expand Down Expand Up @@ -650,81 +660,6 @@ export function tryOptimizedResolve(
}
}

export interface PackageData {
dir: string
hasSideEffects: (id: string) => boolean
webResolvedImports: Record<string, string | undefined>
nodeResolvedImports: Record<string, string | undefined>
setResolvedCache: (key: string, entry: string, targetWeb: boolean) => void
getResolvedCache: (key: string, targetWeb: boolean) => string | undefined
data: {
[field: string]: any
version: string
main: string
module: string
browser: string | Record<string, string | false>
exports: string | Record<string, any> | string[]
dependencies: Record<string, string>
}
}

const packageCache = new Map<string, PackageData>()

export function resolvePackageData(
id: string,
basedir: string,
preserveSymlinks = false
): PackageData | undefined {
const cacheKey = id + basedir
if (packageCache.has(cacheKey)) {
return packageCache.get(cacheKey)
}
try {
const pkgPath = resolveFrom(`${id}/package.json`, basedir, preserveSymlinks)
return loadPackageData(pkgPath, cacheKey)
} catch (e) {
isDebug && debug(`${chalk.red(`[failed loading package.json]`)} ${id}`)
}
}

function loadPackageData(pkgPath: string, cacheKey = pkgPath) {
const data = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
const pkgDir = path.dirname(pkgPath)
const { sideEffects } = data
let hasSideEffects: (id: string) => boolean
if (typeof sideEffects === 'boolean') {
hasSideEffects = () => sideEffects
} else if (Array.isArray(sideEffects)) {
hasSideEffects = createFilter(sideEffects, null, { resolve: pkgDir })
} else {
hasSideEffects = () => true
}

const pkg: PackageData = {
dir: pkgDir,
data,
hasSideEffects,
webResolvedImports: {},
nodeResolvedImports: {},
setResolvedCache(key: string, entry: string, targetWeb: boolean) {
if (targetWeb) {
pkg.webResolvedImports[key] = entry
} else {
pkg.nodeResolvedImports[key] = entry
}
},
getResolvedCache(key: string, targetWeb: boolean) {
if (targetWeb) {
return pkg.webResolvedImports[key]
} else {
return pkg.nodeResolvedImports[key]
}
}
}
packageCache.set(cacheKey, pkg)
return pkg
}

export function resolvePackageEntry(
id: string,
{ dir, data, setResolvedCache, getResolvedCache }: PackageData,
Expand Down
Loading