import { getCurrentInstance } from 'vue' import type { App, VNode } from 'vue' import Hookable from 'hookable' import { defineGetter } from './utils' import { legacyPlugin, LegacyContext } from './legacy' type NuxtMeta = { htmlAttrs?: string headAttrs?: string bodyAttrs?: string headTags?: string bodyPrepend?: string bodyScripts?: string } type HookResult = Promise | void export interface RuntimeNuxtHooks { 'app:created': (app: App) => HookResult 'app:beforeMount': (app: App) => HookResult 'app:mounted': (app: App) => HookResult 'app:rendered': () => HookResult 'page:start': (Component?: VNode) => HookResult 'page:finish': (Component?: VNode) => HookResult } type NuxtAppHookName = keyof RuntimeNuxtHooks export interface Nuxt { app: App globalName: string hooks: { /** Register a function to be run when the named Nuxt hook is called. */ hook (hookName: Hook, callback: RuntimeNuxtHooks[Hook]): HookResult hookOnce (hookName: Hook, callback: RuntimeNuxtHooks[Hook]): HookResult /** Run all Nuxt hooks that have been registered against the hook name. */ callHook (hookName: Hook, ...args: Parameters): ReturnType /** Add all hooks in the object passed in. */ addHooks (hooks: Partial): void } hook: Nuxt['hooks']['hook'] callHook: Nuxt['hooks']['callHook'] [key: string]: any _asyncDataPromises?: Record> _legacyContext?: LegacyContext ssrContext?: Record & { renderMeta?: () => Promise | NuxtMeta } payload: { serverRendered?: true data?: Record rendered?: Function [key: string]: any } provide: (name: string, value: any) => void } export const NuxtPluginIndicator = '__nuxt_plugin' export interface Plugin { (nuxt: Nuxt): Promise | void [NuxtPluginIndicator]?: true } export interface LegacyPlugin { (context: LegacyContext, provide: Nuxt['provide']): Promise | void } export interface CreateOptions { app: Nuxt['app'] ssrContext?: Nuxt['ssrContext'] globalName?: Nuxt['globalName'] } export function createNuxt (options: CreateOptions) { const nuxt: Nuxt = { provide: undefined, globalName: 'nuxt', state: {}, payload: {}, isHydrating: process.client, ...options } as any as Nuxt nuxt.hooks = new Hookable() nuxt.hook = nuxt.hooks.hook nuxt.callHook = nuxt.hooks.callHook nuxt.provide = (name: string, value: any) => { const $name = '$' + name defineGetter(nuxt, $name, value) defineGetter(nuxt.app.config.globalProperties, $name, value) } // Inject $nuxt defineGetter(nuxt.app, '$nuxt', nuxt) defineGetter(nuxt.app.config.globalProperties, '$nuxt', nuxt) // Expose nuxt to the renderContext if (nuxt.ssrContext) { nuxt.ssrContext.nuxt = nuxt } if (process.server) { nuxt.payload = { serverRendered: true } nuxt.ssrContext = nuxt.ssrContext || {} // Expose to server renderer to create window.__NUXT__ nuxt.ssrContext.payload = nuxt.payload } if (process.client) { nuxt.payload = window.__NUXT__ || {} } return nuxt } export function applyPlugin (nuxt: Nuxt, plugin: Plugin) { if (typeof plugin !== 'function') { return } return callWithNuxt(nuxt, () => plugin(nuxt)) } export async function applyPlugins (nuxt: Nuxt, plugins: Plugin[]) { for (const plugin of plugins) { await applyPlugin(nuxt, plugin) } } export function normalizePlugins (_plugins: Array) { let needsLegacyContext = false const plugins = _plugins.map((plugin) => { if (isLegacyPlugin(plugin)) { needsLegacyContext = true return (nuxt: Nuxt) => plugin(nuxt._legacyContext!, nuxt.provide) } return plugin }) if (needsLegacyContext) { plugins.unshift(legacyPlugin) } return plugins as Plugin[] } export function defineNuxtPlugin (plugin: Plugin) { plugin[NuxtPluginIndicator] = true return plugin } export function isLegacyPlugin (plugin: unknown): plugin is LegacyPlugin { return !plugin[NuxtPluginIndicator] } let currentNuxtInstance: Nuxt | null export const setNuxtInstance = (nuxt: Nuxt | null) => { currentNuxtInstance = nuxt } /** * 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 async function callWithNuxt (nuxt: Nuxt, setup: () => any) { setNuxtInstance(nuxt) const p = setup() setNuxtInstance(null) await p } /** * Returns the current Nuxt instance. */ export function useNuxt (): Nuxt { const vm = getCurrentInstance() if (!vm) { if (!currentNuxtInstance) { throw new Error('nuxt instance unavailable') } return currentNuxtInstance } return vm.appContext.app.$nuxt }