mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
feat(nuxt): allow plugins to specify dependencies (#24127)
This commit is contained in:
parent
02306fd13d
commit
5877e11c89
@ -101,9 +101,11 @@ In case you're new to 'alphabetical' numbering, remember that filenames are sort
|
||||
|
||||
## Loading Strategy
|
||||
|
||||
### Parallel Plugins
|
||||
|
||||
By default, Nuxt loads plugins sequentially. You can define a plugin as `parallel` so Nuxt won't wait the end of the plugin's execution before loading the next plugin.
|
||||
|
||||
```ts [plugins/hello.ts]
|
||||
```ts [plugins/my-plugin.ts]
|
||||
export default defineNuxtPlugin({
|
||||
name: 'my-plugin',
|
||||
parallel: true,
|
||||
@ -113,6 +115,20 @@ export default defineNuxtPlugin({
|
||||
})
|
||||
```
|
||||
|
||||
### Plugins With Dependencies
|
||||
|
||||
If a plugin needs to await a parallel plugin before it runs, you can add the plugin's name to the `dependsOn` array.
|
||||
|
||||
```ts [plugins/depending-on-my-plugin.ts]
|
||||
export default defineNuxtPlugin({
|
||||
name: 'depends-on-my-plugin',
|
||||
dependsOn: ['my-plugin']
|
||||
async setup (nuxtApp) {
|
||||
// this plugin will wait for the end of `my-plugin`'s execution before it runs
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Using Composables
|
||||
|
||||
You can use [composables](/docs/guide/directory-structure/composables) as well as [utils](/docs/guide/directory-structure/utils) within Nuxt plugins:
|
||||
|
@ -7,9 +7,7 @@ export * from './composables/index'
|
||||
export * from './components/index'
|
||||
export * from './config'
|
||||
export * from './compat/idle-callback'
|
||||
|
||||
// eslint-disable-next-line import/no-restricted-paths
|
||||
export type { PageMeta } from '../pages/runtime/index'
|
||||
export * from './types'
|
||||
|
||||
export const isVue2 = false
|
||||
export const isVue3 = true
|
||||
|
@ -18,6 +18,8 @@ import type { NuxtError } from '../app/composables/error'
|
||||
import type { AsyncDataRequestStatus } from '../app/composables/asyncData'
|
||||
import type { NuxtAppManifestMeta } from '../app/composables/manifest'
|
||||
|
||||
import type { NuxtAppLiterals } from '#app'
|
||||
|
||||
const nuxtAppCtx = /*@__PURE__*/ getContext<NuxtApp>('nuxt-app', {
|
||||
asyncContext: !!__NUXT_ASYNC_CONTEXT__ && import.meta.server
|
||||
})
|
||||
@ -154,6 +156,10 @@ export const NuxtPluginIndicator = '__nuxt_plugin'
|
||||
export interface PluginMeta {
|
||||
name?: string
|
||||
enforce?: 'pre' | 'default' | 'post'
|
||||
/**
|
||||
* Await for other named plugins to finish before running this plugin.
|
||||
*/
|
||||
dependsOn?: NuxtAppLiterals['pluginName'][]
|
||||
/**
|
||||
* This allows more granular control over plugin order and should only be used by advanced users.
|
||||
* It overrides the value of `enforce` and is used to sort plugins.
|
||||
@ -190,6 +196,10 @@ export interface ObjectPlugin<Injections extends Record<string, unknown> = Recor
|
||||
* @default false
|
||||
*/
|
||||
parallel?: boolean
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_name?: string
|
||||
}
|
||||
|
||||
/** @deprecated Use `ObjectPlugin` */
|
||||
@ -331,26 +341,61 @@ export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin & ObjectPlug
|
||||
}
|
||||
|
||||
export async function applyPlugins (nuxtApp: NuxtApp, plugins: Array<Plugin & ObjectPlugin<any>>) {
|
||||
const resolvedPlugins: string[] = []
|
||||
const unresolvedPlugins: [Set<string>, Plugin & ObjectPlugin<any>][] = []
|
||||
const parallels: Promise<any>[] = []
|
||||
const errors: Error[] = []
|
||||
for (const plugin of plugins) {
|
||||
if (import.meta.server && nuxtApp.ssrContext?.islandContext && plugin.env?.islands === false) { continue }
|
||||
const promise = applyPlugin(nuxtApp, plugin)
|
||||
if (plugin.parallel) {
|
||||
parallels.push(promise.catch(e => errors.push(e)))
|
||||
let promiseDepth = 0
|
||||
|
||||
async function executePlugin (plugin: Plugin & ObjectPlugin<any>) {
|
||||
if (plugin.dependsOn && !plugin.dependsOn.every(name => resolvedPlugins.includes(name))) {
|
||||
unresolvedPlugins.push([new Set(plugin.dependsOn), plugin])
|
||||
} else {
|
||||
await promise
|
||||
const promise = applyPlugin(nuxtApp, plugin).then(async () => {
|
||||
if (plugin._name) {
|
||||
resolvedPlugins.push(plugin._name)
|
||||
await Promise.all(unresolvedPlugins.map(async ([dependsOn, unexecutedPlugin]) => {
|
||||
if (dependsOn.has(plugin._name!)) {
|
||||
dependsOn.delete(plugin._name!)
|
||||
if (dependsOn.size === 0) {
|
||||
promiseDepth++
|
||||
await executePlugin(unexecutedPlugin)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
if (plugin.parallel) {
|
||||
parallels.push(promise.catch(e => errors.push(e)))
|
||||
} else {
|
||||
await promise
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const plugin of plugins) {
|
||||
if (import.meta.server && nuxtApp.ssrContext?.islandContext && plugin.env?.islands === false) { continue }
|
||||
await executePlugin(plugin)
|
||||
}
|
||||
|
||||
await Promise.all(parallels)
|
||||
if (promiseDepth) {
|
||||
for (let i = 0; i < promiseDepth; i++) {
|
||||
await Promise.all(parallels)
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length) { throw errors[0] }
|
||||
}
|
||||
|
||||
/*@__NO_SIDE_EFFECTS__*/
|
||||
export function defineNuxtPlugin<T extends Record<string, unknown>> (plugin: Plugin<T> | ObjectPlugin<T>): Plugin<T> & ObjectPlugin<T> {
|
||||
if (typeof plugin === 'function') { return plugin }
|
||||
|
||||
const _name = plugin._name || plugin.name
|
||||
delete plugin.name
|
||||
return Object.assign(plugin.setup || (() => {}), plugin, { [NuxtPluginIndicator]: true } as const)
|
||||
return Object.assign(plugin.setup || (() => {}), plugin, { [NuxtPluginIndicator]: true, _name } as const)
|
||||
}
|
||||
|
||||
/*@__NO_SIDE_EFFECTS__*/
|
||||
|
6
packages/nuxt/src/app/types.ts
Normal file
6
packages/nuxt/src/app/types.ts
Normal file
@ -0,0 +1,6 @@
|
||||
// eslint-disable-next-line import/no-restricted-paths
|
||||
export type { PageMeta } from '../pages/runtime/index'
|
||||
|
||||
export interface NuxtAppLiterals {
|
||||
[key: string]: string
|
||||
}
|
@ -8,6 +8,8 @@ import * as defaultTemplates from './templates'
|
||||
import { getNameFromPath, hasSuffix, uniqueBy } from './utils'
|
||||
import { extractMetadata, orderMap } from './plugins/plugin-metadata'
|
||||
|
||||
import type { PluginMeta } from '#app'
|
||||
|
||||
export function createApp (nuxt: Nuxt, options: Partial<NuxtApp> = {}): NuxtApp {
|
||||
return defu(options, {
|
||||
dir: nuxt.options.srcDir,
|
||||
@ -185,7 +187,7 @@ function resolvePaths<Item extends Record<string, any>> (items: Item[], key: { [
|
||||
}
|
||||
|
||||
export async function annotatePlugins (nuxt: Nuxt, plugins: NuxtPlugin[]) {
|
||||
const _plugins: NuxtPlugin[] = []
|
||||
const _plugins: Array<NuxtPlugin & Omit<PluginMeta, 'enforce'>> = []
|
||||
for (const plugin of plugins) {
|
||||
try {
|
||||
const code = plugin.src in nuxt.vfs ? nuxt.vfs[plugin.src] : await fsp.readFile(plugin.src!, 'utf-8')
|
||||
@ -201,3 +203,29 @@ export async function annotatePlugins (nuxt: Nuxt, plugins: NuxtPlugin[]) {
|
||||
|
||||
return _plugins.sort((a, b) => (a.order ?? orderMap.default) - (b.order ?? orderMap.default))
|
||||
}
|
||||
|
||||
export function checkForCircularDependencies (_plugins: Array<NuxtPlugin & Omit<PluginMeta, 'enforce'>>) {
|
||||
const deps: Record<string, string[]> = Object.create(null)
|
||||
const pluginNames = _plugins.map(plugin => plugin.name)
|
||||
for (const plugin of _plugins) {
|
||||
// Make sure dependency plugins are registered
|
||||
if (plugin.dependsOn && plugin.dependsOn.some(name => !pluginNames.includes(name))) {
|
||||
console.error(`Plugin \`${plugin.name}\` depends on \`${plugin.dependsOn.filter(name => !pluginNames.includes(name)).join(', ')}\` but they are not registered.`)
|
||||
}
|
||||
// Make graph to detect circular dependencies
|
||||
if (plugin.name) {
|
||||
deps[plugin.name] = plugin.dependsOn || []
|
||||
}
|
||||
}
|
||||
const checkDeps = (name: string, visited: string[] = []): string[] => {
|
||||
if (visited.includes(name)) {
|
||||
console.error(`Circular dependency detected in plugins: ${visited.join(' -> ')} -> ${name}`)
|
||||
return []
|
||||
}
|
||||
visited.push(name)
|
||||
return (deps[name] || []).flatMap(dep => checkDeps(dep, [...visited]))
|
||||
}
|
||||
for (const name in deps) {
|
||||
checkDeps(name)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { CallExpression, Property, SpreadElement } from 'estree'
|
||||
import type { CallExpression, Literal, Property, SpreadElement } from 'estree'
|
||||
import type { Node } from 'estree-walker'
|
||||
import { walk } from 'estree-walker'
|
||||
import { transform } from 'esbuild'
|
||||
@ -87,7 +87,8 @@ type PluginMetaKey = keyof PluginMeta
|
||||
const keys: Record<PluginMetaKey, string> = {
|
||||
name: 'name',
|
||||
order: 'order',
|
||||
enforce: 'enforce'
|
||||
enforce: 'enforce',
|
||||
dependsOn: 'dependsOn'
|
||||
}
|
||||
function isMetadataKey (key: string): key is PluginMetaKey {
|
||||
return key in keys
|
||||
@ -107,6 +108,12 @@ function extractMetaFromObject (properties: Array<Property | SpreadElement>) {
|
||||
if (property.value.type === 'UnaryExpression' && property.value.argument.type === 'Literal') {
|
||||
meta[propertyKey] = JSON.parse(property.value.operator + property.value.argument.raw!)
|
||||
}
|
||||
if (propertyKey === 'dependsOn' && property.value.type === 'ArrayExpression') {
|
||||
if (property.value.elements.some(e => !e || e.type !== 'Literal' || typeof e.value !== 'string')) {
|
||||
throw new Error('dependsOn must take an array of string literals')
|
||||
}
|
||||
meta[propertyKey] = property.value.elements.map(e => (e as Literal)!.value as string)
|
||||
}
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import { hash } from 'ohash'
|
||||
import { camelCase } from 'scule'
|
||||
import { filename } from 'pathe/utils'
|
||||
import type { Nuxt, NuxtApp, NuxtTemplate } from 'nuxt/schema'
|
||||
import { annotatePlugins } from './app'
|
||||
import { annotatePlugins, checkForCircularDependencies } from './app'
|
||||
|
||||
interface TemplateContext {
|
||||
nuxt: Nuxt
|
||||
@ -62,7 +62,7 @@ export const clientPluginTemplate: NuxtTemplate<TemplateContext> = {
|
||||
filename: 'plugins/client.mjs',
|
||||
async getContents (ctx) {
|
||||
const clientPlugins = await annotatePlugins(ctx.nuxt, ctx.app.plugins.filter(p => !p.mode || p.mode !== 'server'))
|
||||
await annotatePlugins(ctx.nuxt, clientPlugins)
|
||||
checkForCircularDependencies(clientPlugins)
|
||||
const exports: string[] = []
|
||||
const imports: string[] = []
|
||||
for (const plugin of clientPlugins) {
|
||||
@ -82,6 +82,7 @@ export const serverPluginTemplate: NuxtTemplate<TemplateContext> = {
|
||||
filename: 'plugins/server.mjs',
|
||||
async getContents (ctx) {
|
||||
const serverPlugins = await annotatePlugins(ctx.nuxt, ctx.app.plugins.filter(p => !p.mode || p.mode !== 'client'))
|
||||
checkForCircularDependencies(serverPlugins)
|
||||
const exports: string[] = []
|
||||
const imports: string[] = []
|
||||
for (const plugin of serverPlugins) {
|
||||
@ -99,7 +100,7 @@ export const serverPluginTemplate: NuxtTemplate<TemplateContext> = {
|
||||
|
||||
export const pluginsDeclaration: NuxtTemplate<TemplateContext> = {
|
||||
filename: 'types/plugins.d.ts',
|
||||
getContents: (ctx) => {
|
||||
getContents: async (ctx) => {
|
||||
const EXTENSION_RE = new RegExp(`(?<=\\w)(${ctx.nuxt.options.extensions.map(e => escapeRE(e)).join('|')})$`, 'g')
|
||||
const tsImports: string[] = []
|
||||
for (const p of ctx.app.plugins) {
|
||||
@ -111,6 +112,8 @@ export const pluginsDeclaration: NuxtTemplate<TemplateContext> = {
|
||||
}
|
||||
}
|
||||
|
||||
const pluginsName = (await annotatePlugins(ctx.nuxt, ctx.app.plugins)).filter(p => p.name).map(p => `'${p.name}'`)
|
||||
|
||||
return `// Generated by Nuxt'
|
||||
import type { Plugin } from '#app'
|
||||
|
||||
@ -122,6 +125,10 @@ type NuxtAppInjections = \n ${tsImports.map(p => `InjectionType<typeof ${genDyn
|
||||
|
||||
declare module '#app' {
|
||||
interface NuxtApp extends NuxtAppInjections { }
|
||||
|
||||
interface NuxtAppLiterals {
|
||||
pluginName: ${pluginsName.join(' | ')}
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'vue' {
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { parse } from 'acorn'
|
||||
|
||||
import { RemovePluginMetadataPlugin, extractMetadata } from '../src/core/plugins/plugin-metadata'
|
||||
import { checkForCircularDependencies } from '../src/core/app'
|
||||
|
||||
describe('plugin-metadata', () => {
|
||||
it('should extract metadata from object-syntax plugins', async () => {
|
||||
@ -63,3 +64,51 @@ describe('plugin-metadata', () => {
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('plugin sanity checking', () => {
|
||||
it('non-existent depends are warned', () => {
|
||||
vi.spyOn(console, 'error')
|
||||
checkForCircularDependencies([
|
||||
{
|
||||
name: 'A',
|
||||
src: ''
|
||||
},
|
||||
{
|
||||
name: 'B',
|
||||
dependsOn: ['D'],
|
||||
src: ''
|
||||
},
|
||||
{
|
||||
name: 'C',
|
||||
src: ''
|
||||
}
|
||||
])
|
||||
expect(console.error).toBeCalledWith('Plugin `B` depends on `D` but they are not registered.')
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('circular dependencies are warned', () => {
|
||||
vi.spyOn(console, 'error')
|
||||
checkForCircularDependencies([
|
||||
{
|
||||
name: 'A',
|
||||
dependsOn: ['B'],
|
||||
src: ''
|
||||
},
|
||||
{
|
||||
name: 'B',
|
||||
dependsOn: ['C'],
|
||||
src: ''
|
||||
},
|
||||
{
|
||||
name: 'C',
|
||||
dependsOn: ['A'],
|
||||
src: ''
|
||||
}
|
||||
])
|
||||
expect(console.error).toBeCalledWith('Circular dependency detected in plugins: A -> B -> C -> A')
|
||||
expect(console.error).toBeCalledWith('Circular dependency detected in plugins: B -> C -> A -> B')
|
||||
expect(console.error).toBeCalledWith('Circular dependency detected in plugins: C -> A -> B -> C')
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
})
|
||||
|
@ -16,6 +16,10 @@ export interface NuxtPlugin {
|
||||
* Default Nuxt priorities can be seen at [here](https://github.com/nuxt/nuxt/blob/9904849bc87c53dfbd3ea3528140a5684c63c8d8/packages/nuxt/src/core/plugins/plugin-metadata.ts#L15-L34).
|
||||
*/
|
||||
order?: number
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
name?: string
|
||||
}
|
||||
|
||||
// Internal type for simpler NuxtTemplate interface extension
|
||||
|
@ -32,7 +32,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
const serverDir = join(rootDir, '.output/server')
|
||||
|
||||
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"199k"')
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"200k"`)
|
||||
|
||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"1847k"')
|
||||
@ -71,7 +71,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
const serverDir = join(rootDir, '.output-inline/server')
|
||||
|
||||
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot('"510k"')
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"511k"`)
|
||||
|
||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"77.0k"')
|
||||
|
12
test/fixtures/basic-types/types.ts
vendored
12
test/fixtures/basic-types/types.ts
vendored
@ -242,6 +242,18 @@ describe('nuxtApp', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('plugins', () => {
|
||||
it('dependsOn is strongly typed', () => {
|
||||
defineNuxtPlugin({
|
||||
// @ts-expect-error invalid plugin name
|
||||
dependsOn: ['something']
|
||||
})
|
||||
defineNuxtPlugin({
|
||||
dependsOn: ['nuxt:router']
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('runtimeConfig', () => {
|
||||
it('generated runtimeConfig types', () => {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
|
26
test/fixtures/basic/plugins/async-plugin.ts
vendored
26
test/fixtures/basic/plugins/async-plugin.ts
vendored
@ -1,13 +1,17 @@
|
||||
export default defineNuxtPlugin(async (/* nuxtApp */) => {
|
||||
const config1 = useRuntimeConfig()
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
const { data } = useFetch('/api/hey', { key: 'hey' })
|
||||
const config2 = useRuntimeConfig()
|
||||
return {
|
||||
provide: {
|
||||
asyncPlugin: () => config1 && config1 === config2
|
||||
? 'Async plugin works! ' + config1.public.testConfig + (data.value?.baz ? 'useFetch works!' : 'useFetch does not work')
|
||||
: 'Async plugin failed!'
|
||||
export default defineNuxtPlugin({
|
||||
name: 'async-plugin',
|
||||
async setup (/* nuxtApp */) {
|
||||
const config1 = useRuntimeConfig()
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
const { data } = useFetch('/api/hey', { key: 'hey' })
|
||||
const config2 = useRuntimeConfig()
|
||||
return {
|
||||
provide: {
|
||||
asyncPlugin: () => config1 && config1 === config2
|
||||
? 'Async plugin works! ' + config1.public.testConfig + (data.value?.baz ? 'useFetch works!' : 'useFetch does not work')
|
||||
: 'Async plugin failed!'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
parallel: true
|
||||
})
|
||||
|
12
test/fixtures/basic/plugins/dependsOnPlugin.ts
vendored
Normal file
12
test/fixtures/basic/plugins/dependsOnPlugin.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
export default defineNuxtPlugin({
|
||||
name: 'depends-on-plugin',
|
||||
dependsOn: ['async-plugin'],
|
||||
async setup () {
|
||||
const nuxtApp = useNuxtApp()
|
||||
if (!nuxtApp.$asyncPlugin) {
|
||||
throw new Error('$asyncPlugin is not defined')
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
},
|
||||
parallel: true
|
||||
})
|
247
test/nuxt/plugin.test.ts
Normal file
247
test/nuxt/plugin.test.ts
Normal file
@ -0,0 +1,247 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { applyPlugins } from '#app/nuxt'
|
||||
import { defineNuxtPlugin } from '#app'
|
||||
|
||||
vi.mock('#app', async (original) => {
|
||||
return {
|
||||
...(await original<typeof import('#app')>()),
|
||||
applyPlugin: vi.fn(async (_nuxtApp, plugin) => {
|
||||
await plugin()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function pluginFactory (name: string, dependsOn?: string[], sequence: string[], parallel = true) {
|
||||
return defineNuxtPlugin({
|
||||
name,
|
||||
dependsOn,
|
||||
async setup () {
|
||||
sequence.push(`start ${name}`)
|
||||
await new Promise(resolve => setTimeout(resolve, 10))
|
||||
sequence.push(`end ${name}`)
|
||||
},
|
||||
parallel
|
||||
})
|
||||
}
|
||||
|
||||
describe('plugin dependsOn', () => {
|
||||
it('expect B to await A to finish before being run', async () => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const sequence: string[] = []
|
||||
const plugins = [
|
||||
pluginFactory('A', undefined, sequence),
|
||||
pluginFactory('B', ['A'], sequence)
|
||||
]
|
||||
|
||||
await applyPlugins(nuxtApp, plugins)
|
||||
|
||||
expect(sequence).toMatchObject([
|
||||
'start A',
|
||||
'end A',
|
||||
'start B',
|
||||
'end B'
|
||||
])
|
||||
})
|
||||
|
||||
it('expect C to await A and B to finish before being run', async () => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const sequence: string[] = []
|
||||
const plugins = [
|
||||
pluginFactory('A', undefined, sequence),
|
||||
pluginFactory('B', ['A'], sequence),
|
||||
pluginFactory('C', ['A', 'B'], sequence)
|
||||
]
|
||||
|
||||
await applyPlugins(nuxtApp, plugins)
|
||||
|
||||
expect(sequence).toMatchObject([
|
||||
'start A',
|
||||
'end A',
|
||||
'start B',
|
||||
'end B',
|
||||
'start C',
|
||||
'end C'
|
||||
])
|
||||
})
|
||||
|
||||
it('expect C to not wait for A to finish before being run', async () => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const sequence: string[] = []
|
||||
const plugins = [
|
||||
pluginFactory('A', undefined, sequence),
|
||||
pluginFactory('B', ['A'], sequence),
|
||||
defineNuxtPlugin({
|
||||
name,
|
||||
async setup () {
|
||||
sequence.push('start C')
|
||||
await new Promise(resolve => setTimeout(resolve, 5))
|
||||
sequence.push('end C')
|
||||
},
|
||||
parallel: true
|
||||
})
|
||||
]
|
||||
|
||||
await applyPlugins(nuxtApp, plugins)
|
||||
|
||||
expect(sequence).toMatchObject([
|
||||
'start A',
|
||||
'start C',
|
||||
'end C',
|
||||
'end A',
|
||||
'start B',
|
||||
'end B'
|
||||
])
|
||||
})
|
||||
|
||||
it('expect C to block the depends on of A-B since C is sequential', async () => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const sequence: string[] = []
|
||||
const plugins = [
|
||||
pluginFactory('A', undefined, sequence),
|
||||
defineNuxtPlugin({
|
||||
name,
|
||||
async setup () {
|
||||
sequence.push('start C')
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
sequence.push('end C')
|
||||
}
|
||||
}),
|
||||
pluginFactory('B', ['A'], sequence)
|
||||
]
|
||||
|
||||
await applyPlugins(nuxtApp, plugins)
|
||||
|
||||
expect(sequence).toMatchObject([
|
||||
'start A',
|
||||
'start C',
|
||||
'end A',
|
||||
'end C',
|
||||
'start B',
|
||||
'end B'
|
||||
])
|
||||
})
|
||||
|
||||
it('relying on plugin not registed yet', async () => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const sequence: string[] = []
|
||||
const plugins = [
|
||||
pluginFactory('C', ['A'], sequence),
|
||||
pluginFactory('A', undefined, sequence, true),
|
||||
pluginFactory('E', ['B', 'C'], sequence, false),
|
||||
pluginFactory('B', undefined, sequence),
|
||||
pluginFactory('D', ['C'], sequence, false)
|
||||
]
|
||||
await applyPlugins(nuxtApp, plugins)
|
||||
|
||||
expect(sequence).toMatchObject([
|
||||
'start A',
|
||||
'start B',
|
||||
'end A',
|
||||
'start C',
|
||||
'end B',
|
||||
'end C',
|
||||
'start E',
|
||||
'start D',
|
||||
'end E',
|
||||
'end D'
|
||||
])
|
||||
})
|
||||
|
||||
it('test depending on not yet registered plugin and already resolved plugin', async () => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const sequence: string[] = []
|
||||
const plugins = [
|
||||
pluginFactory('A', undefined, sequence),
|
||||
pluginFactory('B', ['A', 'C'], sequence),
|
||||
pluginFactory('C', undefined, sequence, false),
|
||||
pluginFactory('D', undefined, sequence, false),
|
||||
pluginFactory('E', ['C'], sequence, false)
|
||||
]
|
||||
await applyPlugins(nuxtApp, plugins)
|
||||
|
||||
expect(sequence).toMatchObject([
|
||||
'start A',
|
||||
'start C',
|
||||
'end A',
|
||||
'end C',
|
||||
'start B',
|
||||
'start D',
|
||||
'end B',
|
||||
'end D',
|
||||
'start E',
|
||||
'end E'
|
||||
])
|
||||
})
|
||||
|
||||
it('multiple depth of plugin dependency', async () => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const sequence: string[] = []
|
||||
const plugins = [
|
||||
pluginFactory('A', undefined, sequence),
|
||||
pluginFactory('C', ['B', 'A'], sequence),
|
||||
pluginFactory('B', undefined, sequence, false),
|
||||
pluginFactory('E', ['D'], sequence, false),
|
||||
pluginFactory('D', ['C'], sequence, false)
|
||||
]
|
||||
await applyPlugins(nuxtApp, plugins)
|
||||
|
||||
expect(sequence).toMatchObject([
|
||||
'start A',
|
||||
'start B',
|
||||
'end A',
|
||||
'end B',
|
||||
'start C',
|
||||
'end C',
|
||||
'start D',
|
||||
'end D',
|
||||
'start E',
|
||||
'end E'
|
||||
])
|
||||
})
|
||||
|
||||
it('does not throw when circular dependency is not a problem', async () => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const sequence: string[] = []
|
||||
const plugins = [
|
||||
pluginFactory('A', ['B'], sequence),
|
||||
pluginFactory('B', ['C'], sequence),
|
||||
pluginFactory('C', ['D'], sequence),
|
||||
pluginFactory('D', [], sequence),
|
||||
]
|
||||
|
||||
await applyPlugins(nuxtApp, plugins)
|
||||
expect(sequence).toMatchObject([
|
||||
'start D',
|
||||
'end D',
|
||||
'start C',
|
||||
'end C',
|
||||
'start B',
|
||||
'end B',
|
||||
'start A',
|
||||
'end A'
|
||||
])
|
||||
})
|
||||
|
||||
it('function plugin', async () => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const sequence: string[] = []
|
||||
const plugins = [
|
||||
pluginFactory('A', undefined, sequence),
|
||||
defineNuxtPlugin(() => {
|
||||
sequence.push('start C')
|
||||
sequence.push('end C')
|
||||
}),
|
||||
pluginFactory('B', undefined, sequence, false)
|
||||
]
|
||||
await applyPlugins(nuxtApp, plugins)
|
||||
|
||||
expect(sequence).toMatchObject([
|
||||
'start A',
|
||||
'start C',
|
||||
'end C',
|
||||
'start B',
|
||||
'end A',
|
||||
'end B'
|
||||
])
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user