2021-10-06 12:37:45 +00:00
|
|
|
/* eslint-disable no-use-before-define */
|
2021-11-02 11:27:25 +00:00
|
|
|
import { getCurrentInstance, reactive } from 'vue'
|
2022-03-11 08:22:16 +00:00
|
|
|
import type { App, onErrorCaptured, VNode } from 'vue'
|
2021-08-27 12:51:40 +00:00
|
|
|
import { createHooks, Hookable } from 'hookable'
|
2021-11-21 16:14:46 +00:00
|
|
|
import type { RuntimeConfig } from '@nuxt/schema'
|
2022-04-01 09:55:23 +00:00
|
|
|
import { getContext } from 'unctx'
|
2022-06-08 19:37:50 +00:00
|
|
|
import type { SSRContext } from 'vue-bundle-renderer'
|
|
|
|
import type { CompatibilityEvent } from 'h3'
|
2021-11-15 10:25:50 +00:00
|
|
|
import { legacyPlugin, LegacyContext } from './compat/legacy-app'
|
2021-01-18 12:46:19 +00:00
|
|
|
|
2022-04-01 09:55:23 +00:00
|
|
|
const nuxtAppCtx = getContext<NuxtApp>('nuxt-app')
|
|
|
|
|
2021-07-15 11:28:04 +00:00
|
|
|
type NuxtMeta = {
|
|
|
|
htmlAttrs?: string
|
|
|
|
headAttrs?: string
|
|
|
|
bodyAttrs?: string
|
|
|
|
headTags?: string
|
2022-02-16 17:56:30 +00:00
|
|
|
bodyScriptsPrepend?: string
|
2021-07-15 11:28:04 +00:00
|
|
|
bodyScripts?: string
|
|
|
|
}
|
|
|
|
|
2021-08-26 18:57:36 +00:00
|
|
|
type HookResult = Promise<void> | void
|
|
|
|
export interface RuntimeNuxtHooks {
|
|
|
|
'app:created': (app: App<Element>) => HookResult
|
|
|
|
'app:beforeMount': (app: App<Element>) => HookResult
|
|
|
|
'app:mounted': (app: App<Element>) => HookResult
|
|
|
|
'app:rendered': () => HookResult
|
2022-04-19 19:13:11 +00:00
|
|
|
'app:redirected': () => HookResult
|
2022-01-26 17:24:54 +00:00
|
|
|
'app:suspense:resolve': (Component?: VNode) => HookResult
|
2022-03-11 08:22:16 +00:00
|
|
|
'app:error': (err: any) => HookResult
|
|
|
|
'app:error:cleared': (options: { redirect?: string }) => HookResult
|
2022-03-28 17:12:41 +00:00
|
|
|
'app:data:refresh': (keys?: string[]) => HookResult
|
2021-08-26 18:57:36 +00:00
|
|
|
'page:start': (Component?: VNode) => HookResult
|
|
|
|
'page:finish': (Component?: VNode) => HookResult
|
2021-10-06 12:37:45 +00:00
|
|
|
'meta:register': (metaRenderers: Array<(nuxt: NuxtApp) => NuxtMeta | Promise<NuxtMeta>>) => HookResult
|
2021-12-21 14:44:35 +00:00
|
|
|
'vue:setup': () => void
|
2022-03-11 08:22:16 +00:00
|
|
|
'vue:error': (...args: Parameters<Parameters<typeof onErrorCaptured>[0]>) => HookResult
|
2021-08-26 18:57:36 +00:00
|
|
|
}
|
|
|
|
|
2021-11-18 13:11:34 +00:00
|
|
|
interface _NuxtApp {
|
2021-10-18 18:31:37 +00:00
|
|
|
vueApp: App<Element>
|
2021-01-18 12:46:19 +00:00
|
|
|
globalName: string
|
|
|
|
|
2021-08-27 12:51:40 +00:00
|
|
|
hooks: Hookable<RuntimeNuxtHooks>
|
2021-11-18 13:11:34 +00:00
|
|
|
hook: _NuxtApp['hooks']['hook']
|
|
|
|
callHook: _NuxtApp['hooks']['callHook']
|
2021-01-18 12:46:19 +00:00
|
|
|
|
|
|
|
[key: string]: any
|
|
|
|
|
2021-04-03 10:03:20 +00:00
|
|
|
_asyncDataPromises?: Record<string, Promise<any>>
|
2021-06-18 17:16:51 +00:00
|
|
|
_legacyContext?: LegacyContext
|
2021-04-03 10:03:20 +00:00
|
|
|
|
2022-06-08 19:37:50 +00:00
|
|
|
ssrContext?: SSRContext & {
|
|
|
|
url: string
|
|
|
|
event: CompatibilityEvent
|
|
|
|
/** @deprecated Use `event` instead. */
|
|
|
|
req?: CompatibilityEvent['req']
|
|
|
|
/** @deprecated Use `event` instead. */
|
|
|
|
res?: CompatibilityEvent['res']
|
|
|
|
runtimeConfig: RuntimeConfig
|
|
|
|
noSSR: boolean
|
|
|
|
error?: any
|
|
|
|
nuxt: _NuxtApp
|
|
|
|
payload: _NuxtApp['payload']
|
|
|
|
teleports?: Record<string, string>
|
2021-07-19 13:27:20 +00:00
|
|
|
renderMeta?: () => Promise<NuxtMeta> | NuxtMeta
|
2021-07-15 11:28:04 +00:00
|
|
|
}
|
2021-01-18 12:46:19 +00:00
|
|
|
payload: {
|
2022-06-08 19:37:50 +00:00
|
|
|
serverRendered?: boolean
|
2021-03-17 09:17:18 +00:00
|
|
|
data?: Record<string, any>
|
2021-10-11 17:48:03 +00:00
|
|
|
state?: Record<string, any>
|
2021-01-18 12:46:19 +00:00
|
|
|
rendered?: Function
|
|
|
|
[key: string]: any
|
|
|
|
}
|
|
|
|
|
|
|
|
provide: (name: string, value: any) => void
|
|
|
|
}
|
|
|
|
|
2021-11-18 13:11:34 +00:00
|
|
|
export interface NuxtApp extends _NuxtApp { }
|
|
|
|
|
2021-06-18 17:16:51 +00:00
|
|
|
export const NuxtPluginIndicator = '__nuxt_plugin'
|
2021-11-18 13:11:34 +00:00
|
|
|
export interface Plugin<Injections extends Record<string, any> = Record<string, any>> {
|
|
|
|
(nuxt: _NuxtApp): Promise<void> | Promise<{ provide?: Injections }> | void | { provide?: Injections }
|
2021-06-18 17:16:51 +00:00
|
|
|
[NuxtPluginIndicator]?: true
|
|
|
|
}
|
|
|
|
export interface LegacyPlugin {
|
2021-08-27 13:30:53 +00:00
|
|
|
(context: LegacyContext, provide: NuxtApp['provide']): Promise<void> | void
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface CreateOptions {
|
2021-10-18 18:31:37 +00:00
|
|
|
vueApp: NuxtApp['vueApp']
|
2021-08-27 13:30:53 +00:00
|
|
|
ssrContext?: NuxtApp['ssrContext']
|
|
|
|
globalName?: NuxtApp['globalName']
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
|
2021-10-02 18:40:10 +00:00
|
|
|
export function createNuxtApp (options: CreateOptions) {
|
2021-10-18 18:31:37 +00:00
|
|
|
const nuxtApp: NuxtApp = {
|
2021-01-18 12:46:19 +00:00
|
|
|
provide: undefined,
|
|
|
|
globalName: 'nuxt',
|
2021-10-11 17:48:03 +00:00
|
|
|
payload: reactive({
|
|
|
|
data: {},
|
|
|
|
state: {},
|
2021-11-24 19:59:04 +00:00
|
|
|
_errors: {},
|
2021-10-11 17:48:03 +00:00
|
|
|
...(process.client ? window.__NUXT__ : { serverRendered: true })
|
|
|
|
}),
|
2021-01-18 12:46:19 +00:00
|
|
|
isHydrating: process.client,
|
2021-10-08 14:21:55 +00:00
|
|
|
_asyncDataPromises: {},
|
2021-01-18 12:46:19 +00:00
|
|
|
...options
|
2021-08-27 13:30:53 +00:00
|
|
|
} as any as NuxtApp
|
2021-01-18 12:46:19 +00:00
|
|
|
|
2021-10-18 18:31:37 +00:00
|
|
|
nuxtApp.hooks = createHooks<RuntimeNuxtHooks>()
|
|
|
|
nuxtApp.hook = nuxtApp.hooks.hook
|
|
|
|
nuxtApp.callHook = nuxtApp.hooks.callHook
|
2021-01-18 12:46:19 +00:00
|
|
|
|
2021-10-18 18:31:37 +00:00
|
|
|
nuxtApp.provide = (name: string, value: any) => {
|
2021-01-18 12:46:19 +00:00
|
|
|
const $name = '$' + name
|
2021-10-18 18:31:37 +00:00
|
|
|
defineGetter(nuxtApp, $name, value)
|
|
|
|
defineGetter(nuxtApp.vueApp.config.globalProperties, $name, value)
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
|
2021-02-03 18:14:30 +00:00
|
|
|
// Inject $nuxt
|
2021-10-18 18:31:37 +00:00
|
|
|
defineGetter(nuxtApp.vueApp, '$nuxt', nuxtApp)
|
|
|
|
defineGetter(nuxtApp.vueApp.config.globalProperties, '$nuxt', nuxtApp)
|
2021-01-18 12:46:19 +00:00
|
|
|
|
|
|
|
// Expose nuxt to the renderContext
|
2021-10-18 18:31:37 +00:00
|
|
|
if (nuxtApp.ssrContext) {
|
|
|
|
nuxtApp.ssrContext.nuxt = nuxtApp
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (process.server) {
|
|
|
|
// Expose to server renderer to create window.__NUXT__
|
2022-06-08 19:37:50 +00:00
|
|
|
nuxtApp.ssrContext = nuxtApp.ssrContext || {} as any
|
2021-10-18 18:31:37 +00:00
|
|
|
nuxtApp.ssrContext.payload = nuxtApp.payload
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
|
2022-04-12 14:59:48 +00:00
|
|
|
// Expose client runtime-config to the payload
|
2021-10-02 20:30:20 +00:00
|
|
|
if (process.server) {
|
2022-04-11 19:55:43 +00:00
|
|
|
nuxtApp.payload.config = {
|
|
|
|
public: options.ssrContext.runtimeConfig.public,
|
|
|
|
app: options.ssrContext.runtimeConfig.app
|
|
|
|
}
|
2021-10-02 20:30:20 +00:00
|
|
|
}
|
|
|
|
|
2022-04-12 14:59:48 +00:00
|
|
|
// Expose runtime config
|
|
|
|
const runtimeConfig = process.server
|
|
|
|
? options.ssrContext.runtimeConfig
|
|
|
|
: reactive(nuxtApp.payload.config)
|
|
|
|
|
|
|
|
// Backward compatibilty following #4254
|
|
|
|
const compatibilityConfig = new Proxy(runtimeConfig, {
|
|
|
|
get (target, prop) {
|
|
|
|
if (prop === 'public') {
|
|
|
|
return target.public
|
|
|
|
}
|
|
|
|
return target[prop] ?? target.public[prop]
|
|
|
|
},
|
|
|
|
set (target, prop, value) {
|
|
|
|
if (process.server || prop === 'public' || prop === 'app') {
|
|
|
|
return false // Throws TypeError
|
|
|
|
}
|
|
|
|
target[prop] = value
|
|
|
|
target.public[prop] = value
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
nuxtApp.provide('config', compatibilityConfig)
|
|
|
|
|
2021-10-18 18:31:37 +00:00
|
|
|
return nuxtApp
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
|
2021-11-18 13:11:34 +00:00
|
|
|
export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin) {
|
2021-02-19 01:08:45 +00:00
|
|
|
if (typeof plugin !== 'function') { return }
|
2022-01-25 12:29:11 +00:00
|
|
|
const { provide } = await callWithNuxt(nuxtApp, plugin, [nuxtApp]) || {}
|
2021-11-18 13:11:34 +00:00
|
|
|
if (provide && typeof provide === 'object') {
|
|
|
|
for (const key in provide) {
|
|
|
|
nuxtApp.provide(key, provide[key])
|
|
|
|
}
|
|
|
|
}
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
|
2021-10-18 18:31:37 +00:00
|
|
|
export async function applyPlugins (nuxtApp: NuxtApp, plugins: Plugin[]) {
|
2021-01-18 12:46:19 +00:00
|
|
|
for (const plugin of plugins) {
|
2021-10-18 18:31:37 +00:00
|
|
|
await applyPlugin(nuxtApp, plugin)
|
2021-01-18 12:46:19 +00:00
|
|
|
}
|
|
|
|
}
|
2021-04-09 13:48:39 +00:00
|
|
|
|
2021-06-18 17:16:51 +00:00
|
|
|
export function normalizePlugins (_plugins: Array<Plugin | LegacyPlugin>) {
|
|
|
|
let needsLegacyContext = false
|
|
|
|
|
|
|
|
const plugins = _plugins.map((plugin) => {
|
2021-11-02 09:49:45 +00:00
|
|
|
if (typeof plugin !== 'function') {
|
|
|
|
return () => {}
|
|
|
|
}
|
2021-06-18 17:16:51 +00:00
|
|
|
if (isLegacyPlugin(plugin)) {
|
|
|
|
needsLegacyContext = true
|
2021-10-18 18:31:37 +00:00
|
|
|
return (nuxtApp: NuxtApp) => plugin(nuxtApp._legacyContext!, nuxtApp.provide)
|
2021-06-18 17:16:51 +00:00
|
|
|
}
|
|
|
|
return plugin
|
|
|
|
})
|
|
|
|
|
|
|
|
if (needsLegacyContext) {
|
|
|
|
plugins.unshift(legacyPlugin)
|
|
|
|
}
|
|
|
|
|
|
|
|
return plugins as Plugin[]
|
|
|
|
}
|
|
|
|
|
2021-11-18 13:11:34 +00:00
|
|
|
export function defineNuxtPlugin<T> (plugin: Plugin<T>) {
|
2021-06-18 17:16:51 +00:00
|
|
|
plugin[NuxtPluginIndicator] = true
|
|
|
|
return plugin
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isLegacyPlugin (plugin: unknown): plugin is LegacyPlugin {
|
|
|
|
return !plugin[NuxtPluginIndicator]
|
|
|
|
}
|
|
|
|
|
2021-04-09 13:48:39 +00:00
|
|
|
/**
|
|
|
|
* Ensures that the setup function passed in has access to the Nuxt instance via `useNuxt`.
|
2021-04-15 18:49:29 +00:00
|
|
|
*
|
2021-04-09 13:48:39 +00:00
|
|
|
* @param nuxt A Nuxt instance
|
|
|
|
* @param setup The function to call
|
|
|
|
*/
|
2022-03-16 22:44:22 +00:00
|
|
|
export function callWithNuxt<T extends (...args: any[]) => any> (nuxt: NuxtApp | _NuxtApp, setup: T, args?: Parameters<T>) {
|
2022-04-01 09:55:23 +00:00
|
|
|
const fn = () => args ? setup(...args as Parameters<T>) : setup()
|
2021-11-23 17:50:20 +00:00
|
|
|
if (process.server) {
|
2022-04-01 09:55:23 +00:00
|
|
|
return nuxtAppCtx.callAsync<ReturnType<T>>(nuxt, fn)
|
|
|
|
} else {
|
|
|
|
// In client side we could assume nuxt app is singleton
|
|
|
|
nuxtAppCtx.set(nuxt)
|
|
|
|
return fn()
|
2021-11-23 17:50:20 +00:00
|
|
|
}
|
2021-04-09 13:48:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the current Nuxt instance.
|
|
|
|
*/
|
2022-02-15 09:50:11 +00:00
|
|
|
export function useNuxtApp () {
|
2022-06-15 12:00:34 +00:00
|
|
|
const nuxtAppInstance = nuxtAppCtx.use()
|
2021-04-09 13:48:39 +00:00
|
|
|
|
2022-06-15 12:00:34 +00:00
|
|
|
if (!nuxtAppInstance) {
|
|
|
|
const vm = getCurrentInstance()
|
|
|
|
if (!vm) {
|
2021-04-09 13:48:39 +00:00
|
|
|
throw new Error('nuxt instance unavailable')
|
|
|
|
}
|
2022-06-15 12:00:34 +00:00
|
|
|
return vm.appContext.app.$nuxt as NuxtApp
|
2021-04-09 13:48:39 +00:00
|
|
|
}
|
|
|
|
|
2022-06-15 12:00:34 +00:00
|
|
|
return nuxtAppInstance
|
2021-04-09 13:48:39 +00:00
|
|
|
}
|
2021-10-02 20:30:20 +00:00
|
|
|
|
2021-11-10 12:40:02 +00:00
|
|
|
export function useRuntimeConfig (): RuntimeConfig {
|
2021-10-02 20:30:20 +00:00
|
|
|
return useNuxtApp().$config
|
|
|
|
}
|
2021-10-18 18:31:37 +00:00
|
|
|
|
|
|
|
function defineGetter<K extends string | number | symbol, V> (obj: Record<K, V>, key: K, val: V) {
|
|
|
|
Object.defineProperty(obj, key, { get: () => val })
|
|
|
|
}
|