Skip to content

fix(unplugin): Avoid generating empty routes#2642

Merged
posva merged 10 commits into
vuejs:mainfrom
FrontEndDog:fix/empty-routes
Apr 24, 2026
Merged

fix(unplugin): Avoid generating empty routes#2642
posva merged 10 commits into
vuejs:mainfrom
FrontEndDog:fix/empty-routes

Conversation

@FrontEndDog

@FrontEndDog FrontEndDog commented Feb 26, 2026

Copy link
Copy Markdown
Contributor

fix #2641

Summary by CodeRabbit

Release Notes

  • Tests

    • Added test coverage for nested route structures with parent file configurations.
  • Bug Fixes

    • Enhanced route tree cleanup to properly remove empty wrapper nodes when children are removed.

Copilot AI review requested due to automatic review settings February 26, 2026 09:27
@netlify

netlify Bot commented Feb 26, 2026

Copy link
Copy Markdown

Deploy Preview for vue-router canceled.

Name Link
🔨 Latest commit 1190e6f
🔍 Latest deploy log https://app.netlify.com/projects/vue-router/deploys/69eb3cb2d5c40f0008cc4626

@coderabbitai

coderabbitai Bot commented Feb 26, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a test case for handling nested _parent files in route generation and implements recursive cleanup logic to delete empty non-matchable parent nodes when child nodes are removed from the route tree.

Changes

Cohort / File(s) Summary
Test Coverage
packages/router/src/unplugin/codegen/generateRouteRecords.spec.ts
Adds test scenario verifying that nested _parent files are skipped when a parent index file exists at a higher level, preventing generation of empty routes (e.g., /users/settings).
Tree Cleanup Logic
packages/router/src/unplugin/core/tree.ts
Implements recursive deletion of parent nodes in TreeNode.delete() when they become empty and non-matchable, cascading cleanup upward through the tree structure.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested reviewers

  • posva

Poem

🐰 Branches pruned, the tree takes shape,
Empty folders fade to escape,
Parents whisper when children depart,
Cleaning from the bottom, a tidy art! 🌳

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(unplugin): Avoid generating empty routes' directly and concisely describes the main objective of the PR.
Linked Issues check ✅ Passed The PR addresses the core requirement from issue #2641 by modifying route generation and tree deletion logic to prevent empty routes, with added test coverage for the fix.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing empty route generation: test additions verify the fix and tree deletion logic prevents orphaned empty nodes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/router/src/unplugin/codegen/generateRouteRecords.ts (1)

22-28: ⚠️ Potential issue | 🟠 Major

Missing .filter(Boolean) for root-level children.

The same empty-route scenario that's fixed at line 101 can occur here. If a root-level child node is non-matchable with no children (lines 33-35), generateRouteRecords returns '', and the join would produce invalid JavaScript like [,\n{...}] or [{...},\n].

