Skip to content

Commit

Permalink
chore: move findNearestPackageData
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed Jul 12, 2023
1 parent c212cdc commit 5b94be8
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 184 deletions.
141 changes: 3 additions & 138 deletions packages/vite-node/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { performance } from 'node:perf_hooks'
import { existsSync, readFileSync, statSync } from 'node:fs'
import { dirname, join, relative, resolve } from 'pathe'
import { type PackageCache, type PackageData, type TransformResult, type ViteDevServer, createFilter } from 'vite'
import { existsSync } from 'node:fs'
import { join, relative, resolve } from 'pathe'
import { type PackageCache, type TransformResult, type ViteDevServer } from 'vite'
import createDebug from 'debug'
import type { EncodedSourceMap } from '@jridgewell/trace-mapping'
import type { DebuggerOptions, FetchResult, ViteNodeResolveId, ViteNodeServerOptions } from './types'
Expand Down Expand Up @@ -140,40 +140,6 @@ export class ViteNodeServer {
return this.transformPromiseMap.get(id)!
}

findNearestPackageData(basedir: string) {
const config = this.server.config
// @ts-expect-error not typed
const packageCache = config.packageCache || this.packageCache
const originalBasedir = basedir
while (basedir) {
if (packageCache) {
const cached = getFnpdCache(packageCache, basedir, originalBasedir)
if (cached)
return cached
}

const pkgPath = join(basedir, 'package.json')
try {
if (statSync(pkgPath, { throwIfNoEntry: false })?.isFile()) {
const pkgData = loadPackageData(pkgPath)

if (packageCache)
setFnpdCache(packageCache, pkgData, basedir, originalBasedir)

return pkgData
}
}
catch {}

const nextBasedir = dirname(basedir)
if (nextBasedir === basedir)
break
basedir = nextBasedir
}

return null
}

getTransformMode(id: string) {
const withoutQuery = id.split('?')[0]

Expand Down Expand Up @@ -275,104 +241,3 @@ export class ViteNodeServer {
return result
}
}

export function loadPackageData(pkgPath: string): PackageData {
const data = JSON.parse(readFileSync(pkgPath, 'utf-8'))
const pkgDir = dirname(pkgPath)
const { sideEffects } = data
let hasSideEffects: (id: string) => boolean
if (typeof sideEffects === 'boolean') {
hasSideEffects = () => sideEffects
}
else if (Array.isArray(sideEffects)) {
const finalPackageSideEffects = sideEffects.map((sideEffect) => {
/*
* The array accepts simple glob patterns to the relevant files... Patterns like *.css, which do not include a /, will be treated like **\/*.css.
* https://webpack.js.org/guides/tree-shaking/
* https://github.com/vitejs/vite/pull/11807
*/
if (sideEffect.includes('/'))
return sideEffect

return `**/${sideEffect}`
})

hasSideEffects = createFilter(finalPackageSideEffects, 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]
},
}

return pkg
}

function getFnpdCache(
packageCache: PackageCache,
basedir: string,
originalBasedir: string,
) {
const cacheKey = getFnpdCacheKey(basedir)
const pkgData = packageCache.get(cacheKey)
if (pkgData) {
traverseBetweenDirs(originalBasedir, basedir, (dir) => {
packageCache.set(getFnpdCacheKey(dir), pkgData)
})
return pkgData
}
}

function setFnpdCache(
packageCache: PackageCache,
pkgData: PackageData,
basedir: string,
originalBasedir: string,
) {
packageCache.set(getFnpdCacheKey(basedir), pkgData)
traverseBetweenDirs(originalBasedir, basedir, (dir) => {
packageCache.set(getFnpdCacheKey(dir), pkgData)
})
}

// package cache key for `findNearestPackageData`
function getFnpdCacheKey(basedir: string) {
return `fnpd_${basedir}`
}

/**
* Traverse between `longerDir` (inclusive) and `shorterDir` (exclusive) and call `cb` for each dir.
* @param longerDir Longer dir path, e.g. `/User/foo/bar/baz`
* @param shorterDir Shorter dir path, e.g. `/User/foo`
*/
function traverseBetweenDirs(
longerDir: string,
shorterDir: string,
cb: (dir: string) => void,
) {
while (longerDir !== shorterDir) {
cb(longerDir)
longerDir = dirname(longerDir)
}
}
48 changes: 47 additions & 1 deletion packages/vite-node/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { fileURLToPath, pathToFileURL } from 'node:url'
import { builtinModules } from 'node:module'
import { existsSync } from 'node:fs'
import { resolve } from 'pathe'
import { dirname, resolve } from 'pathe'
import type { Arrayable, Nullable } from './types'

