mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 15:15:19 +00:00
perf(vite): various performance improvements (#27601)
This commit is contained in:
parent
02945b9fa6
commit
ffdc358561
@ -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"
|
||||||
},
|
},
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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]
|
||||||
|
if (val.file?.endsWith('.css')) {
|
||||||
const key = relative(ctx.config.root!, ctx.entry)
|
const key = relative(ctx.config.root!, ctx.entry)
|
||||||
clientManifest[key].css ||= []
|
clientManifest[key].css ||= []
|
||||||
clientManifest[key].css!.push(entryCSS)
|
clientManifest[key].css!.push(val.file)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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(', ')}]`,
|
||||||
|
@ -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
|
||||||
|
@ -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') {
|
||||||
|
@ -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.`]))
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user