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: resolve import path for dynamic imports #817

Merged
merged 2 commits into from Feb 21, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/vite-node/src/cli.ts
Expand Up @@ -82,6 +82,9 @@ async function run(options: CliOptions = {}) {
fetchModule(id) {
return node.fetchModule(id)
},
resolveId(id, importer) {
return node.resolveId(id, importer)
},
})

// provide the vite define variable in this context
Expand Down
16 changes: 15 additions & 1 deletion packages/vite-node/src/client.ts
@@ -1,7 +1,8 @@
import { createRequire } from 'module'
import { fileURLToPath, pathToFileURL } from 'url'
import vm from 'vm'
import { dirname, resolve } from 'pathe'
import { dirname, isAbsolute, resolve } from 'pathe'
import { isNodeBuiltin } from 'mlly'
import { isPrimitive, normalizeId, slash, toFilePath } from './utils'
import type { ModuleCache, ViteNodeRunnerOptions } from './types'

Expand Down Expand Up @@ -52,6 +53,12 @@ export class ViteNodeRunner {
async directRequest(id: string, fsPath: string, callstack: string[]) {
callstack = [...callstack, id]
const request = async(dep: string) => {
// probably means it was passed as variable
// and wasn't transformed by Vite
if (this.shouldResolveId(dep)) {
const resolvedDep = await this.options.resolveId(dep, id)
dep = resolvedDep?.id || dep
}
if (callstack.includes(dep)) {
if (!this.moduleCache.get(dep)?.exports)
throw new Error(`[vite-node] Circular dependency detected\nStack:\n${[...callstack, dep].reverse().map(p => `- ${p}`).join('\n')}`)
Expand Down Expand Up @@ -132,6 +139,13 @@ export class ViteNodeRunner {
Object.assign(this.moduleCache.get(id), mod)
}

shouldResolveId(dep: string) {
if (isNodeBuiltin(dep))
return false

return !isAbsolute(dep)
}

/**
* Define if a module should be interop-ed
* This function mostly for the ability to override by subclass
Expand Down
3 changes: 3 additions & 0 deletions packages/vite-node/src/types.ts
Expand Up @@ -29,6 +29,8 @@ export interface FetchResult {

export type FetchFunction = (id: string) => Promise<FetchResult>

export type ResolveIdFunction = (id: string, importer?: string) => Promise<ViteNodeResolveId | null>

export interface ModuleCache {
promise?: Promise<any>
exports?: any
Expand All @@ -37,6 +39,7 @@ export interface ModuleCache {

export interface ViteNodeRunnerOptions {
fetchModule: FetchFunction
resolveId: ResolveIdFunction
root: string
base?: string
moduleCache?: Map<string, ModuleCache>
Expand Down
5 changes: 1 addition & 4 deletions packages/vitest/src/node/execute.ts
@@ -1,14 +1,11 @@
import { ViteNodeRunner } from 'vite-node/client'
import type { ModuleCache, ViteNodeResolveId, ViteNodeRunnerOptions } from 'vite-node'
import type { ModuleCache, ViteNodeRunnerOptions } from 'vite-node'
import type { SuiteMocks } from './mocker'
import { VitestMocker } from './mocker'

export type ResolveIdFunction = (id: string, importer?: string) => Promise<ViteNodeResolveId | null>

export interface ExecuteOptions extends ViteNodeRunnerOptions {
files: string[]
mockMap: SuiteMocks
resolveId: ResolveIdFunction
}

export async function executeInViteNode(options: ExecuteOptions) {
Expand Down
3 changes: 3 additions & 0 deletions packages/vitest/src/node/plugins/globalSetup.ts
Expand Up @@ -18,6 +18,9 @@ async function loadGlobalSetupFiles(ctx: Vitest): Promise<GlobalSetupFile[]> {
fetchModule(id) {
return node.fetchModule(id)
},
resolveId(id, importer) {
return node.resolveId(id, importer)
},
})
const globalSetupFiles = toArray(server.config.test?.globalSetup)
return Promise.all(globalSetupFiles.map(file => loadGlobalSetupFile(file, runner)))
Expand Down
28 changes: 28 additions & 0 deletions test/core/test/imports.test.ts
@@ -0,0 +1,28 @@
import { expect, test } from 'vitest'

test('dynamic relative import works', async() => {
const { timeout } = await import('./../src/timeout')

const timeoutPath = './../src/timeout'
const { timeout: dynamicTimeout } = await import(timeoutPath)

expect(timeout).toBe(dynamicTimeout)
})

test('dynamic aliased import works', async() => {
const { timeout } = await import('./../src/timeout')

const timeoutPath = '@/timeout'
const { timeout: dynamicTimeout } = await import(timeoutPath)

expect(timeout).toBe(dynamicTimeout)
})

test('dynamic absolute import works', async() => {
const { timeout } = await import('./../src/timeout')

const timeoutPath = '/src/timeout'
const { timeout: dynamicTimeout } = await import(timeoutPath)

expect(timeout).toBe(dynamicTimeout)
})
6 changes: 6 additions & 0 deletions test/core/vitest.config.ts
@@ -1,3 +1,4 @@
import { resolve } from 'pathe'
import { defineConfig } from 'vite'

export default defineConfig({
Expand Down Expand Up @@ -30,6 +31,11 @@ export default defineConfig({
'SOME.VARIABLE': '"variable"',
'SOME.SOME.VARIABLE': '"nested variable"',
},
resolve: {
alias: [
{ find: '@', replacement: resolve(__dirname, 'src') },
],
},
test: {
testTimeout: 2000,
// threads: false,
Expand Down