-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
fix(nuxt): correct layer priority order for extends and auto-scanned layers #33654
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
Conversation
|
|
@nuxt/kit
nuxt
@nuxt/rspack-builder
@nuxt/schema
@nuxt/vite-builder
@nuxt/webpack-builder
commit: |
WalkthroughThis pull request changes component layer prioritisation in Nuxt: the module now iterates nuxt.options._layers with an index-aware loop and computes a per-layer priority as Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Pre-merge checks and finishing touches✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
CodSpeed Performance ReportMerging #33654 will degrade performances by 11.24%Comparing Summary
Benchmarks breakdown
|
|
autoscanned layers should have higher priority than layers specified in I'm pretty sure the docs are correct but if they are confusing we can update.
for this example, 2 overrides 1, and 3 overrides 2. |
|
Okay, I think I understand now... But one question: What's the point of using For example, if I have:
Does the I don't want to force a change – especially since that would be a break from the original behavior – it just seems a bit contradictory to me. When would you use |
|
the point of if you need order in your local layers, do |
|
Okay, I'll take another look at it. There's a minor bug in module.ts where all non-root layers get the same priority (0), which means the layer order isn't respected for component resolution. The bigger issue, in my opinion, is the misleading documentation. The numbered list (1, 2, 3) reads like a priority order (highest to lowest), but it's actually the processing order where later entries override earlier ones. I'd like to improve the wording to make this clearer. |
There was a problem hiding this 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 (2)
docs/1.getting-started/14.layers.md (1)
91-95: Optional: Reduce word repetition in "When to Use Each" bullets.The word "directory" appears at the end of the first bullet and start of the second, which can be smoothed by rewording one instance. For example, change the second bullet to:
- **`~~/layers` folder** - Use for local layers that are part of your projectAlternatively, consider:
- **Auto-scanned `~~/layers`** - Use for local layers that are part of your projectBoth approaches preserve meaning while avoiding the repetition.
docs/2.guide/3.going-further/7.layers.md (1)
71-110: Documentation is consistent with getting‑started guide; same minor word repetition opportunity exists.The Layer Priority section, When to Use Each guidance, and example scenario mirror the content in docs/1.getting-started/14.layers.md. The same word repetition of "directory" appears in the bullet points (lines ~81–82) and could be improved using the refactor suggested for file 1. Consider applying the refactor to both files simultaneously for consistency.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
docs/1.getting-started/14.layers.md(1 hunks)docs/2.guide/3.going-further/7.layers.md(1 hunks)packages/nuxt/src/components/module.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/nuxt/src/components/module.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-11T12:34:22.648Z
Learnt from: Tofandel
Repo: nuxt/nuxt PR: 0
File: :0-0
Timestamp: 2024-11-11T12:34:22.648Z
Learning: Ensure that AI-generated summaries accurately reflect the key changes in the PR, focusing on notable changes such as the removal of unused imports and variables starting with underscores.
Applied to files:
docs/1.getting-started/14.layers.mddocs/2.guide/3.going-further/7.layers.md
🪛 LanguageTool
docs/1.getting-started/14.layers.md
[duplication] ~93-~93: Possible typo: you repeated a word.
Context: ...itories) or layers outside your project directory - ~~/layers directory - Use for local layers that are part ...
(ENGLISH_WORD_REPEAT_RULE)
[uncategorized] ~95-~95: Loose punctuation mark.
Context: ...l layers that are part of your project ::tip If you need to control the order of...
(UNLIKELY_OPENING_PUNCTUATION)
[uncategorized] ~97-97: Possible missing comma found./layers/2.a-layer
Context: .../1.z-layer, . This way 2.a-layer` will have higher priority t...
(AI_HYDRA_LEO_MISSING_COMMA)
[uncategorized] ~97-~97: Loose punctuation mark.
Context: ... have higher priority than 1.z-layer. :: ### Example ```ts [nuxt.config.ts] e...
(UNLIKELY_OPENING_PUNCTUATION)
docs/2.guide/3.going-further/7.layers.md
[duplication] ~81-~81: Possible typo: you repeated a word.
Context: ...itories) or layers outside your project directory - ~~/layers directory - Use for local layers that are part ...
(ENGLISH_WORD_REPEAT_RULE)
[uncategorized] ~83-~83: Loose punctuation mark.
Context: ...l layers that are part of your project ::tip If you need to control the order of...
(UNLIKELY_OPENING_PUNCTUATION)
[uncategorized] ~85-85: Possible missing comma found./layers/2.a-layer
Context: .../1.z-layer, . This way 2.a-layer` will have higher priority t...
(AI_HYDRA_LEO_MISSING_COMMA)
[uncategorized] ~85-~85: Loose punctuation mark.
Context: ... have higher priority than 1.z-layer. :: ### Example ```ts [nuxt.config.ts] e...
(UNLIKELY_OPENING_PUNCTUATION)
⏰ 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). (16)
- GitHub Check: test-fixtures (windows-latest, built, rspack, default, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (windows-latest, dev, vite, default, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (windows-latest, built, vite-env-api, default, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (windows-latest, built, vite-env-api, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (windows-latest, dev, vite, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (windows-latest, dev, vite, default, manifest-off, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-off, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite-env-api, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite-env-api, default, manifest-on, json, lts/-1)
- GitHub Check: typecheck (ubuntu-latest, bundler)
- GitHub Check: release-pkg-pr-new
- GitHub Check: test-benchmark
- GitHub Check: typecheck (windows-latest, bundler)
- GitHub Check: test-size
- GitHub Check: code
🔇 Additional comments (3)
docs/1.getting-started/14.layers.md (2)
83-90: Priority order documentation is accurate and well‑structured.The numbered list clearly conveys the override hierarchy and provides specificity on auto-scanned layer alphabetical sorting and extends entry ordering. This aligns with the fix implemented in the PR.
100-122: Example clearly demonstrates layer priority with concrete scenario.The introduction of the ~/layers/custom scenario and explicit priority stack provides learners with a concrete reference. The concluding explanation is clear and reinforces the documented behaviour.
docs/2.guide/3.going-further/7.layers.md (1)
275-277: Notes section reinforces layer priority model correctly.The notes accurately state that earlier array items have higher priority and that the user's project is first. This aligns with and reinforces the documented priority order in the Layer Priority section.
Co-authored-by: Horu <73709188+HigherOrderLogic@users.noreply.github.com>
danielroe
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you ❤️
There was a problem hiding this 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 (3)
packages/nuxt/test/component-layer-priority.test.ts (3)
28-30: Replaceas any[]with proper typing.The
as any[]type assertion bypasses TypeScript's type checking, which could hide type mismatches or future API changes.Apply this diff to use proper typing:
nuxt.hook('components:dirs', (dirs) => { - captured.push(...dirs as any[]) + captured.push(...dirs as ComponentsDir[]) })
12-55: Consider adding try/finally for guaranteed cleanup.If the test fails (e.g., assertion error at line 39), the
nuxt.close()call at line 54 won't execute, potentially leaving the Nuxt instance open and causing resource leaks in the test environment.Apply this diff to guarantee cleanup:
it('assigns priorities to component dirs based on layer order', async () => { const captured: ComponentsDir[] = [] const nuxt = await loadNuxt({ cwd: layerFixtureDir, ready: true, overrides: { builder: { bundle: () => { nuxt.hooks.removeAllHooks() return Promise.resolve() }, }, }, }) - nuxt.hook('components:dirs', (dirs) => { - captured.push(...dirs as any[]) - }) - - await buildNuxt(nuxt) - - const dirs = captured.map(dir => [ - dir.path.replace(layerFixtureDir, '<root>'), - dir.priority, - ]) - - expect(Object.fromEntries(dirs)).toStrictEqual({ - // user project: highest priority - '<root>/components': 3, - '<root>/components/global': 3, - '<root>/components/islands': 3, - // local layer - '<root>/layers/auto/components': 2, - '<root>/layers/auto/components/global': 2, - '<root>/layers/auto/components/islands': 2, - // explicitly extended layer - '<root>/custom/components': 1, - '<root>/custom/components/global': 1, - '<root>/custom/components/islands': 1, - }) - - await nuxt.close() + try { + nuxt.hook('components:dirs', (dirs) => { + captured.push(...dirs as any[]) + }) + + await buildNuxt(nuxt) + + const dirs = captured.map(dir => [ + dir.path.replace(layerFixtureDir, '<root>'), + dir.priority, + ]) + + expect(Object.fromEntries(dirs)).toStrictEqual({ + // user project: highest priority + '<root>/components': 3, + '<root>/components/global': 3, + '<root>/components/islands': 3, + // local layer + '<root>/layers/auto/components': 2, + '<root>/layers/auto/components/global': 2, + '<root>/layers/auto/components/islands': 2, + // explicitly extended layer + '<root>/custom/components': 1, + '<root>/custom/components/global': 1, + '<root>/custom/components/islands': 1, + }) + } finally { + await nuxt.close() + } })
39-52: Test coverage validates the core fix.The assertions correctly validate the priority ordering described in the PR: root project (3) > auto-scanned layers (2) > extended layers (1).
For more comprehensive coverage, consider adding tests for edge cases in follow-up work:
- No layers (only root project)
- Multiple auto-scanned layers (verifying alphabetical sort)
- Multiple extended layers (verifying order)
- Only extended layers (no auto-scanned)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/nuxt/test/component-layer-priority.test.ts(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/component-layer-priority.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/component-layer-priority.test.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: huang-julien
Repo: nuxt/nuxt PR: 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
Repo: nuxt/nuxt PR: 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/component-layer-priority.test.ts
📚 Learning: 2025-07-18T16:46:07.446Z
Learnt from: CR
Repo: nuxt/nuxt PR: 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:
packages/nuxt/test/component-layer-priority.test.ts
📚 Learning: 2024-11-05T15:22:54.759Z
Learnt from: GalacticHypernova
Repo: nuxt/nuxt PR: 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/test/component-layer-priority.test.ts
📚 Learning: 2024-12-12T12:36:34.871Z
Learnt from: huang-julien
Repo: nuxt/nuxt PR: 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/test/component-layer-priority.test.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). (10)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-on, js, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, default, manifest-off, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, vite, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, built, rspack, default, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite-env-api, async, manifest-on, json, lts/-1)
- GitHub Check: test-fixtures (ubuntu-latest, dev, vite-env-api, default, manifest-on, json, lts/-1)
- GitHub Check: release-pkg-pr-new
- GitHub Check: typecheck (ubuntu-latest, bundler)
- GitHub Check: code
🔇 Additional comments (2)
packages/nuxt/test/component-layer-priority.test.ts (2)
1-9: LGTM!The imports and fixture path setup are appropriate. Path normalization ensures cross-platform compatibility.
19-24: No changes needed—removeAllHooks() does not interfere with component directory resolution.The test flow shows that the
components:dirshook listener is registered beforebuildNuxt()is called, and the hook fires during the module setup phase (before the builder.bundle override executes). TheremoveAllHooks()call happens inside the bundle function, which runs after the hook has already been processed. The test successfully captures and validates component directories with their priorities, confirming the hook fires correctly. TheremoveAllHooks()call is a test optimisation to skip post-bundling hooks, not a requirement for the test to function.
🔗 Linked issue
Fixes: #33587
Bug Description
Component priority doesn't respect layer order. All non-root layers receive the same priority value (
0), which means when multiple layers define the same component, the resolution becomes unpredictable instead of following the documented layer priority order.🔍 Root Cause
In
packages/nuxt/src/components/module.ts:75:This assigns:
When two layers have the same priority, component resolution doesn't follow the layer order - it becomes dependent on processing order, which is unpredictable.
✅ Solution
Assign priority based on layer index position:
const layerCount = nuxt.options._layers.length
for (let i = 0; i < layerCount; i++) {
const layer = nuxt.options._layers[i]
const priority = layerCount - i // Lower index = higher priority
// ...
}
This ensures the documented priority order is respected:
📚 Documentation Improvements
The layer priority documentation was confusing. The numbered list (1, 2, 3) read like a priority ranking, but it was actually describing the processing
order (where later entries can override earlier ones).
Changes:
📌 About the Linked Issue
The https://stackblitz.com/edit/github-xah69ved describes layers in ~/layers directory that are also listed in extends:
extends: ['./layers/pecav', './layers/webmail']
This is not a bug - it's the documented behavior:
The actual bug was that component priority didn't respect this layer order at all - all layers had equal priority. This PR fixes that.
Solutions for the user:
🧪 Testing
All existing unit tests pass without modification. The layer loading order was already correct - only the component priority calculation was broken.
Manual testing confirms:
📝 Notes