Skip to content

fix(nuxt): ignore #components import mapping inside packages that use it internally#33049

Merged
danielroe merged 9 commits intonuxt:mainfrom
8ctavio:fix/#components-imports-conflict
Sep 4, 2025
Merged

fix(nuxt): ignore #components import mapping inside packages that use it internally#33049
danielroe merged 9 commits intonuxt:mainfrom
8ctavio:fix/#components-imports-conflict

Conversation

@8ctavio
Copy link
Contributor

@8ctavio 8ctavio commented Aug 24, 2025

🔗 Linked issue

Resolves #33032.

📚 Description

This PR adds a check to the components transform plugin's transform hook to detect whether a file is part of a package with a package.json that defines a #components import mapping, in which case it is assumed the file's #components is referencing its package's internal import map.

Currently, a file is not transformed only if it does not contain #components. With this PR, in addition, when #components is found:

  1. the file's package.json file is looked up
  2. if the package.json defines a #components import mapping, the file is not further processed to prevent its #components import from being replaced with nuxt's registered components.

If package.json is not found, or it does not define a #components import mapping, the file is normally processed.

There's also a new test and a simple fixture with only a package.json that defines the #components import mapping. The test ensures that code including #components in a file that is part of the package defined by the fixture's package.json won't get transformed by the components transform plugin.

@8ctavio 8ctavio requested a review from danielroe as a code owner August 24, 2025 03:33
@bolt-new-by-stackblitz
Copy link

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 24, 2025

Walkthrough

  • Modified packages/nuxt/src/components/plugins/transform.ts: added imports isObject (from @vue/shared), isAbsolute (from pathe) and readPackage (from pkg-types); when code contains #components the transformer now, if the source id is an absolute path, calls readPackage(id, { try: true }) and, when the result is an object whose imports (or exports) declares a #components mapping (checked with isObject and Object.hasOwn), returns early to skip the dynamic import modification; package read errors are swallowed. If code does not contain #components, behaviour is unchanged.
  • Added a test in packages/nuxt/test/components-transform.test.ts that uses fileURLToPath, pathe.join and a package fixture virtual path to assert the transformer ignores an internal #components mapping.
  • Updated pnpm-workspace.yaml to exclude packages/nuxt/test/package-fixture from workspace discovery.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b42d432 and ebca1ab.

📒 Files selected for processing (1)
  • packages/nuxt/src/components/plugins/transform.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/nuxt/src/components/plugins/transform.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
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit 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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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
Contributor

@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: 2

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

137-137: Use a safer own-property check

Object.hasOwn is fine on Node 18+, but the canonical guard avoids potential polyfill issues and reads clearly in TS downlevel targets.