export const isWindows = process.platform === 'win32'
Expand Down Expand Up @@ -140,3 +140,49 @@ export function toArray<T>(array?: Nullable<Arrayable<T>>): Array<T> {

return [array]
}

export function getCachedData<T>(
cache: Map<string, T>,
basedir: string,
originalBasedir: string,
) {
const pkgData = cache.get(getFnpdCacheKey(basedir))
if (pkgData) {
traverseBetweenDirs(originalBasedir, basedir, (dir) => {
cache.set(getFnpdCacheKey(dir), pkgData)
})
return pkgData
}
}

export function setCacheData<T>(
cache: Map<string, T>,
data: T,
basedir: string,
originalBasedir: string,
) {
cache.set(getFnpdCacheKey(basedir), data)
traverseBetweenDirs(originalBasedir, basedir, (dir) => {
cache.set(getFnpdCacheKey(dir), data)
})
}

function getFnpdCacheKey(basedir: string) {
return `fnpd_${basedir}`
}

/**
* Traverse between `longerDir` (inclusive) and `shorterDir` (exclusive) and call `cb` for each dir.
* @param longerDir Longer dir path, e.g. `/User/foo/bar/baz`
* @param shorterDir Shorter dir path, e.g. `/User/foo`
*/
function traverseBetweenDirs(
longerDir: string,
shorterDir: string,
cb: (dir: string) => void,
) {
while (longerDir !== shorterDir) {
cb(longerDir)
longerDir = dirname(longerDir)
}
}
4 changes: 0 additions & 4 deletions packages/vitest/src/node/pools/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,5 @@ export function createMethodsRPC(project: WorkspaceProject): RuntimeRPC {
getCountOfFailedTests() {
return ctx.state.getCountOfFailedTests()
},
async findNearestPackageData(file) {
const pkgData = project.vitenode.findNearestPackageData(file)
return pkgData?.data || {}
},
}
}
15 changes: 8 additions & 7 deletions packages/vitest/src/runtime/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { ViteNodeRunnerOptions } from 'vite-node'
import { normalize, relative, resolve } from 'pathe'
import { processError } from '@vitest/utils/error'
import type { MockMap } from '../types/mocker'
import type { ResolvedConfig, ResolvedTestEnvironment, RuntimeRPC, WorkerGlobalState } from '../types'
import type { ResolvedConfig, ResolvedTestEnvironment, WorkerGlobalState } from '../types'
import { distDir } from '../paths'
import { getWorkerState } from '../utils/global'
import { VitestMocker } from './mocker'
Expand All @@ -16,9 +16,9 @@ const entryUrl = pathToFileURL(resolve(distDir, 'entry.js')).href

export interface ExecuteOptions extends ViteNodeRunnerOptions {
mockMap: MockMap
packageCache: Map<string, string>
moduleDirectories?: string[]
context?: vm.Context
findNearestPackageData?: RuntimeRPC['findNearestPackageData']
state: WorkerGlobalState
}

Expand All @@ -36,6 +36,7 @@ let _viteNode: {
executor: VitestExecutor
}

export const packageCache = new Map<string, any>()
export const moduleCache = new ModuleCacheMap()
export const mockMap: MockMap = new Map()

Expand All @@ -56,7 +57,6 @@ export interface ContextExecutorOptions {
mockMap?: MockMap
moduleCache?: ModuleCacheMap
context?: vm.Context
findNearestPackageData?: RuntimeRPC['findNearestPackageData']
state: WorkerGlobalState
}