Proposed fix to apply the same filter
   if (node.isRoot()) {
     return `[
 ${node
   .getChildrenSorted()
   .map(child => generateRouteRecords(child, options, importsMap, indent + 1))
+  .filter(Boolean)
   .join(',\n')}
 ]`
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router/src/unplugin/codegen/generateRouteRecords.ts` around lines 22
- 28, The root branch in generateRouteRecords (when node.isRoot()) maps over
node.getChildrenSorted() but doesn't filter out empty return values from
generateRouteRecords, so non-matchable children can produce '' and create
invalid array output; update the root branch to first filter out falsy results
(e.g., .filter(Boolean)) before mapping and joining (same approach used
elsewhere in generateRouteRecords), keeping the rest of the call using
generateRouteRecords(child, options, importsMap, indent + 1) so empty strings
are excluded from the joined array.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/router/src/unplugin/codegen/generateRouteRecords.ts`:
- Around line 22-28: The root branch in generateRouteRecords (when
node.isRoot()) maps over node.getChildrenSorted() but doesn't filter out empty
return values from generateRouteRecords, so non-matchable children can produce
'' and create invalid array output; update the root branch to first filter out
falsy results (e.g., .filter(Boolean)) before mapping and joining (same approach
used elsewhere in generateRouteRecords), keeping the rest of the call using
generateRouteRecords(child, options, importsMap, indent + 1) so empty strings
are excluded from the joined array.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1832ea1 and 646bbf5.

📒 Files selected for processing (1)
  • packages/router/src/unplugin/codegen/generateRouteRecords.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Fixes route code generation in the router unplugin to avoid emitting “empty routes” when route nodes are deleted (e.g., via beforeWriteFiles) but empty intermediate folders remain in the route tree.

Changes:

  • Filters out falsy generated child route-record strings during children array generation to prevent empty array slots.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/router/src/unplugin/codegen/generateRouteRecords.ts
Comment thread packages/router/src/unplugin/codegen/generateRouteRecords.ts
Comment thread packages/router/src/unplugin/codegen/generateRouteRecords.ts

@posva posva left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the PR!
This is probably not the right fix. Maybe the delete is leaving some unused nodes in the tree or not cleaning itself from the parent.

A unit test for the tree is also needed

@pkg-pr-new

pkg-pr-new Bot commented Feb 26, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/vue-router@2642

commit: 1190e6f

@codecov

codecov Bot commented Feb 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.96%. Comparing base (4713069) to head (1190e6f).
⚠️ Report is 53 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2642      +/-   ##
==========================================
+ Coverage   85.58%   85.96%   +0.37%     
==========================================
  Files          86       88       +2     
  Lines        9960    10087     +127     
  Branches     2285     2321      +36     
==========================================
+ Hits         8524     8671     +147     
+ Misses       1423     1405      -18     
+ Partials       13       11       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@FrontEndDog

Copy link
Copy Markdown
Contributor Author

Thanks for the PR! This is probably not the right fix. Maybe the delete is leaving some unused nodes in the tree or not cleaning itself from the parent.

A unit test for the tree is also needed

Additional supplements

unplugin-vue-router@0.19.2 does not have this issue.

These codes were newly added in vue-router@5

Image

I will attempt to repair it using the correct method.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/router/src/unplugin/codegen/generateRouteRecords.ts`:
- Around line 22-28: The current loop in generateRouteRecords.ts mutates
node.children during forEach (child.delete()) and won't remove parents that
become empty after their children are pruned; change to a safe post-order
pruning: traverse children first (recursively prune each child), collect
children to delete (using child.isMatchable() and child.children.size === 0
checks after recursive pruning), then delete them in a separate pass using
child.delete(); alternatively implement a separate cleanup pass (pruneNode) that
returns whether a node should be deleted so parent callers can remove now-empty
non-matchable nodes. Ensure references to node.children, child.isMatchable(),
and child.delete() are used in the new post-order logic.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 646bbf5 and a07b7fe.

📒 Files selected for processing (1)
  • packages/router/src/unplugin/codegen/generateRouteRecords.ts

Comment thread packages/router/src/unplugin/codegen/generateRouteRecords.ts Outdated
@FrontEndDog FrontEndDog requested a review from posva February 27, 2026 02:07
@FrontEndDog

Copy link
Copy Markdown
Contributor Author

I have revised the logic for skipping lone parent nodes and added unit test.

@FrontEndDog

Copy link
Copy Markdown
Contributor Author

I discovered an issue during subsequent testing, if the file structure is test1/test2/Test.vue, After the path "Test.vue" is deleted , There will be an lone route for test1, maybe I should modify the delete method of nodeTree.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/router/src/unplugin/core/tree.ts`:
- Around line 212-216: The cascade delete can climb to the root and trigger the
"Cannot delete the root node" error; modify the condition around the recursive
delete (the block using this.parent.isMatchable(), this.parent.children.size and
calling this.parent.delete()) to guard against deleting the root—e.g. check that
this.parent is not the root (use whatever root predicate exists or ensure
this.parent.parent !== undefined) before calling this.parent.delete(); update
the condition so the root node is never passed to delete().

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f610c3 and e41658f.

📒 Files selected for processing (1)
  • packages/router/src/unplugin/core/tree.ts

Comment thread packages/router/src/unplugin/core/tree.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/router/src/unplugin/core/tree.ts Outdated
Comment thread packages/router/src/unplugin/codegen/generateRouteRecords.spec.ts
Comment thread packages/router/src/unplugin/codegen/generateRouteRecords.spec.ts
@FrontEndDog FrontEndDog requested a review from posva February 27, 2026 06:37
@FrontEndDog

Copy link
Copy Markdown
Contributor Author

Just a moment. I've found some other issues. I'll fix them.

@FrontEndDog

FrontEndDog commented Feb 27, 2026

Copy link
Copy Markdown
Contributor Author

Hi @posva ,I find a new problem.
The directory structure is as follows:
image

router tree.ts add log

image

When I try to delete route start with a capital letter in beforeWriteFiles, an error is reported.
image

@FrontEndDog

FrontEndDog commented Feb 27, 2026

Copy link
Copy Markdown
Contributor Author
image

Maybe this line of code should be deleted or modify the judgment logic of the root node

When deleting /admin for the second time, due to the execution of this.parent = undefined, /admin may be mistakenly recognized as the root node.

@FrontEndDog

Copy link
Copy Markdown
Contributor Author

I have already dealt with all the issues.

@FrontEndDog

Copy link
Copy Markdown
Contributor Author

Hi @posva I have already dealt with all the issues. Could you spare some time to review this PR.

@posva posva left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@posva posva merged commit 10a8b77 into vuejs:main Apr 24, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

generated routes by the unplugin may include empty routes

3 participants