mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-26 15:42:09 +00:00
feat(app): defineNuxtPlugin
+ legacy plugin handling (#237)
Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
parent
36a3d285d8
commit
f8435681d4
@ -1,12 +1,10 @@
|
|||||||
import head from '#app/plugins/head'
|
import head from '#app/plugins/head'
|
||||||
import legacy from '#app/plugins/legacy'
|
|
||||||
import preload from '#app/plugins/preload.server'
|
import preload from '#app/plugins/preload.server'
|
||||||
|
|
||||||
<%= utils.importSources(app.plugins.map(p => p.src)) %>
|
<%= utils.importSources(app.plugins.map(p => p.src)) %>
|
||||||
|
|
||||||
const commonPlugins = [
|
const commonPlugins = [
|
||||||
head,
|
head,
|
||||||
legacy,
|
|
||||||
<%= app.plugins.filter(p => !p.mode || p.mode === 'all').map(p => utils.importName(p.src)).join(',\n ') %>
|
<%= app.plugins.filter(p => !p.mode || p.mode === 'all').map(p => utils.importName(p.src)).join(',\n ') %>
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { createSSRApp, createApp, nextTick } from 'vue'
|
import { createSSRApp, createApp, nextTick } from 'vue'
|
||||||
import { createNuxt, applyPlugins } from '@nuxt/app'
|
import { createNuxt, applyPlugins, normalizePlugins } from '@nuxt/app'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import plugins from '#build/plugins'
|
import _plugins from '#build/plugins'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import App from '#build/app'
|
import App from '#build/app'
|
||||||
|
|
||||||
let entry: Function
|
let entry: Function
|
||||||
|
|
||||||
|
const plugins = normalizePlugins(_plugins)
|
||||||
|
|
||||||
if (process.server) {
|
if (process.server) {
|
||||||
entry = async function createNuxtAppServer (ssrContext = {}) {
|
entry = async function createNuxtAppServer (ssrContext = {}) {
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
197
packages/app/src/legacy.ts
Normal file
197
packages/app/src/legacy.ts
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
import type { IncomingMessage, ServerResponse } from 'http'
|
||||||
|
import type { App } from 'vue'
|
||||||
|
import type { Component } from '@vue/runtime-core'
|
||||||
|
import mockContext from 'unenv/runtime/mock/proxy'
|
||||||
|
import type { Nuxt } from './nuxt'
|
||||||
|
|
||||||
|
type Route = any
|
||||||
|
type Store = any
|
||||||
|
|
||||||
|
export type LegacyApp = App<Element> & {
|
||||||
|
$root: LegacyApp
|
||||||
|
constructor: LegacyApp
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LegacyContext {
|
||||||
|
// -> $config
|
||||||
|
$config: Record<string, any>
|
||||||
|
env: Record<string, any>
|
||||||
|
// -> app
|
||||||
|
app: Component
|
||||||
|
// -> unsupported
|
||||||
|
isClient: boolean
|
||||||
|
isServer: boolean
|
||||||
|
isStatic: boolean
|
||||||
|
// TODO: needs app implementation
|
||||||
|
isDev: boolean
|
||||||
|
isHMR: boolean
|
||||||
|
// -> unsupported
|
||||||
|
store: Store
|
||||||
|
// vue-router integration
|
||||||
|
route: Route
|
||||||
|
params: Route['params']
|
||||||
|
query: Route['query']
|
||||||
|
base: string /** TODO: */
|
||||||
|
payload: any /** TODO: */
|
||||||
|
from: Route /** TODO: */
|
||||||
|
// -> nuxt.payload.data
|
||||||
|
nuxtState: Record<string, any>
|
||||||
|
// TODO: needs app implementation
|
||||||
|
beforeNuxtRender (fn: (params: { Components: any, nuxtState: Record<string, any> }) => void): void
|
||||||
|
beforeSerialize (fn: (nuxtState: Record<string, any>) => void): void
|
||||||
|
// TODO: needs app implementation
|
||||||
|
enablePreview?: (previewData?: Record<string, any>) => void
|
||||||
|
$preview?: Record<string, any>
|
||||||
|
// -> ssrContext.{req,res}
|
||||||
|
req: IncomingMessage
|
||||||
|
res: ServerResponse
|
||||||
|
/** TODO: */
|
||||||
|
next?: (err?: any) => any
|
||||||
|
error (params: any): void
|
||||||
|
redirect (status: number, path: string, query?: Route['query']): void
|
||||||
|
redirect (path: string, query?: Route['query']): void
|
||||||
|
redirect (location: Location): void
|
||||||
|
redirect (status: number, location: Location): void
|
||||||
|
ssrContext?: {
|
||||||
|
// -> ssrContext.{req,res,url,runtimeConfig}
|
||||||
|
req: LegacyContext['req']
|
||||||
|
res: LegacyContext['res']
|
||||||
|
url: string
|
||||||
|
runtimeConfig: {
|
||||||
|
public: Record<string, any>
|
||||||
|
private: Record<string, any>
|
||||||
|
}
|
||||||
|
// -> unsupported
|
||||||
|
target: string
|
||||||
|
spa?: boolean
|
||||||
|
modern: boolean
|
||||||
|
fetchCounters: Record<string, number>
|
||||||
|
// TODO:
|
||||||
|
next: LegacyContext['next']
|
||||||
|
redirected: boolean
|
||||||
|
beforeRenderFns: Array<() => any>
|
||||||
|
beforeSerializeFns: Array<() => any>
|
||||||
|
// -> nuxt.payload
|
||||||
|
nuxt: {
|
||||||
|
serverRendered: boolean
|
||||||
|
// TODO:
|
||||||
|
layout: string
|
||||||
|
data: Array<Record<string, any>>
|
||||||
|
fetch: Array<Record<string, any>>
|
||||||
|
error: any
|
||||||
|
state: Array<Record<string, any>>
|
||||||
|
routePath: string
|
||||||
|
config: Record<string, any>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mock (warning: string) {
|
||||||
|
console.warn(warning)
|
||||||
|
return mockContext
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsupported = new Set<keyof LegacyContext | keyof LegacyContext['ssrContext']>([
|
||||||
|
'isClient',
|
||||||
|
'isServer',
|
||||||
|
'isStatic',
|
||||||
|
'store',
|
||||||
|
'target',
|
||||||
|
'spa',
|
||||||
|
'env',
|
||||||
|
'modern',
|
||||||
|
'fetchCounters'
|
||||||
|
])
|
||||||
|
|
||||||
|
const todo = new Set<keyof LegacyContext | keyof LegacyContext['ssrContext']>([
|
||||||
|
'isDev',
|
||||||
|
'isHMR',
|
||||||
|
// Routing handlers - needs implementation or deprecation
|
||||||
|
'base',
|
||||||
|
'payload',
|
||||||
|
'from',
|
||||||
|
'next',
|
||||||
|
'error',
|
||||||
|
'redirect',
|
||||||
|
'redirected',
|
||||||
|
// needs app implementation
|
||||||
|
'enablePreview',
|
||||||
|
'$preview',
|
||||||
|
'beforeNuxtRender',
|
||||||
|
'beforeSerialize'
|
||||||
|
])
|
||||||
|
|
||||||
|
const routerKeys: Array<keyof LegacyContext | keyof LegacyContext['ssrContext']> = ['route', 'params', 'query']
|
||||||
|
|
||||||
|
export const legacyPlugin = (nuxt: Nuxt) => {
|
||||||
|
nuxt._legacyContext = new Proxy(nuxt, {
|
||||||
|
get (nuxt, p: keyof LegacyContext | keyof LegacyContext['ssrContext']) {
|
||||||
|
// Unsupported keys
|
||||||
|
if (unsupported.has(p)) {
|
||||||
|
return mock(`Accessing ${p} is not supported in Nuxt3.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (todo.has(p)) {
|
||||||
|
return mock(`Accessing ${p} is not yet supported in Nuxt3.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// vue-router implementation
|
||||||
|
if (routerKeys.includes(p)) {
|
||||||
|
if (!('$router' in nuxt)) {
|
||||||
|
return mock('vue-router is not being used in this project.')
|
||||||
|
}
|
||||||
|
switch (p) {
|
||||||
|
case 'route':
|
||||||
|
return nuxt.$router.currentRoute.value
|
||||||
|
case 'params':
|
||||||
|
case 'query':
|
||||||
|
return nuxt.$router.currentRoute.value[p]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === '$config') {
|
||||||
|
// TODO: needs implementation
|
||||||
|
return mock('Accessing runtime config is not yet supported in Nuxt3.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === 'ssrContext') {
|
||||||
|
return nuxt._legacyContext
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nuxt.ssrContext && p in nuxt.ssrContext) {
|
||||||
|
return nuxt.ssrContext[p]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === 'nuxt') {
|
||||||
|
return nuxt.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p === 'nuxtState') {
|
||||||
|
return nuxt.payload.data
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p in nuxt.app) {
|
||||||
|
return nuxt.app[p]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p in nuxt) {
|
||||||
|
return nuxt[p]
|
||||||
|
}
|
||||||
|
|
||||||
|
return mock(`Accessing ${p} is not supported in Nuxt3.`)
|
||||||
|
}
|
||||||
|
}) as unknown as LegacyContext
|
||||||
|
|
||||||
|
if (process.client) {
|
||||||
|
nuxt.hook('app:created', () => {
|
||||||
|
const legacyApp = { ...nuxt.app } as LegacyApp
|
||||||
|
legacyApp.$root = legacyApp
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
// TODO: https://github.com/nuxt/framework/issues/244
|
||||||
|
legacyApp.constructor = legacyApp
|
||||||
|
|
||||||
|
window[`$${nuxt.globalName}`] = legacyApp
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { App, getCurrentInstance } from 'vue'
|
import { App, getCurrentInstance } from 'vue'
|
||||||
import Hookable from 'hookable'
|
import Hookable from 'hookable'
|
||||||
import { defineGetter } from './utils'
|
import { defineGetter } from './utils'
|
||||||
|
import { legacyPlugin, LegacyContext } from './legacy'
|
||||||
|
|
||||||
export interface Nuxt {
|
export interface Nuxt {
|
||||||
app: App
|
app: App
|
||||||
@ -13,6 +14,7 @@ export interface Nuxt {
|
|||||||
[key: string]: any
|
[key: string]: any
|
||||||
|
|
||||||
_asyncDataPromises?: Record<string, Promise<any>>
|
_asyncDataPromises?: Record<string, Promise<any>>
|
||||||
|
_legacyContext?: LegacyContext
|
||||||
|
|
||||||
ssrContext?: Record<string, any>
|
ssrContext?: Record<string, any>
|
||||||
payload: {
|
payload: {
|
||||||
@ -25,8 +27,13 @@ export interface Nuxt {
|
|||||||
provide: (name: string, value: any) => void
|
provide: (name: string, value: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const NuxtPluginIndicator = '__nuxt_plugin'
|
||||||
export interface Plugin {
|
export interface Plugin {
|
||||||
(nuxt: Nuxt, provide?: Nuxt['provide']): Promise<void> | void
|
(nuxt: Nuxt): Promise<void> | void
|
||||||
|
[NuxtPluginIndicator]?: true
|
||||||
|
}
|
||||||
|
export interface LegacyPlugin {
|
||||||
|
(context: LegacyContext, provide: Nuxt['provide']): Promise<void> | void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateOptions {
|
export interface CreateOptions {
|
||||||
@ -66,7 +73,7 @@ export function createNuxt (options: CreateOptions) {
|
|||||||
|
|
||||||
if (process.server) {
|
if (process.server) {
|
||||||
nuxt.payload = {
|
nuxt.payload = {
|
||||||
serverRendered: true // TODO: legacy
|
serverRendered: true
|
||||||
}
|
}
|
||||||
|
|
||||||
nuxt.ssrContext = nuxt.ssrContext || {}
|
nuxt.ssrContext = nuxt.ssrContext || {}
|
||||||
@ -84,7 +91,7 @@ export function createNuxt (options: CreateOptions) {
|
|||||||
|
|
||||||
export function applyPlugin (nuxt: Nuxt, plugin: Plugin) {
|
export function applyPlugin (nuxt: Nuxt, plugin: Plugin) {
|
||||||
if (typeof plugin !== 'function') { return }
|
if (typeof plugin !== 'function') { return }
|
||||||
return callWithNuxt(nuxt, () => plugin(nuxt, nuxt.provide))
|
return callWithNuxt(nuxt, () => plugin(nuxt))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function applyPlugins (nuxt: Nuxt, plugins: Plugin[]) {
|
export async function applyPlugins (nuxt: Nuxt, plugins: Plugin[]) {
|
||||||
@ -93,6 +100,33 @@ export async function applyPlugins (nuxt: Nuxt, plugins: Plugin[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizePlugins (_plugins: Array<Plugin | LegacyPlugin>) {
|
||||||
|
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
|
let currentNuxtInstance: Nuxt | null
|
||||||
|
|
||||||
export const setNuxtInstance = (nuxt: Nuxt | null) => {
|
export const setNuxtInstance = (nuxt: Nuxt | null) => {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { createHead, renderHeadToString } from '@vueuse/head'
|
import { createHead, renderHeadToString } from '@vueuse/head'
|
||||||
import type { Plugin } from '@nuxt/app'
|
import { defineNuxtPlugin } from '@nuxt/app'
|
||||||
import { Head, Html, Body, Title, Meta, Link, Script, Style } from './head'
|
import { Head, Html, Body, Title, Meta, Link, Script, Style } from './head'
|
||||||
|
|
||||||
export default <Plugin> function head (nuxt) {
|
export default defineNuxtPlugin((nuxt) => {
|
||||||
const { app, ssrContext } = nuxt
|
const { app, ssrContext } = nuxt
|
||||||
const head = createHead()
|
const head = createHead()
|
||||||
|
|
||||||
@ -20,4 +20,4 @@ export default <Plugin> function head (nuxt) {
|
|||||||
if (process.server) {
|
if (process.server) {
|
||||||
ssrContext.head = () => renderHeadToString(head)
|
ssrContext.head = () => renderHeadToString(head)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import type { App } from 'vue'
|
|
||||||
import type { Plugin } from '@nuxt/app'
|
|
||||||
|
|
||||||
export type LegacyApp = App<Element> & {
|
|
||||||
$root: LegacyApp
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: plugins second argument (inject)
|
|
||||||
// TODO: payload.serverRrendered
|
|
||||||
|
|
||||||
export default <Plugin> function legacy ({ app }) {
|
|
||||||
app.$nuxt.context = {}
|
|
||||||
|
|
||||||
if (process.client) {
|
|
||||||
const legacyApp = { ...app } as LegacyApp
|
|
||||||
legacyApp.$root = legacyApp
|
|
||||||
window[app.$nuxt.globalName] = legacyApp
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.server) {
|
|
||||||
const { ssrContext } = app.$nuxt
|
|
||||||
app.$nuxt.context.req = ssrContext.req
|
|
||||||
app.$nuxt.context.res = ssrContext.res
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import type { Plugin } from '@nuxt/app'
|
import { defineNuxtPlugin } from '@nuxt/app'
|
||||||
|
|
||||||
export default <Plugin> function logs ({ app }) {
|
export default defineNuxtPlugin(({ app }) => {
|
||||||
// Only activate in development
|
// Only activate in development
|
||||||
const logs = app.$nuxt.payload.logs || []
|
const logs = app.$nuxt.payload.logs || []
|
||||||
if (logs.length > 0) {
|
if (logs.length > 0) {
|
||||||
@ -11,4 +11,4 @@ export default <Plugin> function logs ({ app }) {
|
|||||||
delete app.$nuxt.payload.logs
|
delete app.$nuxt.payload.logs
|
||||||
console.groupEnd && console.groupEnd()
|
console.groupEnd && console.groupEnd()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Plugin } from '@nuxt/app'
|
import { defineNuxtPlugin } from '@nuxt/app'
|
||||||
|
|
||||||
export default <Plugin> function preload ({ app }) {
|
export default defineNuxtPlugin(({ app }) => {
|
||||||
app.mixin({
|
app.mixin({
|
||||||
beforeCreate () {
|
beforeCreate () {
|
||||||
const { _registeredComponents } = this.$nuxt.ssrContext
|
const { _registeredComponents } = this.$nuxt.ssrContext
|
||||||
@ -8,4 +8,4 @@ export default <Plugin> function preload ({ app }) {
|
|||||||
_registeredComponents.add(__moduleIdentifier)
|
_registeredComponents.add(__moduleIdentifier)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Plugin } from '@nuxt/app'
|
import { defineNuxtPlugin } from '@nuxt/app'
|
||||||
|
|
||||||
export default <Plugin> function progressbar ({ app }) {
|
export default defineNuxtPlugin(({ app }) => {
|
||||||
const { $nuxt } = app
|
const { $nuxt } = app
|
||||||
$nuxt.hook('app:mounted', () => {
|
$nuxt.hook('app:mounted', () => {
|
||||||
const el = document.createElement('div')
|
const el = document.createElement('div')
|
||||||
@ -41,4 +41,4 @@ export default <Plugin> function progressbar ({ app }) {
|
|||||||
}, 500)
|
}, 500)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { createVuex, defineStore, useStore } from 'vuex5/dist/vuex.esm'
|
import { createVuex, defineStore, useStore } from 'vuex5/dist/vuex.esm'
|
||||||
import type { Plugin } from '@nuxt/app'
|
|
||||||
import { useHydration } from '../composables'
|
import { useHydration } from '../composables'
|
||||||
|
import { defineNuxtPlugin } from '../nuxt'
|
||||||
|
|
||||||
export default <Plugin> function ({ app }) {
|
export default defineNuxtPlugin(({ app }) => {
|
||||||
const vuex = createVuex({ })
|
const vuex = createVuex({ })
|
||||||
|
|
||||||
app.use(vuex)
|
app.use(vuex)
|
||||||
@ -15,7 +15,7 @@ export default <Plugin> function ({ app }) {
|
|||||||
// vuex.replaceStateTree(state)
|
// vuex.replaceStateTree(state)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
export function createStore (arg1: any, arg2?: any) {
|
export function createStore (arg1: any, arg2?: any) {
|
||||||
const store = defineStore(arg1, arg2)
|
const store = defineStore(arg1, arg2)
|
||||||
|
@ -6,12 +6,12 @@ import {
|
|||||||
RouterLink
|
RouterLink
|
||||||
} from 'vue-router'
|
} from 'vue-router'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import type { Plugin } from '@nuxt/app'
|
import { defineNuxtPlugin } from '@nuxt/app'
|
||||||
import NuxtPage from './page.vue'
|
import NuxtPage from './page.vue'
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import routes from '#build/routes'
|
import routes from '#build/routes'
|
||||||
|
|
||||||
export default <Plugin> function router (nuxt) {
|
export default defineNuxtPlugin((nuxt) => {
|
||||||
const { app } = nuxt
|
const { app } = nuxt
|
||||||
|
|
||||||
app.component('NuxtPage', NuxtPage)
|
app.component('NuxtPage', NuxtPage)
|
||||||
@ -50,4 +50,4 @@ export default <Plugin> function router (nuxt) {
|
|||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user