Skip to content

Commit

Permalink
perf(nuxt): extract and apply plugin order at build time (#21611)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielroe authored Jun 19, 2023
1 parent bb4ed5e commit 2abcc16
Show file tree
Hide file tree
Showing 13 changed files with 344 additions and 108 deletions.
1 change: 1 addition & 0 deletions packages/nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 89,7 @@
"prompts": "^2.4.2",
"scule": "^1.0.0",
"strip-literal": "^1.0.1",
"typescript-estree": "^18.1.0",
"ufo": "^1.1.2",
"ultrahtml": "^1.2.0",
"uncrypto": "^0.1.3",
Expand Down
6 changes: 2 additions & 4 deletions packages/nuxt/src/app/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 8,11 @@ import type { $Fetch, NitroFetchRequest } from 'nitropack'
import { baseURL } from '#build/paths.mjs'

import type { CreateOptions } from '#app'
import { applyPlugins, createNuxtApp, normalizePlugins } from '#app/nuxt'
import { applyPlugins, createNuxtApp } from '#app/nuxt'

import '#build/css'
// @ts-expect-error virtual file
import _plugins from '#build/plugins'
import plugins from '#build/plugins'
// @ts-expect-error virtual file
import RootComponent from '#build/root-component.mjs'
// @ts-expect-error virtual file
Expand All @@ -26,8 26,6 @@ if (!globalThis.$fetch) {

let entry: Function

const plugins = normalizePlugins(_plugins)

if (process.server) {
entry = async function createNuxtAppServer (ssrContext: CreateOptions['ssrContext']) {
const vueApp = createApp(RootComponent)
Expand Down
118 changes: 21 additions & 97 deletions packages/nuxt/src/app/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 160,6 @@ export interface PluginMeta {

export interface ResolvedPluginMeta {
name?: string
order: number
parallel?: boolean
}

Expand All @@ -170,7 169,7 @@ export interface Plugin<Injections extends Record<string, unknown> = Record<stri
meta?: ResolvedPluginMeta
}

export interface ObjectPluginInput<Injections extends Record<string, unknown> = Record<string, unknown>> extends PluginMeta {
export interface ObjectPlugin<Injections extends Record<string, unknown> = Record<string, unknown>> extends PluginMeta {
hooks?: Partial<RuntimeNuxtHooks>
setup?: Plugin<Injections>
/**
Expand All @@ -181,6 180,9 @@ export interface ObjectPluginInput<Injections extends Record<string, unknown> =
parallel?: boolean
}

/** @deprecated Use `ObjectPlugin` */
export type ObjectPluginInput<Injections extends Record<string, unknown> = Record<string, unknown>> = ObjectPlugin<Injections>

export interface CreateOptions {
vueApp: NuxtApp['vueApp']
ssrContext?: NuxtApp['ssrContext']
Expand Down Expand Up @@ -301,22 303,26 @@ export function createNuxtApp (options: CreateOptions) {
return nuxtApp
}

export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin) {
if (typeof plugin !== 'function') { return }
const { provide } = await nuxtApp.runWithContext(() => plugin(nuxtApp)) || {}
if (provide && typeof provide === 'object') {
for (const key in provide) {
nuxtApp.provide(key, provide[key])
export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin & ObjectPlugin<any>) {
if (plugin.hooks) {
nuxtApp.hooks.addHooks(plugin.hooks)
}
if (typeof plugin === 'function') {
const { provide } = await nuxtApp.runWithContext(() => 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[]) {
export async function applyPlugins (nuxtApp: NuxtApp, plugins: Array<Plugin & ObjectPlugin<any>>) {
const parallels: Promise<any>[] = []
const errors: Error[] = []
for (const plugin of plugins) {
const promise = applyPlugin(nuxtApp, plugin)
if (plugin.meta?.parallel) {
if (plugin.parallel) {
parallels.push(promise.catch(e => errors.push(e)))
} else {
await promise
Expand All @@ -326,97 332,15 @@ export async function applyPlugins (nuxtApp: NuxtApp, plugins: Plugin[]) {
if (errors.length) { throw errors[0] }
}

export function normalizePlugins (_plugins: Plugin[]) {
const unwrappedPlugins: Plugin[] = []
const legacyInjectPlugins: Plugin[] = []
const invalidPlugins: Plugin[] = []

const plugins: Plugin[] = []

for (const plugin of _plugins) {
if (typeof plugin !== 'function') {
if (process.dev) { invalidPlugins.push(plugin) }
continue
}

// TODO: Skip invalid plugins in next releases
let _plugin = plugin
if (plugin.length > 1) {
// Allow usage without wrapper but warn
if (process.dev) { legacyInjectPlugins.push(plugin) }
// @ts-expect-error deliberate invalid second argument
_plugin = (nuxtApp: NuxtApp) => plugin(nuxtApp, nuxtApp.provide)
}

// Allow usage without wrapper but warn
if (process.dev && !isNuxtPlugin(_plugin)) { unwrappedPlugins.push(_plugin) }

plugins.push(_plugin)
}

plugins.sort((a, b) => (a.meta?.order || orderMap.default) - (b.meta?.order || orderMap.default))

if (process.dev && legacyInjectPlugins.length) {
console.warn('[warn] [nuxt] You are using a plugin with legacy Nuxt 2 format (context, inject) which is likely to be broken. In the future they will be ignored:', legacyInjectPlugins.map(p => p.name || p).join(','))
}
if (process.dev && invalidPlugins.length) {
console.warn('[warn] [nuxt] Some plugins are not exposing a function and skipped:', invalidPlugins)
}
if (process.dev && unwrappedPlugins.length) {
console.warn('[warn] [nuxt] You are using a plugin that has not been wrapped in `defineNuxtPlugin`. It is advised to wrap your plugins as in the future this may enable enhancements:', unwrappedPlugins.map(p => p.name || p).join(','))
}

return plugins
}

// -50: pre-all (nuxt)
// -40: custom payload revivers (user)
// -30: payload reviving (nuxt)
// -20: pre (user) <-- pre mapped to this
// -10: default (nuxt)
// 0: default (user) <-- default behavior
// 10: post (nuxt)
// 20: post (user) <-- post mapped to this
// 30: post-all (nuxt)

const orderMap: Record<NonNullable<ObjectPluginInput['enforce']>, number> = {
pre: -20,
default: 0,
post: 20
}

/*! @__NO_SIDE_EFFECTS__ */
export function definePayloadPlugin<T extends Record<string, unknown>> (plugin: Plugin<T> | ObjectPluginInput<T>) {
return defineNuxtPlugin(plugin, { order: -40 })
export function defineNuxtPlugin<T extends Record<string, unknown>> (plugin: Plugin<T> | ObjectPlugin<T>): Plugin<T> & ObjectPlugin<T> {
if (typeof plugin === 'function') { return plugin }
delete plugin.name
return Object.assign(plugin.setup || (() => {}), plugin, { [NuxtPluginIndicator]: true } as const)
}

/*! @__NO_SIDE_EFFECTS__ */
export function defineNuxtPlugin<T extends Record<string, unknown>> (plugin: Plugin<T> | ObjectPluginInput<T>, meta?: PluginMeta): Plugin<T> {
if (typeof plugin === 'function') { return defineNuxtPlugin({ setup: plugin }, meta) }

const wrapper: Plugin<T> = (nuxtApp) => {
if (plugin.hooks) {
nuxtApp.hooks.addHooks(plugin.hooks)
}
if (plugin.setup) {
return plugin.setup(nuxtApp)
}
}

wrapper.meta = {
name: meta?.name || plugin.name || plugin.setup?.name,
parallel: plugin.parallel,
order:
meta?.order ||
plugin.order ||
orderMap[plugin.enforce || 'default'] ||
orderMap.default
}

wrapper[NuxtPluginIndicator] = true

return wrapper
}
export const definePayloadPlugin = defineNuxtPlugin

export function isNuxtPlugin (plugin: unknown) {
return typeof plugin === 'function' && NuxtPluginIndicator in plugin
Expand Down
19 changes: 19 additions & 0 deletions packages/nuxt/src/core/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 6,7 @@ import type { Nuxt, NuxtApp, NuxtPlugin, NuxtTemplate, ResolvedNuxtTemplate } fr

import * as defaultTemplates from './templates'
import { getNameFromPath, hasSuffix, uniqueBy } from './utils'
import { extractMetadata, orderMap } from './plugins/plugin-metadata'

export function createApp (nuxt: Nuxt, options: Partial<NuxtApp> = {}): NuxtApp {
return defu(options, {
Expand Down Expand Up @@ -149,3 150,21 @@ function resolvePaths<Item extends Record<string, any>> (items: Item[], key: { [
}
}))
}

export async function annotatePlugins (nuxt: Nuxt, plugins: NuxtPlugin[]) {
const _plugins: NuxtPlugin[] = []
for (const plugin of plugins) {
try {
const code = plugin.src in nuxt.vfs ? nuxt.vfs[plugin.src] : await fsp.readFile(plugin.src!, 'utf-8')
_plugins.push({
...extractMetadata(code),
...plugin
})
} catch (e) {
console.warn(`[nuxt] Could not resolve \`${plugin.src}\`.`)
_plugins.push(plugin)
}
}

return _plugins.sort((a, b) => (a.order ?? orderMap.default) - (b.order ?? orderMap.default))
}
6 changes: 5 additions & 1 deletion packages/nuxt/src/core/nuxt.ts
Original file line number Diff line number Diff line change
@@ -1,7 1,7 @@
import { join, normalize, relative, resolve } from 'pathe'
import { createDebugger, createHooks } from 'hookable'
import type { LoadNuxtOptions } from '@nuxt/kit'
import { addComponent, addPlugin, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, logger, nuxtCtx, resolveAlias, resolveFiles, resolvePath, tryResolveModule } from '@nuxt/kit'
import { addBuildPlugin, addComponent, addPlugin, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, logger, nuxtCtx, resolveAlias, resolveFiles, resolvePath, tryResolveModule } from '@nuxt/kit'
import type { Nuxt, NuxtHooks, NuxtOptions } from 'nuxt/schema'

import escapeRE from 'escape-string-regexp'
Expand All @@ -24,6 24,7 @@ import { LayerAliasingPlugin } from './plugins/layer-aliasing'
import { addModuleTranspiles } from './modules'
import { initNitro } from './nitro'
import schemaModule from './schema'
import { RemovePluginMetadataPlugin } from './plugins/plugin-metadata'

export function createNuxt (options: NuxtOptions): Nuxt {
const hooks = createHooks<NuxtHooks>()
Expand Down Expand Up @@ -72,6 73,9 @@ async function initNuxt (nuxt: Nuxt) {
}
})

// Add plugin normalisation plugin
addBuildPlugin(RemovePluginMetadataPlugin(nuxt))

// Add import protection
const config = {
rootDir: nuxt.options.rootDir,
Expand Down
Loading

0 comments on commit 2abcc16

Please sign in to comment.