feat: provide fallback instance for backward compatibility

This commit is contained in:
Julien Huang 2025-01-09 15:04:13 +01:00
parent a3487893a3
commit 680690fbdc
3 changed files with 48 additions and 18 deletions

View File

@ -1,9 +1,11 @@
import { getContext } from 'unctx' import { getContext } from 'unctx'
import type { Nuxt } from '@nuxt/schema' import type { Nuxt } from '@nuxt/schema'
import { asyncNameStorage } from './utils' import { asyncNameStorage } from './utils'
import { logger } from './logger'
/** Direct access to the Nuxt context - see https://github.com/unjs/unctx. */ /** Direct access to the Nuxt context - see https://github.com/unjs/unctx. */
export const nuxtCtx = () => getContext<Nuxt>(asyncNameStorage.getStore()!) export const nuxtCtx = () => getContext<Nuxt>(asyncNameStorage.getStore()!)
export const fallbackNuxtCtx = getContext<Nuxt>('nuxt-fallback')
// TODO: Use use/tryUse from unctx. https://github.com/unjs/unctx/issues/6 // TODO: Use use/tryUse from unctx. https://github.com/unjs/unctx/issues/6
@ -19,6 +21,11 @@ export const nuxtCtx = () => getContext<Nuxt>(asyncNameStorage.getStore()!)
export function useNuxt (): Nuxt { export function useNuxt (): Nuxt {
const instance = nuxtCtx().tryUse() const instance = nuxtCtx().tryUse()
if (!instance) { if (!instance) {
const fallbackInstance = fallbackNuxtCtx.tryUse()
if (fallbackInstance) {
logger.warn('Using fallback global Nuxt instance. You may be using a @nuxt/kit composable outside of a Nuxt context, this behavior is deprecated and will be removed in v4.')
return fallbackInstance }
throw new Error('Nuxt instance is unavailable!') throw new Error('Nuxt instance is unavailable!')
} }
return instance return instance
@ -37,5 +44,10 @@ export function useNuxt (): Nuxt {
* ``` * ```
*/ */
export function tryUseNuxt (): Nuxt | null { export function tryUseNuxt (): Nuxt | null {
return nuxtCtx().tryUse() const nuxt = nuxtCtx().tryUse()
if(!nuxt) {
logger.warn('Using fallback global Nuxt instance. You may be using a @nuxt/kit composable outside of a Nuxt context, this behavior is deprecated and will be removed in v4.')
return fallbackNuxtCtx.tryUse()
}
return nuxt
} }

View File

@ -4,7 +4,7 @@ import { join, normalize, relative, resolve } from 'pathe'
import { createDebugger, createHooks } from 'hookable' import { createDebugger, createHooks } from 'hookable'
import ignore from 'ignore' import ignore from 'ignore'
import type { LoadNuxtOptions } from '@nuxt/kit' import type { LoadNuxtOptions } from '@nuxt/kit'
import { addBuildPlugin, addComponent, addPlugin, addPluginTemplate, addRouteMiddleware, addServerPlugin, addTypeTemplate, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, tryResolveModule, useNitro, asyncNameStorage } from '@nuxt/kit' import { addBuildPlugin, addComponent, addPlugin, addPluginTemplate, addRouteMiddleware, addServerPlugin, addTypeTemplate, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, tryResolveModule, useNitro, asyncNameStorage, fallbackNuxtCtx } from '@nuxt/kit'
import type { Nuxt, NuxtHooks, NuxtModule, NuxtOptions } from 'nuxt/schema' import type { Nuxt, NuxtHooks, NuxtModule, NuxtOptions } from 'nuxt/schema'
import type { PackageJson } from 'pkg-types' import type { PackageJson } from 'pkg-types'
import { readPackageJSON } from 'pkg-types' import { readPackageJSON } from 'pkg-types'
@ -48,7 +48,7 @@ import { PrehydrateTransformPlugin } from './plugins/prehydrate'
import { VirtualFSPlugin } from './plugins/virtual' import { VirtualFSPlugin } from './plugins/virtual'
import { randomUUID } from 'uncrypto' import { randomUUID } from 'uncrypto'
export function createNuxt (options: NuxtOptions): Nuxt { export function createNuxt(options: NuxtOptions): Nuxt {
const hooks = createHooks<NuxtHooks>() const hooks = createHooks<NuxtHooks>()
const name = randomUUID() const name = randomUUID()
const nuxt: Nuxt = { const nuxt: Nuxt = {
@ -58,7 +58,7 @@ export function createNuxt (options: NuxtOptions): Nuxt {
callHook: hooks.callHook, callHook: hooks.callHook,
addHooks: hooks.addHooks, addHooks: hooks.addHooks,
hook: hooks.hook, hook: hooks.hook,
ready: () => asyncNameStorage.run(name, () => initNuxt(nuxt)) , ready: () => asyncNameStorage.run(name, () => initNuxt(nuxt)),
close: () => hooks.callHook('close', nuxt), close: () => hooks.callHook('close', nuxt),
vfs: {}, vfs: {},
apps: {}, apps: {},
@ -88,7 +88,7 @@ const keyDependencies = [
let warnedAboutCompatDate = false let warnedAboutCompatDate = false
async function initNuxt (nuxt: Nuxt) { async function initNuxt(nuxt: Nuxt) {
// Register user hooks // Register user hooks
for (const config of nuxt.options._layers.map(layer => layer.config).reverse()) { for (const config of nuxt.options._layers.map(layer => layer.config).reverse()) {
if (config.hooks) { if (config.hooks) {
@ -108,7 +108,7 @@ async function initNuxt (nuxt: Nuxt) {
logger.info(`Using \`${fallbackCompatibilityDate}\` as fallback compatibility date.`) logger.info(`Using \`${fallbackCompatibilityDate}\` as fallback compatibility date.`)
} }
async function promptAndUpdate () { async function promptAndUpdate() {
const result = await consola.prompt(`Do you want to update your ${colorize('cyan', 'nuxt.config')} to set ${colorize('cyan', `compatibilityDate: '${todaysDate}'`)}?`, { const result = await consola.prompt(`Do you want to update your ${colorize('cyan', 'nuxt.config')} to set ${colorize('cyan', `compatibilityDate: '${todaysDate}'`)}?`, {
type: 'confirm', type: 'confirm',
default: true, default: true,
@ -122,7 +122,7 @@ async function initNuxt (nuxt: Nuxt) {
const res = await updateConfig({ const res = await updateConfig({
configFile: 'nuxt.config', configFile: 'nuxt.config',
cwd: nuxt.options.rootDir, cwd: nuxt.options.rootDir,
async onCreate ({ configFile }) { async onCreate({ configFile }) {
const shallCreate = await consola.prompt(`Do you want to create ${colorize('cyan', relative(nuxt.options.rootDir, configFile))}?`, { const shallCreate = await consola.prompt(`Do you want to create ${colorize('cyan', relative(nuxt.options.rootDir, configFile))}?`, {
type: 'confirm', type: 'confirm',
default: true, default: true,
@ -132,7 +132,7 @@ async function initNuxt (nuxt: Nuxt) {
} }
return _getDefaultNuxtConfig() return _getDefaultNuxtConfig()
}, },
onUpdate (config) { onUpdate(config) {
config.compatibilityDate = todaysDate config.compatibilityDate = todaysDate
}, },
}) })
@ -173,10 +173,18 @@ async function initNuxt (nuxt: Nuxt) {
} }
} }
}) })
if (!fallbackNuxtCtx.tryUse()) {
// backward compatibility with 3.x
fallbackNuxtCtx.set(nuxt)
nuxt.hook('close', () => {
fallbackNuxtCtx.unset()
})
}
// Set nuxt instance for useNuxt // Set nuxt instance for useNuxt
nuxtCtx().set(nuxt) nuxtCtx().set(nuxt)
nuxt.hook('close', () => nuxtCtx().unset()) nuxt.hook('close', () => {
nuxtCtx().unset()
})
const coreTypePackages = nuxt.options.typescript.hoist || [] const coreTypePackages = nuxt.options.typescript.hoist || []
@ -722,7 +730,7 @@ export default defineNuxtPlugin({
await nuxt.callHook('ready', nuxt) await nuxt.callHook('ready', nuxt)
} }
export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> { export async function loadNuxt(opts: LoadNuxtOptions): Promise<Nuxt> {
const options = await loadNuxtConfig(opts) const options = await loadNuxtConfig(opts)
// Temporary until finding better placement for each // Temporary until finding better placement for each
@ -795,7 +803,7 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
configurable: false, configurable: false,
enumerable: true, enumerable: true,
get: () => nitroOptions, get: () => nitroOptions,
set (value) { set(value) {
Object.assign(nitroOptions, value) Object.assign(nitroOptions, value)
}, },
}, },
@ -823,7 +831,7 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
return nuxt return nuxt
} }
async function checkDependencyVersion (name: string, nuxtVersion: string): Promise<void> { async function checkDependencyVersion(name: string, nuxtVersion: string): Promise<void> {
const path = await resolvePath(name, { fallbackToOriginal: true }).catch(() => null) const path = await resolvePath(name, { fallbackToOriginal: true }).catch(() => null)
if (!path || path === name) { return } if (!path || path === name) { return }
@ -836,7 +844,7 @@ async function checkDependencyVersion (name: string, nuxtVersion: string): Promi
const RESTART_RE = /^(?:app|error|app\.config)\.(?:js|ts|mjs|jsx|tsx|vue)$/i const RESTART_RE = /^(?:app|error|app\.config)\.(?:js|ts|mjs|jsx|tsx|vue)$/i
function deduplicateArray<T = unknown> (maybeArray: T): T { function deduplicateArray<T = unknown>(maybeArray: T): T {
if (!Array.isArray(maybeArray)) { return maybeArray } if (!Array.isArray(maybeArray)) { return maybeArray }
const fresh: any[] = [] const fresh: any[] = []
@ -851,7 +859,7 @@ function deduplicateArray<T = unknown> (maybeArray: T): T {
return fresh as T return fresh as T
} }
function createPortalProperties (sourceValue: any, options: NuxtOptions, paths: string[]) { function createPortalProperties(sourceValue: any, options: NuxtOptions, paths: string[]) {
let sharedValue = sourceValue let sharedValue = sourceValue
for (const path of paths) { for (const path of paths) {
@ -871,7 +879,7 @@ function createPortalProperties (sourceValue: any, options: NuxtOptions, paths:
configurable: false, configurable: false,
enumerable: true, enumerable: true,
get: () => sharedValue, get: () => sharedValue,
set (value) { set(value) {
sharedValue = value sharedValue = value
}, },
}, },

View File

@ -35,7 +35,7 @@ describe('loadNuxt', () => {
ready: true, ready: true,
overrides: { overrides: {
hooks: { hooks: {
ready () { ready() {
hookRan = true hookRan = true
}, },
}, },
@ -44,6 +44,16 @@ describe('loadNuxt', () => {
await nuxt.close() await nuxt.close()
expect(hookRan).toBe(true) expect(hookRan).toBe(true)
}) })
it('load multiple nuxt', async () => {
await Promise.all([
loadNuxt({
cwd: repoRoot,
}),
loadNuxt({
cwd: repoRoot,
})
])
})
}) })
describe('dependency mismatch', () => { describe('dependency mismatch', () => {