mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +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",
|
||||
"scule": "^0.2.1",
|
||||
"ufo": "^0.8.3",
|
||||
"unctx": "^1.1.4",
|
||||
"unimport": "^0.1.3",
|
||||
"unplugin": "^0.6.1",
|
||||
"untyped": "^0.4.4",
|
||||
|
@ -3,8 +3,11 @@ import { getCurrentInstance, reactive } from 'vue'
|
||||
import type { App, onErrorCaptured, VNode } from 'vue'
|
||||
import { createHooks, Hookable } from 'hookable'
|
||||
import type { RuntimeConfig } from '@nuxt/schema'
|
||||
import { getContext } from 'unctx'
|
||||
import { legacyPlugin, LegacyContext } from './compat/legacy-app'
|
||||
|
||||
const nuxtAppCtx = getContext<NuxtApp>('nuxt-app')
|
||||
|
||||
type NuxtMeta = {
|
||||
htmlAttrs?: string
|
||||
headAttrs?: string
|
||||
@ -172,12 +175,6 @@ export function isLegacyPlugin (plugin: unknown): plugin is LegacyPlugin {
|
||||
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`.
|
||||
*
|
||||
@ -185,13 +182,14 @@ export const setNuxtAppInstance = (nuxt: NuxtApp | null) => {
|
||||
* @param setup The function to call
|
||||
*/
|
||||
export function callWithNuxt<T extends (...args: any[]) => any> (nuxt: NuxtApp | _NuxtApp, setup: T, args?: Parameters<T>) {
|
||||
setNuxtAppInstance(nuxt as NuxtApp)
|
||||
const p: ReturnType<T> = args ? setup(...args as Parameters<T>) : setup()
|
||||
const fn = () => args ? setup(...args as Parameters<T>) : setup()
|
||||
if (process.server) {
|
||||
// Unset nuxt instance to prevent context-sharing in server-side
|
||||
setNuxtAppInstance(null)
|
||||
return nuxtAppCtx.callAsync<ReturnType<T>>(nuxt, fn)
|
||||
} 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()
|
||||
|
||||
if (!vm) {
|
||||
if (!currentNuxtAppInstance) {
|
||||
const nuxtAppInstance = nuxtAppCtx.use()
|
||||
if (!nuxtAppInstance) {
|
||||
throw new Error('nuxt instance unavailable')
|
||||
}
|
||||
return currentNuxtAppInstance
|
||||
return nuxtAppInstance
|
||||
}
|
||||
|
||||
return vm.appContext.app.$nuxt as NuxtApp
|
||||
|
@ -12,6 +12,7 @@ import autoImportsModule from '../auto-imports/module'
|
||||
import { distDir, pkgDir } from '../dirs'
|
||||
import { version } from '../../package.json'
|
||||
import { ImportProtectionPlugin, vueAppPatterns } from './plugins/import-protection'
|
||||
import { UnctxTransformPlugin } from './plugins/unctx'
|
||||
import { addModuleTranspiles } from './modules'
|
||||
|
||||
export function createNuxt (options: NuxtOptions): Nuxt {
|
||||
@ -57,7 +58,6 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
})
|
||||
|
||||
// Add import protection
|
||||
|
||||
const config = {
|
||||
rootDir: nuxt.options.rootDir,
|
||||
patterns: vueAppPatterns(nuxt)
|
||||
@ -65,6 +65,10 @@ async function initNuxt (nuxt: Nuxt) {
|
||||
addVitePlugin(ImportProtectionPlugin.vite(config))
|
||||
addWebpackPlugin(ImportProtectionPlugin.webpack(config))
|
||||
|
||||
// Add unctx transform
|
||||
addVitePlugin(UnctxTransformPlugin(nuxt).vite())
|
||||
addWebpackPlugin(UnctxTransformPlugin(nuxt).webpack())
|
||||
|
||||
// Init user modules
|
||||
await nuxt.callHook('modules:before', { nuxt } as ModuleContainer)
|
||||
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
|
||||
expect(html).toContain('Composable | foo: auto imported from ~/components/foo.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
|
||||
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', () => {
|
||||
it('should apply custom layout', async () => {
|
||||
const html = await $fetch('/with-layout')
|
||||
|
@ -1,5 +1,6 @@
|
||||
export default defineNuxtRouteMiddleware((to) => {
|
||||
export default defineNuxtRouteMiddleware(async (to) => {
|
||||
if (to.path.startsWith('/redirect/')) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
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>Composable | foo: {{ foo }}</div>
|
||||
<div>Composable | bar: {{ bar }}</div>
|
||||
<div>Plugin | myPlugin: {{ $myPlugin() }}</div>
|
||||
<SugarCounter :count="12" />
|
||||
<CustomComponent />
|
||||
</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