diff --git a/package.json b/package.json
index 3307cbb10..0c974d8a3 100644
--- a/package.json
+++ b/package.json
@@ -67,12 +67,13 @@
"dev:prepare": "nuxt prepare && unbuild --stub && pnpm -r dev:prepare"
},
"dependencies": {
- "@nuxt/kit": "^4.2.1",
+ "@nuxt/kit": "^3.20.1",
"c12": "^3.3.2",
"consola": "^3.4.2",
"defu": "^6.1.4",
"destr": "^2.0.5",
"estree-walker": "^3.0.3",
+ "exsolve": "^1.0.8",
"fake-indexeddb": "^6.2.5",
"get-port-please": "^3.2.0",
"h3": "^1.15.4",
@@ -128,6 +129,7 @@
"peerDependencies": {
"@cucumber/cucumber": "^10.3.1 || >=11.0.0",
"@jest/globals": "^29.5.0 || >=30.0.0",
+ "@nuxt/kit": ">=3.20.1",
"@playwright/test": "^1.43.1",
"@testing-library/vue": "^7.0.0 || ^8.0.1",
"@vue/test-utils": "^2.4.2",
@@ -143,6 +145,9 @@
"@jest/globals": {
"optional": true
},
+ "@nuxt/kit": {
+ "optional": true
+ },
"@playwright/test": {
"optional": true
},
@@ -170,7 +175,6 @@
},
"resolutions": {
"@cucumber/cucumber": "12.3.0",
- "@nuxt/kit": "^4.2.1",
"@nuxt/schema": "4.2.1",
"@nuxt/test-utils": "workspace:*",
"@types/node": "24.10.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b3743f7b9..0bf278d4d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -6,7 +6,6 @@ settings:
overrides:
'@cucumber/cucumber': 12.3.0
- '@nuxt/kit': ^4.2.1
'@nuxt/schema': 4.2.1
'@nuxt/test-utils': workspace:*
'@types/node': 24.10.1
@@ -26,8 +25,8 @@ importers:
.:
dependencies:
'@nuxt/kit':
- specifier: ^4.2.1
- version: 4.2.1(magicast@0.5.1)
+ specifier: ^3.20.1
+ version: 3.20.1(magicast@0.5.1)
c12:
specifier: ^3.3.2
version: 3.3.2(magicast@0.5.1)
@@ -43,6 +42,9 @@ importers:
estree-walker:
specifier: ^3.0.3
version: 3.0.3
+ exsolve:
+ specifier: ^1.0.8
+ version: 1.0.8
fake-indexeddb:
specifier: ^6.2.5
version: 6.2.5
@@ -1470,6 +1472,10 @@ packages:
'@nuxt/icon@2.1.0':
resolution: {integrity: sha512-m+XQrgzeK5gQ1HkB7G7u1os6egoD07fiHKijG7NPxqT5yZUGOjKJ7X/Le10l3QWRKyCB+IiU0t+eUqSvh+SULg==}
+ '@nuxt/kit@3.20.1':
+ resolution: {integrity: sha512-TIslaylfI5kd3AxX5qts0qyrIQ9Uq3HAA1bgIIJ+c+zpDfK338YS+YrCWxBBzDMECRCbAS58mqAd2MtJfG1ENA==}
+ engines: {node: '>=18.12.0'}
+
'@nuxt/kit@4.2.1':
resolution: {integrity: sha512-lLt8KLHyl7IClc3RqRpRikz15eCfTRlAWL9leVzPyg5N87FfKE/7EWgWvpiL/z4Tf3dQCIqQb88TmHE0JTIDvA==}
engines: {node: '>=18.12.0'}
@@ -7527,7 +7533,7 @@ packages:
resolution: {integrity: sha512-vfBI/SvD9hJqYNinipVOAj5n8dS8DJXFlCKFR5iLDp2SaQwsfdnfLXgZ+34Kd3YY3YEY9omk8XQg0bwos3Q8ug==}
engines: {node: '>=14'}
peerDependencies:
- '@nuxt/kit': ^4.2.1
+ '@nuxt/kit': ^4.0.0
'@vueuse/core': '*'
peerDependenciesMeta:
'@nuxt/kit':
@@ -7548,7 +7554,7 @@ packages:
engines: {node: '>=14'}
peerDependencies:
'@babel/parser': ^7.15.8
- '@nuxt/kit': ^4.2.1
+ '@nuxt/kit': ^3.2.2 || ^4.0.0
vue: ^3.5.25
peerDependenciesMeta:
'@babel/parser':
@@ -9367,7 +9373,7 @@ snapshots:
'@nuxt/devtools-kit@2.7.0(magicast@0.5.1)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.40.0)(yaml@2.8.1))':
dependencies:
- '@nuxt/kit': 4.2.1(magicast@0.5.1)
+ '@nuxt/kit': 3.20.1(magicast@0.5.1)
execa: 8.0.1
vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.40.0)(yaml@2.8.1)
transitivePeerDependencies:
@@ -9541,6 +9547,32 @@ snapshots:
- vite
- vue
+ '@nuxt/kit@3.20.1(magicast@0.5.1)':
+ dependencies:
+ c12: 3.3.2(magicast@0.5.1)
+ consola: 3.4.2
+ defu: 6.1.4
+ destr: 2.0.5
+ errx: 0.1.0
+ exsolve: 1.0.8
+ ignore: 7.0.5
+ jiti: 2.6.1
+ klona: 2.0.6
+ knitwork: 1.3.0
+ mlly: 1.8.0
+ ohash: 2.0.11
+ pathe: 2.0.3
+ pkg-types: 2.3.0
+ rc9: 2.1.2
+ scule: 1.3.0
+ semver: 7.7.3
+ tinyglobby: 0.2.15
+ ufo: 1.6.1
+ unctx: 2.4.1
+ untyped: 2.0.0
+ transitivePeerDependencies:
+ - magicast
+
'@nuxt/kit@4.2.1(magicast@0.5.1)':
dependencies:
c12: 3.3.2(magicast@0.5.1)
@@ -9723,7 +9755,7 @@ snapshots:
'@nuxt/telemetry@2.6.6(magicast@0.5.1)':
dependencies:
- '@nuxt/kit': 4.2.1(magicast@0.5.1)
+ '@nuxt/kit': 3.20.1(magicast@0.5.1)
citty: 0.1.6
consola: 3.4.2
destr: 2.0.5
@@ -9952,7 +9984,7 @@ snapshots:
'@nuxtjs/color-mode@3.5.2(magicast@0.5.1)':
dependencies:
- '@nuxt/kit': 4.2.1(magicast@0.5.1)
+ '@nuxt/kit': 3.20.1(magicast@0.5.1)
pathe: 1.1.2
pkg-types: 1.3.1
semver: 7.7.3
diff --git a/src/config.ts b/src/config.ts
index c7458a888..7a410f574 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -9,9 +9,9 @@ import type { DotenvOptions } from 'c12'
import type { UserConfig as ViteUserConfig } from 'vite'
import type { DateString } from 'compatx'
import { defu } from 'defu'
-import { loadNuxt, buildNuxt, createResolver, findPath } from '@nuxt/kit'
+import { createResolver, findPath } from '@nuxt/kit'
-import { applyEnv } from './utils'
+import { applyEnv, loadKit } from './utils'
interface GetVitestConfigOptions {
nuxt: Nuxt
@@ -25,6 +25,7 @@ interface LoadNuxtOptions {
// https://github.com/nuxt/framework/issues/6496
async function startNuxtAndGetViteConfig(rootDir = process.cwd(), options: LoadNuxtOptions = {}) {
+ const { buildNuxt, loadNuxt } = await loadKit(rootDir)
const nuxt = await loadNuxt({
cwd: rootDir,
dev: false,
diff --git a/src/e2e/nuxt.ts b/src/e2e/nuxt.ts
index b6fa72d07..c04557874 100644
--- a/src/e2e/nuxt.ts
+++ b/src/e2e/nuxt.ts
@@ -1,11 +1,8 @@
import { existsSync, promises as fsp } from 'node:fs'
import { resolve } from 'node:path'
import { defu } from 'defu'
-import * as _kit from '@nuxt/kit'
import { useTestContext } from './context'
-
-// @ts-expect-error type cast kit default export
-const kit: typeof _kit = _kit.default || _kit
+import { loadKit } from '../utils'
const isNuxtApp = (dir: string) => {
return existsSync(dir) && (
@@ -59,7 +56,8 @@ export async function loadFixture() {
// TODO: share Nuxt instance with running Nuxt if possible
if (ctx.options.build) {
- ctx.nuxt = await kit.loadNuxt({
+ const { loadNuxt } = await loadKit(ctx.options.rootDir)
+ ctx.nuxt = await loadNuxt({
cwd: ctx.options.rootDir,
dev: ctx.options.dev,
overrides: ctx.options.nuxtConfig,
@@ -78,9 +76,11 @@ export async function loadFixture() {
export async function buildFixture() {
const ctx = useTestContext()
+ const { buildNuxt, logger } = await loadKit(ctx.options.rootDir)
+
// Hide build info for test
- const prevLevel = kit.logger.level
- kit.logger.level = ctx.options.logLevel
- await kit.buildNuxt(ctx.nuxt!)
- kit.logger.level = prevLevel
+ const prevLevel = logger.level
+ logger.level = ctx.options.logLevel
+ await buildNuxt(ctx.nuxt!)
+ logger.level = prevLevel
}
diff --git a/src/experimental.ts b/src/experimental.ts
index a435327ec..990b3b2f3 100644
--- a/src/experimental.ts
+++ b/src/experimental.ts
@@ -1,5 +1,4 @@
import { $fetch as _$fetch, fetch as _fetch } from 'ofetch'
-import * as _kit from '@nuxt/kit'
import { resolve } from 'pathe'
import { stringifyQuery } from 'ufo'
import { useTestContext } from './e2e/context'
diff --git a/src/module.ts b/src/module.ts
index 8a6dfe8e3..653d55c8e 100644
--- a/src/module.ts
+++ b/src/module.ts
@@ -1,7 +1,7 @@
///
import { pathToFileURL } from 'node:url'
-import { addVitePlugin, createResolver, defineNuxtModule, logger, resolvePath, importModule } from '@nuxt/kit'
+import { createResolver, defineNuxtModule, logger, resolvePath, importModule } from '@nuxt/kit'
import type { Vitest, UserConfig as VitestConfig } from 'vitest/node'
import type { Reporter } from 'vitest/reporters'
import type { RunnerTestFile } from 'vitest'
@@ -11,11 +11,12 @@ import { h } from 'vue'
import { debounce } from 'perfect-debounce'
import { isCI } from 'std-env'
import { defu } from 'defu'
+import { join, relative } from 'pathe'
import { getVitestConfigFromNuxt } from './config'
import { setupImportMocking } from './module/mock'
import { NuxtRootStubPlugin } from './module/plugins/entry'
-import { join, relative } from 'pathe'
+import { loadKit } from './utils'
export interface NuxtVitestOptions {
startOnBoot?: boolean
@@ -40,9 +41,11 @@ export default defineNuxtModule({
},
async setup(options, nuxt) {
if (nuxt.options.test || nuxt.options.dev) {
- setupImportMocking()
+ await setupImportMocking(nuxt)
}
+ const { addVitePlugin } = await loadKit(nuxt.options.rootDir)
+
const resolver = createResolver(import.meta.url)
addVitePlugin(NuxtRootStubPlugin.vite({
entry: await resolvePath('#app/entry', { alias: nuxt.options.alias }),
diff --git a/src/module/mock.ts b/src/module/mock.ts
index 8997a621a..462e5280b 100644
--- a/src/module/mock.ts
+++ b/src/module/mock.ts
@@ -1,16 +1,17 @@
+import type { Nuxt } from '@nuxt/schema'
import type { Unimport } from 'unimport'
-import { addVitePlugin, resolveIgnorePatterns, useNuxt } from '@nuxt/kit'
+import { resolveIgnorePatterns } from '@nuxt/kit'
import { createMockPlugin } from './plugins/mock'
import type { MockPluginContext } from './plugins/mock'
+import { loadKit } from '../utils'
/**
* This module is a macro that transforms `mockNuxtImport()` to `vi.mock()`,
* which make it possible to mock Nuxt imports.
*/
-export function setupImportMocking() {
- const nuxt = useNuxt()
-
+export async function setupImportMocking(nuxt: Nuxt) {
+ const { addVitePlugin } = await loadKit(nuxt.options.rootDir)
const ctx: MockPluginContext = {
components: [],
imports: [],
diff --git a/src/runtime/global-setup.ts b/src/runtime/global-setup.ts
index c3e1ba25e..ac85dd9ed 100644
--- a/src/runtime/global-setup.ts
+++ b/src/runtime/global-setup.ts
@@ -1,17 +1,14 @@
-import * as _kit from '@nuxt/kit'
+import { consola } from 'consola'
import { createTest, exposeContextToEnv } from '@nuxt/test-utils/e2e'
-// @ts-expect-error type cast kit default export
-const kit: typeof _kit = _kit.default || _kit
-
const options = JSON.parse(process.env.NUXT_TEST_OPTIONS || '{}')
const hooks = createTest(options)
export const setup = async () => {
- kit.logger.info('Building Nuxt app...')
+ consola.info('Building Nuxt app...')
await hooks.beforeAll()
exposeContextToEnv()
- kit.logger.info('Running tests...')
+ consola.info('Running tests...')
}
export const teardown = async () => {
diff --git a/src/utils.ts b/src/utils.ts
index 053744749..eb668eb4c 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -6,6 +6,8 @@
import destr from 'destr'
import { snakeCase } from 'scule'
+import { pathToFileURL } from 'node:url'
+import { resolveModulePath } from 'exsolve'
type EnvOptions = {
env?: Record
@@ -52,3 +54,38 @@ export function applyEnv(obj: Record, opts: EnvOptions, parentKey =
}
return obj
}
+
+export async function loadKit(rootDir: string): Promise {
+ try {
+ const kitPath = resolveModulePath('@nuxt/kit', { from: tryResolveNuxt(rootDir) || rootDir })
+
+ let kit: typeof import('@nuxt/kit') = await import(pathToFileURL(kitPath).href)
+ if (!kit.writeTypes) {
+ kit = {
+ ...kit,
+ writeTypes: () => {
+ throw new Error('`writeTypes` is not available in this version of `@nuxt/kit`. Please upgrade to v3.7 or newer.')
+ },
+ }
+ }
+ return kit
+ }
+ catch (e: any) {
+ if (e.toString().includes('Cannot find module \'@nuxt/kit\'')) {
+ throw new Error(
+ 'nuxi requires `@nuxt/kit` to be installed in your project. Try installing `nuxt` v3+ or `@nuxt/bridge` first.',
+ )
+ }
+ throw e
+ }
+}
+
+export function tryResolveNuxt(rootDir: string) {
+ for (const pkg of ['nuxt-nightly', 'nuxt', 'nuxt3', 'nuxt-edge']) {
+ const path = resolveModulePath(pkg, { from: rootDir, try: true })
+ if (path) {
+ return path
+ }
+ }
+ return null
+}