Expand Down Expand Up @@ -98,9 +98,7 @@ export async function startVitestExecutor(options: ContextExecutorOptions) {
resolveId(id, importer) {
return rpc().resolveId(id, importer, getTransformMode())
},
findNearestPackageData(file) {
return rpc().findNearestPackageData(file)
},
packageCache,
moduleCache,
mockMap,
get interopDefault() { return state().config.deps.interopDefault },
Expand Down Expand Up @@ -160,7 +158,10 @@ export class VitestExecutor extends ViteNodeRunner {
}
}
else {
this.externalModules = new ExternalModulesExecutor(options.context, options.findNearestPackageData || (() => Promise.resolve({})))
this.externalModules = new ExternalModulesExecutor({
context: options.context,
packageCache: options.packageCache,
})
const clientStub = vm.runInContext(`(defaultClient) => ({ ...defaultClient, updateStyle: ${updateStyle.toString()} })`, options.context)(DEFAULT_REQUEST_STUBS['@vite/client'])
this.options.requestStubs = {
'/@vite/client': clientStub,
Expand Down
52 changes: 44 additions & 8 deletions packages/vitest/src/runtime/external-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import vm from 'node:vm'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { dirname } from 'node:path'
import { Module as _Module, createRequire } from 'node:module'
import { readFileSync } from 'node:fs'
import { readFileSync, statSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import { basename, extname } from 'pathe'
import { isNodeBuiltin } from 'vite-node/utils'
import type { RuntimeRPC } from '../types'
import { basename, extname, join } from 'pathe'
import { getCachedData, isNodeBuiltin, setCacheData } from 'vite-node/utils'

// need to copy paste types for vm
// because they require latest @types/node which we don't bundle
Expand Down Expand Up @@ -92,6 +91,11 @@ const _require = createRequire(import.meta.url)

const nativeResolve = import.meta.resolve!

interface ExternalModulesExecutorOptions {
context: vm.Context
packageCache: Map<string, any>
}

// TODO: improve Node.js strict mode support in #2854
export class ExternalModulesExecutor {
private requireCache: Record<string, NodeModule> = Object.create(null)
Expand All @@ -100,18 +104,20 @@ export class ExternalModulesExecutor {
private extensions: Record<string, (m: NodeModule, filename: string) => unknown> = Object.create(null)

private esmLinkMap = new WeakMap<VMModule, Promise<void>>()
private context: vm.Context

private Module: typeof _Module
private primitives: {
Object: typeof Object
}

constructor(private context: vm.Context, private findNearestPackageData: RuntimeRPC['findNearestPackageData']) {
this.context = context
constructor(private options: ExternalModulesExecutorOptions) {
this.context = options.context

// eslint-disable-next-line @typescript-eslint/no-this-alias
const executor = this

// primitive implementation, some fields are not filled yet, like "paths" - #2854
this.Module = class Module {
exports: any
isPreloading = false
Expand Down Expand Up @@ -143,7 +149,7 @@ export class ExternalModulesExecutor {
} as any)
// @ts-expect-error mark script with current identifier
script.identifier = filename
const fn = script.runInContext(context)
const fn = script.runInContext(executor.context)
const __dirname = dirname(filename)
executor.requireCache[filename] = this
try {
Expand Down Expand Up @@ -200,7 +206,7 @@ export class ExternalModulesExecutor {
this.extensions['.js'] = this.requireJs
this.extensions['.json'] = this.requireJson

this.primitives = vm.runInContext('({ Object })', context)
this.primitives = vm.runInContext('({ Object })', this.context)
}

private requireJs = (m: NodeModule, filename: string) => {
Expand All @@ -227,6 +233,36 @@ export class ExternalModulesExecutor {
return nativeResolve(specifier, parent)
}

private async findNearestPackageData(basedir: string) {
const originalBasedir = basedir
const packageCache = this.options.packageCache
while (basedir) {
const cached = getCachedData(packageCache, basedir, originalBasedir)
if (cached)
return cached

const pkgPath = join(basedir, 'package.json')
try {
if (statSync(pkgPath, { throwIfNoEntry: false })?.isFile()) {
const pkgData = JSON.parse(readFileSync(pkgPath, 'utf-8'))

if (packageCache)
setCacheData(packageCache, pkgData, basedir, originalBasedir)

return pkgData
}
}
catch {}

const nextBasedir = dirname(basedir)
if (nextBasedir === basedir)
break
basedir = nextBasedir
}

return null
}

private async wrapSynteticModule(identifier: string, format: 'esm' | 'builtin' | 'cjs', exports: Record<string, unknown>) {
// TODO: technically module should be parsed to find static exports, implement for #2854
const moduleKeys = Object.keys(exports)
Expand Down
Loading

0 comments on commit 5b94be8

Please sign in to comment.