fix: correctly set context when creating nuxt and provide an internal run fn and wrap callhooks

This commit is contained in:
Julien Huang 2025-01-12 00:50:07 +01:00
parent 37fd4ab37c
commit 78649f6662
3 changed files with 64 additions and 25 deletions

View File

@ -1,6 +1,7 @@
import { existsSync } from 'node:fs' import { existsSync } from 'node:fs'
import { rm } from 'node:fs/promises' import { rm } from 'node:fs/promises'
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks'
import { join, normalize, relative, resolve } from 'pathe' 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'
@ -52,6 +53,12 @@ import { VirtualFSPlugin } from './plugins/virtual'
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 { callHook, callHookParallel, callHookWith } = hooks
hooks.callHook = (...args) => asyncNameStorage.run(name, () => callHook(...args))
hooks.callHookParallel = (...args) => asyncNameStorage.run(name, () => callHookParallel(...args))
hooks.callHookWith = (...args) => asyncNameStorage.run(name, () => callHookWith(...args))
const nuxt: Nuxt = { const nuxt: Nuxt = {
_version: version, _version: version,
options, options,
@ -64,8 +71,24 @@ export function createNuxt (options: NuxtOptions): Nuxt {
vfs: {}, vfs: {},
apps: {}, apps: {},
__name: name, __name: name,
run: fn => asyncNameStorage.run(name, fn),
} }
if (!nuxtCtx.tryUse()) {
// backward compatibility with 3.x
nuxtCtx.set(nuxt)
nuxt.hook('close', () => {
nuxtCtx.unset()
})
}
nuxt.run(() => {
// Set nuxt instance for useNuxt
getNuxtCtx().set(nuxt)
nuxt.hook('close', () => {
getNuxtCtx().unset()
})
})
hooks.hookOnce('close', () => { hooks.removeAllHooks() }) hooks.hookOnce('close', () => { hooks.removeAllHooks() })
return nuxt return nuxt
@ -175,19 +198,6 @@ async function initNuxt (nuxt: Nuxt) {
} }
} }
}) })
if (!nuxtCtx.tryUse()) {
// backward compatibility with 3.x
nuxtCtx.set(nuxt)
nuxt.hook('close', () => {
nuxtCtx.unset()
})
}
// Set nuxt instance for useNuxt
getNuxtCtx().set(nuxt)
nuxt.hook('close', () => {
getNuxtCtx().unset()
})
const coreTypePackages = nuxt.options.typescript.hoist || [] const coreTypePackages = nuxt.options.typescript.hoist || []
// Disable environment types entirely if `typescript.builder` is false // Disable environment types entirely if `typescript.builder` is false
@ -812,6 +822,7 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
const nuxt = createNuxt(options) const nuxt = createNuxt(options)
nuxt.run(() => {
for (const dep of keyDependencies) { for (const dep of keyDependencies) {
checkDependencyVersion(dep, nuxt._version) checkDependencyVersion(dep, nuxt._version)
} }
@ -824,6 +835,7 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
if (nuxt.options.debug) { if (nuxt.options.debug) {
createDebugger(nuxt.hooks, { tag: 'nuxt' }) createDebugger(nuxt.hooks, { tag: 'nuxt' })
} }
})
if (opts.ready !== false) { if (opts.ready !== false) {
await nuxt.ready() await nuxt.ready()

View File

@ -4,9 +4,11 @@ import { normalize } from 'pathe'
import { withoutTrailingSlash } from 'ufo' import { withoutTrailingSlash } from 'ufo'
import { readPackageJSON } from 'pkg-types' import { readPackageJSON } from 'pkg-types'
import { inc } from 'semver' import { inc } from 'semver'
import { asyncNameStorage, useNuxt } from '@nuxt/kit'
import { loadNuxt } from '../src' import { loadNuxt } from '../src'
import { version } from '../package.json' import { version } from '../package.json'
import { logger } from '@nuxt/kit'
import { beforeEach } from 'node:test'
const repoRoot = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../', import.meta.url)))) const repoRoot = withoutTrailingSlash(normalize(fileURLToPath(new URL('../../../', import.meta.url))))
vi.stubGlobal('console', { vi.stubGlobal('console', {
@ -15,6 +17,7 @@ vi.stubGlobal('console', {
warn: vi.fn(console.warn), warn: vi.fn(console.warn),
}) })
const loggerWarn = vi.spyOn(logger, 'warn')
vi.mock('pkg-types', async (og) => { vi.mock('pkg-types', async (og) => {
const originalPkgTypes = (await og<typeof import('pkg-types')>()) const originalPkgTypes = (await og<typeof import('pkg-types')>())
return { return {
@ -23,6 +26,9 @@ vi.mock('pkg-types', async (og) => {
} }
}) })
beforeEach(() => {
loggerWarn.mockClear()
})
afterEach(() => { afterEach(() => {
vi.clearAllMocks() vi.clearAllMocks()
}) })
@ -53,7 +59,24 @@ describe('loadNuxt', () => {
cwd: repoRoot, cwd: repoRoot,
}), }),
]) ])
expect(console.warn).not.toHaveBeenCalled() expect(loggerWarn).not.toHaveBeenCalled()
})
it('expect hooks to get the correct context outside of initNuxt', async () => {
const nuxt = await loadNuxt({
cwd: repoRoot,
})
// @ts-expect-error - random hook
await nuxt.hook('test', () => {
const nuxt = useNuxt()
expect(asyncNameStorage.getStore()).toBe(nuxt.__name)
})
// @ts-expect-error - random hook
await nuxt.callHook('test')
expect(loggerWarn).not.toHaveBeenCalled()
}) })
}) })

View File

@ -87,6 +87,10 @@ export interface Nuxt {
_version: string _version: string
_ignore?: Ignore _ignore?: Ignore
_dependencies?: Set<string> _dependencies?: Set<string>
/**
* @internal
*/
run: <T extends (...args: any[]) => any>(fn: T) => ReturnType<T>
/** The resolved Nuxt configuration. */ /** The resolved Nuxt configuration. */
options: NuxtOptions options: NuxtOptions