Skip to content

Conversation

@danielroe
Copy link
Member

@danielroe danielroe commented Sep 29, 2025

🔗 Linked issue

📚 Description

this updates to use a precomputed dependency graph (nuxt-contrib/vue-bundle-renderer#270).

we adopt seroval to deduplicate common strings and significantly save on the otherwise increased space the precomputed dependencies would take up in the server bundle.

we might ship this as an opt-out feature.

@bolt-new-by-stackblitz
Copy link

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Sep 30, 2025

Open in StackBlitz

@nuxt/kit

npm i https://pkg.pr.new/@nuxt/kit@33361

nuxt

npm i https://pkg.pr.new/nuxt@33361

@nuxt/rspack-builder

npm i https://pkg.pr.new/@nuxt/rspack-builder@33361

@nuxt/schema

npm i https://pkg.pr.new/@nuxt/schema@33361

@nuxt/vite-builder

npm i https://pkg.pr.new/@nuxt/vite-builder@33361

@nuxt/webpack-builder

npm i https://pkg.pr.new/@nuxt/webpack-builder@33361

commit: 06cdf3a

@codspeed-hq
Copy link

codspeed-hq bot commented Sep 30, 2025

CodSpeed Performance Report

Merging #33361 will not alter performance

Comparing perf/bundle-precomputed (06cdf3a) with main (27fe465)1

Summary

✅ 10 untouched

Footnotes

  1. No successful run was found on main (df559ad) during the generation of this report, so 27fe465 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@danielroe danielroe marked this pull request as ready for review October 6, 2025 16:13
@coderabbitai
Copy link

coderabbitai bot commented Oct 6, 2025

Walkthrough

Adds generation and consumption of precomputed client dependency data and a Nitro virtual module for entry IDs. Vite and Webpack now emit client.manifest.mjs and client.precomputed.mjs (using precomputeDependencies and serialize). Vite additionally creates and wires a Nitro virtual module '#internal/nuxt/entry-ids.mjs' containing entry IDs; Nitro core also registers a default virtual entry-ids module. Renderer code now imports a static entryIds module and conditionally loads precomputed data (manifest mainly in dev). Webpack client plugin and build outputs are routed into dist/server. Tests updated for a bundle size change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly describes that this change updates Nuxt’s bundle renderer to a prerelease version with precompute support, directly reflecting the core performance enhancement introduced by the pull request.
Description Check ✅ Passed The description clearly explains that the update uses a precomputed dependency graph and adopts seroval for string deduplication, directly aligning with the changes to the bundle renderer and precompute flow introduced in the pull request.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch perf/bundle-precomputed

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
packages/vite/src/manifest.ts (1)

69-72: Parity: also emit client.manifest.json in Vite for DX/tooling

Webpack writes client.manifest.json alongside .mjs files; Vite writes only .mjs. Emitting JSON here keeps outputs consistent and helps debugging and tooling.

Apply:

   const precomputed = precomputeDependencies(manifest)
-  await writeFile(resolve(serverDist, 'client.manifest.mjs'), 'export default ' + serialize(manifest), 'utf8')
+  await writeFile(resolve(serverDist, 'client.manifest.json'), JSON.stringify(manifest, null, 2), 'utf8')
+  await writeFile(resolve(serverDist, 'client.manifest.mjs'), 'export default ' + serialize(manifest), 'utf8')
   await writeFile(resolve(serverDist, 'client.precomputed.mjs'), 'export default ' + serialize(precomputed), 'utf8')
packages/webpack/src/presets/vue.ts (1)

18-24: Optional: set entry IDs via build:manifest hook for Webpack (central place)

You can mirror the Vite approach here to keep logic out of the plugin class.

Outside the shown hunk, add:

// at top
import { useNitro } from '@nuxt/kit'

// inside vue(ctx)
if (!ctx.nuxt.options.dev) {
  const nitro = useNitro()
  ctx.nuxt.hook('build:manifest', (manifest: any) => {
    const ids = Object.values(manifest)
      .filter((c: any) => c?.isEntry && c?.src)
      .map((c: any) => c.src)
    const mod = () => `export default ${JSON.stringify(ids)}`
    nitro.options.virtual['#internal/nuxt/entry-ids.mjs'] = mod
    nitro.options._config.virtual ||= {}
    nitro.options._config.virtual['#internal/nuxt/entry-ids.mjs'] = mod
  })
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca76b89 and efbe202.

⛔ Files ignored due to path filters (5)
  • packages/nuxt/package.json is excluded by !**/package.json
  • packages/rspack/package.json is excluded by !**/package.json
  • packages/vite/package.json is excluded by !**/package.json
  • packages/webpack/package.json is excluded by !**/package.json
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (9)
  • packages/nuxt/src/core/nitro.ts (1 hunks)
  • packages/nuxt/src/core/runtime/nitro/handlers/renderer.ts (3 hunks)
  • packages/nuxt/src/core/runtime/nitro/utils/renderer/build-files.ts (4 hunks)
  • packages/vite/src/manifest.ts (2 hunks)
  • packages/vite/src/vite-node.ts (1 hunks)
  • packages/vite/src/vite.ts (1 hunks)
  • packages/webpack/src/plugins/vue/client.ts (2 hunks)
  • packages/webpack/src/presets/vue.ts (1 hunks)
  • test/bundle.test.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Follow standard TypeScript conventions and best practices

Files:

  • packages/nuxt/src/core/nitro.ts
  • packages/nuxt/src/core/runtime/nitro/handlers/renderer.ts
  • packages/vite/src/vite.ts
  • test/bundle.test.ts
  • packages/vite/src/vite-node.ts
  • packages/nuxt/src/core/runtime/nitro/utils/renderer/build-files.ts
  • packages/vite/src/manifest.ts
  • packages/webpack/src/presets/vue.ts
  • packages/webpack/src/plugins/vue/client.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Write unit tests for core functionality using vitest

Files:

  • test/bundle.test.ts
🧠 Learnings (1)
📚 Learning: 2024-11-05T15:22:54.759Z
Learnt from: GalacticHypernova
PR: nuxt/nuxt#26468
File: packages/nuxt/src/components/plugins/loader.ts:24-24
Timestamp: 2024-11-05T15:22:54.759Z
Learning: In `packages/nuxt/src/components/plugins/loader.ts`, the references to `resolve` and `distDir` are legacy code from before Nuxt used the new unplugin VFS and will be removed.

Applied to files:

  • packages/webpack/src/presets/vue.ts
🧬 Code graph analysis (2)
packages/vite/src/vite.ts (1)
packages/kit/src/index.ts (1)
  • useNitro (37-37)
packages/webpack/src/presets/vue.ts (1)
packages/webpack/src/plugins/vue/client.ts (1)
  • VueSSRClientPlugin (22-143)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: test-fixtures (windows-latest, built, webpack, async, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (windows-latest, built, vite, async, manifest-off, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-on, js, lts/-1)
  • GitHub Check: test-fixtures (windows-latest, built, vite, default, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (windows-latest, dev, vite, default, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, built, webpack, async, manifest-on, json, lts/-1)
  • GitHub Check: test-fixtures (ubuntu-latest, built, rspack, async, manifest-on, json, lts/-1)
🔇 Additional comments (9)
packages/webpack/src/presets/vue.ts (1)

19-20: Plugin options change looks correct

Dropping filename aligns with the client plugin writing to serverDist.

packages/nuxt/src/core/runtime/nitro/handlers/renderer.ts (1)

142-145: Potential duplicate CSS when inlining entry chunk CSS

With inline styles, entryIds are forced into ssrContext.modules. In Vite, non-entry chunks have CSS lists cleared, but entry chunks keep CSS. This may render inlined CSS plus a stylesheet link for the entry chunk. Please double‑check duplication on a minimal page.

Minimal check suggestion: inspect head output for duplicate CSS when features.inlineStyles = true.

packages/vite/src/manifest.ts (1)

7-11: Update package constraints for vue-bundle-renderer and seroval
Require versions that export precomputeDependencies and serialize (by adding or bumping the minimum version in package.json) to prevent missing-API build/runtime failures.

packages/nuxt/src/core/nitro.ts (1)

149-150: Approve default stub for entry IDs
Vite plugin (packages/vite/src/vite.ts:267) populates real entry IDs, overriding this fallback in production.

packages/vite/src/vite.ts (1)

253-270: Centralised entry IDs + Nitro virtual wiring looks good
Entry chunks retain CSS (chunk.css &&= [] only applies to non-entry); verify with a simple route that inlining doesn’t duplicate styles.

packages/vite/src/vite-node.ts (1)

506-514: Dev stub acceptable due to runtime guard
The import.meta.dev check ensures the undefined stub is only used in development and enforces a real precomputed file in production. No changes required.

test/bundle.test.ts (1)

61-61: Snapshot bumps look fine; confirm stability across OS/Node environments
The ∼+1 k increases in all three default server bundle size tests are expected with precompute/seroval. Run the full test suite locally (Node v24.3.0 on macOS/Linux) and in CI to verify consistent snapshot outputs. If you observe drift or flaky failures, replace strict inline snapshots with a range-based assertion (e.g. allow ±1 k).

packages/nuxt/src/core/runtime/nitro/utils/renderer/build-files.ts (2)

28-31: LGTM: precomputed loader mirrors manifest loader.

Implementation is consistent and resilient to default/function exports.


39-47: Revert the precomputed fallback — unsupported by vue-bundle-renderer v2.2.0
vue-bundle-renderer’s createRenderer API in v2.x does not accept a precomputed option; the proposed diff will break renderer creation.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/nuxt/src/core/runtime/nitro/utils/renderer/build-files.ts (1)

64-86: SPA: mirror SSR fallback and catch precomputed import errors.

Without this, SPA can fail when precomputed is disabled/not generated.

-  const precomputed = import.meta.dev ? undefined : await getPrecomputedDependencies()
+  const precomputed = import.meta.dev ? undefined : await getPrecomputedDependencies().catch(() => undefined)
+  const manifest = (import.meta.dev || !precomputed) ? await getClientManifest().catch(() => undefined) : undefined
@@
-  const renderer = createRenderer(() => () => {}, {
-    precomputed,
-    manifest: import.meta.dev ? await getClientManifest() : undefined,
+  const renderer = createRenderer(() => () => {}, {
+    precomputed,
+    manifest,
     renderToString: () => spaTemplate,
     buildAssetsURL,
   })
🧹 Nitpick comments (2)
packages/nuxt/src/core/runtime/nitro/utils/renderer/build-files.ts (2)

22-26: Drop redundant type assertion on the promise.

The return type is already declared as Promise. The trailing assertion is unnecessary.

-  .then(r => typeof r === 'function' ? r() : r) as Promise<Manifest>
+  .then(r => typeof r === 'function' ? r() : r)

27-30: Loader normalisation duplicated; consider a tiny helper.

Both loaders share the same default-or-call pattern. Extracting a small normalise helper keeps them in sync.

Example (outside this range):

const normalise = <T>(p: Promise<any>): Promise<T> =>
  p.then((r) => r.default || r).then((r) => (typeof r === 'function' ? r() : r))

Usage:

const getClientManifest = () => normalise<Manifest>(import('#build/dist/server/client.manifest.mjs'))
const getPrecomputedDependencies = () => normalise<PrecomputedData>(import('#build/dist/server/client.precomputed.mjs'))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between efbe202 and 5a77b11.

⛔ Files ignored due to path filters (5)
  • packages/nuxt/package.json is excluded by !**/package.json
  • packages/rspack/package.json is excluded by !**/package.json
  • packages/vite/package.json is excluded by !**/package.json
  • packages/webpack/package.json is excluded by !**/package.json
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (2)
  • packages/nuxt/src/core/runtime/nitro/utils/renderer/build-files.ts (4 hunks)
  • test/bundle.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/bundle.test.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Follow standard TypeScript conventions and best practices

Files:

  • packages/nuxt/src/core/runtime/nitro/utils/renderer/build-files.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: code
🔇 Additional comments (1)
packages/nuxt/src/core/runtime/nitro/utils/renderer/build-files.ts (1)

4-4: Import source correction looks good.

Using types from vue-bundle-renderer (not Vite) aligns with renderer expectations.

Comment on lines +38 to +47
// Load precomputed dependencies
const precomputed = import.meta.dev ? undefined : await getPrecomputedDependencies()

// Create renderer
const renderer = createRenderer(createSSRApp, {
precomputed,
manifest: import.meta.dev ? await getClientManifest() : undefined,
renderToString,
buildAssetsURL,
}
// Create renderer
const renderer = createRenderer(createSSRApp, options)
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

SSR: add graceful fallback (precomputed → manifest) and handle missing precomputed.

Precomputed may be opt-out or absent; avoid crashing and fall back to manifest.

-  // Load precomputed dependencies
-  const precomputed = import.meta.dev ? undefined : await getPrecomputedDependencies()
+  // Load precomputed dependencies (graceful fallback)
+  const precomputed = import.meta.dev ? undefined : await getPrecomputedDependencies().catch(() => undefined)
+  const manifest = (import.meta.dev || !precomputed) ? await getClientManifest().catch(() => undefined) : undefined
@@
-  const renderer = createRenderer(createSSRApp, {
-    precomputed,
-    manifest: import.meta.dev ? await getClientManifest() : undefined,
+  const renderer = createRenderer(createSSRApp, {
+    precomputed,
+    manifest,
     renderToString,
     buildAssetsURL,
   })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Load precomputed dependencies
const precomputed = import.meta.dev ? undefined : await getPrecomputedDependencies()
// Create renderer
const renderer = createRenderer(createSSRApp, {
precomputed,
manifest: import.meta.dev ? await getClientManifest() : undefined,
renderToString,
buildAssetsURL,
}
// Create renderer
const renderer = createRenderer(createSSRApp, options)
})
// Load precomputed dependencies (graceful fallback)
const precomputed = import.meta.dev
? undefined
: await getPrecomputedDependencies().catch(() => undefined)
const manifest = (import.meta.dev || !precomputed)
? await getClientManifest().catch(() => undefined)
: undefined
// Create renderer
const renderer = createRenderer(createSSRApp, {
precomputed,
manifest,
renderToString,
buildAssetsURL,
})

@danielroe danielroe merged commit 766a637 into main Oct 7, 2025
49 checks passed
@danielroe danielroe deleted the perf/bundle-precomputed branch October 7, 2025 21:15
@github-actions github-actions bot mentioned this pull request Oct 7, 2025
@github-actions github-actions bot mentioned this pull request Oct 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants