feat(nuxi): bundle analyzer (#701)

Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
Daniel Roe 2021-10-21 20:51:44 +01:00 committed by GitHub
parent 694e95b2b4
commit f0b9474b40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 156 additions and 26 deletions

View File

@ -15,9 +15,7 @@ export default {
/** /**
* Nuxt uses `webpack-bundle-analyzer` to visualize your bundles and how to optimize them. * Nuxt uses `webpack-bundle-analyzer` to visualize your bundles and how to optimize them.
* *
* This option is normally enabled by the CLI argument `--analyze`. * Set to `true` to enable bundle analysis, or pass an object with options: [for webpack](https://github.com/webpack-contrib/webpack-bundle-analyzer#options-for-plugin) or [for vite](https://github.com/btd/rollup-plugin-visualizer#options).
*
* Set to `true` to enable bundle analysis, or pass [an object with options](https://github.com/webpack-contrib/webpack-bundle-analyzer#options-for-plugin).
* *
* @example * @example
* ```js * ```js
@ -25,7 +23,6 @@ export default {
* analyzerMode: 'static' * analyzerMode: 'static'
* } * }
* ``` * ```
* @version 2
*/ */
analyze: false, analyze: false,

View File

@ -58,8 +58,8 @@
"pathe": "^0.2.0", "pathe": "^0.2.0",
"pretty-bytes": "^5.6.0", "pretty-bytes": "^5.6.0",
"rollup": "^2.58.0", "rollup": "^2.58.0",
"rollup-plugin-analyzer": "^4.0.0",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"rollup-plugin-visualizer": "^5.5.2",
"serve-placeholder": "^1.2.4", "serve-placeholder": "^1.2.4",
"serve-static": "^1.14.1", "serve-static": "^1.14.1",
"std-env": "^2.3.1", "std-env": "^2.3.1",

View File

@ -4,6 +4,7 @@ import defu from 'defu'
import { createHooks, Hookable, NestedHooks } from 'hookable' import { createHooks, Hookable, NestedHooks } from 'hookable'
import type { Preset } from 'unenv' import type { Preset } from 'unenv'
import type { NuxtHooks, NuxtOptions } from '@nuxt/kit' import type { NuxtHooks, NuxtOptions } from '@nuxt/kit'
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer'
import { tryImport, resolvePath, detectTarget, extendPreset } from './utils' import { tryImport, resolvePath, detectTarget, extendPreset } from './utils'
import * as PRESETS from './presets' import * as PRESETS from './presets'
import type { NodeExternalsOptions } from './rollup/plugins/externals' import type { NodeExternalsOptions } from './rollup/plugins/externals'
@ -28,7 +29,7 @@ export interface NitroContext {
minify: boolean minify: boolean
sourceMap: boolean sourceMap: boolean
externals: boolean | NodeExternalsOptions externals: boolean | NodeExternalsOptions
analyze: boolean analyze: false | PluginVisualizerOptions
entry: string entry: string
node: boolean node: boolean
preset: string preset: string
@ -94,7 +95,7 @@ export function getNitroContext (nuxtOptions: NuxtOptions, input: NitroInput): N
minify: undefined, minify: undefined,
sourceMap: undefined, sourceMap: undefined,
externals: undefined, externals: undefined,
analyze: undefined, analyze: nuxtOptions.build.analyze as any,
entry: undefined, entry: undefined,
node: undefined, node: undefined,
preset: undefined, preset: undefined,

View File

@ -12,7 +12,7 @@ import replace from '@rollup/plugin-replace'
import virtual from '@rollup/plugin-virtual' import virtual from '@rollup/plugin-virtual'
import wasmPlugin from '@rollup/plugin-wasm' import wasmPlugin from '@rollup/plugin-wasm'
import inject from '@rollup/plugin-inject' import inject from '@rollup/plugin-inject'
import analyze from 'rollup-plugin-analyzer' import { visualizer } from 'rollup-plugin-visualizer'
import * as unenv from 'unenv' import * as unenv from 'unenv'
import type { Preset } from 'unenv' import type { Preset } from 'unenv'
@ -309,11 +309,6 @@ export const getRollupConfig = (nitroContext: NitroContext) => {
// https://github.com/rollup/plugins/tree/master/packages/inject // https://github.com/rollup/plugins/tree/master/packages/inject
rollupConfig.plugins.push(inject(env.inject)) rollupConfig.plugins.push(inject(env.inject))
if (nitroContext.analyze) {
// https://github.com/doesdev/rollup-plugin-analyzer
rollupConfig.plugins.push(analyze())
}
// https://github.com/TrySound/rollup-plugin-terser // https://github.com/TrySound/rollup-plugin-terser
// https://github.com/terser/terser#minify-nitroContext // https://github.com/terser/terser#minify-nitroContext
if (nitroContext.minify) { if (nitroContext.minify) {
@ -328,5 +323,14 @@ export const getRollupConfig = (nitroContext: NitroContext) => {
})) }))
} }
if (nitroContext.analyze) {
// https://github.com/btd/rollup-plugin-visualizer
rollupConfig.plugins.push(visualizer({
...nitroContext.analyze,
filename: nitroContext.analyze.filename.replace('{name}', 'nitro'),
title: 'Nitro Server bundle stats'
}))
}
return rollupConfig return rollupConfig
} }

