feat(nuxt3): automatically inject returns from plugins (#2001)

Co-authored-by: pooya parsa <pyapar@gmail.com>
This commit is contained in:
Daniel Roe 2021-11-18 13:11:34 +00:00 committed by GitHub
parent dd4dd9e30e
commit 4e9a27257b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 101 additions and 16 deletions

View File

@ -36,6 +36,10 @@ console.log(nuxtApp.$hello('name')) // Prints "Hello name!"
In Nuxt 2 plugins, this was referred to as [inject function](https://nuxtjs.org/docs/directory-structure/plugins#inject-in-root--context)
::alert{icon=👉}
It is possible to inject helpers by returning an object with a `provide` key. See the [plugins documentation](/docs/directory-structure/plugins) for more information.
::
## NuxtApp interface (advanced)
`nuxtApp` has the following properties: (note: this is an internal interface and some properties might change until stable release)

View File

@ -24,22 +24,63 @@ export default defineNuxtPlugin(nuxtApp => {
})
```
## Typing plugins
## Automatically providing helpers
If you provide a global property on the nuxt app instance, you can declare the type of this property like this:
If you would like to provide a helper on the `NuxtApp` instance, just return it from the plugin under a `provide` key. For example:
```ts
import { defineNuxtPlugin } from '#app'
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.provide('hello', msg => `Hello ${msg}!`);
export default defineNuxtPlugin(() => {
return {
provide: {
hello: () => 'world'
}
}
})
```
In another file you can use this:
```vue
<template>
<div>
{{ $hello() }}
</div>
</template>
<script setup lang="ts">
// alternatively, you can also use it here
const { $hello } = useNuxtApp()
</script>
```
## Typing plugins
If you return your helpers from the plugin, they will be typed automatically; you'll find them typed for the return of `useNuxtApp()` and within your templates.
::alert
If you need to use a provided helper _within_ another plugin, you can call `useNuxtApp()` to get the typed version. But in general this should be avoided unless you are certain of the plugins' order.
::
### Advanced
For advanced use-cases, you can declare the type of injected properties like this:
```ts [index.d.ts]
declare module '#app' {
interface NuxtApp {
$hello (msg: string): string
}
}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$hello (msg: string): string
}
}
export { }
```
## Vue plugins

View File

@ -25,13 +25,13 @@ export interface RuntimeNuxtHooks {
'meta:register': (metaRenderers: Array<(nuxt: NuxtApp) => NuxtMeta | Promise<NuxtMeta>>) => HookResult
}
export interface NuxtApp {
interface _NuxtApp {
vueApp: App<Element>
globalName: string
hooks: Hookable<RuntimeNuxtHooks>
hook: NuxtApp['hooks']['hook']
callHook: NuxtApp['hooks']['callHook']
hook: _NuxtApp['hooks']['hook']
callHook: _NuxtApp['hooks']['callHook']
[key: string]: any
@ -52,9 +52,11 @@ export interface NuxtApp {
provide: (name: string, value: any) => void
}
export interface NuxtApp extends _NuxtApp { }
export const NuxtPluginIndicator = '__nuxt_plugin'
export interface Plugin {
(nuxt: NuxtApp): Promise<void> | void
export interface Plugin<Injections extends Record<string, any> = Record<string, any>> {
(nuxt: _NuxtApp): Promise<void> | Promise<{ provide?: Injections }> | void | { provide?: Injections }
[NuxtPluginIndicator]?: true
}
export interface LegacyPlugin {
@ -117,9 +119,14 @@ export function createNuxtApp (options: CreateOptions) {
return nuxtApp
}
export function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin) {
export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin) {
if (typeof plugin !== 'function') { return }
return callWithNuxt(nuxtApp, () => plugin(nuxtApp))
const { provide } = await callWithNuxt(nuxtApp, () => plugin(nuxtApp)) || {}
if (provide && typeof provide === 'object') {
for (const key in provide) {
nuxtApp.provide(key, provide[key])
}
}
}
export async function applyPlugins (nuxtApp: NuxtApp, plugins: Plugin[]) {
@ -149,7 +156,7 @@ export function normalizePlugins (_plugins: Array<Plugin | LegacyPlugin>) {
return plugins as Plugin[]
}
export function defineNuxtPlugin (plugin: Plugin) {
export function defineNuxtPlugin<T> (plugin: Plugin<T>) {
plugin[NuxtPluginIndicator] = true
return plugin
}
@ -170,11 +177,11 @@ export const setNuxtAppInstance = (nuxt: NuxtApp | null) => {
* @param nuxt A Nuxt instance
* @param setup The function to call
*/
export async function callWithNuxt (nuxt: NuxtApp, setup: () => any) {
export function callWithNuxt<T extends () => any> (nuxt: NuxtApp, setup: T) {
setNuxtAppInstance(nuxt)
const p = setup()
const p: ReturnType<T> = setup()
setNuxtAppInstance(null)
await p
return p
}
/**

View File

@ -42,6 +42,7 @@ async function initNuxt (nuxt: Nuxt) {
// Add nuxt3 types
nuxt.hook('prepare:types', (opts) => {
opts.references.push({ types: 'nuxt3' })
opts.references.push({ path: resolve(nuxt.options.buildDir, 'plugins.d.ts') })
})
// Init user modules

View File

@ -1,6 +1,8 @@
import { templateUtils } from '@nuxt/kit'
import type { Nuxt, NuxtApp } from '@nuxt/kit'
import { relative } from 'pathe'
type TemplateContext = {
nuxt: Nuxt;
app: NuxtApp;
@ -74,3 +76,32 @@ export const appViewTemplate = {
`
}
}
export const pluginsDeclaration = {
filename: 'plugins.d.ts',
write: true,
getContents: (ctx: TemplateContext) => {
const EXTENSION_RE = new RegExp(`(?<=\\w)(${ctx.nuxt.options.extensions.map(e => `\\${e}`).join('|')})$`, 'g')
const tsImports = ctx.app.plugins.map(p => relative(ctx.nuxt.options.buildDir, p.src).replace(EXTENSION_RE, ''))
return `// Generated by Nuxt3'
import type { Plugin } from '#app'
type Decorate<T extends Record<string, any>> = { [K in keyof T as K extends string ? \`$\${K}\` : never]: T[K] }
type InjectionType<A extends Plugin> = A extends Plugin<infer T> ? Decorate<T> : unknown
type NuxtAppInjections = \n ${tsImports.map(p => `InjectionType<typeof import('${p}').default>`).join(' &\n ')}
declare module '#app' {
interface NuxtApp extends NuxtAppInjections { }
}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties extends NuxtAppInjections { }
}
export { }
`
}
}

View File

@ -27,7 +27,6 @@ export default defineNuxtPlugin((nuxtApp) => {
routes
})
nuxtApp.vueApp.use(router)
nuxtApp.provide('router', router)
const previousRoute = shallowRef(router.currentRoute.value)
router.afterEach((_to, from) => {
@ -53,4 +52,6 @@ export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.ssrContext.error = error
}
})
return { provide: { router } }
})