diff --git a/packages/nuxi/src/commands/analyze.ts b/packages/nuxi/src/commands/analyze.ts
index f450897a79..09987c5324 100644
--- a/packages/nuxi/src/commands/analyze.ts
+++ b/packages/nuxi/src/commands/analyze.ts
@@ -2,7 +2,7 @@ import { promises as fsp } from 'node:fs'
import { join, resolve } from 'pathe'
import { createApp, eventHandler, lazyEventHandler, toNodeListener } from 'h3'
import { listen } from 'listhen'
-import { writeTypes } from '../utils/prepare'
+import type { NuxtAnalyzeMeta } from '@nuxt/schema'
import { loadKit } from '../utils/kit'
import { clearDir } from '../utils/fs'
import { overrideEnv } from '../utils/env'
@@ -11,62 +11,99 @@ import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
meta: {
name: 'analyze',
- usage: 'npx nuxi analyze [--log-level] [rootDir]',
+ usage: 'npx nuxi analyze [--log-level] [--name] [--no-serve] [rootDir]',
description: 'Build nuxt and analyze production bundle (experimental)'
},
async invoke (args) {
overrideEnv('production')
+ const name = args.name || 'default'
+ const slug = name.trim().replace(/[^a-z0-9_-]/gi, '_')
const rootDir = resolve(args._[0] || '.')
- const statsDir = join(rootDir, '.nuxt/stats')
+
+ let analyzeDir = join(rootDir, '.nuxt/analyze', slug)
+ let buildDir = join(analyzeDir, '.nuxt')
+ let outDir = join(analyzeDir, '.output')
+
+ const startTime = Date.now()
const { loadNuxt, buildNuxt } = await loadKit(rootDir)
const nuxt = await loadNuxt({
rootDir,
overrides: {
- build: { analyze: true },
+ build: {
+ analyze: true
+ },
+ analyzeDir,
+ buildDir,
+ nitro: {
+ output: {
+ dir: outDir
+ }
+ },
logLevel: args['log-level']
}
})
- await clearDir(nuxt.options.buildDir)
- await writeTypes(nuxt)
+ analyzeDir = nuxt.options.analyzeDir
+ buildDir = nuxt.options.buildDir
+ outDir = nuxt.options.nitro.output?.dir || outDir
+
+ await clearDir(analyzeDir)
await buildNuxt(nuxt)
- const app = createApp()
+ const endTime = Date.now()
- const serveFile = (filePath: string) => lazyEventHandler(async () => {
- const contents = await fsp.readFile(filePath, 'utf-8')
- return eventHandler((event) => { event.node.res.end(contents) })
- })
+ const meta: NuxtAnalyzeMeta = {
+ name,
+ slug,
+ startTime,
+ endTime,
+ analyzeDir,
+ buildDir,
+ outDir
+ }
+ await nuxt.callHook('build:analyze:done', meta)
+ await fsp.writeFile(join(analyzeDir, 'meta.json'), JSON.stringify(meta, null, 2), 'utf-8')
+
+ console.info('Analyze results are available at: `' + analyzeDir + '`')
console.warn('Do not deploy analyze results! Use `nuxi build` before deploying.')
- console.info('Starting stats server...')
+ if (args.serve !== false && !process.env.CI) {
+ const app = createApp()
- app.use('/client', serveFile(join(statsDir, 'client.html')))
- app.use('/nitro', serveFile(join(statsDir, 'nitro.html')))
- app.use(eventHandler(() => `
-
-
-
- Nuxt Bundle Stats (experimental)
-
- Nuxt Bundle Stats (experimental)
-
-
- `))
+ const serveFile = (filePath: string) => lazyEventHandler(async () => {
+ const contents = await fsp.readFile(filePath, 'utf-8')
+ return eventHandler((event) => { event.node.res.end(contents) })
+ })
- await listen(toNodeListener(app))
+ console.info('Starting stats server...')
- return 'wait' as const
+ app.use('/client', serveFile(join(analyzeDir, 'client.html')))
+ app.use('/nitro', serveFile(join(analyzeDir, 'nitro.html')))
+ app.use(eventHandler(() => `
+
+
+
+ Nuxt Bundle Stats (experimental)
+
+ Nuxt Bundle Stats (experimental)
+
+
+ `))
+
+ await listen(toNodeListener(app))
+
+ return 'wait' as const
+ }
}
})
diff --git a/packages/nuxi/src/commands/build.ts b/packages/nuxi/src/commands/build.ts
index dc2c2ab545..a582d1209d 100644
--- a/packages/nuxi/src/commands/build.ts
+++ b/packages/nuxi/src/commands/build.ts
@@ -2,7 +2,7 @@ import { relative, resolve } from 'pathe'
import { consola } from 'consola'
import { writeTypes } from '../utils/prepare'
import { loadKit } from '../utils/kit'
-import { clearDir } from '../utils/fs'
+import { clearBuildDir } from '../utils/fs'
import { overrideEnv } from '../utils/env'
import { showVersions } from '../utils/banner'
import { defineNuxtCommand } from './index'
@@ -36,7 +36,7 @@ export default defineNuxtCommand({
// Use ? for backward compatibility for Nuxt <= RC.10
const nitro = useNitro?.()
- await clearDir(nuxt.options.buildDir)
+ await clearBuildDir(nuxt.options.buildDir)
await writeTypes(nuxt)
diff --git a/packages/nuxi/src/commands/dev.ts b/packages/nuxi/src/commands/dev.ts
index ee527aff48..b2c488de67 100644
--- a/packages/nuxi/src/commands/dev.ts
+++ b/packages/nuxi/src/commands/dev.ts
@@ -12,7 +12,8 @@ import { writeTypes } from '../utils/prepare'
import { loadKit } from '../utils/kit'
import { importModule } from '../utils/esm'
import { overrideEnv } from '../utils/env'
-import { cleanupNuxtDirs, loadNuxtManifest, writeNuxtManifest } from '../utils/nuxt'
+import { loadNuxtManifest, writeNuxtManifest } from '../utils/nuxt'
+import { clearBuildDir } from '../utils/fs'
import { defineNuxtCommand } from './index'
export default defineNuxtCommand({
@@ -110,7 +111,7 @@ export default defineNuxtCommand({
const previousManifest = await loadNuxtManifest(currentNuxt.options.buildDir)
const newManifest = await writeNuxtManifest(currentNuxt)
if (previousManifest && newManifest && previousManifest._hash !== newManifest._hash) {
- await cleanupNuxtDirs(currentNuxt.options.rootDir)
+ await clearBuildDir(currentNuxt.options.buildDir)
}
}
diff --git a/packages/nuxi/src/commands/prepare.ts b/packages/nuxi/src/commands/prepare.ts
index 4e194e03eb..43cd2f0561 100644
--- a/packages/nuxi/src/commands/prepare.ts
+++ b/packages/nuxi/src/commands/prepare.ts
@@ -1,6 +1,6 @@
import { relative, resolve } from 'pathe'
import { consola } from 'consola'
-import { clearDir } from '../utils/fs'
+import { clearBuildDir } from '../utils/fs'
import { loadKit } from '../utils/kit'
import { writeTypes } from '../utils/prepare'
import { defineNuxtCommand } from './index'
@@ -23,7 +23,7 @@ export default defineNuxtCommand({
logLevel: args['log-level']
}
})
- await clearDir(nuxt.options.buildDir)
+ await clearBuildDir(nuxt.options.buildDir)
await buildNuxt(nuxt)
await writeTypes(nuxt)
diff --git a/packages/nuxi/src/utils/fs.ts b/packages/nuxi/src/utils/fs.ts
index 857cb47f2d..7f8640eb4f 100644
--- a/packages/nuxi/src/utils/fs.ts
+++ b/packages/nuxi/src/utils/fs.ts
@@ -1,5 +1,5 @@
-import { promises as fsp } from 'node:fs'
-import { dirname } from 'pathe'
+import { existsSync, promises as fsp } from 'node:fs'
+import { dirname, join } from 'pathe'
import { consola } from 'consola'
// Check if a file exists
@@ -12,11 +12,24 @@ export async function exists (path: string) {
}
}
-export async function clearDir (path: string) {
- await fsp.rm(path, { recursive: true, force: true })
+export async function clearDir (path: string, exclude?: string[]) {
+ if (!exclude) {
+ await fsp.rm(path, { recursive: true, force: true })
+ } else if (existsSync(path)) {
+ const files = await fsp.readdir(path)
+ await Promise.all(files.map(async (name) => {
+ if (!exclude.includes(name)) {
+ await fsp.rm(join(path, name), { recursive: true, force: true })
+ }
+ }))
+ }
await fsp.mkdir(path, { recursive: true })
}
+export function clearBuildDir (path: string) {
+ return clearDir(path, ['cache', 'analyze'])
+}
+
export async function rmRecursive (paths: string[]) {
await Promise.all(paths.filter(p => typeof p === 'string').map(async (path) => {
consola.debug('Removing recursive path', path)
diff --git a/packages/nuxt/src/core/nitro.ts b/packages/nuxt/src/core/nitro.ts
index 4e0e5dce9f..10d0ba12b6 100644
--- a/packages/nuxt/src/core/nitro.ts
+++ b/packages/nuxt/src/core/nitro.ts
@@ -65,7 +65,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
analyze: nuxt.options.build.analyze && {
template: 'treemap',
projectRoot: nuxt.options.rootDir,
- filename: join(nuxt.options.rootDir, '.nuxt/stats', '{name}.html')
+ filename: join(nuxt.options.analyzeDir, '{name}.html')
},
scanDirs: nuxt.options._layers.map(layer => (layer.config.serverDir || layer.config.srcDir) && resolve(layer.cwd, layer.config.serverDir || resolve(layer.config.srcDir, 'server'))).filter(Boolean),
renderer: resolve(distDir, 'core/runtime/nitro/renderer'),
diff --git a/packages/schema/src/config/build.ts b/packages/schema/src/config/build.ts
index c905fae4ce..cccdd038fa 100644
--- a/packages/schema/src/config/build.ts
+++ b/packages/schema/src/config/build.ts
@@ -119,10 +119,11 @@ export default defineUntypedSchema({
return val ?? false
}
const rootDir = await get('rootDir')
+ const analyzeDir = await get('analyzeDir')
return {
template: 'treemap',
projectRoot: rootDir,
- filename: join(rootDir, '.nuxt/stats', '{name}.html')
+ filename: join(analyzeDir, '{name}.html')
}
}
},
diff --git a/packages/schema/src/config/common.ts b/packages/schema/src/config/common.ts
index bc0437e454..16ebeaeef0 100644
--- a/packages/schema/src/config/common.ts
+++ b/packages/schema/src/config/common.ts
@@ -117,20 +117,20 @@ export default defineUntypedSchema({
},
/**
- * Used to set the modules directories for path resolving (for example, webpack's
- * `resolveLoading`, `nodeExternals` and `postcss`).
- *
- * The configuration path is relative to `options.rootDir` (default is current working directory).
- *
- * Setting this field may be necessary if your project is organized as a yarn workspace-styled mono-repository.
- *
- * @example
- * ```js
- * export default {
- * modulesDir: ['../../node_modules']
- * }
- * ```
- */
+ * Used to set the modules directories for path resolving (for example, webpack's
+ * `resolveLoading`, `nodeExternals` and `postcss`).
+ *
+ * The configuration path is relative to `options.rootDir` (default is current working directory).
+ *
+ * Setting this field may be necessary if your project is organized as a yarn workspace-styled mono-repository.
+ *
+ * @example
+ * ```js
+ * export default {
+ * modulesDir: ['../../node_modules']
+ * }
+ * ```
+ */
modulesDir: {
$default: ['node_modules'],
$resolve: async (val, get) => [
@@ -139,6 +139,17 @@ export default defineUntypedSchema({
]
},
+ /**
+ * The directory where Nuxt will store the generated files when running `nuxt analyze`.
+ *
+ * If a relative path is specified, it will be relative to your `rootDir`.
+ */
+ analyzeDir: {
+ $resolve: async (val, get) => val
+ ? resolve(await get('rootDir'), val)
+ : resolve(await get('buildDir'), 'analyze')
+ },
+
/**
* Whether Nuxt is running in development mode.
*
@@ -346,6 +357,7 @@ export default defineUntypedSchema({
'**/*.d.ts', // ignore type declarations
'.output',
'.git',
+ await get('analyzeDir'),
await get('ignorePrefix') && `**/${await get('ignorePrefix')}*.*`
].concat(val).filter(Boolean)
},
diff --git a/packages/schema/src/config/webpack.ts b/packages/schema/src/config/webpack.ts
index edd3cf4689..6675d44a56 100644
--- a/packages/schema/src/config/webpack.ts
+++ b/packages/schema/src/config/webpack.ts
@@ -22,10 +22,11 @@ export default defineUntypedSchema({
return val ?? false
}
const rootDir = await get('rootDir')
+ const analyzeDir = await get('analyzeDir')
return {
template: 'treemap',
projectRoot: rootDir,
- filename: join(rootDir, '.nuxt/stats', '{name}.html')
+ filename: join(analyzeDir, '{name}.html')
}
}
},
diff --git a/packages/schema/src/types/hooks.ts b/packages/schema/src/types/hooks.ts
index b11ed93fa6..e314336975 100644
--- a/packages/schema/src/types/hooks.ts
+++ b/packages/schema/src/types/hooks.ts
@@ -47,6 +47,16 @@ export interface GenerateAppOptions {
filter?: (template: ResolvedNuxtTemplate) => boolean
}
+export interface NuxtAnalyzeMeta {
+ name: string
+ slug: string
+ startTime: number
+ endTime: number
+ analyzeDir: string
+ buildDir: string
+ outDir: string
+}
+
/**
* The listeners to Nuxt build time events
*/
@@ -131,6 +141,13 @@ export interface NuxtHooks {
*/
'build:manifest': (manifest: Manifest) => HookResult
+ /**
+ * Called when `nuxt analyze` is finished
+ * @param meta the analyze meta object, mutations will be saved to `meta.json`
+ * @returns Promise
+ */
+ 'build:analyze:done': (meta: NuxtAnalyzeMeta) => HookResult
+
/**
* Called before generating the app.
* @param options GenerateAppOptions object