import { effectScope, getCurrentInstance, getCurrentScope, hasInjectionContext, reactive, shallowReactive } from 'vue' import type { App, EffectScope, Ref, VNode, onErrorCaptured } from 'vue' import type { RouteLocationNormalizedLoaded } from 'vue-router' import type { HookCallback, Hookable } from 'hookable' import { createHooks } from 'hookable' import { getContext } from 'unctx' import type { SSRContext, createRenderer } from 'vue-bundle-renderer/runtime' import type { EventHandlerRequest, H3Event } from 'h3' import type { AppConfig, AppConfigInput, RuntimeConfig } from 'nuxt/schema' import type { RenderResponse } from 'nitropack' import type { LogObject } from 'consola' import type { MergeHead, VueHeadClient } from '@unhead/vue' import type { NuxtAppLiterals } from 'nuxt/app' // TODO: temporary module for backwards compatibility import type { DefaultAsyncDataErrorValue, DefaultErrorValue } from 'nuxt/app/defaults' import type { NuxtIslandContext } from '../app/types' import type { RouteMiddleware } from '../app/composables/router' import type { NuxtError } from '../app/composables/error' import type { AsyncDataRequestStatus } from '../app/composables/asyncData' import type { NuxtAppManifestMeta } from '../app/composables/manifest' import type { LoadingIndicator } from '../app/composables/loading-indicator' import type { RouteAnnouncer } from '../app/composables/route-announcer' // @ts-expect-error virtual file import { appId, chunkErrorEvent, multiApp } from '#build/nuxt.config.mjs' function getNuxtAppCtx (id = appId || 'nuxt-app') { return getContext(id, { asyncContext: !!__NUXT_ASYNC_CONTEXT__ && import.meta.server, }) } type HookResult = Promise | void type AppRenderedContext = { ssrContext: NuxtApp['ssrContext'], renderResult: null | Awaited['renderToString']>> } export interface RuntimeNuxtHooks { 'app:created': (app: App) => HookResult 'app:beforeMount': (app: App) => HookResult 'app:mounted': (app: App) => HookResult 'app:rendered': (ctx: AppRenderedContext) => HookResult 'app:redirected': () => HookResult 'app:suspense:resolve': (Component?: VNode) => HookResult 'app:error': (err: any) => HookResult 'app:error:cleared': (options: { redirect?: string }) => HookResult 'app:chunkError': (options: { error: any }) => HookResult 'app:data:refresh': (keys?: string[]) => HookResult 'app:manifest:update': (meta?: NuxtAppManifestMeta) => HookResult 'dev:ssr-logs': (logs: LogObject[]) => HookResult 'link:prefetch': (link: string) => HookResult 'page:start': (Component?: VNode) => HookResult 'page:finish': (Component?: VNode) => HookResult 'page:transition:start': () => HookResult 'page:transition:finish': (Component?: VNode) => HookResult 'page:view-transition:start': (transition: ViewTransition) => HookResult 'page:loading:start': () => HookResult 'page:loading:end': () => HookResult 'vue:setup': () => void 'vue:error': (...args: Parameters[0]>) => HookResult } export interface NuxtSSRContext extends SSRContext { url: string event: H3Event runtimeConfig: RuntimeConfig noSSR: boolean /** whether we are rendering an SSR error */ error?: boolean nuxt: _NuxtApp payload: Partial head: VueHeadClient /** This is used solely to render runtime config with SPA renderer. */ config?: Pick teleports?: Record islandContext?: NuxtIslandContext /** @internal */ _renderResponse?: Partial /** @internal */ _payloadReducers: Record any> /** @internal */ _sharedPrerenderCache?: { get (key: string): Promise | undefined set (key: string, value: Promise): Promise } /** @internal */ _preloadManifest?: boolean } export interface NuxtPayload { path?: string serverRendered?: boolean prerenderedAt?: number data: Record state: Record once: Set config?: Pick error?: NuxtError | DefaultErrorValue _errors: Record [key: string]: unknown } interface _NuxtApp { vueApp: App globalName: string versions: Record hooks: Hookable hook: _NuxtApp['hooks']['hook'] callHook: _NuxtApp['hooks']['callHook'] runWithContext: any>(fn: T) => ReturnType | Promise>> [key: string]: unknown /** @internal */ _cookies?: Record /** * The id of the Nuxt application. * @internal */ _id: string /** @internal */ _scope: EffectScope /** @internal */ _asyncDataPromises: Record | undefined> /** @internal */ _asyncData: Record /** * @deprecated This may be removed in a future major version. */ pending: Ref error: Ref status: Ref /** @internal */ _default: () => unknown } | undefined> /** @internal */ _loadingIndicator?: LoadingIndicator /** @internal */ _loadingIndicatorDeps?: number /** @internal */ _middleware: { global: RouteMiddleware[] named: Record } /** @internal */ _once: { [key: string]: Promise } /** @internal */ _observer?: { observe: (element: Element, callback: () => void) => () => void } /** @internal */ _payloadCache?: Record> | Record | null> /** @internal */ _appConfig: AppConfig /** @internal */ _route: RouteLocationNormalizedLoaded /** @internal */ _islandPromises?: Record> /** @internal */ _payloadRevivers: Record any> /** @internal */ _routeAnnouncer?: RouteAnnouncer /** @internal */ _routeAnnouncerDeps?: number // Nuxt injections $config: RuntimeConfig isHydrating?: boolean deferHydration: () => () => void | Promise ssrContext?: NuxtSSRContext payload: NuxtPayload static: { data: Record } provide: (name: string, value: any) => void } // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface NuxtApp extends _NuxtApp {} export const NuxtPluginIndicator = '__nuxt_plugin' export interface PluginMeta { name?: string enforce?: 'pre' | 'default' | 'post' /** * Await for other named plugins to finish before running this plugin. */ dependsOn?: NuxtAppLiterals['pluginName'][] /** * This allows more granular control over plugin order and should only be used by advanced users. * It overrides the value of `enforce` and is used to sort plugins. */ order?: number } export interface PluginEnvContext { /** * This enable the plugin for islands components. * Require `experimental.componentsIslands`. * @default true */ islands?: boolean } export interface ResolvedPluginMeta { name?: string parallel?: boolean } export interface Plugin = Record> { (nuxt: _NuxtApp): Promise | Promise<{ provide?: Injections }> | void | { provide?: Injections } [NuxtPluginIndicator]?: true meta?: ResolvedPluginMeta } export interface ObjectPlugin = Record> extends PluginMeta { hooks?: Partial setup?: Plugin env?: PluginEnvContext /** * Execute plugin in parallel with other parallel plugins. * @default false */ parallel?: boolean /** * @internal */ _name?: string } /** @deprecated Use `ObjectPlugin` */ export type ObjectPluginInput = Record> = ObjectPlugin export interface CreateOptions { vueApp: NuxtApp['vueApp'] ssrContext?: NuxtApp['ssrContext'] globalName?: NuxtApp['globalName'] /** * The id of the Nuxt application, overrides the default id specified in the Nuxt config (default: `nuxt-app`). */ id?: NuxtApp['_id'] } /** @since 3.0.0 */ export function createNuxtApp (options: CreateOptions) { let hydratingCount = 0 const nuxtApp: NuxtApp = { _id: options.id || appId || 'nuxt-app', _scope: effectScope(), provide: undefined, globalName: 'nuxt', versions: { get nuxt () { return __NUXT_VERSION__ }, get vue () { return nuxtApp.vueApp.version }, }, payload: shallowReactive({ ...options.ssrContext?.payload || {}, data: shallowReactive({}), state: reactive({}), once: new Set(), _errors: shallowReactive({}), }), static: { data: {}, }, runWithContext (fn: () => T) { if (nuxtApp._scope.active && !getCurrentScope()) { return nuxtApp._scope.run(() => callWithNuxt(nuxtApp, fn)) } return callWithNuxt(nuxtApp, fn) }, isHydrating: import.meta.client, deferHydration () { if (!nuxtApp.isHydrating) { return () => {} } hydratingCount++ let called = false return () => { if (called) { return } called = true hydratingCount-- if (hydratingCount === 0) { nuxtApp.isHydrating = false return nuxtApp.callHook('app:suspense:resolve') } } }, _asyncDataPromises: {}, _asyncData: shallowReactive({}), _payloadRevivers: {}, ...options, } as any as NuxtApp if (import.meta.server) { nuxtApp.payload.serverRendered = true } if (import.meta.server && nuxtApp.ssrContext) { nuxtApp.payload.path = nuxtApp.ssrContext.url // Expose nuxt to the renderContext nuxtApp.ssrContext.nuxt = nuxtApp nuxtApp.ssrContext.payload = nuxtApp.payload // Expose client runtime-config to the payload nuxtApp.ssrContext.config = { public: nuxtApp.ssrContext.runtimeConfig.public, app: nuxtApp.ssrContext.runtimeConfig.app, } } if (import.meta.client) { const __NUXT__ = multiApp ? window.__NUXT__?.[nuxtApp._id] : window.__NUXT__ // TODO: remove/refactor in https://github.com/nuxt/nuxt/issues/25336 if (__NUXT__) { for (const key in __NUXT__) { switch (key) { case 'data': case 'state': case '_errors': // Preserve reactivity for non-rich payload support Object.assign(nuxtApp.payload[key], __NUXT__[key]) break default: nuxtApp.payload[key] = __NUXT__[key] } } } } nuxtApp.hooks = createHooks() nuxtApp.hook = nuxtApp.hooks.hook if (import.meta.server) { const contextCaller = async function (hooks: HookCallback[], args: any[]) { for (const hook of hooks) { await nuxtApp.runWithContext(() => hook(...args)) } } // Patch callHook to preserve NuxtApp context on server // TODO: Refactor after https://github.com/unjs/hookable/issues/74 nuxtApp.hooks.callHook = (name: any, ...args: any[]) => nuxtApp.hooks.callHookWith(contextCaller, name, ...args) } nuxtApp.callHook = nuxtApp.hooks.callHook nuxtApp.provide = (name: string, value: any) => { const $name = '$' + name defineGetter(nuxtApp, $name, value) defineGetter(nuxtApp.vueApp.config.globalProperties, $name, value) } // Inject $nuxt defineGetter(nuxtApp.vueApp, '$nuxt', nuxtApp) defineGetter(nuxtApp.vueApp.config.globalProperties, '$nuxt', nuxtApp) if (import.meta.client) { // Listen to chunk load errors if (chunkErrorEvent) { window.addEventListener(chunkErrorEvent, (event) => { nuxtApp.callHook('app:chunkError', { error: (event as Event & { payload: Error }).payload }) if (nuxtApp.isHydrating || event.payload.message.includes('Unable to preload CSS')) { event.preventDefault() } }) } window.useNuxtApp = window.useNuxtApp || useNuxtApp // Log errors captured when running plugins, in the `app:created` and `app:beforeMount` hooks // as well as when mounting the app. const unreg = nuxtApp.hook('app:error', (...args) => { console.error('[nuxt] error caught during app initialization', ...args) }) nuxtApp.hook('app:mounted', unreg) } // Expose runtime config const runtimeConfig = import.meta.server ? options.ssrContext!.runtimeConfig : nuxtApp.payload.config! nuxtApp.provide('config', import.meta.client && import.meta.dev ? wrappedConfig(runtimeConfig) : runtimeConfig) return nuxtApp } /** @since 3.12.0 */ export function registerPluginHooks (nuxtApp: NuxtApp, plugin: Plugin & ObjectPlugin) { if (plugin.hooks) { nuxtApp.hooks.addHooks(plugin.hooks) } } /** @since 3.0.0 */ export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin & ObjectPlugin) { if (typeof plugin === 'function') { const { provide } = await nuxtApp.runWithContext(() => plugin(nuxtApp)) || {} if (provide && typeof provide === 'object') { for (const key in provide) { nuxtApp.provide(key, provide[key]) } } } } /** @since 3.0.0 */ export async function applyPlugins (nuxtApp: NuxtApp, plugins: Array>) { const resolvedPlugins: string[] = [] const unresolvedPlugins: [Set, Plugin & ObjectPlugin][] = [] const parallels: Promise[] = [] const errors: Error[] = [] let promiseDepth = 0 async function executePlugin (plugin: Plugin & ObjectPlugin) { const unresolvedPluginsForThisPlugin = plugin.dependsOn?.filter(name => plugins.some(p => p._name === name) && !resolvedPlugins.includes(name)) ?? [] if (unresolvedPluginsForThisPlugin.length > 0) { unresolvedPlugins.push([new Set(unresolvedPluginsForThisPlugin), plugin]) } else { const promise = applyPlugin(nuxtApp, plugin).then(async () => { if (plugin._name) { resolvedPlugins.push(plugin._name) await Promise.all(unresolvedPlugins.map(async ([dependsOn, unexecutedPlugin]) => { if (dependsOn.has(plugin._name!)) { dependsOn.delete(plugin._name!) if (dependsOn.size === 0) { promiseDepth++ await executePlugin(unexecutedPlugin) } } })) } }) if (plugin.parallel) { parallels.push(promise.catch(e => errors.push(e))) } else { await promise } } } for (const plugin of plugins) { if (import.meta.server && nuxtApp.ssrContext?.islandContext && plugin.env?.islands === false) { continue } registerPluginHooks(nuxtApp, plugin) } for (const plugin of plugins) { if (import.meta.server && nuxtApp.ssrContext?.islandContext && plugin.env?.islands === false) { continue } await executePlugin(plugin) } await Promise.all(parallels) if (promiseDepth) { for (let i = 0; i < promiseDepth; i++) { await Promise.all(parallels) } } if (errors.length) { throw errors[0] } } /** @since 3.0.0 */ /* @__NO_SIDE_EFFECTS__ */ export function defineNuxtPlugin> (plugin: Plugin | ObjectPlugin): Plugin & ObjectPlugin { if (typeof plugin === 'function') { return plugin } const _name = plugin._name || plugin.name delete plugin.name return Object.assign(plugin.setup || (() => {}), plugin, { [NuxtPluginIndicator]: true, _name } as const) } /* @__NO_SIDE_EFFECTS__ */ export const definePayloadPlugin = defineNuxtPlugin /** @since 3.0.0 */ export function isNuxtPlugin (plugin: unknown) { return typeof plugin === 'function' && NuxtPluginIndicator in plugin } /** * Ensures that the setup function passed in has access to the Nuxt instance via `useNuxtApp`. * @param nuxt A Nuxt instance * @param setup The function to call * @since 3.0.0 */ export function callWithNuxt any> (nuxt: NuxtApp | _NuxtApp, setup: T, args?: Parameters) { const fn: () => ReturnType = () => args ? setup(...args as Parameters) : setup() const nuxtAppCtx = getNuxtAppCtx(nuxt._id) if (import.meta.server) { return nuxt.vueApp.runWithContext(() => nuxtAppCtx.callAsync(nuxt as NuxtApp, fn)) } else { // In client side we could assume nuxt app is singleton nuxtAppCtx.set(nuxt as NuxtApp) return nuxt.vueApp.runWithContext(fn) } } /* @__NO_SIDE_EFFECTS__ */ /** * Returns the current Nuxt instance. * * Returns `null` if Nuxt instance is unavailable. * @since 3.10.0 */ export function tryUseNuxtApp (): NuxtApp | null export function tryUseNuxtApp (id?: string): NuxtApp | null { let nuxtAppInstance if (hasInjectionContext()) { nuxtAppInstance = getCurrentInstance()?.appContext.app.$nuxt } nuxtAppInstance = nuxtAppInstance || getNuxtAppCtx(id).tryUse() return nuxtAppInstance || null } /* @__NO_SIDE_EFFECTS__ */ /** * Returns the current Nuxt instance. * * Throws an error if Nuxt instance is unavailable. * @since 3.0.0 */ export function useNuxtApp (): NuxtApp export function useNuxtApp (id?: string): NuxtApp { // @ts-expect-error internal usage of id const nuxtAppInstance = tryUseNuxtApp(id) if (!nuxtAppInstance) { if (import.meta.dev) { throw new Error('[nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug. Find out more at `https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables`.') } else { throw new Error('[nuxt] instance unavailable') } } return nuxtAppInstance } /** @since 3.0.0 */ /* @__NO_SIDE_EFFECTS__ */ export function useRuntimeConfig (_event?: H3Event): RuntimeConfig { return useNuxtApp().$config } function defineGetter (obj: Record, key: K, val: V) { Object.defineProperty(obj, key, { get: () => val }) } /** @since 3.0.0 */ export function defineAppConfig (config: C): C { return config } /** * Configure error getter on runtime secret property access that doesn't exist on the client side */ const loggedKeys = new Set() function wrappedConfig (runtimeConfig: Record) { if (!import.meta.dev || import.meta.server) { return runtimeConfig } const keys = Object.keys(runtimeConfig).map(key => `\`${key}\``) const lastKey = keys.pop() return new Proxy(runtimeConfig, { get (target, p, receiver) { if (typeof p === 'string' && p !== 'public' && !(p in target) && !p.startsWith('__v') /* vue check for reactivity, e.g. `__v_isRef` */) { if (!loggedKeys.has(p)) { loggedKeys.add(p) console.warn(`[nuxt] Could not access \`${p}\`. The only available runtime config keys on the client side are ${keys.join(', ')} and ${lastKey}. See https://nuxt.com/docs/guide/going-further/runtime-config for more information.`) } } return Reflect.get(target, p, receiver) }, }) }