perf(nuxt): extract and apply plugin order at build time (#21611)

This commit is contained in:
Daniel Roe 2023-06-20 00:00:03 +01:00 committed by GitHub
parent bb4ed5e406
commit 2abcc16cfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 345 additions and 109 deletions

View File

@ -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",

View File

@ -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
@ -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)

View File

@ -160,7 +160,6 @@ export interface PluginMeta {
export interface ResolvedPluginMeta {
name?: string
order: number
parallel?: boolean
}
@ -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>
/**
@ -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']
@ -301,22 +303,26 @@ export function createNuxtApp (options: CreateOptions) {
return nuxtApp
}
export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin) {
if (typeof plugin !== 'function') { return }
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
@ -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 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 definePayloadPlugin<T extends Record<string, unknown>> (plugin: Plugin<T> | ObjectPluginInput<T>) {
return defineNuxtPlugin(plugin, { order: -40 })
}
/*! @__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

View File

@ -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, {
@ -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))
}

View File

@ -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'
@ -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>()
@ -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,

View File

@ -0,0 +1,189 @@
import type { CallExpression, Property, SpreadElement } from 'estree'
import type { Node } from 'estree-walker'
import { walk } from 'estree-walker'
import { parse } from 'typescript-estree'
import { defu } from 'defu'
import { findExports } from 'mlly'
import type { Nuxt } from '@nuxt/schema'
import { createUnplugin } from 'unplugin'
import MagicString from 'magic-string'
import { normalize } from 'pathe'
// eslint-disable-next-line import/no-restricted-paths
import type { ObjectPlugin, PluginMeta } from '#app'
const internalOrderMap = {
// -50: pre-all (nuxt)
'nuxt-pre-all': -50,
// -40: custom payload revivers (user)
'user-revivers': -40,
// -30: payload reviving (nuxt)
'nuxt-revivers': -30,
// -20: pre (user) <-- pre mapped to this
'user-pre': -20,
// -10: default (nuxt)
'nuxt-default': -10,
// 0: default (user) <-- default behavior
'user-default': 0,
// +10: post (nuxt)
'nuxt-post': 10,
// +20: post (user) <-- post mapped to this
'user-post': 20,
// +30: post-all (nuxt)
'nuxt-post-all': 30
}
export const orderMap: Record<NonNullable<ObjectPlugin['enforce']>, number> = {
pre: internalOrderMap['user-pre'],
default: internalOrderMap['user-default'],
post: internalOrderMap['user-post']
}
const metaCache: Record<string, Omit<PluginMeta, 'enforce'>> = {}
export function extractMetadata (code: string) {
let meta: PluginMeta = {}
if (metaCache[code]) {
return metaCache[code]
}
walk(parse(code) as Node, {
enter (_node) {
if (_node.type !== 'CallExpression' || (_node as CallExpression).callee.type !== 'Identifier') { return }
const node = _node as CallExpression & { start: number, end: number }
const name = 'name' in node.callee && node.callee.name
if (name !== 'defineNuxtPlugin' && name !== 'definePayloadPlugin') { return }
if (name === 'definePayloadPlugin') {
meta.order = internalOrderMap['user-revivers']
}
const metaArg = node.arguments[1]
if (metaArg) {
if (metaArg.type !== 'ObjectExpression') {
throw new Error('Invalid plugin metadata')
}
meta = extractMetaFromObject(metaArg.properties)
}
const plugin = node.arguments[0]
if (plugin.type === 'ObjectExpression') {
meta = defu(extractMetaFromObject(plugin.properties), meta)
}
meta.order = meta.order || orderMap[meta.enforce || 'default'] || orderMap.default
delete meta.enforce
}
})
metaCache[code] = meta
return meta as Omit<PluginMeta, 'enforce'>
}
type PluginMetaKey = keyof PluginMeta
const keys: Record<PluginMetaKey, string> = {
name: 'name',
order: 'order',
enforce: 'enforce'
}
function isMetadataKey (key: string): key is PluginMetaKey {
return key in keys
}
function extractMetaFromObject (properties: Array<Property | SpreadElement>) {
const meta: PluginMeta = {}
for (const property of properties) {
if (property.type === 'SpreadElement' || !('name' in property.key)) {
throw new Error('Invalid plugin metadata')
}
const propertyKey = property.key.name
if (!isMetadataKey(propertyKey)) { continue }
if (property.value.type === 'Literal') {
meta[propertyKey] = property.value.value as any
}
if (property.value.type === 'UnaryExpression' && property.value.argument.type === 'Literal') {
meta[propertyKey] = JSON.parse(property.value.operator + property.value.argument.raw!)
}
}
return meta
}
export const RemovePluginMetadataPlugin = (nuxt: Nuxt) => createUnplugin(() => {
return {
name: 'nuxt:remove-plugin-metadata',
enforce: 'pre',
transform (code, id) {
id = normalize(id)
const plugin = nuxt.apps.default.plugins.find(p => p.src === id)
if (!plugin) { return }
const s = new MagicString(code)
const exports = findExports(code)
const defaultExport = exports.find(e => e.type === 'default' || e.name === 'default')
if (!defaultExport) {
console.error(`[warn] [nuxt] Plugin \`${plugin.src}\` has no default export and will be ignored at build time. Add \`export default defineNuxtPlugin(() => {})\` to your plugin.`)
s.overwrite(0, code.length, 'export default () => {}')
return {
code: s.toString(),
map: nuxt.options.sourcemap.client || nuxt.options.sourcemap.server ? s.generateMap({ hires: true }) : null
}
}
let wrapped = false
try {
walk(parse(code) as Node, {
enter (_node) {
if (_node.type === 'ExportDefaultDeclaration' && (_node.declaration.type === 'FunctionDeclaration' || _node.declaration.type === 'ArrowFunctionExpression')) {
if ('params' in _node.declaration && _node.declaration.params.length > 1) {
console.warn(`[warn] [nuxt] Plugin \`${plugin.src}\` is in legacy Nuxt 2 format (context, inject) which is likely to be broken and will be ignored.`)
s.overwrite(0, code.length, 'export default () => {}')
wrapped = true // silence a duplicate error
return
}
}
if (_node.type !== 'CallExpression' || (_node as CallExpression).callee.type !== 'Identifier') { return }
const node = _node as CallExpression & { start: number, end: number }
const name = 'name' in node.callee && node.callee.name
if (name !== 'defineNuxtPlugin' && name !== 'definePayloadPlugin') { return }
wrapped = true
if (node.arguments[0].type !== 'ObjectExpression') {
// TODO: Warn if legacy plugin format is detected
if ('params' in node.arguments[0] && node.arguments[0].params.length > 1) {
console.warn(`[warn] [nuxt] Plugin \`${plugin.src}\` is in legacy Nuxt 2 format (context, inject) which is likely to be broken and will be ignored.`)
s.overwrite(0, code.length, 'export default () => {}')
return
}
}
// Remove metadata that already has been extracted
if (!('order' in plugin) && !('name' in plugin)) { return }
for (const [argIndex, arg] of node.arguments.entries()) {
if (arg.type !== 'ObjectExpression') { continue }
for (const [propertyIndex, property] of arg.properties.entries()) {
if (property.type === 'SpreadElement' || !('name' in property.key)) { continue }
const propertyKey = property.key.name
if (propertyKey === 'order' || propertyKey === 'enforce' || propertyKey === 'name') {
const nextIndex = arg.properties[propertyIndex + 1]?.range?.[0] || node.arguments[argIndex + 1]?.range?.[0] || (arg.range![1] - 1)
s.remove(property.range![0], nextIndex)
}
}
}
}
})
} catch (e) {
console.error(e)
return
}
if (!wrapped) {
console.warn(`[warn] [nuxt] Plugin \`${plugin.src}\` is not wrapped in \`defineNuxtPlugin\`. It is advised to wrap your plugins as in the future this may enable enhancements.`)
}
if (s.hasChanged()) {
return {
code: s.toString(),
map: nuxt.options.sourcemap.client || nuxt.options.sourcemap.server ? s.generateMap({ hires: true }) : null
}
}
}
}
})

View File

@ -8,6 +8,7 @@ import { camelCase } from 'scule'
import { resolvePath } from 'mlly'
import { filename } from 'pathe/utils'
import type { Nuxt, NuxtApp, NuxtTemplate } from 'nuxt/schema'
import { annotatePlugins } from './app'
export interface TemplateContext {
nuxt: Nuxt
@ -54,8 +55,9 @@ export const cssTemplate: NuxtTemplate<TemplateContext> = {
export const clientPluginTemplate: NuxtTemplate<TemplateContext> = {
filename: 'plugins/client.mjs',
getContents (ctx) {
const clientPlugins = ctx.app.plugins.filter(p => !p.mode || p.mode !== 'server')
async getContents (ctx) {
const clientPlugins = await annotatePlugins(ctx.nuxt, ctx.app.plugins.filter(p => !p.mode || p.mode !== 'server'))
await annotatePlugins(ctx.nuxt, clientPlugins)
const exports: string[] = []
const imports: string[] = []
for (const plugin of clientPlugins) {
@ -73,8 +75,8 @@ export const clientPluginTemplate: NuxtTemplate<TemplateContext> = {
export const serverPluginTemplate: NuxtTemplate<TemplateContext> = {
filename: 'plugins/server.mjs',
getContents (ctx) {
const serverPlugins = ctx.app.plugins.filter(p => !p.mode || p.mode !== 'client')
async getContents (ctx) {
const serverPlugins = await annotatePlugins(ctx.nuxt, ctx.app.plugins.filter(p => !p.mode || p.mode !== 'client'))
const exports: string[] = []
const imports: string[] = []
for (const plugin of serverPlugins) {

View File

@ -0,0 +1,64 @@
import { describe, expect, it } from 'vitest'
import { RemovePluginMetadataPlugin, extractMetadata } from '../src/core/plugins/plugin-metadata'
describe('plugin-metadata', () => {
it('should extract metadata from object-syntax plugins', () => {
const properties = Object.entries({
name: 'test',
enforce: 'post',
hooks: { 'app:mounted': () => {} },
setup: () => {},
order: 1
})
for (const item of properties) {
const obj = [...properties.filter(([key]) => key !== item[0]), item]
const meta = extractMetadata([
'export default defineNuxtPlugin({',
...obj.map(([key, value]) => `${key}: ${typeof value === 'function' ? value.toString() : JSON.stringify(value)},`),
'})'
].join('\n'))
expect(meta).toMatchInlineSnapshot(`
{
"name": "test",
"order": 1,
}
`)
}
})
const transformPlugin: any = RemovePluginMetadataPlugin({
options: { sourcemap: { client: true } },
apps: { default: { plugins: [{ src: 'my-plugin.mjs', order: 10 }] } }
} as any).raw({}, {} as any)
it('should overwrite invalid plugins', () => {
const invalidPlugins = [
'export const plugin = {}',
'export default function (ctx, inject) {}'
]
for (const plugin of invalidPlugins) {
expect(transformPlugin.transform(plugin, 'my-plugin.mjs').code).toBe('export default () => {}')
}
})
it('should remove order/name properties from object-syntax plugins', () => {
const plugin = `
export default defineNuxtPlugin({
name: 'test',
enforce: 'post',
setup: () => {},
}, { order: 10, name: test })
`
expect(transformPlugin.transform(plugin, 'my-plugin.mjs').code).toMatchInlineSnapshot(`
"
export default defineNuxtPlugin({
setup: () => {},
}, { })
"
`)
})
})

View File

@ -52,6 +52,13 @@ export interface NuxtPlugin {
ssr?: boolean
src: string
mode?: 'all' | 'server' | 'client'
/**
* This allows more granular control over plugin order and should only be used by advanced users.
* Lower numbers run first, and user plugins default to `0`.
*
* 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
}
export interface NuxtApp {

View File

@ -433,6 +433,9 @@ importers:
strip-literal:
specifier: ^1.0.1
version: 1.0.1
typescript-estree:
specifier: ^18.1.0
version: 18.1.0(typescript@5.0.4)
ufo:
specifier: ^1.1.2
version: 1.1.2
@ -6156,6 +6159,10 @@ packages:
resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==}
dev: false
/lodash.unescape@4.0.1:
resolution: {integrity: sha512-DhhGRshNS1aX6s5YdBE3njCCouPgnG29ebyHvImlZzXZf2SHgt+J08DHgytTPnpywNbO1Y8mNUFyQuIDBq2JZg==}
dev: false
/lodash.union@4.6.0:
resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==}
@ -7808,6 +7815,11 @@ packages:
/scule@1.0.0:
resolution: {integrity: sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ==}
/semver@5.5.0:
resolution: {integrity: sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==}
hasBin: true
dev: false
/semver@5.7.1:
resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
hasBin: true
@ -8389,6 +8401,18 @@ packages:
is-typed-array: 1.1.10
dev: true
/typescript-estree@18.1.0(typescript@5.0.4):
resolution: {integrity: sha512-LvvwoTuPgMO5UyckFXZeNI+m1UH1bkgAXEaHDEtkmxugtlMKM3mzyvSQUdnQ/FhifwaLuoAsNPqTGLrJI/UUaQ==}
engines: {node: '>=6.14.0'}
deprecated: This package was moved to @typescript-eslint/typescript-estree, please install the latest version from there instead
peerDependencies:
typescript: '*'
dependencies:
lodash.unescape: 4.0.1
semver: 5.5.0
typescript: 5.0.4
dev: false
/typescript@5.0.4:
resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==}
engines: {node: '>=12.20'}

View File

@ -25,7 +25,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
it('default client bundle size', async () => {
stats.client = await analyzeSizes('**/*.js', publicDir)
expect(roundToKilobytes(stats.client.totalBytes)).toMatchInlineSnapshot('"97.3k"')
expect(roundToKilobytes(stats.client.totalBytes)).toMatchInlineSnapshot('"96.7k"')
expect(stats.client.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
[
"_nuxt/entry.js",
@ -35,7 +35,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
it('default server bundle size', async () => {
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"62.0k"')
expect(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"61.0k"')
const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2295k"')

View File

@ -0,0 +1 @@
import 'nonexistent-package'

View File

@ -0,0 +1,3 @@
export default function previousPlugin (one, inject) {
inject()
}