diff --git a/packages/nuxt/src/core/nuxt.ts b/packages/nuxt/src/core/nuxt.ts index 6c97e61afd..816c6d51e4 100644 --- a/packages/nuxt/src/core/nuxt.ts +++ b/packages/nuxt/src/core/nuxt.ts @@ -1,6 +1,7 @@ import { existsSync } from 'node:fs' import { rm } from 'node:fs/promises' import { randomUUID } from 'node:crypto' +import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks' import { join, normalize, relative, resolve } from 'pathe' import { createDebugger, createHooks } from 'hookable' import ignore from 'ignore' @@ -52,6 +53,12 @@ import { VirtualFSPlugin } from './plugins/virtual' export function createNuxt (options: NuxtOptions): Nuxt { const hooks = createHooks() 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 = { _version: version, options, @@ -64,8 +71,24 @@ export function createNuxt (options: NuxtOptions): Nuxt { vfs: {}, apps: {}, __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() }) 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 || [] // Disable environment types entirely if `typescript.builder` is false @@ -812,18 +822,20 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise { const nuxt = createNuxt(options) - for (const dep of keyDependencies) { - checkDependencyVersion(dep, nuxt._version) - } + nuxt.run(() => { + for (const dep of keyDependencies) { + checkDependencyVersion(dep, nuxt._version) + } - // We register hooks layer-by-layer so any overrides need to be registered separately - if (opts.overrides?.hooks) { - nuxt.hooks.addHooks(opts.overrides.hooks) - } + // We register hooks layer-by-layer so any overrides need to be registered separately + if (opts.overrides?.hooks) { + nuxt.hooks.addHooks(opts.overrides.hooks) + } - if (nuxt.options.debug) { - createDebugger(nuxt.hooks, { tag: 'nuxt' }) - } + if (nuxt.options.debug) { + createDebugger(nuxt.hooks, { tag: 'nuxt' }) + } + }) if (opts.ready !== false) { await nuxt.ready() diff --git a/packages/nuxt/test/load-nuxt.test.ts b/packages/nuxt/test/load-nuxt.test.ts index e24790fe22..1e0f02a343 100644 --- a/packages/nuxt/test/load-nuxt.test.ts +++ b/packages/nuxt/test/load-nuxt.test.ts @@ -4,9 +4,11 @@ import { normalize } from 'pathe' import { withoutTrailingSlash } from 'ufo' import { readPackageJSON } from 'pkg-types' import { inc } from 'semver' +import { asyncNameStorage, useNuxt } from '@nuxt/kit' import { loadNuxt } from '../src' 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)))) vi.stubGlobal('console', { @@ -15,6 +17,7 @@ vi.stubGlobal('console', { warn: vi.fn(console.warn), }) +const loggerWarn = vi.spyOn(logger, 'warn') vi.mock('pkg-types', async (og) => { const originalPkgTypes = (await og()) return { @@ -23,6 +26,9 @@ vi.mock('pkg-types', async (og) => { } }) +beforeEach(() => { + loggerWarn.mockClear() +}) afterEach(() => { vi.clearAllMocks() }) @@ -53,7 +59,24 @@ describe('loadNuxt', () => { 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() }) }) diff --git a/packages/schema/src/types/nuxt.ts b/packages/schema/src/types/nuxt.ts index 072c5edcdb..ac3b2e2b5a 100644 --- a/packages/schema/src/types/nuxt.ts +++ b/packages/schema/src/types/nuxt.ts @@ -87,6 +87,10 @@ export interface Nuxt { _version: string _ignore?: Ignore _dependencies?: Set + /** + * @internal + */ + run: any>(fn: T) => ReturnType /** The resolved Nuxt configuration. */ options: NuxtOptions