perf(vite): various performance improvements (#27601)

This commit is contained in:
Daniel Roe 2024-06-13 23:35:00 +01:00 committed by GitHub
parent 02945b9fa6
commit ffdc358561
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 77 additions and 38 deletions

View File

@ -28,6 +28,7 @@
"@types/clear": "0.1.4", "@types/clear": "0.1.4",
"@types/estree": "1.0.5", "@types/estree": "1.0.5",
"@types/fs-extra": "11.0.4", "@types/fs-extra": "11.0.4",
"rollup": "^4.18.0",
"unbuild": "latest", "unbuild": "latest",
"vue": "3.4.27" "vue": "3.4.27"
}, },

View File

@ -163,10 +163,11 @@ export async function buildClient (ctx: ViteBuildContext) {
} }
// We want to respect users' own rollup output options // We want to respect users' own rollup output options
const fileNames = withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, '[hash].js'))
clientConfig.build!.rollupOptions = defu(clientConfig.build!.rollupOptions!, { clientConfig.build!.rollupOptions = defu(clientConfig.build!.rollupOptions!, {
output: { output: {
chunkFileNames: ctx.nuxt.options.dev ? undefined : withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, '[hash].js')), chunkFileNames: ctx.nuxt.options.dev ? undefined : fileNames,
entryFileNames: ctx.nuxt.options.dev ? 'entry.js' : withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, '[hash].js')), entryFileNames: ctx.nuxt.options.dev ? 'entry.js' : fileNames,
} satisfies NonNullable<BuildOptions['rollupOptions']>['output'], } satisfies NonNullable<BuildOptions['rollupOptions']>['output'],
}) as any }) as any
@ -228,7 +229,13 @@ export async function buildClient (ctx: ViteBuildContext) {
}) })
const viteMiddleware = defineEventHandler(async (event) => { const viteMiddleware = defineEventHandler(async (event) => {
const viteRoutes = viteServer.middlewares.stack.map(m => m.route).filter(r => r.length > 1) const viteRoutes: string[] = []
for (const viteRoute of viteServer.middlewares.stack) {
const m = viteRoute.route
if (m.length > 1) {
viteRoutes.push(m)
}
}
if (!event.path.startsWith(clientConfig.base!) && !viteRoutes.some(route => event.path.startsWith(route))) { if (!event.path.startsWith(clientConfig.base!) && !viteRoutes.some(route => event.path.startsWith(route))) {
// @ts-expect-error _skip_transform is a private property // @ts-expect-error _skip_transform is a private property
event.node.req._skip_transform = true event.node.req._skip_transform = true

View File

@ -3,6 +3,8 @@ import type { Nuxt } from '@nuxt/schema'
import type { InlineConfig as ViteConfig } from 'vite' import type { InlineConfig as ViteConfig } from 'vite'
import { distDir } from './dirs' import { distDir } from './dirs'
const lastPlugins = ['autoprefixer', 'cssnano']
export function resolveCSSOptions (nuxt: Nuxt): ViteConfig['css'] { export function resolveCSSOptions (nuxt: Nuxt): ViteConfig['css'] {
const css: ViteConfig['css'] & { postcss: NonNullable<Exclude<NonNullable<ViteConfig['css']>['postcss'], string>> } = { const css: ViteConfig['css'] & { postcss: NonNullable<Exclude<NonNullable<ViteConfig['css']>['postcss'], string>> } = {
postcss: { postcss: {
@ -10,19 +12,22 @@ export function resolveCSSOptions (nuxt: Nuxt): ViteConfig['css'] {
}, },
} }
const lastPlugins = ['autoprefixer', 'cssnano'] css.postcss.plugins = []
css.postcss.plugins = Object.entries(nuxt.options.postcss.plugins)
const plugins = Object.entries(nuxt.options.postcss.plugins)
.sort((a, b) => lastPlugins.indexOf(a[0]) - lastPlugins.indexOf(b[0])) .sort((a, b) => lastPlugins.indexOf(a[0]) - lastPlugins.indexOf(b[0]))
.filter(([, opts]) => opts)
.map(([name, opts]) => { for (const [name, opts] of plugins) {
if (opts) {
const plugin = requireModule(name, { const plugin = requireModule(name, {
paths: [ paths: [
...nuxt.options.modulesDir, ...nuxt.options.modulesDir,
distDir, distDir,
], ],
}) })
return plugin(opts) css.postcss.plugins.push(plugin(opts))
}) }
}
return css return css
} }

View File

@ -238,7 +238,13 @@ export async function initViteDevBundler (ctx: ViteBuildContext, onBuild: () =>
const { code, ids } = await bundleRequest(options, ctx.entry) const { code, ids } = await bundleRequest(options, ctx.entry)
await fse.writeFile(resolve(ctx.nuxt.options.buildDir, 'dist/server/server.mjs'), code, 'utf-8') await fse.writeFile(resolve(ctx.nuxt.options.buildDir, 'dist/server/server.mjs'), code, 'utf-8')
// Have CSS in the manifest to prevent FOUC on dev SSR // Have CSS in the manifest to prevent FOUC on dev SSR
await writeManifest(ctx, ids.filter(isCSS).map(i => i.slice(1))) const manifestIds: string[] = []
for (const i of ids) {
if (isCSS(i)) {
manifestIds.push(i.slice(1))
}
}
await writeManifest(ctx, manifestIds)
const time = (Date.now() - start) const time = (Date.now() - start)
logger.success(`Vite server built in ${time}ms`) logger.success(`Vite server built in ${time}ms`)
await onBuild() await onBuild()

View File

@ -50,11 +50,14 @@ export async function writeManifest (ctx: ViteBuildContext, css: string[] = [])
await fse.mkdirp(serverDist) await fse.mkdirp(serverDist)
if (ctx.config.build?.cssCodeSplit === false) { if (ctx.config.build?.cssCodeSplit === false) {
const entryCSS = Object.values(clientManifest as Record<string, { file?: string }>).find(val => (val).file?.endsWith('.css'))?.file for (const key in clientManifest as Record<string, { file?: string }>) {
if (entryCSS) { const val = clientManifest[key]
const key = relative(ctx.config.root!, ctx.entry) if (val.file?.endsWith('.css')) {
clientManifest[key].css ||= [] const key = relative(ctx.config.root!, ctx.entry)
clientManifest[key].css!.push(entryCSS) clientManifest[key].css ||= []
clientManifest[key].css!.push(val.file)
break
}
} }
} }

View File

@ -3,6 +3,7 @@ import { transform } from 'esbuild'
import { visualizer } from 'rollup-plugin-visualizer' import { visualizer } from 'rollup-plugin-visualizer'
import defu from 'defu' import defu from 'defu'
import type { NuxtOptions } from 'nuxt/schema' import type { NuxtOptions } from 'nuxt/schema'
import type { RenderedModule } from 'rollup'
import type { ViteBuildContext } from '../vite' import type { ViteBuildContext } from '../vite'
export function analyzePlugin (ctx: ViteBuildContext): Plugin[] { export function analyzePlugin (ctx: ViteBuildContext): Plugin[] {
@ -13,14 +14,18 @@ export function analyzePlugin (ctx: ViteBuildContext): Plugin[] {
{ {
name: 'nuxt:analyze-minify', name: 'nuxt:analyze-minify',
async generateBundle (_opts, outputBundle) { async generateBundle (_opts, outputBundle) {
for (const [_bundleId, bundle] of Object.entries(outputBundle)) { for (const _bundleId in outputBundle) {
const bundle = outputBundle[_bundleId]
if (bundle.type !== 'chunk') { continue } if (bundle.type !== 'chunk') { continue }
const originalEntries = Object.entries(bundle.modules) const minifiedModuleEntryPromises: Array<Promise<[string, RenderedModule]>> = []
const minifiedEntries = await Promise.all(originalEntries.map(async ([moduleId, module]) => { for (const moduleId in bundle.modules) {
const { code } = await transform(module.code || '', { minify: true }) const module = bundle.modules[moduleId]
return [moduleId, { ...module, code }] minifiedModuleEntryPromises.push(
})) transform(module.code || '', { minify: true })
bundle.modules = Object.fromEntries(minifiedEntries) .then(result => [moduleId, { ...module, code: result.code }]),
)
}
bundle.modules = Object.fromEntries(await Promise.all(minifiedModuleEntryPromises))
} }
}, },
}, },

View File

@ -23,12 +23,15 @@ const SUPPORTED_EXT_RE = /\.(?:m?[jt]sx?|vue)/
export const composableKeysPlugin = createUnplugin((options: ComposableKeysOptions) => { export const composableKeysPlugin = createUnplugin((options: ComposableKeysOptions) => {
const composableMeta: Record<string, any> = {} const composableMeta: Record<string, any> = {}
const composableLengths = new Set<number>()
const keyedFunctions = new Set<string>()
for (const { name, ...meta } of options.composables) { for (const { name, ...meta } of options.composables) {
composableMeta[name] = meta composableMeta[name] = meta
keyedFunctions.add(name)
composableLengths.add(meta.argumentLength)
} }
const maxLength = Math.max(...options.composables.map(({ argumentLength }) => argumentLength)) const maxLength = Math.max(...composableLengths)
const keyedFunctions = new Set(options.composables.map(({ name }) => name))
const KEYED_FUNCTIONS_RE = new RegExp(`\\b(${[...keyedFunctions].map(f => escapeRE(f)).join('|')})\\b`) const KEYED_FUNCTIONS_RE = new RegExp(`\\b(${[...keyedFunctions].map(f => escapeRE(f)).join('|')})\\b`)
return { return {

View File

@ -25,7 +25,7 @@ export const VitePublicDirsPlugin = createUnplugin((options: { sourcemap?: boole
resolveId: { resolveId: {
enforce: 'post', enforce: 'post',
handler (id) { handler (id) {
if (id === '/__skip_vite' || !id.startsWith('/') || id.startsWith('/@fs')) { return } if (id === '/__skip_vite' || id[0] !== '/' || id.startsWith('/@fs')) { return }
if (resolveFromPublicAssets(id)) { if (resolveFromPublicAssets(id)) {
return PREFIX + encodeURIComponent(id) return PREFIX + encodeURIComponent(id)

View File

@ -66,12 +66,12 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
const { files, inBundle } = cssMap[file] const { files, inBundle } = cssMap[file]
// File has been tree-shaken out of build (or there are no styles to inline) // File has been tree-shaken out of build (or there are no styles to inline)
if (!files.length || !inBundle) { continue } if (!files.length || !inBundle) { continue }
const fileName = filename(file)
const base = typeof outputOptions.assetFileNames === 'string' const base = typeof outputOptions.assetFileNames === 'string'
? outputOptions.assetFileNames ? outputOptions.assetFileNames
: outputOptions.assetFileNames({ : outputOptions.assetFileNames({
type: 'asset', type: 'asset',
name: `${filename(file)}-styles.mjs`, name: `${fileName}-styles.mjs`,
source: '', source: '',
}) })
@ -79,7 +79,7 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
emitted[file] = this.emitFile({ emitted[file] = this.emitFile({
type: 'asset', type: 'asset',
name: `${filename(file)}-styles.mjs`, name: `${fileName}-styles.mjs`,
source: [ source: [
...files.map((css, i) => `import style_${i} from './${relative(baseDir, this.getFileName(css))}';`), ...files.map((css, i) => `import style_${i} from './${relative(baseDir, this.getFileName(css))}';`),
`export default [${files.map((_, i) => `style_${i}`).join(', ')}]`, `export default [${files.map((_, i) => `style_${i}`).join(', ')}]`,

View File

@ -105,7 +105,7 @@ export async function buildServer (ctx: ViteBuildContext) {
if (!ctx.nuxt.options.dev) { if (!ctx.nuxt.options.dev) {
const nitroDependencies = await tryResolveModule('nitropack/package.json', ctx.nuxt.options.modulesDir) const nitroDependencies = await tryResolveModule('nitropack/package.json', ctx.nuxt.options.modulesDir)
.then(r => import(r!)).then(r => Object.keys(r.dependencies || {})).catch(() => []) .then(r => import(r!)).then(r => r.dependencies ? Object.keys(r.dependencies) : []).catch(() => [])
if (Array.isArray(serverConfig.ssr!.external)) { if (Array.isArray(serverConfig.ssr!.external)) {
serverConfig.ssr!.external.push( serverConfig.ssr!.external.push(
// explicit dependencies we use in our ssr renderer - these can be inlined (if necessary) in the nitro build // explicit dependencies we use in our ssr renderer - these can be inlined (if necessary) in the nitro build

View File

@ -10,7 +10,7 @@ interface Envs {
export function transpile (envs: Envs): Array<string | RegExp> { export function transpile (envs: Envs): Array<string | RegExp> {
const nuxt = useNuxt() const nuxt = useNuxt()
const transpile = [] const transpile: Array<string | RegExp> = []
for (let pattern of nuxt.options.build.transpile) { for (let pattern of nuxt.options.build.transpile) {
if (typeof pattern === 'function') { if (typeof pattern === 'function') {

View File

@ -4,6 +4,7 @@ import { dirname, join, normalize, resolve } from 'pathe'
import type { Nuxt, NuxtBuilder, ViteConfig } from '@nuxt/schema' import type { Nuxt, NuxtBuilder, ViteConfig } from '@nuxt/schema'
import { addVitePlugin, isIgnored, logger, resolvePath } from '@nuxt/kit' import { addVitePlugin, isIgnored, logger, resolvePath } from '@nuxt/kit'
import replace from '@rollup/plugin-replace' import replace from '@rollup/plugin-replace'
import type { RollupReplaceOptions } from '@rollup/plugin-replace'
import { sanitizeFilePath } from 'mlly' import { sanitizeFilePath } from 'mlly'
import { withoutLeadingSlash } from 'ufo' import { withoutLeadingSlash } from 'ufo'
import { filename } from 'pathe/utils' import { filename } from 'pathe/utils'
@ -102,10 +103,7 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
rootDir: nuxt.options.rootDir, rootDir: nuxt.options.rootDir,
composables: nuxt.options.optimization.keyedComposables, composables: nuxt.options.optimization.keyedComposables,
}), }),
replace({ replace({ preventAssignment: true, ...globalThisReplacements }),
...Object.fromEntries([';', '(', '{', '}', ' ', '\t', '\n'].map(d => [`${d}global.`, `${d}globalThis.`])),
preventAssignment: true,
}),
virtual(nuxt.vfs), virtual(nuxt.vfs),
], ],
server: { server: {
@ -164,10 +162,16 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
await nuxt.callHook('vite:extend', ctx) await nuxt.callHook('vite:extend', ctx)
nuxt.hook('vite:extendConfig', (config) => { nuxt.hook('vite:extendConfig', (config) => {
config.plugins!.push(replace({ const replaceOptions: RollupReplaceOptions = Object.create(null)
preventAssignment: true, replaceOptions.preventAssignment = true
...Object.fromEntries(Object.entries(config.define!).filter(([key]) => key.startsWith('import.meta.'))),
})) for (const key in config.define!) {
if (key.startsWith('import.meta.')) {
replaceOptions[key] = config.define![key]
}
}
config.plugins!.push(replace(replaceOptions))
}) })
if (!ctx.nuxt.options.dev) { if (!ctx.nuxt.options.dev) {
@ -224,3 +228,5 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
await buildClient(ctx) await buildClient(ctx)
await buildServer(ctx) await buildServer(ctx)
} }
const globalThisReplacements = Object.fromEntries([';', '(', '{', '}', ' ', '\t', '\n'].map(d => [`${d}global.`, `${d}globalThis.`]))

View File

@ -744,6 +744,9 @@ importers:
'@types/fs-extra': '@types/fs-extra':
specifier: 11.0.4 specifier: 11.0.4
version: 11.0.4 version: 11.0.4
rollup:
specifier: ^4.18.0
version: 4.18.0
unbuild: unbuild:
specifier: latest specifier: latest
version: 2.0.0(sass@1.69.4)(typescript@5.4.5) version: 2.0.0(sass@1.69.4)(typescript@5.4.5)