Skip to content

fix(nuxt): use rollup to calculate island component filenames #32421

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 24, 2025

Conversation

Ibra-cesar
Copy link
Contributor

@Ibra-cesar Ibra-cesar commented Jun 17, 2025

…g (#32411)

🔗 Linked issue

resolves #32411

📚 Description

Fix the harcoded build path in componentIsland and dynamic hashing for every change in components.

Example:
initial output 'http://localhost:3000/07332f681020/_nuxt/nJKChgM0-iThgYsLbhDKxBipcnkOFskrNVzUzZPaAts.mjs'
after fix output 'http://localhost:3000/{BuildAssetsDir}/{component-kebabCase}.{[hash]}.mjs'

@Ibra-cesar Ibra-cesar requested a review from danielroe as a code owner June 17, 2025 21:19
Copy link

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

Copy link

coderabbitai bot commented Jun 17, 2025

Walkthrough

The changes refactor the setup and implementation of the ComponentsChunkPlugin used in the componentIslands experimental feature. The plugin logic is consolidated: instead of separate handling for client and server plugins and explicit hooks for Vite config extension, the plugin is now conditionally added as a Vite plugin when selectiveClient is enabled and the builder is Vite. The ComponentsChunkPlugin itself is rewritten to return an array of two Vite plugins, handling chunk emission and mapping generation more directly within Vite's lifecycle. File system writes are removed, and the mapping is now provided via a virtual module. No exported or public entity declarations were removed; a new dev option was added.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eeb18c6 and 573a526.

📒 Files selected for processing (2)
  • packages/nuxt/src/components/module.ts (1 hunks)
  • packages/nuxt/src/components/plugins/islands-transform.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/nuxt/src/components/module.ts
  • packages/nuxt/src/components/plugins/islands-transform.ts
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build
  • GitHub Check: codeql (javascript-typescript)
  • GitHub Check: codeql (actions)
  • GitHub Check: code
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate Unit Tests
  • Create PR with Unit Tests
  • Post Copyable Unit Tests in Comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 0

🔭 Outside diff range comments (1)
packages/nuxt/src/components/plugins/islands-transform.ts (1)

234-241: Race condition: server build may write an empty chunk map

ids is populated in the client build; the server build (where this code runs) can start first, producing an empty component-chunk.mjs. Result: runtime cannot resolve islands.

Fix: derive the map locally (or delay write until all ids exist). Minimal change:

- buildStart () {
+ generateBundle () {
   // ensure map is up-to-date even if client build ran after server build start
   if (ids.size === 0) {
     for (const c of options.getComponents()) {
       if ((c.mode === 'client' || c.mode === 'all') && !ids.has(c.pascalName)) {
         const file = join(finalBuildAssetsDir, `${kebabCase(c.pascalName)}.*.mjs`)
         ids.set(c.pascalName, file)
       }
     }
   }
   writeFileSync(
     join(useNuxt().options.buildDir, 'component-chunk.mjs'),
     `export default {${Array.from(ids.entries()).map(([n, i]) => `${JSON.stringify(n)}: ${JSON.stringify('/' + i)}`).join(',\n')}}`,
   )
 }

This removes the ordering dependency between the two builds.

🧹 Nitpick comments (2)
packages/nuxt/src/components/plugins/islands-transform.ts (2)

189-192: Normalisation helper can be simplified & made safer

The current two-step construction is a little brittle (e.g. '', '.', superfluous double slashes). Using join plus replace(/^\/+|\/+$/g, '') results in cleaner, single-point logic:

-const normalizedBuildAssetsDir = options.buildAssetsDir.startsWith('/') ? options.buildAssetsDir.slice(1) : options.buildAssetsDir + '/'
-const finalBuildAssetsDir = normalizedBuildAssetsDir.endsWith('/') ? normalizedBuildAssetsDir : normalizedBuildAssetsDir + '/'
+const finalBuildAssetsDir = (options.buildAssetsDir || '_nuxt')
+  .replace(/^\/+|\/+$/g, '')   // trim both ends
+  .concat('/')                 // ensure trailing slash

204-208: Blocking I/O in buildStart – switch to async to avoid event-loop stalls

readFileSync is executed once per client/all component. Large component sets will freeze the event loop during bundle start-up. Vite’s this.emitFile is already sync-friendly, so switching to fs.promises.readFile keeps the build responsive:

-const componentsContent = readFileSync(component.filePath, 'utf-8')
+const componentsContent = await fs.promises.readFile(component.filePath, 'utf-8')

Wrap the parent handler in async (Rollup tolerates promises in buildStart).
[note: minor impact in small projects, but projects with thousands of islands will appreciate it]

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b62ede4 and 3a3a728.

📒 Files selected for processing (2)
  • packages/nuxt/src/components/module.ts (1 hunks)
  • packages/nuxt/src/components/plugins/islands-transform.ts (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: codeql (javascript-typescript)
  • GitHub Check: build
  • GitHub Check: codeql (actions)
  • GitHub Check: code
🔇 Additional comments (1)
packages/nuxt/src/components/module.ts (1)

252-252: Path argument looks good – just confirm it’s always defined

nuxt.options.app.buildAssetsDir is forwarded unguarded. Nuxt sets a default ("/_nuxt/"), but if a user explicitly sets it to false/undefined this will now throw at runtime inside the plugin.
A quick null-coalescing fallback avoids any accidental mis-configuration:

-const chunk = ComponentsChunkPlugin({ getComponents, buildAssetsDir: nuxt.options.app.buildAssetsDir })
+const chunk = ComponentsChunkPlugin({
+  getComponents,
+  buildAssetsDir: nuxt.options.app.buildAssetsDir ?? '/_nuxt/'
+})

Copy link

pkg-pr-new bot commented Jun 17, 2025

Open in StackBlitz

@nuxt/kit

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

nuxt

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

@nuxt/rspack-builder

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

@nuxt/schema

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

@nuxt/vite-builder

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

@nuxt/webpack-builder

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

commit: 573a526

Copy link

codspeed-hq bot commented Jun 17, 2025

CodSpeed Performance Report

Merging #32421 will not alter performance

Comparing Ibra-cesar:fix/component-islands-build-paths (573a526) with main (f99b7b0)

Summary

✅ 10 untouched benchmarks

@danielroe danielroe requested a review from huang-julien June 18, 2025 10:29
@danielroe danielroe changed the title fix(components): Fix componentIsland hardcoded build paths and hashin… fix(nuxt): do not hard-code component island paths Jun 18, 2025
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/components/plugins/islands-transform.ts (1)

204-204: Fix the async/await syntax error.

The await keyword cannot be used in a non-async function. The buildStart method needs to be made async.

-        vite: {
-          buildStart () {
+        vite: {
+          async buildStart () {

This matches the pattern suggested in the past review comment and resolves the static analysis error.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a3a728 and e96905b.

📒 Files selected for processing (1)
  • packages/nuxt/src/components/plugins/islands-transform.ts (3 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/nuxt/src/components/plugins/islands-transform.ts

[error] 204-204: await is only allowed within async functions and at the top levels of modules.

(parse)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: codeql (javascript-typescript)
  • GitHub Check: codeql (actions)
🔇 Additional comments (3)
packages/nuxt/src/components/plugins/islands-transform.ts (3)

2-3: LGTM! New imports support the enhanced functionality.

The added imports (readFileSync, hash, kebabCase) are appropriate for the content-based hashing and improved file naming strategy.

Also applies to: 11-11


183-183: Good addition for configurable build assets directory.

Adding buildAssetsDir to the options type aligns with the PR objective to eliminate hardcoded paths.


207-207: Excellent improvement to content-based hashing.

The new file naming strategy using kebabCase(component.pascalName) and hash(componentsContent) effectively addresses the PR objective of dynamic hashing that updates with component changes, replacing the previous hardcoded approach.

@danielroe danielroe added this to the 4.0 milestone Jun 24, 2025
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: 0

🧹 Nitpick comments (1)
packages/nuxt/src/components/plugins/islands-transform.ts (1)

186-196: Consider removing the fallback plugin implementation.

The early return plugin that exports an empty object may be unnecessary complexity. If selective client mode is disabled, the consuming code should handle the absence of chunk mappings gracefully.

Consider simplifying to:

-export const ComponentsChunkPlugin = (options: ChunkPluginOptions) => createUnplugin((_, meta) => {
-  if (meta.framework !== 'vite' || !options.selectiveClient) {
-    return {
-      name: 'nuxt:components-chunk',
-      load (id) {
-        if (id === COMPONENT_CHUNK_ID) {
-          return `export default {}`
-        }
-      },
-    }
-  }
+export const ComponentsChunkPlugin = (options: ChunkPluginOptions) => createUnplugin((_, meta) => {
+  if (meta.framework !== 'vite' || !options.selectiveClient) {
+    return { name: 'nuxt:components-chunk' }
+  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e96905b and eeb18c6.

📒 Files selected for processing (3)
  • packages/nuxt/src/app/components/nuxt-teleport-island-component.ts (1 hunks)
  • packages/nuxt/src/components/module.ts (1 hunks)
  • packages/nuxt/src/components/plugins/islands-transform.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/nuxt/src/app/components/nuxt-teleport-island-component.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/nuxt/src/components/module.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.ts`: Follow standard TypeScript conventions and best practices

**/*.ts: Follow standard TypeScript conventions and best practices

  • packages/nuxt/src/components/plugins/islands-transform.ts
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: codeql (javascript-typescript)
  • GitHub Check: codeql (actions)
  • GitHub Check: build
  • GitHub Check: code
🔇 Additional comments (7)
packages/nuxt/src/components/plugins/islands-transform.ts (7)

7-7: LGTM! Import addition supports the new implementation.

The knitwork utilities are correctly imported and used later for generating JavaScript object literals and strings in the virtual module implementation.


177-181: LGTM! Type definition properly extended.

The addition of dev and selectiveClient boolean properties to ChunkPluginOptions is well-typed and supports the enhanced plugin configuration.


183-184: LGTM! Virtual module constants are well-defined.

The constants for virtual module IDs follow Nuxt conventions and clearly separate the public ID from the resolved internal ID.


198-217: LGTM! Client plugin chunk emission logic is sound.

The client plugin correctly:

  • Applies only to production client builds
  • Emits chunks for components with file paths that aren't server-only
  • Uses appropriate chunk naming convention with Pascal case
  • Preserves strict signatures for proper tree-shaking

218-233: LGTM! Bundle generation handling is correct.

The generateBundle logic properly:

  • Tracks emitted chunk filenames
  • Maps component names to generated filenames
  • Marks component chunks as non-entry points to prevent unwanted code splitting

235-245: LGTM! Virtual module resolution is properly implemented.

The server plugin correctly resolves the virtual module ID using pre-order resolution, which ensures it takes precedence over other resolvers.


246-268: LGTM! Virtual module loading handles both dev and production modes correctly.

The implementation properly:

  • Uses @fs/ prefix for development file system access
  • Uses / prefix for production chunk paths (appropriate for public asset URLs)
  • Leverages knitwork utilities to generate safe JavaScript object literals
  • Filters out server-only components consistently

The path prefixing logic aligns with Vite's asset handling conventions.

@danielroe
Copy link
Member

ported some refactors from #32461 and moved more logic into the vite plugin.

basically we shouldn't be hashing/calculating file names at all as that's something that rollup can happily do for us.

maybe in future we can also use buildAssetsURL in production...

do look to see if you spot any ways to improve (cc: @huang-julien)

@danielroe danielroe changed the title fix(nuxt): do not hard-code component island paths fix(nuxt): use rollup to calculate island component paths Jun 24, 2025
@danielroe danielroe changed the title fix(nuxt): use rollup to calculate island component paths fix(nuxt): use rollup to calculate island component filenames Jun 24, 2025
@danielroe danielroe merged commit 2dafcc7 into nuxt:main Jun 24, 2025
84 of 86 checks passed
@github-actions github-actions bot mentioned this pull request Jun 24, 2025
@github-actions github-actions bot mentioned this pull request Jul 1, 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.

Nuxt componentIslands have hardcoded build location and uses outdated hashes
3 participants