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/estree": "1.0.5",
"@types/fs-extra": "11.0.4",
"rollup": "^4.18.0",
"unbuild": "latest",
"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
const fileNames = withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, '[hash].js'))
clientConfig.build!.rollupOptions = defu(clientConfig.build!.rollupOptions!, {
output: {
chunkFileNames: ctx.nuxt.options.dev ? undefined : withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, '[hash].js')),
entryFileNames: ctx.nuxt.options.dev ? 'entry.js' : withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, '[hash].js')),
chunkFileNames: ctx.nuxt.options.dev ? undefined : fileNames,
entryFileNames: ctx.nuxt.options.dev ? 'entry.js' : fileNames,
} satisfies NonNullable<BuildOptions['rollupOptions']>['output'],
}) as any
@ -228,7 +229,13 @@ export async function buildClient (ctx: ViteBuildContext) {
})
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))) {
// @ts-expect-error _skip_transform is a private property
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 { distDir } from './dirs'
const lastPlugins = ['autoprefixer', 'cssnano']
export function resolveCSSOptions (nuxt: Nuxt): ViteConfig['css'] {
const css: ViteConfig['css'] & { postcss: NonNullable<Exclude<NonNullable<ViteConfig['css']>['postcss'], string>> } = {
postcss: {
@ -10,19 +12,22 @@ export function resolveCSSOptions (nuxt: Nuxt): ViteConfig['css'] {
},
}
const lastPlugins = ['autoprefixer', 'cssnano']
css.postcss.plugins = Object.entries(nuxt.options.postcss.plugins)
css.postcss.plugins = []
const plugins = Object.entries(nuxt.options.postcss.plugins)
.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, {
paths: [
...nuxt.options.modulesDir,
distDir,
],
})
return plugin(opts)
})
css.postcss.plugins.push(plugin(opts))
}
}
return css
}

View File

@ -238,7 +238,13 @@ export async function initViteDevBundler (ctx: ViteBuildContext, onBuild: () =>
const { code, ids } = await bundleRequest(options, ctx.entry)
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
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)
logger.success(`Vite server built in ${time}ms`)
await onBuild()

View File

@ -50,11 +50,14 @@ export async function writeManifest (ctx: ViteBuildContext, css: string[] = [])
await fse.mkdirp(serverDist)
if (ctx.config.build?.cssCodeSplit === false) {
const entryCSS = Object.values(clientManifest as Record<string, { file?: string }>).find(val => (val).file?.endsWith('.css'))?.file
if (entryCSS) {
const key = relative(ctx.config.root!, ctx.entry)
clientManifest[key].css ||= []
clientManifest[key].css!.push(entryCSS)
for (const key in clientManifest as Record<string, { file?: string }>) {
const val = clientManifest[key]
if (val.file?.endsWith('.css')) {
const key = relative(ctx.config.root!, ctx.entry)
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 defu from 'defu'
import type { NuxtOptions } from 'nuxt/schema'
import type { RenderedModule } from 'rollup'
import type { ViteBuildContext } from '../vite'
export function analyzePlugin (ctx: ViteBuildContext): Plugin[] {
@ -13,14 +14,18 @@ export function analyzePlugin (ctx: ViteBuildContext): Plugin[] {
{
name: 'nuxt:analyze-minify',
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 }
const originalEntries = Object.entries(bundle.modules)
const minifiedEntries = await Promise.all(originalEntries.map(async ([moduleId, module]) => {
const { code } = await transform(module.code || '', { minify: true })
return [moduleId, { ...module, code }]
}))
bundle.modules = Object.fromEntries(minifiedEntries)
const minifiedModuleEntryPromises: Array<Promise<[string, RenderedModule]>> = []
for (const moduleId in bundle.modules) {
const module = bundle.modules[moduleId]
minifiedModuleEntryPromises.push(
transform(module.code || '', { minify: true })
.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) => {
const composableMeta: Record<string, any> = {}
const composableLengths = new Set<number>()
const keyedFunctions = new Set<string>()
for (const { name, ...meta } of options.composables) {
composableMeta[name] = meta
keyedFunctions.add(name)
composableLengths.add(meta.argumentLength)
}
const maxLength = Math.max(...options.composables.map(({ argumentLength }) => argumentLength))
const keyedFunctions = new Set(options.composables.map(({ name }) => name))
const maxLength = Math.max(...composableLengths)
const KEYED_FUNCTIONS_RE = new RegExp(`\\b(${[...keyedFunctions].map(f => escapeRE(f)).join('|')})\\b`)
return {

View File

@ -25,7 +25,7 @@ export const VitePublicDirsPlugin = createUnplugin((options: { sourcemap?: boole
resolveId: {
enforce: 'post',
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)) {
return PREFIX + encodeURIComponent(id)

View File

@ -66,12 +66,12 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
const { files, inBundle } = cssMap[file]
// File has been tree-shaken out of build (or there are no styles to inline)
if (!files.length || !inBundle) { continue }
const fileName = filename(file)
const base = typeof outputOptions.assetFileNames === 'string'
? outputOptions.assetFileNames
: outputOptions.assetFileNames({
type: 'asset',
name: `${filename(file)}-styles.mjs`,
name: `${fileName}-styles.mjs`,
source: '',
})
@ -79,7 +79,7 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
emitted[file] = this.emitFile({
type: 'asset',
name: `${filename(file)}-styles.mjs`,
name: `${fileName}-styles.mjs`,
source: [
...files.map((css, i) => `import style_${i} from './${relative(baseDir, this.getFileName(css))}';`),
`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) {
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)) {
serverConfig.ssr!.external.push(
// 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> {
const nuxt = useNuxt()
const transpile = []
const transpile: Array<string | RegExp> = []
for (let pattern of nuxt.options.build.transpile) {
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 { addVitePlugin, isIgnored, logger, resolvePath } from '@nuxt/kit'
import replace from '@rollup/plugin-replace'
import type { RollupReplaceOptions } from '@rollup/plugin-replace'
import { sanitizeFilePath } from 'mlly'
import { withoutLeadingSlash } from 'ufo'
import { filename } from 'pathe/utils'
@ -102,10 +103,7 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
rootDir: nuxt.options.rootDir,
composables: nuxt.options.optimization.keyedComposables,
}),
replace({
...Object.fromEntries([';', '(', '{', '}', ' ', '\t', '\n'].map(d => [`${d}global.`, `${d}globalThis.`])),
preventAssignment: true,
}),
replace({ preventAssignment: true, ...globalThisReplacements }),
virtual(nuxt.vfs),
],
server: {
@ -164,10 +162,16 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
await nuxt.callHook('vite:extend', ctx)
nuxt.hook('vite:extendConfig', (config) => {
config.plugins!.push(replace({
preventAssignment: true,
...Object.fromEntries(Object.entries(config.define!).filter(([key]) => key.startsWith('import.meta.'))),
}))
const replaceOptions: RollupReplaceOptions = Object.create(null)
replaceOptions.preventAssignment = true
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) {
@ -224,3 +228,5 @@ export const bundle: NuxtBuilder['bundle'] = async (nuxt) => {
await buildClient(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':
specifier: 11.0.4
version: 11.0.4
rollup:
specifier: ^4.18.0
version: 4.18.0
unbuild:
specifier: latest
version: 2.0.0(sass@1.69.4)(typescript@5.4.5)