View File

@ -0,0 +1,77 @@
import { promises as fsp } from 'fs'
import { join, resolve } from 'pathe'
import { createApp, lazyHandle } from 'h3'
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer'
import { createServer } from '../utils/server'
import { writeTypes } from '../utils/prepare'
import { loadKit } from '../utils/kit'
import { clearDir } from '../utils/fs'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'analyze',
usage: 'npx nuxi analyze [rootDir]',
description: 'Build nuxt and analyze production bundle (experimental)'
},
async invoke (args) {
process.env.NODE_ENV = process.env.NODE_ENV || 'production'
const rootDir = resolve(args._[0] || '.')
const statsDir = join(rootDir, '.nuxt/stats')
const { loadNuxt, buildNuxt } = await loadKit(rootDir)
const analyzeOptions: PluginVisualizerOptions = {
template: 'treemap',
projectRoot: rootDir,
filename: join(statsDir, '{name}.html')
}
const nuxt = await loadNuxt({
rootDir,
config: {
build: {
// @ts-ignore
analyze: analyzeOptions
}
}
})
await clearDir(nuxt.options.buildDir)
await writeTypes(nuxt)
await buildNuxt(nuxt)
const app = createApp()
const server = createServer(app)
const serveFile = (filePath: string) => lazyHandle(async () => {
const contents = await fsp.readFile(filePath, 'utf-8')
return (_req, res) => { res.end(contents) }
})
console.warn('Do not deploy analyze results! Use `nuxi build` before deployng.')
console.info('Starting stats server...')
app.use('/client', serveFile(join(statsDir, 'client.html')))
app.use('/nitro', serveFile(join(statsDir, 'nitro.html')))
app.use(() => `<!DOCTYPE html>
<html lang="en">
<head>
<title><meta charset="utf-8">Nuxt Bundle Stats (experimental)</title>
</head>
<h1>Nuxt Bundle Stats (experimental)</h1>
<ul>
<li>
<a href="/nitro">Nitro server bundle stats</a>
</li>
<li>
<a href="/client">Client bundle stats</a>
</li>
</ul>
</html>`)
await server.listen()
}
})

View File

