mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 15:15:19 +00:00
feat: support async plugins and middlewares (#3884)
This commit is contained in:
parent
9a2fc45a11
commit
4c77c88325
@ -59,6 +59,7 @@
|
|||||||
"perfect-debounce": "^0.1.3",
|
"perfect-debounce": "^0.1.3",
|
||||||
"scule": "^0.2.1",
|
"scule": "^0.2.1",
|
||||||
"ufo": "^0.8.3",
|
"ufo": "^0.8.3",
|
||||||
|
"unctx": "^1.1.4",
|
||||||
"unimport": "^0.1.3",
|
"unimport": "^0.1.3",
|
||||||
"unplugin": "^0.6.1",
|
"unplugin": "^0.6.1",
|
||||||
"untyped": "^0.4.4",
|
"untyped": "^0.4.4",
|
||||||
|
@ -3,8 +3,11 @@ import { getCurrentInstance, reactive } from 'vue'
|
|||||||
import type { App, onErrorCaptured, VNode } from 'vue'
|
import type { App, onErrorCaptured, VNode } from 'vue'
|
||||||
import { createHooks, Hookable } from 'hookable'
|
import { createHooks, Hookable } from 'hookable'
|
||||||
import type { RuntimeConfig } from '@nuxt/schema'
|
import type { RuntimeConfig } from '@nuxt/schema'
|
||||||
|
import { getContext } from 'unctx'
|
||||||
import { legacyPlugin, LegacyContext } from './compat/legacy-app'
|
import { legacyPlugin, LegacyContext } from './compat/legacy-app'
|
||||||
|
|
||||||
|
const nuxtAppCtx = getContext<NuxtApp>('nuxt-app')
|
||||||
|
|
||||||
type NuxtMeta = {
|
type NuxtMeta = {
|
||||||
htmlAttrs?: string
|
htmlAttrs?: string
|
||||||
headAttrs?: string
|
headAttrs?: string
|
||||||
@ -172,12 +175,6 @@ export function isLegacyPlugin (plugin: unknown): plugin is LegacyPlugin {
|
|||||||
return !plugin[NuxtPluginIndicator]
|
return !plugin[NuxtPluginIndicator]
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentNuxtAppInstance: NuxtApp | null
|
|
||||||
|
|
||||||
export const setNuxtAppInstance = (nuxt: NuxtApp | null) => {
|
|
||||||
currentNuxtAppInstance = nuxt
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures that the setup function passed in has access to the Nuxt instance via `useNuxt`.
|
* Ensures that the setup function passed in has access to the Nuxt instance via `useNuxt`.
|
||||||
*
|
*
|
||||||
@ -185,13 +182,14 @@ export const setNuxtAppInstance = (nuxt: NuxtApp | null) => {
|
|||||||
* @param setup The function to call
|
* @param setup The function to call
|
||||||
*/
|
*/
|
||||||
export function callWithNuxt<T extends (...args: any[]) => any> (nuxt: NuxtApp | _NuxtApp, setup: T, args?: Parameters<T>) {
|
export function callWithNuxt<T extends (...args: any[]) => any> (nuxt: NuxtApp | _NuxtApp, setup: T, args?: Parameters<T>) {
|
||||||
setNuxtAppInstance(nuxt as NuxtApp)
|
const fn = () => args ? setup(...args as Parameters<T>) : setup()
|
||||||
const p: ReturnType<T> = args ? setup(...args as Parameters<T>) : setup()
|
|
||||||
if (process.server) {
|
if (process.server) {
|
||||||
// Unset nuxt instance to prevent context-sharing in server-side
|
return nuxtAppCtx.callAsync<ReturnType<T>>(nuxt, fn)
|
||||||
setNuxtAppInstance(null)
|
} else {
|
||||||
|
// In client side we could assume nuxt app is singleton
|
||||||
|
nuxtAppCtx.set(nuxt)
|
||||||
|
return fn()
|
||||||
}
|
}
|
||||||
return p
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -201,10 +199,11 @@ export function useNuxtApp () {
|
|||||||
const vm = getCurrentInstance()
|
const vm = getCurrentInstance()
|
||||||
|
|
||||||
if (!vm) {
|
if (!vm) {
|
||||||
if (!currentNuxtAppInstance) {
|
const nuxtAppInstance = nuxtAppCtx.use()
|
||||||
|
if (!nuxtAppInstance) {
|
||||||
throw new Error('nuxt instance unavailable')
|
throw new Error('nuxt instance unavailable')
|
||||||
}
|
}
|
||||||
return currentNuxtAppInstance
|
return nuxtAppInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
return vm.appContext.app.$nuxt as NuxtApp
|
return vm.appContext.app.$nuxt as NuxtApp
|
||||||
|
@ -12,6 +12,7 @@ import autoImportsModule from '../auto-imports/module'
|
|||||||
import { distDir, pkgDir } from '../dirs'
|
import { distDir, pkgDir } from '../dirs'
|
||||||
import { version } from '../../package.json'
|
import { version } from '../../package.json'
|
||||||
import { ImportProtectionPlugin, vueAppPatterns } from './plugins/import-protection'
|
import { ImportProtectionPlugin, vueAppPatterns } from './plugins/import-protection'
|
||||||
|
import { UnctxTransformPlugin } from './plugins/unctx'
|
||||||
import { addModuleTranspiles } from './modules'
|
import { addModuleTranspiles } from './modules'
|
||||||
|
|
||||||
export function createNuxt (options: NuxtOptions): Nuxt {
|
export function createNuxt (options: NuxtOptions): Nuxt {
|
||||||
@ -57,7 +58,6 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Add import protection
|
// Add import protection
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
rootDir: nuxt.options.rootDir,
|
rootDir: nuxt.options.rootDir,
|
||||||
patterns: vueAppPatterns(nuxt)
|
patterns: vueAppPatterns(nuxt)
|
||||||
@ -65,6 +65,10 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
addVitePlugin(ImportProtectionPlugin.vite(config))
|
addVitePlugin(ImportProtectionPlugin.vite(config))
|
||||||
addWebpackPlugin(ImportProtectionPlugin.webpack(config))
|
addWebpackPlugin(ImportProtectionPlugin.webpack(config))
|
||||||
|
|
||||||
|
// Add unctx transform
|
||||||
|
addVitePlugin(UnctxTransformPlugin(nuxt).vite())
|
||||||
|
addWebpackPlugin(UnctxTransformPlugin(nuxt).webpack())
|
||||||
|
|
||||||
// Init user modules
|
// Init user modules
|
||||||
await nuxt.callHook('modules:before', { nuxt } as ModuleContainer)
|
await nuxt.callHook('modules:before', { nuxt } as ModuleContainer)
|
||||||
const modulesToInstall = [
|
const modulesToInstall = [
|
||||||
|
31
packages/nuxt3/src/core/plugins/unctx.ts
Normal file
31
packages/nuxt3/src/core/plugins/unctx.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Nuxt, NuxtApp, NuxtMiddleware } from '@nuxt/schema'
|
||||||
|
import { createTransformer } from 'unctx/transform'
|
||||||
|
import { createUnplugin } from 'unplugin'
|
||||||
|
|
||||||
|
export const UnctxTransformPlugin = (nuxt: Nuxt) => {
|
||||||
|
const transformer = createTransformer({
|
||||||
|
asyncFunctions: ['defineNuxtPlugin', 'defineNuxtRouteMiddleware']
|
||||||
|
})
|
||||||
|
|
||||||
|
let app: NuxtApp | undefined
|
||||||
|
let middleware: NuxtMiddleware[] = []
|
||||||
|
nuxt.hook('app:resolve', (_app) => { app = _app })
|
||||||
|
nuxt.hook('pages:middleware:extend', (_middlewares) => { middleware = _middlewares })
|
||||||
|
|
||||||
|
return createUnplugin(() => ({
|
||||||
|
name: 'unctx:transfrom',
|
||||||
|
enforce: 'post',
|
||||||
|
transformInclude (id) {
|
||||||
|
return Boolean(app?.plugins.find(i => i.src === id) || middleware.find(m => m.path === id))
|
||||||
|
},
|
||||||
|
transform (code, id) {
|
||||||
|
const result = transformer.transform(code)
|
||||||
|
if (result) {
|
||||||
|
return {
|
||||||
|
code: result.code,
|
||||||
|
map: result.magicString.generateMap({ source: id, includeContent: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
@ -40,8 +40,6 @@ describe('pages', () => {
|
|||||||
// composables auto import
|
// composables auto import
|
||||||
expect(html).toContain('Composable | foo: auto imported from ~/components/foo.ts')
|
expect(html).toContain('Composable | foo: auto imported from ~/components/foo.ts')
|
||||||
expect(html).toContain('Composable | bar: auto imported from ~/components/useBar.ts')
|
expect(html).toContain('Composable | bar: auto imported from ~/components/useBar.ts')
|
||||||
// plugins
|
|
||||||
expect(html).toContain('Plugin | myPlugin: Injected by my-plugin')
|
|
||||||
// should import components
|
// should import components
|
||||||
expect(html).toContain('This is a custom component with a named export.')
|
expect(html).toContain('This is a custom component with a named export.')
|
||||||
})
|
})
|
||||||
@ -146,6 +144,18 @@ describe('middlewares', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('plugins', () => {
|
||||||
|
it('basic plugin', async () => {
|
||||||
|
const html = await $fetch('/plugins')
|
||||||
|
expect(html).toContain('myPlugin: Injected by my-plugin')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('async plugin', async () => {
|
||||||
|
const html = await $fetch('/plugins')
|
||||||
|
expect(html).toContain('asyncPlugin: Async plugin works! 123')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('layouts', () => {
|
describe('layouts', () => {
|
||||||
it('should apply custom layout', async () => {
|
it('should apply custom layout', async () => {
|
||||||
const html = await $fetch('/with-layout')
|
const html = await $fetch('/with-layout')
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export default defineNuxtRouteMiddleware((to) => {
|
export default defineNuxtRouteMiddleware(async (to) => {
|
||||||
if (to.path.startsWith('/redirect/')) {
|
if (to.path.startsWith('/redirect/')) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100))
|
||||||
return navigateTo(to.path.slice('/redirect/'.length - 1))
|
return navigateTo(to.path.slice('/redirect/'.length - 1))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
1
test/fixtures/basic/pages/index.vue
vendored
1
test/fixtures/basic/pages/index.vue
vendored
@ -7,7 +7,6 @@
|
|||||||
<div>RuntimeConfig | testConfig: {{ config.testConfig }}</div>
|
<div>RuntimeConfig | testConfig: {{ config.testConfig }}</div>
|
||||||
<div>Composable | foo: {{ foo }}</div>
|
<div>Composable | foo: {{ foo }}</div>
|
||||||
<div>Composable | bar: {{ bar }}</div>
|
<div>Composable | bar: {{ bar }}</div>
|
||||||
<div>Plugin | myPlugin: {{ $myPlugin() }}</div>
|
|
||||||
<SugarCounter :count="12" />
|
<SugarCounter :count="12" />
|
||||||
<CustomComponent />
|
<CustomComponent />
|
||||||
</div>
|
</div>
|
||||||
|
6
test/fixtures/basic/pages/plugins.vue
vendored
Normal file
6
test/fixtures/basic/pages/plugins.vue
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div>myPlugin: {{ $myPlugin() }}</div>
|
||||||
|
<div>asyncPlugin: {{ $asyncPlugin() }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
12
test/fixtures/basic/plugins/async-plugin.ts
vendored
Normal file
12
test/fixtures/basic/plugins/async-plugin.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export default defineNuxtPlugin(async (/* nuxtApp */) => {
|
||||||
|
const config1 = useRuntimeConfig()
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100))
|
||||||
|
const config2 = useRuntimeConfig()
|
||||||
|
return {
|
||||||
|
provide: {
|
||||||
|
asyncPlugin: () => config1 && config1 === config2
|
||||||
|
? 'Async plugin works! ' + config1.testConfig
|
||||||
|
: 'Async plugin failed!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user