/* eslint-disable no-use-before-define */ import { getCurrentInstance, hasInjectionContext, reactive } from 'vue' import type { App, 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 { H3Event } from 'h3' import type { AppConfig, AppConfigInput, RuntimeConfig } from 'nuxt/schema' import type { RenderResponse } from 'nitropack' // eslint-disable-next-line import/no-restricted-paths import type { NuxtIslandContext } from '../core/runtime/nitro/renderer' import type { RouteMiddleware } from '../../app' import type { NuxtError } from '../app/composables/error' import type { AsyncDataRequestStatus } from '../app/composables/asyncData' const nuxtAppCtx = /* #__PURE__ */ getContext('nuxt-app') type NuxtMeta = { htmlAttrs?: string headAttrs?: string bodyAttrs?: string headTags?: string bodyScriptsPrepend?: string bodyScripts?: string } 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 '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 '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: _NuxtApp['payload'] teleports?: Record renderMeta?: () => Promise | NuxtMeta islandContext?: NuxtIslandContext /** @internal */ _renderResponse?: Partial /** @internal */ _payloadReducers: Record any> } 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 */ _asyncDataPromises: Record | undefined> /** @internal */ _asyncData: Record pending: Ref error: Ref status: Ref } | undefined> /** @internal */ _middleware: { global: RouteMiddleware[] named: Record } /** @internal */ _observer?: { observe: (element: Element, callback: () => void) => () => void } /** @internal */ _payloadCache?: Record> | Record> /** @internal */ _appConfig: AppConfig /** @internal */ _route: RouteLocationNormalizedLoaded /** @internal */ _islandPromises?: Record> /** @internal */ _payloadRevivers: Record any> // Nuxt injections $config: RuntimeConfig isHydrating?: boolean deferHydration: () => () => void | Promise ssrContext?: NuxtSSRContext payload: { path?: string serverRendered?: boolean prerenderedAt?: number data: Record state: Record error?: Error | { url: string statusCode: number statusMessage: string message: string description: string data?: any } | null _errors: Record [key: string]: any } static: { data: Record } provide: (name: string, value: any) => void } export interface NuxtApp extends _NuxtApp {} export const NuxtPluginIndicator = '__nuxt_plugin' export interface PluginMeta { name?: string enforce?: 'pre' | 'default' | 'post' /** * 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 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 /** * Execute plugin in parallel with other parallel plugins. * * @default false */ parallel?: boolean } /** @deprecated Use `ObjectPlugin` */ export type ObjectPluginInput = Record> = ObjectPlugin export interface CreateOptions { vueApp: NuxtApp['vueApp'] ssrContext?: NuxtApp['ssrContext'] globalName?: NuxtApp['globalName'] } export function createNuxtApp (options: CreateOptions) { let hydratingCount = 0 const nuxtApp: NuxtApp = { provide: undefined, globalName: 'nuxt', versions: { get nuxt () { return __NUXT_VERSION__ }, get vue () { return nuxtApp.vueApp.version } }, payload: reactive({ data: {}, state: {}, _errors: {}, ...(process.client ? window.__NUXT__ ?? {} : { serverRendered: true }) }), static: { data: {} }, runWithContext: (fn: any) => callWithNuxt(nuxtApp, fn), isHydrating: process.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: {}, _payloadRevivers: {}, ...options } as any as NuxtApp nuxtApp.hooks = createHooks() nuxtApp.hook = nuxtApp.hooks.hook if (process.server) { async function contextCaller (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 (process.server) { if (nuxtApp.ssrContext) { // Expose nuxt to the renderContext nuxtApp.ssrContext.nuxt = nuxtApp // Expose payload types nuxtApp.ssrContext._payloadReducers = {} // Expose current path nuxtApp.payload.path = nuxtApp.ssrContext.event.path } // Expose to server renderer to create payload nuxtApp.ssrContext = nuxtApp.ssrContext || {} as any if (nuxtApp.ssrContext!.payload) { Object.assign(nuxtApp.payload, nuxtApp.ssrContext!.payload) } nuxtApp.ssrContext!.payload = nuxtApp.payload // Expose client runtime-config to the payload nuxtApp.ssrContext!.config = { public: options.ssrContext!.runtimeConfig.public, app: options.ssrContext!.runtimeConfig.app } } // Listen to chunk load errors if (process.client) { window.addEventListener('nuxt.preloadError', (event) => { nuxtApp.callHook('app:chunkError', { error: (event as Event & { payload: Error }).payload }) }) 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 = process.server ? options.ssrContext!.runtimeConfig : reactive(nuxtApp.payload.config) nuxtApp.provide('config', runtimeConfig) return nuxtApp } export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin & ObjectPlugin) { if (plugin.hooks) { nuxtApp.hooks.addHooks(plugin.hooks) } 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]) } } } } export async function applyPlugins (nuxtApp: NuxtApp, plugins: Array>) { const parallels: Promise[] = [] const errors: Error[] = [] for (const plugin of plugins) { const promise = applyPlugin(nuxtApp, plugin) if (plugin.parallel) { parallels.push(promise.catch(e => errors.push(e))) } else { await promise } } await Promise.all(parallels) if (errors.length) { throw errors[0] } } /*! @__NO_SIDE_EFFECTS__ */ export function defineNuxtPlugin> (plugin: Plugin | ObjectPlugin): Plugin & ObjectPlugin { if (typeof plugin === 'function') { return plugin } delete plugin.name return Object.assign(plugin.setup || (() => {}), plugin, { [NuxtPluginIndicator]: true } as const) } /*! @__NO_SIDE_EFFECTS__ */ export const definePayloadPlugin = defineNuxtPlugin 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 `useNuxt`. * * @param nuxt A Nuxt instance * @param setup The function to call */ export function callWithNuxt any> (nuxt: NuxtApp | _NuxtApp, setup: T, args?: Parameters) { const fn: () => ReturnType = () => args ? setup(...args as Parameters) : setup() if (process.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. */ export function useNuxtApp (): NuxtApp { let nuxtAppInstance if (hasInjectionContext()) { nuxtAppInstance = getCurrentInstance()?.appContext.app.$nuxt } nuxtAppInstance = nuxtAppInstance || nuxtAppCtx.tryUse() if (!nuxtAppInstance) { if (process.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#using-vue-and-nuxt-composables`.') } else { throw new Error('[nuxt] instance unavailable') } } return nuxtAppInstance } /*! @__NO_SIDE_EFFECTS__ */ export function useRuntimeConfig (): RuntimeConfig { return useNuxtApp().$config } function defineGetter (obj: Record, key: K, val: V) { Object.defineProperty(obj, key, { get: () => val }) } export function defineAppConfig (config: C): C { return config }