-            if (isObject(pkg) && isObject(pkg.imports) && Object.hasOwn(pkg.imports, '#components')) {
+            if (isObject(pkg) && isObject(pkg.imports) && Object.prototype.hasOwnProperty.call(pkg.imports, '#components')) {

116-147: Micro-cache package boundary checks to cut I/O in large repos

We may traverse and read the same package.json multiple times during a build. A tiny in-memory cache keyed by nearest containing dir will reduce repeated FS calls.

Below are minimal additions; happy to wire them if you prefer.

Add near the top of TransformPlugin (inside the factory):

// Cache: dir -> boolean (true means skip transform)
const pkgImportsCache = new Map<string, boolean>()

Then memoise around the traversal:

const cacheKey = dir
const cached = pkgImportsCache.get(cacheKey)
if (cached !== undefined) {
  if (cached) return
} else {
  // after determining `hasInternalMapping`, store it:
  pkgImportsCache.set(cacheKey, /* boolean */ hasInternalMapping)
}
packages/nuxt/test/components-transform.test.ts (2)

28-36: Prefer explicit matcher over snapshot for undefined

Asserting undefined is clearer and avoids snapshot churn.

-    expect(code).toMatchInlineSnapshot(`undefined`)
+    expect(code).toBeUndefined()

34-36: Add a couple more edge-case tests

  • ID with a querystring (e.g. '/foo.vue?vue&type=script') to ensure the traversal still ignores internal #components.
  • Nearest package.json without imports['#components'] to confirm we still transform.

I can add both tests using the existing fixture and a second fixture without the mapping—want me to push a follow-up patch?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5d78366 and d5b64fd.

⛔ Files ignored due to path filters (1)
  • packages/nuxt/test/package-fixture/package.json is excluded by !**/package.json
📒 Files selected for processing (3)
  • packages/nuxt/src/components/plugins/transform.ts (2 hunks)
  • packages/nuxt/test/components-transform.test.ts (2 hunks)
  • pnpm-workspace.yaml (1 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/test/components-transform.test.ts
  • packages/nuxt/src/components/plugins/transform.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

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

Write unit tests for core functionality using vitest

Files:

  • packages/nuxt/test/components-transform.test.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: huang-julien
PR: nuxt/nuxt#29366
File: packages/nuxt/src/app/components/nuxt-root.vue:16-19
Timestamp: 2024-12-12T12:36:34.871Z
Learning: In `packages/nuxt/src/app/components/nuxt-root.vue`, when optimizing bundle size by conditionally importing components based on route metadata, prefer using inline conditional imports like:

```js
const IsolatedPage = route?.meta?.isolate ? defineAsyncComponent(() => import('#build/isolated-page.mjs')) : null
```

instead of wrapping the import in a computed property or importing the component unconditionally.
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.
📚 Learning: 2025-07-18T16:46:07.446Z
Learnt from: CR
PR: nuxt/nuxt#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-18T16:46:07.446Z
Learning: Applies to **/e2e/**/*.{ts,js} : Write end-to-end tests using Playwright and `nuxt/test-utils`

Applied to files:

  • pnpm-workspace.yaml
📚 Learning: 2025-07-18T16:46:07.446Z
Learnt from: CR
PR: nuxt/nuxt#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-18T16:46:07.446Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : Write unit tests for core functionality using `vitest`

Applied to files:

  • packages/nuxt/test/components-transform.test.ts
📚 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/nuxt/src/components/plugins/transform.ts
📚 Learning: 2024-12-12T12:36:34.871Z
Learnt from: huang-julien
PR: nuxt/nuxt#29366
File: packages/nuxt/src/app/components/nuxt-root.vue:16-19
Timestamp: 2024-12-12T12:36:34.871Z
Learning: In `packages/nuxt/src/app/components/nuxt-root.vue`, when optimizing bundle size by conditionally importing components based on route metadata, prefer using inline conditional imports like:

```js
const IsolatedPage = route?.meta?.isolate ? defineAsyncComponent(() => import('#build/isolated-page.mjs')) : null
```

instead of wrapping the import in a computed property or importing the component unconditionally.

Applied to files:

  • packages/nuxt/src/components/plugins/transform.ts
🔇 Additional comments (2)
pnpm-workspace.yaml (1)

6-6: Verify the fixture path before updating the exclusion

It appears that packages/nuxt/test/package-fixture does not exist in the checked-out repository, so the exclusion may be unnecessary or the path could be incorrect.

• Confirm whether the package-fixture directory (and any subfolders) should be present under packages/nuxt/test/.
• If it is indeed intended to exist, update the exclusion to recursively omit all children:

-  - '!packages/nuxt/test/package-fixture'
+  - '!packages/nuxt/test/package-fixture/**'

• If the fixtures live elsewhere or the path has changed, adjust the pattern accordingly or remove the redundant entry.

packages/nuxt/src/components/plugins/transform.ts (1)

1-2: Verify new runtime deps and cross-bundler compatibility

  • Adding exsolve (for resolveModulePath) and @vue/shared (isObject) in this hot path is fine, but please verify:
    • exsolve is already a dependency of the Nuxt package, or add it explicitly.
    • It works in both Vite and Webpack contexts during dev/build (no ESM/CJS interop surprises).

Would you like me to open a small follow-up to pin exsolve in the package manifest and add a minimal smoke test for both bundlers?

Also applies to: 9-10

Copy link
Contributor

@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

🧹 Nitpick comments (2)
packages/nuxt/test/components-transform.test.ts (2)

9-11: Give the virtual path an extension to better emulate real files.

Some transforms in tooling rely on file extensions for heuristics. Using an extension (e.g. .ts) reduces the risk of future false negatives if the transform tightens its guards.

Apply this diff:

-const virtualFilePath = join(pkgPath, 'foo', 'bar', 'baz')
+const virtualFilePath = join(pkgPath, 'foo', 'bar', 'baz.ts')

28-36: Prefer a direct undefined assertion over an inline snapshot

The inline snapshot of undefined can be replaced with a clearer assertion:

-    expect(code).toMatchInlineSnapshot(`undefined`)
+    expect(code).toBeUndefined()

No external fixture verification is required—this change is purely stylistic and reduces snapshot churn.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d5b64fd and 3e0f975.

📒 Files selected for processing (1)
  • packages/nuxt/test/components-transform.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/test/components-transform.test.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

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

Write unit tests for core functionality using vitest

Files:

  • packages/nuxt/test/components-transform.test.ts
🧠 Learnings (2)
📓 Common learnings
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.
Learnt from: huang-julien
PR: nuxt/nuxt#29366
File: packages/nuxt/src/app/components/nuxt-root.vue:16-19
Timestamp: 2024-12-12T12:36:34.871Z
Learning: In `packages/nuxt/src/app/components/nuxt-root.vue`, when optimizing bundle size by conditionally importing components based on route metadata, prefer using inline conditional imports like:

```js
const IsolatedPage = route?.meta?.isolate ? defineAsyncComponent(() => import('#build/isolated-page.mjs')) : null
```

instead of wrapping the import in a computed property or importing the component unconditionally.
📚 Learning: 2025-07-18T16:46:07.446Z
Learnt from: CR
PR: nuxt/nuxt#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-18T16:46:07.446Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : Write unit tests for core functionality using `vitest`

Applied to files:

  • packages/nuxt/test/components-transform.test.ts
🪛 GitHub Check: build
packages/nuxt/test/components-transform.test.ts

[failure] 9-9:
Cannot find name 'fileURLToPath'.

🪛 GitHub Check: code
packages/nuxt/test/components-transform.test.ts

[failure] 9-9: packages/nuxt/test/components-transform.test.ts
ReferenceError: fileURLToPath is not defined
❯ packages/nuxt/test/components-transform.test.ts:9:17

🪛 GitHub Actions: autofix.ci
packages/nuxt/test/components-transform.test.ts

[error] 9-9: ReferenceError: fileURLToPath is not defined. Ensure to import { fileURLToPath } from 'url' in this test setup. Command 'vitest run --project unit -u' failed.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Aug 24, 2025

Open in StackBlitz

@nuxt/kit

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

nuxt

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

@nuxt/rspack-builder

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

@nuxt/schema

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

@nuxt/vite-builder

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

@nuxt/webpack-builder

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

commit: ebca1ab

@codspeed-hq
Copy link

codspeed-hq bot commented Aug 24, 2025

CodSpeed Performance Report

Merging #33049 will not alter performance

Comparing 8ctavio:fix/#components-imports-conflict (ebca1ab) with main (9ed0833)1

Summary

✅ 10 untouched benchmarks

Footnotes

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

@danielroe
Copy link
Member

it feels like we shouldn't reimplement this logic. can we use exsolve to handle it?

@8ctavio 8ctavio marked this pull request as draft August 27, 2025 22:56
@8ctavio 8ctavio force-pushed the fix/#components-imports-conflict branch from ded605d to b5905de Compare August 28, 2025 01:40
@8ctavio
Copy link
Contributor Author

8ctavio commented Aug 28, 2025

it feels like we shouldn't reimplement this logic. can we use exsolve to handle it?

Ah, yes; pkg-types has a readPackage to handle the package.json.

@8ctavio 8ctavio marked this pull request as ready for review August 28, 2025 02:05
Copy link
Contributor

@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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 85da23c and b5905de.

⛔ Files ignored due to path filters (1)
  • packages/nuxt/test/package-fixture/package.json is excluded by !**/package.json
📒 Files selected for processing (3)
  • packages/nuxt/src/components/plugins/transform.ts (2 hunks)
  • packages/nuxt/test/components-transform.test.ts (2 hunks)
  • pnpm-workspace.yaml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • pnpm-workspace.yaml
  • packages/nuxt/test/components-transform.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/components/plugins/transform.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: huang-julien
PR: nuxt/nuxt#29366
File: packages/nuxt/src/app/components/nuxt-root.vue:16-19
Timestamp: 2024-12-12T12:36:34.871Z
Learning: In `packages/nuxt/src/app/components/nuxt-root.vue`, when optimizing bundle size by conditionally importing components based on route metadata, prefer using inline conditional imports like:

```js
const IsolatedPage = route?.meta?.isolate ? defineAsyncComponent(() => import('#build/isolated-page.mjs')) : null
```

instead of wrapping the import in a computed property or importing the component unconditionally.
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.
📚 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/nuxt/src/components/plugins/transform.ts
📚 Learning: 2024-12-12T12:36:34.871Z
Learnt from: huang-julien
PR: nuxt/nuxt#29366
File: packages/nuxt/src/app/components/nuxt-root.vue:16-19
Timestamp: 2024-12-12T12:36:34.871Z
Learning: In `packages/nuxt/src/app/components/nuxt-root.vue`, when optimizing bundle size by conditionally importing components based on route metadata, prefer using inline conditional imports like:

```js
const IsolatedPage = route?.meta?.isolate ? defineAsyncComponent(() => import('#build/isolated-page.mjs')) : null
```

instead of wrapping the import in a computed property or importing the component unconditionally.

Applied to files:

  • packages/nuxt/src/components/plugins/transform.ts
🔇 Additional comments (1)
packages/nuxt/src/components/plugins/transform.ts (1)

1-1: LGTM: importing isObject is appropriate for the runtime guard

No concerns here; usage below is correct.

Copy link
Member

@danielroe danielroe left a comment

Choose a reason for hiding this comment

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

thank you ❤️

@danielroe danielroe merged commit 32d5655 into nuxt:main Sep 4, 2025
47 checks passed
@github-actions github-actions bot mentioned this pull request Sep 4, 2025
danielroe pushed a commit that referenced this pull request Sep 5, 2025
This was referenced Sep 5, 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.

Component imports injection conflict for packages using the #components import mapping internally

2 participants