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: correctly resolve filename, when running code #2439

Merged
merged 10 commits into from Dec 6, 2022
6 changes: 4 additions & 2 deletions packages/coverage-c8/src/provider.ts
Expand Up @@ -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 { }

Expand Down
41 changes: 24 additions & 17 deletions packages/vite-node/src/client.ts
Expand Up @@ -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)
Expand Down Expand Up @@ -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:'))
Expand All @@ -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)
Expand All @@ -257,19 +268,16 @@ export class ViteNodeRunner {
throw new Error(`[vite-node] Failed to load ${id}`)

// disambiguate the `<UNIT>:/` 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,
configurable: false,
})
// 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 = {}
Expand All @@ -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) {
Expand Down Expand Up @@ -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,
})
Expand Down
23 changes: 15 additions & 8 deletions packages/vite-node/src/server.ts
Expand Up @@ -4,17 +4,14 @@ 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'

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<string, Promise<FetchResult>>()
private transformPromiseMap = new Map<string, Promise<TransformResult | null | undefined>>()
Expand Down Expand Up @@ -83,6 +80,7 @@ export class ViteNodeServer {
}

async fetchModule(id: string): Promise<FetchResult> {
id = normalizeModuleId(id)
// reuse transform for concurrent requests
if (!this.fetchPromiseMap.has(id)) {
this.fetchPromiseMap.set(id,
Expand Down Expand Up @@ -130,27 +128,36 @@ 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) {
result = { externalize }
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,
})

Expand Down
2 changes: 2 additions & 0 deletions packages/vite-node/src/types.ts
Expand Up @@ -31,6 +31,8 @@ export interface FetchResult {
code?: string
externalize?: string
map?: RawSourceMap
id?: string
file?: string
}

export type HotContext = Omit<ViteHotContext, 'acceptDeps' | 'decline'>
Expand Down
6 changes: 6 additions & 0 deletions packages/vite-node/src/utils.ts
Expand Up @@ -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, '/')
Expand Down
9 changes: 6 additions & 3 deletions packages/vitest/src/node/core.ts
Expand Up @@ -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() {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions test/shard/shard-test.test.ts
Expand Up @@ -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 () => {
Expand Down