@ -5,6 +5,7 @@ const _rDefault = r => r.default || r
export const commands = { export const commands = {
dev: () => import('./dev').then(_rDefault), dev: () => import('./dev').then(_rDefault),
build: () => import('./build').then(_rDefault), build: () => import('./build').then(_rDefault),
analyze: () => import('./analyze').then(_rDefault),
generate: () => import('./generate').then(_rDefault), generate: () => import('./generate').then(_rDefault),
prepare: () => import('./prepare').then(_rDefault), prepare: () => import('./prepare').then(_rDefault),
usage: () => import('./usage').then(_rDefault), usage: () => import('./usage').then(_rDefault),

View File

@ -2,10 +2,10 @@ import type { RequestListener } from 'http'
import type { ListenOptions } from 'listhen' import type { ListenOptions } from 'listhen'
import { loading } from '@nuxt/design' import { loading } from '@nuxt/design'
export function createServer () { export function createServer (defaultApp?) {
const listener = createDynamicFunction(createLoadingHandler('Loading...')) const listener = createDynamicFunction(defaultApp || createLoadingHandler('Loading...'))
async function listen (opts: Partial<ListenOptions>) { async function listen (opts?: Partial<ListenOptions>) {
const { listen } = await import('listhen') const { listen } = await import('listhen')
return listen(listener.call, opts) return listen(listener.call, opts)
} }

View File

@ -32,6 +32,7 @@
"postcss-import": "^14.0.2", "postcss-import": "^14.0.2",
"postcss-import-resolver": "^2.0.0", "postcss-import-resolver": "^2.0.0",
"postcss-url": "^10.1.3", "postcss-url": "^10.1.3",
"rollup-plugin-visualizer": "^5.5.2",
"ufo": "^0.7.9", "ufo": "^0.7.9",
"vite": "^2.6.10" "vite": "^2.6.10"
}, },

View File

@ -5,6 +5,8 @@ import vitePlugin from '@vitejs/plugin-vue'
import viteJsxPlugin from '@vitejs/plugin-vue-jsx' import viteJsxPlugin from '@vitejs/plugin-vue-jsx'
import type { Connect } from 'vite' import type { Connect } from 'vite'
import { visualizer } from 'rollup-plugin-visualizer'
import { transform } from 'esbuild'
import { cacheDirPlugin } from './plugins/cache-dir' import { cacheDirPlugin } from './plugins/cache-dir'
import { replace } from './plugins/replace' import { replace } from './plugins/replace'
import { wpfs } from './utils/wpfs' import { wpfs } from './utils/wpfs'
@ -45,6 +47,33 @@ export async function buildClient (ctx: ViteBuildContext) {
} }
} as ViteOptions) } as ViteOptions)
// Add analyze plugin if needed
if (ctx.nuxt.options.build.analyze) {
clientConfig.plugins.push({
name: 'nuxt-analyze-minify',
async generateBundle (_opts, outputBundle) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const [_bundleId, bundle] of Object.entries(outputBundle)) {
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)
}
return null
}
})
clientConfig.plugins.push(visualizer({
...ctx.nuxt.options.build.analyze as any,
// @ts-ignore
filename: ctx.nuxt.options.build.analyze.filename.replace('{name}', 'client'),
title: 'Client bundle stats',
gzipSize: true
}))
}
await ctx.nuxt.callHook('vite:extendConfig', clientConfig, { isClient: true, isServer: false }) await ctx.nuxt.callHook('vite:extendConfig', clientConfig, { isClient: true, isServer: false })
const viteServer = await vite.createServer(clientConfig) const viteServer = await vite.createServer(clientConfig)

View File

