From 2e7892cb23314e3d6e8f29ea3dcf0778c9e4d237 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 6 Dec 2022 14:44:55 +0100 Subject: [PATCH] fix: correctly resolve filename, when running code (#2439) * fix: correctly resolve filename, when running code * fix: update lastHMRTimestamp to enable caching * chore: make test files unique * test: fix flacky shard test * chore: use consistent id, when transforming module * fix: use filepath for c8 coverage * fix: ensure correct caching, if fsPath and filepath are different * chore: check fsPath as clean url * chore: ignore error on wrong cache * chore: ignore paths with query instead --- packages/coverage-c8/src/provider.ts | 6 ++-- packages/vite-node/src/client.ts | 41 ++++++++++++++++------------ packages/vite-node/src/server.ts | 23 ++++++++++------ packages/vite-node/src/types.ts | 2 ++ packages/vite-node/src/utils.ts | 6 ++++ packages/vitest/src/node/core.ts | 9 ++++-- test/shard/shard-test.test.ts | 4 +-- 7 files changed, 59 insertions(+), 32 deletions(-) diff --git a/packages/coverage-c8/src/provider.ts b/packages/coverage-c8/src/provider.ts index 23f2c65a6bb2..7c410367df7a 100644 --- a/packages/coverage-c8/src/provider.ts +++ b/packages/coverage-c8/src/provider.ts @@ -58,11 +58,13 @@ export class C8CoverageProvider implements CoverageProvider { if (!map) return - const url = _url.pathToFileURL(file.split('?')[0]).href + const filepath = result.file || file.split('?')[0] + + const url = _url.pathToFileURL(filepath).href let code: string | undefined try { - code = (await fs.readFile(file)).toString() + code = (await fs.readFile(filepath)).toString() } catch { } diff --git a/packages/vite-node/src/client.ts b/packages/vite-node/src/client.ts index 633fba4a035c..3984547c308a 100644 --- a/packages/vite-node/src/client.ts +++ b/packages/vite-node/src/client.ts @@ -191,7 +191,7 @@ export class ViteNodeRunner { async directRequest(id: string, fsPath: string, _callstack: string[]) { const callstack = [..._callstack, fsPath] - const mod = this.moduleCache.get(fsPath) + let mod = this.moduleCache.get(fsPath) const request = async (dep: string) => { const depFsPath = toFilePath(normalizeRequestId(dep, this.options.base), this.root) @@ -222,11 +222,6 @@ export class ViteNodeRunner { Object.defineProperty(request, 'callstack', { get: () => callstack }) const resolveId = async (dep: string, callstackPosition = 1) => { - // probably means it was passed as variable - // and wasn't transformed by Vite - // or some dependency name was passed - // runner.executeFile('@scope/name') - // runner.executeFile(myDynamicName) if (this.options.resolveId && this.shouldResolveId(dep)) { let importer = callstack[callstack.length - callstackPosition] if (importer && importer.startsWith('mock:')) @@ -238,14 +233,30 @@ export class ViteNodeRunner { return dep } - id = await resolveId(id, 2) - const requestStubs = this.options.requestStubs || DEFAULT_REQUEST_STUBS if (id in requestStubs) return requestStubs[id] // eslint-disable-next-line prefer-const - let { code: transformed, externalize } = await this.options.fetchModule(id) + let { code: transformed, externalize, file } = await this.options.fetchModule(id) + + // in case we resolved fsPath incorrectly, Vite will return the correct file path + // in that case we need to update cache, so we don't have the same module as different exports + // but we ignore fsPath that has custom query, because it might need to be different + if (file && !fsPath.includes('?') && fsPath !== file) { + if (this.moduleCache.has(file)) { + mod = this.moduleCache.get(file) + this.moduleCache.set(fsPath, mod) + if (mod.promise) + return mod.promise + if (mod.exports) + return mod.exports + } + else { + this.moduleCache.set(file, mod) + } + } + if (externalize) { debugNative(externalize) const exports = await this.interopedImport(externalize) @@ -257,9 +268,9 @@ export class ViteNodeRunner { throw new Error(`[vite-node] Failed to load ${id}`) // disambiguate the `:/` on windows: see nodejs/node#31710 - const url = pathToFileURL(fsPath).href + const url = pathToFileURL(file || fsPath).href const meta = { url } - const exports: any = Object.create(null) + const exports = Object.create(null) Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module', enumerable: false, @@ -267,9 +278,6 @@ export class ViteNodeRunner { }) // this prosxy is triggered only on exports.name and module.exports access const cjsExports = new Proxy(exports, { - get(_, p, receiver) { - return Reflect.get(exports, p, receiver) - }, set(_, p, value) { if (!Reflect.has(exports, 'default')) exports.default = {} @@ -289,8 +297,7 @@ export class ViteNodeRunner { }, }) - Object.assign(mod, { code: transformed, exports, evaluated: false }) - + Object.assign(mod, { code: transformed, exports }) const __filename = fileURLToPath(url) const moduleProxy = { set exports(value) { @@ -345,7 +352,7 @@ export class ViteNodeRunner { const codeDefinition = `'use strict';async (${Object.keys(context).join(',')})=>{{` const code = `${codeDefinition}${transformed}\n}}` const fn = vm.runInThisContext(code, { - filename: fsPath, + filename: __filename, lineOffset: 0, columnOffset: -codeDefinition.length, }) diff --git a/packages/vite-node/src/server.ts b/packages/vite-node/src/server.ts index f92a2ae687e4..eca6154a7d6c 100644 --- a/packages/vite-node/src/server.ts +++ b/packages/vite-node/src/server.ts @@ -4,7 +4,7 @@ import type { TransformResult, ViteDevServer } from 'vite' import createDebug from 'debug' import type { DebuggerOptions, FetchResult, RawSourceMap, ViteNodeResolveId, ViteNodeServerOptions } from './types' import { shouldExternalize } from './externalize' -import { toArray, toFilePath } from './utils' +import { cleanUrl, normalizeModuleId, toArray, toFilePath } from './utils' import { Debugger } from './debug' import { withInlineSourcemap } from './source-map' @@ -12,9 +12,6 @@ export * from './externalize' const debugRequest = createDebug('vite-node:server:request') -// store the original reference to avoid it been mocked -const RealDate = Date - export class ViteNodeServer { private fetchPromiseMap = new Map>() private transformPromiseMap = new Map>() @@ -83,6 +80,7 @@ export class ViteNodeServer { } async fetchModule(id: string): Promise { + id = normalizeModuleId(id) // reuse transform for concurrent requests if (!this.fetchPromiseMap.has(id)) { this.fetchPromiseMap.set(id, @@ -130,11 +128,14 @@ export class ViteNodeServer { const filePath = toFilePath(id, this.server.config.root) const module = this.server.moduleGraph.getModuleById(id) - const timestamp = module?.lastHMRTimestamp || RealDate.now() + const timestamp = module ? module.lastHMRTimestamp : null const cache = this.fetchCache.get(filePath) - if (timestamp && cache && cache.timestamp >= timestamp) + if (cache?.result.id) + id = cache.result.id + if (timestamp !== null && cache && cache.timestamp >= timestamp) return cache.result + const time = Date.now() const externalize = await this.shouldExternalize(filePath) let duration: number | undefined if (externalize) { @@ -142,15 +143,21 @@ export class ViteNodeServer { this.debugger?.recordExternalize(id, externalize) } else { + let file = module?.file + if (!file) { + const [, resolvedId] = await this.server.moduleGraph.resolveUrl(id, true) + id = resolvedId + file = cleanUrl(resolvedId) + } const start = performance.now() const r = await this._transformRequest(id) duration = performance.now() - start - result = { code: r?.code, map: r?.map as unknown as RawSourceMap } + result = { file, id, code: r?.code, map: r?.map as unknown as RawSourceMap } } this.fetchCache.set(filePath, { duration, - timestamp, + timestamp: time, result, }) diff --git a/packages/vite-node/src/types.ts b/packages/vite-node/src/types.ts index 606c0f00666a..93bf92b44b00 100644 --- a/packages/vite-node/src/types.ts +++ b/packages/vite-node/src/types.ts @@ -31,6 +31,8 @@ export interface FetchResult { code?: string externalize?: string map?: RawSourceMap + id?: string + file?: string } export type HotContext = Omit diff --git a/packages/vite-node/src/utils.ts b/packages/vite-node/src/utils.ts index dd041e388b70..7107d1c77567 100644 --- a/packages/vite-node/src/utils.ts +++ b/packages/vite-node/src/utils.ts @@ -34,6 +34,12 @@ export function normalizeRequestId(id: string, base?: string): string { .replace(/\?+$/, '') // remove end query mark } +export const queryRE = /\?.*$/s +export const hashRE = /#.*$/s + +export const cleanUrl = (url: string): string => + url.replace(hashRE, '').replace(queryRE, '') + export function normalizeModuleId(id: string) { return id .replace(/\\/g, '/') diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 2cd058f64cb2..f6e3295c2f20 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -115,9 +115,7 @@ export class Vitest { try { await this.cache.results.readFromCache() } - catch (err) { - this.logger.error(`[vitest] Error, while trying to parse cache in ${this.cache.results.getCachePath()}:`, err) - } + catch {} } async initCoverageProvider() { @@ -311,6 +309,8 @@ export class Vitest { } async runFiles(paths: string[]) { + paths = Array.from(new Set(paths)) + // previous run await this.runningPromise this.state.startCollectingPaths() @@ -396,6 +396,9 @@ export class Vitest { private _rerunTimer: any private async scheduleRerun(triggerId: string) { + const mod = this.server.moduleGraph.getModuleById(triggerId) + if (mod) + mod.lastHMRTimestamp = Date.now() const currentCount = this.restartsCount clearTimeout(this._rerunTimer) await this.runningPromise diff --git a/test/shard/shard-test.test.ts b/test/shard/shard-test.test.ts index b7099f1b80ea..5fb9ada75663 100644 --- a/test/shard/shard-test.test.ts +++ b/test/shard/shard-test.test.ts @@ -8,11 +8,11 @@ const runVitest = async (args: string[]) => { } const parsePaths = (stdout: string) => { - return stdout + return Array.from(new Set(stdout .split('\n') .filter(line => line && line.includes('.test.js')) .map(file => basename(file.trim().split(' ')[1])) - .sort() + .sort())) } test('--shard=1/1', async () => {