@ -80,7 +80,7 @@ function clientPlugins (ctx: WebpackConfigContext) {
// Webpack Bundle Analyzer // Webpack Bundle Analyzer
// https://github.com/webpack-contrib/webpack-bundle-analyzer // https://github.com/webpack-contrib/webpack-bundle-analyzer
if (!ctx.isDev && options.build.analyze) { if (!ctx.isDev && ctx.name === 'client' && options.build.analyze) {
const statsDir = resolve(options.buildDir, 'stats') const statsDir = resolve(options.buildDir, 'stats')
// @ts-ignore // @ts-ignore

View File

@ -2673,8 +2673,8 @@ __metadata:
pathe: ^0.2.0 pathe: ^0.2.0
pretty-bytes: ^5.6.0 pretty-bytes: ^5.6.0
rollup: ^2.58.0 rollup: ^2.58.0
rollup-plugin-analyzer: ^4.0.0
rollup-plugin-terser: ^7.0.2 rollup-plugin-terser: ^7.0.2
rollup-plugin-visualizer: ^5.5.2
serve-placeholder: ^1.2.4 serve-placeholder: ^1.2.4
serve-static: ^1.14.1 serve-static: ^1.14.1
std-env: ^2.3.1 std-env: ^2.3.1
@ -2815,6 +2815,7 @@ __metadata:
postcss-import: ^14.0.2 postcss-import: ^14.0.2
postcss-import-resolver: ^2.0.0 postcss-import-resolver: ^2.0.0
postcss-url: ^10.1.3 postcss-url: ^10.1.3
rollup-plugin-visualizer: ^5.5.2
ufo: ^0.7.9 ufo: ^0.7.9
unbuild: latest unbuild: latest
vite: ^2.6.10 vite: ^2.6.10
@ -13158,7 +13159,7 @@ fsevents@~2.3.2:
languageName: node languageName: node
linkType: hard linkType: hard
"nanoid@npm:^3.1.23, nanoid@npm:^3.1.30": "nanoid@npm:^3.1.22, nanoid@npm:^3.1.23, nanoid@npm:^3.1.30":
version: 3.1.30 version: 3.1.30
resolution: "nanoid@npm:3.1.30" resolution: "nanoid@npm:3.1.30"
bin: bin:
@ -14027,6 +14028,16 @@ fsevents@~2.3.2:
languageName: node languageName: node
linkType: hard linkType: hard
"open@npm:^7.4.2":
version: 7.4.2
resolution: "open@npm:7.4.2"
dependencies:
is-docker: ^2.0.0
is-wsl: ^2.1.1
checksum: 3333900ec0e420d64c23b831bc3467e57031461d843c801f569b2204a1acc3cd7b3ec3c7897afc9dde86491dfa289708eb92bba164093d8bd88fb2c231843c91
languageName: node
linkType: hard
"open@npm:^8.0.5": "open@npm:^8.0.5":
version: 8.3.0 version: 8.3.0
resolution: "open@npm:8.3.0" resolution: "open@npm:8.3.0"
@ -16906,13 +16917,6 @@ fsevents@~2.3.2:
languageName: node languageName: node
linkType: hard linkType: hard
"rollup-plugin-analyzer@npm:^4.0.0":
version: 4.0.0
resolution: "rollup-plugin-analyzer@npm:4.0.0"
checksum: 72f794f79efe4f620674a48949be9f64dc3cbf601c52ff90ae7cbeaacb604b86bb34321695d0f8f320e5c0b47880022849d92c51d231e1b32c6757a2c627be5e
languageName: node
linkType: hard
"rollup-plugin-dts@npm:^4.0.0": "rollup-plugin-dts@npm:^4.0.0":
version: 4.0.0 version: 4.0.0
resolution: "rollup-plugin-dts@npm:4.0.0" resolution: "rollup-plugin-dts@npm:4.0.0"
@ -16957,6 +16961,22 @@ fsevents@~2.3.2:
languageName: node languageName: node
linkType: hard linkType: hard
"rollup-plugin-visualizer@npm:^5.5.2":
version: 5.5.2
resolution: "rollup-plugin-visualizer@npm:5.5.2"
dependencies:
nanoid: ^3.1.22
open: ^7.4.2
source-map: ^0.7.3
yargs: ^16.2.0
peerDependencies:
rollup: ^2.0.0
bin:
rollup-plugin-visualizer: dist/bin/cli.js
checksum: b8a252c25efcf3dbd17557517768acc43208005dc9e3b805c3411dc226dd6765fc9779bf5c91577e909801a83b5f0bc2f6338e5b715f8ca8b4ecc924b12e8f25
languageName: node
linkType: hard
"rollup-pluginutils@npm:^2.8.2": "rollup-pluginutils@npm:^2.8.2":
version: 2.8.2 version: 2.8.2
resolution: "rollup-pluginutils@npm:2.8.2" resolution: "rollup-pluginutils@npm:2.8.2"