Skip to content

Conversation

posva
Copy link
Owner

@posva posva commented Aug 15, 2025

Summary by CodeRabbit

  • New Features

    • Experimental custom route param parsers and per-route param definitions; generated deterministic route resolver for more predictable routing.
    • New playground-experimental demo app with example pages, navigation, Not Found page, inspector and local JSON server.
  • Improvements

    • Routes export made immutable; improved date/number param parsing, richer route/query param handling and stronger route typings.
    • HMR/resolver updates for faster route updates.
  • Tests

    • Expanded unit tests for resolver ordering, param-parsers, and codegen.
  • Chores

    • Dependency, workspace and config updates; new playground scripts and .gitignore entry.

Copy link

coderabbitai bot commented Aug 15, 2025

Walkthrough

Adds experimental param-parser support, a generated resolver virtual module with HMR, param-parser-aware codegen and DTS injection, extended tree/param model and resolver generation, many tests, utility exports, and a new playground-experimental demo package with typed routes.

Changes

Cohort / File(s) Summary
Virtual resolver & HMR
src/index.ts, src/core/moduleConstants.ts, src/core/vite/index.ts, src/core/context.ts
Introduce vue-router/auto-resolver virtual module, wire resolver generation/loading/invalidation into plugin lifecycle, expose generateResolver(), and update HMR/reload flows.
Param-parsers runtime & config
src/options.ts, src/core/context.ts, src/core/customBlock.ts, src/runtime.ts
Add experimental paramParsers option and dir handling, scan/watch parser files, surface parser metadata in custom/define-page blocks, and extend runtime types (ParamParserType, DefinePage query/path params).
Tree parsing & node model
src/core/treeNodeValue.ts, src/core/tree.ts, src/core/extendRoutes.ts, src/core/utils.ts, src/core/moduleConstants.ts
Support [param=type] syntax, split path vs query params (TreePathParam/TreeQueryParam), add helpers (escapeRegex, isTreeParam*), add TreeNode getters (regexp, score, pathParams, params, queryParams, matcher parts, isSplat) and isMatchable type guard, and adjust override merging.
Codegen: param parsers, resolver & DTS
src/codegen/generateParamParsers.ts, src/codegen/generateRouteResolver.ts, src/codegen/generateRouteMap.ts, src/codegen/generateRouteParams.ts, src/codegen/generateDTS.ts, src/codegen/generateRouteRecords.ts, tests under src/codegen/*.spec.ts
New param-parsers codegen utilities, thread ParamParsersMap through generation, add EXPERIMENTAL_generateRouteParams and ParamValue* types, generate a resolver module, and inject params type declarations into generated DTS.
DefinePage parsing & API
src/core/definePage.ts, src/core/definePage.spec.ts, src/core/customBlock.ts
Replace extractDefinePageNameAndPath with extractDefinePageInfo, parse nested params (path & query) in shorthand/full forms, and add DefinePageInfo type.
Generator helpers & utils
src/codegen/generateRouteRecords.ts, src/utils/index.ts
Export generatePageImport and formatMeta; add fileExists(filePath) helper.
Tree/code tests & other tests
src/core/*.spec.ts, src/codegen/*.spec.ts, src/codegen/generateRouteResolver.spec.ts
Add/extend many tests for param-parsers, resolver ordering, route resolver generation; update tests to new signatures and renamed symbols.
Client typing tweak
client.d.ts
Make exported routes readonly: RouteRecordRaw[]readonly RouteRecordRaw[].
Playground-experimental package (new)
playground-experimental/* (e.g. package.json, vite.config.ts, tsconfig*, index.html, src/**, typed-router.d.ts, auto-imports.d.ts, db.json)
Add an experimental playground package with Vite config, multi-source routes, data-loaders, param parsers (date), many demo pages/components, generated typed-router DTS and auto-imports.
Playground & infra edits
playground/*, pnpm-workspace.yaml, package.json, tsconfig.json, vitest.config.ts, vitest.workspace.js, examples/nuxt/package.json
Workspace updates (add playground-experimental), dependency changes (@babel/generator, @types/*, pin vue-router URL), tsconfig/vitest formatting tweaks, remove vitest.workspace.js, and add .gitignore entry.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related issues

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Add experimental custom resolver" accurately and concisely captures the primary change in the patch — introduction of an experimental resolver and related codegen/resolver integration — and is clear and specific enough for a teammate scanning history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/experimental-router

📜 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 e5a73fc and 8b22754.

📒 Files selected for processing (1)
  • src/runtime.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-23T12:54:44.918Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-150
Timestamp: 2025-08-23T12:54:44.918Z
Learning: In src/core/context.ts, when paramParsers is defined in resolved options, the dir property is guaranteed to be an array, so optional chaining on options.experimental.paramParsers?.dir.map() is sufficient protection against undefined values.

Applied to files:

  • src/runtime.ts
📚 Learning: 2025-08-16T13:01:42.709Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateParamParsers.ts:91-94
Timestamp: 2025-08-16T13:01:42.709Z
Learning: In the unplugin-vue-router codebase, path normalization for import specifiers is handled elsewhere in the system (not at individual call sites like generateParamParsers.ts). Individual functions should not normalize paths themselves as it violates separation of concerns.

Applied to files:

  • src/runtime.ts
🧬 Code graph analysis (1)
src/runtime.ts (1)
client.d.ts (1)
  • TypesConfig (38-40)
⏰ 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). (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (4)
src/runtime.ts (4)

84-84: Fix placeholder URL

Replace TODO link with actual docs URL or remove.

-   * and any parser in the {@link https://uvr.esm.is/TODO | params folder }. If
+   * and any parser in the params folder. If

96-96: Type allows function defaults

Good - types now align with extractor which accepts ArrowFunctionExpression.


1-1: Import TypesConfig from client types

TypesConfig is exported by this package, not vue-router.

-import type { RouteRecordRaw, TypesConfig } from 'vue-router'
+import type { RouteRecordRaw } from 'vue-router'
+import type { TypesConfig } from '../client'

8-8: Incorrect — _definePage occurrences are generated/test aliases; no import renames needed.

Search shows _definePage only in codegen and spec artifacts (src/codegen/, src/core/.spec.ts) and client.d.ts; runtime already exports definePage, so do not change imports.

Likely an incorrect or invalid review comment.


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

Copy link

socket-security bot commented Aug 15, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​tanstack/​vue-query-devtools@​5.87.4 ⏵ 5.89.0100 +110067 +397 +1100
Added@​types/​babel__generator@​7.27.01001007278100
Updated@​pinia/​nuxt@​0.11.1 ⏵ 0.11.2100 +110076 +286 -1100
Updated@​types/​node@​22.18.3 ⏵ 24.3.0100 +110081 +196 +1100
Added@​vue/​tsconfig@​0.7.01001009987100
Updated@​tanstack/​vue-query@​5.87.4 ⏵ 5.89.099 +110010097100

View full report

Copy link

pkg-pr-new bot commented Aug 15, 2025

Open in StackBlitz

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

commit: 8b22754

Copy link

cloudflare-workers-and-pages bot commented Aug 15, 2025

Deploying unplugin-vue-router with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8b22754
Status: ✅  Deploy successful!
Preview URL: https://fd864f64.unplugin-vue-router.pages.dev
Branch Preview URL: https://feat-experimental-router.unplugin-vue-router.pages.dev

View logs

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

🔭 Outside diff range comments (1)
tsconfig.json (1)

2-7: Fix invalid include pattern (trailing comma inside the string).

This likely prevents test files from being included by TS.

   "include": [
     "./src/**/*.ts",
     "./src/*.d.ts",
     "./client.d.ts",
-    "./tests/**/*.ts,"
+    "./tests/**/*.ts"
   ],
🧹 Nitpick comments (41)
playground-experimental/.gitignore (1)

1-1: Prefer wildcard ignore for TS build info artifacts

Covers multiple tsconfigs (e.g., tsconfig.config.tsbuildinfo) without future updates.

-tsconfig.tsbuildinfo
+*.tsbuildinfo
playground-experimental/src/pages/[name]/24.vue (1)

5-5: Fix typo in copy

"Too test" → "To test".

-  <p>Too test specificity in routes</p>
+  <p>To test specificity in routes</p>
playground-experimental/src/pages/b.vue (1)

3-5: Align heading with route/page name

Labeling this page “Home” can be confusing next to the actual home page. Consider a clearer title.

-  <h1>Home</h1>
+  <h1>Page B</h1>
package.json (1)

206-206: Verify CI/install stability for private vue-router artifact

Using a PR artifact URL for vue-router in devDependencies is fine locally, but can break CI or future installs if the artifact is GC’d or the service is down. Ensure CI runners have access and consider documenting a fallback or pinning a public prerelease if available.

I can add a short README note or a CI guard that fails with a clear message if the artifact can’t be fetched. Want me to draft it?

playground-experimental/src/pages/a.[b].c.[d].vue (1)

1-5: Prefer useRoute over $route in templates (optional).

Leverage typed access and avoid relying on the global in templates.

-<script lang="ts" setup></script>
+<script lang="ts" setup>
+import { useRoute } from 'vue-router'
+const route = useRoute()
+</script>

 <template>
-  <h1>{{ String($route.name) }} - {{ $route.path }}</h1>
+  <h1>{{ String(route.name) }} - {{ route.path }}</h1>
 </template>
src/data-loaders/meta-extensions.ts (1)

13-45: Type-only import OK; consider inlining the type in the augmentation (optional).

This keeps the augmentation self-contained and avoids top-level imports if verbatim module syntax is enabled later.

-import { type RouteLocationNormalizedLoaded } from 'vue-router'
+// (optional) Inline the type import within the augmentation below to avoid a top-level import.

 declare module 'vue-router' {
+  // Inline type import to avoid a top-level import in augmentations
+  type RouteLocationNormalizedLoaded = import('vue-router').RouteLocationNormalizedLoaded
   export interface Router {
     /**
      * Pending navigation that is waiting for data loaders to resolve.
      * @internal
      */
     [PENDING_LOCATION_KEY]: RouteLocationNormalizedLoaded | null
playground-experimental/src/pages/users/sub-[first]-[second].vue (1)

1-5: Mirror the optional useRoute pattern for consistency.

Keeps access typed and aligned with other pages.

-<script lang="ts" setup></script>
+<script lang="ts" setup>
+import { useRoute } from 'vue-router'
+const route = useRoute()
+</script>

 <template>
-  <h1>{{ String($route.name) }} - {{ $route.path }}</h1>
+  <h1>{{ String(route.name) }} - {{ route.path }}</h1>
 </template>
tsconfig.json (1)

17-18: Comment and config mismatch.

The comment suggests enabling baseUrl, but it’s disabled. Either remove the comment or restore baseUrl if you still want canonical imports.

playground/src/params/date.ts (3)

1-3: Consolidate invalid() helper (avoid duplication).

You already noted this should come from vue-router. Either import it or move it to a shared local util so number/date stay in sync.

Option A: import from vue-router (when available):

-// NOTE: should be imported from vue-router
-const invalid = (...args: ConstructorParameters<typeof Error>) =>
-  new Error(...args)
+import { invalid } from 'vue-router'

Option B: shared local util (apply similar change in number.ts):
Create playground/src/params/_utils.ts:

export const invalid = (...args: ConstructorParameters<typeof Error>) =>
  new Error(...args)

Then:

-// NOTE: should be imported from vue-router
-const invalid = (...args: ConstructorParameters<typeof Error>) =>
-  new Error(...args)
+import { invalid } from './_utils'

5-12: Confirm accepted formats (date-only vs any parseable Date).

new Date(value) accepts many formats; if you only want YYYY-MM-DD, add a guard. Otherwise, current behavior is fine.

Example tightening:

 export const parse = (value: string): Date => {
+  // Accept only YYYY-MM-DD (ISO date without time)
+  if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
+    throw invalid(`Invalid date (expected YYYY-MM-DD): "${value}"`)
+  }
   const asDate = new Date(value)
   if (Number.isNaN(asDate.getTime())) {
     throw invalid(`Invalid date: "${value}"`)
   }

   return asDate
 }

14-18: toString(): prefer explicit date-only trimming.

.replace('T00:00:00.000Z', '') only handles UTC midnight. If you want stable date-only output for midnight values, make the intent explicit.

-export const toString = (value: Date): string =>
-  value
-    .toISOString()
-    // allows keeping simple dates like 2023-10-01 without time
-    .replace('T00:00:00.000Z', '')
+export const toString = (value: Date): string => {
+  const iso = value.toISOString()
+  // keep simple dates like 2023-10-01 without time
+  return iso.endsWith('T00:00:00.000Z') ? iso.slice(0, 10) : iso
+}
src/runtime.ts (2)

73-74: Make QueryParamType extensible via registry pattern.

Hard-coding 'int' | 'bool' will drift from available parsers. Suggest a registry type users/codegen can augment.

-export type QueryParamType = 'int' | 'bool'
+// Open registry for param types. Augment in userland / codegen.
+export interface QueryParamTypesRegistry {
+  int: unknown
+  bool: unknown
+}
+export type QueryParamType = keyof QueryParamTypesRegistry

78-84: Fix broken doc link.

The TODO link points to https://uvr.esm.is/TODO. Replace with the actual params folder docs URL.

playground-experimental/src/pages/users/[userId=int].vue (1)

2-3: Remove unused expression or make intent explicit.

route.params.userId on its own can trip linters. Either use it or mark it intentionally unused.

 const route = useRoute()
-route.params.userId
+// Access to assert types without emitting code
+void route.params.userId
playground/src/params/number.ts (1)

1-4: Deduplicate invalid() helper (same as date.ts).

Centralize invalid() to a shared util to avoid divergence.

Option: shared util (apply similar change in date.ts):
Create playground/src/params/_utils.ts:

export const invalid = (...args: ConstructorParameters<typeof Error>) =>
  new Error(...args)

Then:

-// NOTE: should be imported from vue-router
-const invalid = (...args: ConstructorParameters<typeof Error>) =>
-  new Error(...args)
+import { invalid } from './_utils'
src/core/tree.ts (1)

282-297: Inefficient regexp construction

The regexp getter walks up the tree on every call without caching. For deeply nested routes, this could impact performance.

Consider caching the regexp value or building it incrementally as nodes are added to the tree:

+  private _regexp: string | null = null
+
   get regexp(): string {
+    if (this._regexp !== null) {
+      return this._regexp
+    }
+
     let re = ''
     let node: TreeNode | undefined = this
 
     while (node) {
       if (node.value.isParam() && node.value.re) {
         re = node.value.re + (re ? '\\/' : '') + re
       } else {
         re = node.value.pathSegment + (re ? '\\/' : '') + re
       }
 
       node = node.parent
     }
 
-    return '/^' + re + '$/i'
+    this._regexp = '/^' + re + '$/i'
+    return this._regexp
   }

Note: You'll need to invalidate the cache when the tree structure changes.

playground-experimental/vite.config.ts (2)

5-6: Remove @ts-ignore comment

The @ts-ignore comment suppresses type checking. Consider properly typing the import or using a more specific type assertion.

-// @ts-ignore: the plugin should not be checked in the playground
-import VueRouter from '../src/vite'
+import VueRouter from '../src/vite' as any

Or better yet, ensure the module has proper type exports.


107-116: Inconsistent exclude patterns

The exclude patterns mix different formats (glob patterns with ** and simple patterns). Line 113 has a commented absolute path resolution that differs from the string patterns.

Consider standardizing the exclude patterns format for consistency:

       exclude: [
-        '**/ignored/**',
-        // '**/ignored/**/*',
-        '**/__*',
-        '**/__**/*',
+        '**/ignored/**/*',
+        '**/__*/**',
         '**/*.component.vue',
-        // resolve(__dirname, './src/pages/ignored'),
-        //
-        // './src/pages/**/*.spec.ts',
       ],
playground-experimental/src/pages/[name].vue (1)

1-7: Make params readable and align with useRoute for consistency

Interpolating an object prints “[object Object]”. Also, other pages use useRoute(). Suggest switching to useRoute() and pretty-printing params.

Apply:

-<script lang="ts" setup></script>
+<script lang="ts" setup>
+const route = useRoute()
+</script>

-  <pre>{{ $route.params }}</pre>
+  <pre>{{ JSON.stringify(route.params, null, 2) }}</pre>

If you prefer to keep $route, minimally do:

-  <pre>{{ $route.params }}</pre>
+  <pre>{{ JSON.stringify($route.params, null, 2) }}</pre>
playground-experimental/src/pages/(home).vue (1)

1-2: Remove empty script block

The empty <script setup> adds noise. Either remove it or use it.

-<script lang="ts" setup></script>
-
playground-experimental/index.html (2)

8-13: Add rel="noopener noreferrer" to external link opened in new tab

Prevents tabnabbing and avoids exposing window.opener.

-      target="_blank"
+      target="_blank"
+      rel="noopener noreferrer"

3-6: Add a document title

Improves UX and browser tab labeling.

   <head>
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vue Router Playground (experimental)</title>
   </head>
playground-experimental/src/pages/events/[when=date].vue (1)

7-7: Show a stable, useful representation of the Date

typeof date is always “object” and toString() is environment-dependent. Prefer instanceof Date and toISOString() for clarity.

-  <pre>{{ typeof route.params.when }} - {{ route.params.when }}</pre>
+  <pre>
+    {{
+      route.params.when instanceof Date ? 'date' : typeof route.params.when
+    }} - {{
+      route.params.when instanceof Date
+        ? route.params.when.toISOString()
+        : String(route.params.when)
+    }}
+  </pre>
playground-experimental/src/pages/[name]/[userId=int].vue (2)

1-1: Remove empty script block.

No script logic is present. Dropping the empty block keeps the SFC lean.

-<script lang="ts" setup></script>
+

3-7: Clarify intent: route renders content despite “should never be visible”.

If this file is meant to be a layout-only segment, the

will render when navigating to /[name]/[userId=int]. Consider removing the heading or redirecting/adding an index child to ensure only children render.

src/codegen/generateRouteMap.spec.ts (1)

22-24: Deduplicate the repeated empty paramParsersMap argument.

Every test passes new Map() to generateRouteNamedMap. Introduce a single constant or helper to reduce noise and centralize future changes.

Apply this at the top of the file (outside the shown ranges):

const EMPTY_PARAM_PARSERS = new Map()
const gen = (tree: PrefixTree) =>
  formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, EMPTY_PARAM_PARSERS))

Then within the changed lines, replace calls like:

-formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+gen(tree)

Additionally, consider adding one test asserting behavior with a non-empty paramParsersMap to exercise the new API path.

Also applies to: 45-47, 68-70, 84-86, 104-106, 128-130, 149-151, 171-173, 189-191, 209-211, 226-228, 240-242, 257-259, 281-283, 297-299, 311-313, 325-327, 345-347, 379-381, 400-402

playground-experimental/src/params/date.ts (2)

3-5: Throw a TypeError directly; drop the local invalid wrapper.

A direct TypeError is clearer and avoids an unnecessary helper.

-// NOTE: should be imported from vue-router
-const invalid = (...args: ConstructorParameters<typeof Error>) =>
-  new Error(...args)
+// NOTE: could be replaced by a shared `invalid()` helper from vue-router when available

@@
-    if (Number.isNaN(asDate.getTime())) {
-      throw invalid(`Invalid date: "${value}"`)
-    }
+    if (Number.isNaN(asDate.getTime())) {
+      throw new TypeError(`Invalid date: "${value}"`)
+    }

Also applies to: 10-12


16-21: Anchor the time-stripping replace.

Use a regex anchored to the end to avoid accidental mid-string replacements.

-  set: (value: Date): string =>
-    value
-      .toISOString()
-      // allows keeping simple dates like 2023-10-01 without time
-      .replace('T00:00:00.000Z', ''),
+  set: (value: Date): string =>
+    value
+      .toISOString()
+      // allows keeping simple dates like 2023-10-01 without time
+      .replace(/T00:00:00\.000Z$/, ''),
src/codegen/generateRouteRecords.ts (1)

148-152: Add explicit return type to the newly exported API.

Public functions benefit from explicit return types to prevent unintended API drift.

-export function generatePageImport(
+export function generatePageImport(
   filepath: string,
   importMode: ResolvedOptions['importMode'],
   importsMap: ImportsMap
-) {
+): string {
src/index.ts (1)

122-123: Generate resolver on load; consider HMR invalidation too.

The load branch for auto-resolver is correct. For DX, also invalidate the resolver module during HMR when pages change (mirroring auto-routes).

You can augment handleHotUpdate to include the resolver module if present:

const resolverModule = server.moduleGraph.getModuleById(
  asVirtualId(MODULE_RESOLVER_PATH)
)
if (resolverModule) {
  return [
    ...modules,
    definePageModule,
    routesModule,
    resolverModule,
  ]
}

Also applies to: 146-151

src/codegen/generateRouteParams.ts (1)

36-37: Simplify redundant fallback expression.

The expression type || '/* INVALID */ unknown' is redundant since type is already checked for truthiness.

-            type
-              ? type || '/* INVALID */ unknown'
+            type
+              ? type
src/options.ts (1)

342-347: Consider extracting common normalization logic.

The pattern for normalizing and resolving paths is duplicated between paramMatchersDir and autoExportsDataLoaders. Consider extracting a helper function.

+function normalizeAndResolvePaths(
+  value: string | string[] | undefined,
+  root: string
+): string[] | undefined {
+  if (!value) return undefined
+  return (isArray(value) ? value : [value]).map((path) => resolve(root, path))
+}

 const paramMatchersDir = (
   paramMatchers?.dir
     ? isArray(paramMatchers.dir)
       ? paramMatchers.dir
       : [paramMatchers.dir]
     : []
 ).map((dir) => resolve(root, dir))

-const autoExportsDataLoaders = options.experimental?.autoExportsDataLoaders
-  ? (isArray(options.experimental.autoExportsDataLoaders)
-      ? options.experimental.autoExportsDataLoaders
-      : [options.experimental.autoExportsDataLoaders]
-    ).map((path) => resolve(root, path))
-  : undefined
+const autoExportsDataLoaders = normalizeAndResolvePaths(
+  options.experimental?.autoExportsDataLoaders,
+  root
+)
playground-experimental/src/utils.ts (1)

35-40: Consider removing unused utilities.

The dummy functions and constants appear to be test utilities that should be removed or moved to test files.

src/codegen/generateRouteMap.ts (1)

37-55: Param-matcher types applied to both raw and decoded params — confirm intent.
When paramMatchers are enabled, paramType is used for both the “raw” and “decoded” type slots, losing the ParamValue/ distinction. If that’s intentional for the experiment, fine; otherwise consider generating distinct raw/decoded variants.

src/codegen/generateParamParsers.ts (3)

17-32: Drop unnecessary async from warnMissingParamParsers.
The function is synchronous; async adds noise and leads to un-awaited calls downstream.

Apply this diff:

-export async function warnMissingParamParsers(
+export function warnMissingParamParsers(
   tree: PrefixTree,
   paramParsers: ParamParsersMap
 ) {

68-82: Fix param name typo for clarity (parparsersMap → paramParsersMap).
Pure readability; avoids confusion across the codebase.

Apply this diff:

-export function generateParamsTypes(
-  params: TreeRouteParam[],
-  parparsersMap: ParamParsersMap
-): Array<string | null> {
+export function generateParamsTypes(
+  params: TreeRouteParam[],
+  paramParsersMap: ParamParsersMap
+): Array<string | null> {
   return params.map((param) => {
     if (param.parser) {
-      if (parparsersMap.has(param.parser)) {
-        return parparsersMap.get(param.parser)!.typeName
+      if (paramParsersMap.has(param.parser)) {
+        return paramParsersMap.get(param.parser)!.typeName
       } else if (param.parser === 'int') {
         return 'number'
       }
     }
     return null
   })
 }

102-109: Improve error message in generateParamsOptions.
The current message is cryptic.

Apply this diff:

-  if (!paramParsers) {
-    throw new Error('what')
-  }
+  if (!paramParsers) {
+    throw new Error('paramParsers map is required to generate params options')
+  }
src/core/context.ts (2)

211-229: Normalize relativePath to avoid '././' in generated imports.
setupParamParserWatcher prefixes './' but generateParamParsersTypesDeclarations also adds './', yielding '././path'. It works but is sloppy.

Apply this diff:

-          relativePath: './' + relative(options.root, absolutePath),
+          relativePath: relative(options.root, absolutePath),

332-339: warnMissingParamParsers is called without awaiting — align with its sync/async nature.
If you keep warnMissingParamParsers async, await it here; otherwise (preferred) make it sync per earlier suggestion.

src/codegen/generateRouteResolver.ts (1)

35-40: Avoid importing unused MatcherPatternPathStar.
It’s added unconditionally but not used, which may trigger noUnusedLocals or add dead imports to output.

Apply this diff:

   importsMap.add('vue-router/experimental', 'createStaticResolver')
   importsMap.add('vue-router/experimental', 'MatcherPatternPathStatic')
   importsMap.add('vue-router/experimental', 'MatcherPatternPathCustomParams')
-  importsMap.add('vue-router/experimental', 'MatcherPatternPathStar')
   importsMap.add('vue-router/experimental', 'normalizeRouteRecord')
playground-experimental/typed-router.d.ts (1)

3-3: Remove @ts-nocheck from a .d.ts file (prefer catching generator mismatches).

Suppressing diagnostics on a declaration file can hide structural/type drift from generators. If feasible, emit without this directive.

-// @ts-nocheck
📜 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 44f833b and 57c91c0.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (51)
  • client.d.ts (1 hunks)
  • package.json (1 hunks)
  • playground-experimental/.gitignore (1 hunks)
  • playground-experimental/auto-imports.d.ts (1 hunks)
  • playground-experimental/db.json (1 hunks)
  • playground-experimental/env.d.ts (1 hunks)
  • playground-experimental/index.html (1 hunks)
  • playground-experimental/package.json (1 hunks)
  • playground-experimental/src/App.vue (1 hunks)
  • playground-experimental/src/main.ts (1 hunks)
  • playground-experimental/src/pages/(home).vue (1 hunks)
  • playground-experimental/src/pages/[name].vue (1 hunks)
  • playground-experimental/src/pages/[name]/24.vue (1 hunks)
  • playground-experimental/src/pages/[name]/[userId=int].vue (1 hunks)
  • playground-experimental/src/pages/a.[b].c.[d].vue (1 hunks)
  • playground-experimental/src/pages/b.vue (1 hunks)
  • playground-experimental/src/pages/events/[when=date].vue (1 hunks)
  • playground-experimental/src/pages/users/[userId=int].vue (1 hunks)
  • playground-experimental/src/pages/users/sub-[first]-[second].vue (1 hunks)
  • playground-experimental/src/params/date.ts (1 hunks)
  • playground-experimental/src/router.ts (1 hunks)
  • playground-experimental/src/utils.ts (1 hunks)
  • playground-experimental/tsconfig.config.json (1 hunks)
  • playground-experimental/tsconfig.json (1 hunks)
  • playground-experimental/typed-router.d.ts (1 hunks)
  • playground-experimental/vite.config.ts (1 hunks)
  • playground/src/pages/(some-layout).vue (1 hunks)
  • playground/src/params/date.ts (1 hunks)
  • playground/src/params/number.ts (1 hunks)
  • playground/vite.config.ts (1 hunks)
  • pnpm-workspace.yaml (1 hunks)
  • src/codegen/generateDTS.ts (2 hunks)
  • src/codegen/generateParamParsers.ts (1 hunks)
  • src/codegen/generateRouteMap.spec.ts (20 hunks)
  • src/codegen/generateRouteMap.ts (1 hunks)
  • src/codegen/generateRouteParams.ts (1 hunks)
  • src/codegen/generateRouteRecords.ts (1 hunks)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/codegen/generateRouteResolver.ts (1 hunks)
  • src/core/context.ts (10 hunks)
  • src/core/extendRoutes.spec.ts (9 hunks)
  • src/core/moduleConstants.ts (2 hunks)
  • src/core/tree.spec.ts (2 hunks)
  • src/core/tree.ts (1 hunks)
  • src/core/treeNodeValue.ts (10 hunks)
  • src/data-loaders/meta-extensions.ts (1 hunks)
  • src/index.ts (5 hunks)
  • src/options.ts (3 hunks)
  • src/runtime.ts (1 hunks)
  • src/utils/index.ts (2 hunks)
  • tsconfig.json (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (17)
playground/src/params/date.ts (1)
playground/src/params/number.ts (2)
  • parse (5-11)
  • toString (14-14)
playground/src/params/number.ts (2)
playground/src/params/date.ts (2)
  • parse (5-12)
  • toString (14-18)
src/core/treeNodeValue.ts (2)
  • toString (91-93)
  • toString (285-298)
src/codegen/generateRouteMap.spec.ts (3)
src/codegen/generateRouteMap.ts (1)
  • generateRouteNamedMap (9-35)
src/options.ts (1)
  • DEFAULT_OPTIONS (249-266)
src/index.ts (1)
  • DEFAULT_OPTIONS (29-29)
playground-experimental/src/utils.ts (2)
playground-experimental/typed-router.d.ts (1)
  • RouteNamedMap (23-33)
playground-experimental/src/router.ts (2)
  • RouteNamedMap (16-31)
  • router (9-12)
src/codegen/generateRouteResolver.spec.ts (7)
src/options.ts (2)
  • DEFAULT_OPTIONS (249-266)
  • resolveOptions (313-395)
src/index.ts (1)
  • DEFAULT_OPTIONS (29-29)
src/codegen/generateRouteResolver.ts (2)
  • generateRouteRecord (56-122)
  • generateRouteResolver (17-54)
src/codegen/generateRouteRecords.ts (1)
  • generateRouteRecord (15-113)
src/core/tree.ts (1)
  • PrefixTree (368-402)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/codegen/generateParamParsers.ts (1)
  • ParamParsersMap (7-15)
src/codegen/generateParamParsers.ts (4)
src/core/tree.ts (1)
  • PrefixTree (368-402)
src/core/treeNodeValue.ts (1)
  • TreeRouteParam (226-233)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/utils/index.ts (1)
  • ts (18-18)
src/codegen/generateRouteMap.ts (4)
src/core/tree.ts (2)
  • TreeNode (15-362)
  • params (268-280)
src/options.ts (1)
  • ResolvedOptions (400-400)
src/codegen/generateParamParsers.ts (2)
  • ParamParsersMap (7-15)
  • generateParamsTypes (68-82)
src/codegen/generateRouteParams.ts (2)
  • EXPERIMENTAL_generateRouteParams (24-50)
  • generateRouteParams (3-22)
src/core/tree.spec.ts (2)
src/core/tree.ts (1)
  • PrefixTree (368-402)
src/core/treeNodeValue.ts (1)
  • TreeRouteParam (226-233)
playground-experimental/src/main.ts (2)
client.d.ts (1)
  • DataLoaderPlugin (52-52)
playground-experimental/src/router.ts (1)
  • router (9-12)
src/codegen/generateRouteResolver.ts (7)
src/core/tree.ts (3)
  • PrefixTree (368-402)
  • path (217-222)
  • TreeNode (15-362)
src/options.ts (1)
  • ResolvedOptions (400-400)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/codegen/generateParamParsers.ts (2)
  • ParamParsersMap (7-15)
  • generateParamsOptions (102-120)
src/codegen/generateRouteRecords.ts (2)
  • generateRouteRecord (15-113)
  • generatePageImport (148-169)
src/utils/index.ts (1)
  • ts (18-18)
src/core/treeNodeValue.ts (1)
  • path (74-76)
src/core/tree.ts (2)
src/core/treeNodeValue.ts (2)
  • re (269-283)
  • score (251-267)
src/core/extendRoutes.ts (1)
  • params (175-177)
src/index.ts (1)
src/core/moduleConstants.ts (6)
  • MODULE_RESOLVER_PATH (7-7)
  • routeBlockQueryRE (41-41)
  • MODULE_ROUTES_PATH (6-6)
  • MODULE_VUE_ROUTER_AUTO (4-4)
  • VIRTUAL_PREFIX (26-26)
  • ROUTES_LAST_LOAD_TIME (14-21)
src/options.ts (1)
src/core/utils.ts (1)
  • isArray (65-66)
playground-experimental/vite.config.ts (1)
src/index.ts (1)
  • VueRouterAutoImports (240-253)
src/core/treeNodeValue.ts (2)
src/core/tree.ts (1)
  • params (268-280)
src/core/extendRoutes.ts (1)
  • params (175-177)
playground-experimental/typed-router.d.ts (2)
playground-experimental/src/router.ts (1)
  • RouteNamedMap (16-31)
src/codegen/generateRouteParams.ts (1)
  • ParamValue (83-85)
src/core/context.ts (11)
src/core/RoutesFolderWatcher.ts (1)
  • RoutesFolderWatcher (15-84)
src/codegen/generateParamParsers.ts (3)
  • ParamParsersMap (7-15)
  • warnMissingParamParsers (17-32)
  • generateParamParsersTypesDeclarations (57-66)
src/core/tree.ts (1)
  • name (238-245)
src/core/extendRoutes.ts (2)
  • name (88-90)
  • name (95-97)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/codegen/generateRouteResolver.ts (1)
  • generateRouteResolver (17-54)
src/utils/index.ts (1)
  • ts (18-18)
src/codegen/generateDTS.ts (1)
  • generateDTS (18-83)
src/core/moduleConstants.ts (1)
  • MODULE_ROUTES_PATH (6-6)
src/codegen/generateRouteMap.ts (1)
  • generateRouteNamedMap (9-35)
src/codegen/generateRouteFileInfoMap.ts (1)
  • generateRouteFileInfoMap (4-54)
🔇 Additional comments (53)
playground/vite.config.ts (1)

51-54: Explicitly disabling param matchers in the stable playground looks good

Keeps experimental behavior out of the non-experimental playground.

pnpm-workspace.yaml (1)

3-3: Verified: playground-experimental is marked private

Confirmed that playground-experimental/package.json contains "private": true, so it’s safe from accidental publishing.

playground/src/pages/(some-layout).vue (1)

2-4: Using name: false to opt out of a route name is fine

Clearer than an empty string and supported by the page metadata.

playground-experimental/db.json (1)

1-24: LGTM: simple fixture data

Structure is valid and works for json-server usage.

client.d.ts (1)

7-7: Readonly routes signature aligns with Vue Router v4.5.x
Vue Router v4.5.x declares RouterOptions['routes'] as readonly RouteRecordRaw[] (or Readonly<RouteRecordRaw[]>), so exporting

export const routes: readonly RouteRecordRaw[]

is fully compatible with createRouter.

Optionally, for consistency you can also mark the hot-update callback parameter as readonly:

export function handleHotUpdate(
  router: Router,
  hotUpdateCallback?: (newRoutes: readonly RouteRecordRaw[]) => void
): void
src/data-loaders/meta-extensions.ts (1)

44-44: Confirm the correct route type for “pending” navigation.

If this is truly pending (before components are resolved), RouteLocationNormalized might be more accurate than RouteLocationNormalizedLoaded. If loaders run after resolution, current type is fine.

playground-experimental/env.d.ts (1)

1-2: LGTM.

Ambient refs for Vite and unplugin-vue-router client types are appropriate for the playground.

tsconfig.json (1)

43-45: Paths look good; verify virtual module coverage.

If the new virtual module vue-router/auto-resolver is imported anywhere for types, consider adding a paths alias so TS can resolve it.

src/runtime.ts (2)

61-65: LGTM: definePage gains query field.

Nice additive type; matches playground usage.


61-65: Confirm codegen/runtime alignment

No definePage calls with query.type were found in the repo scan. Please manually verify that:

  • Your codegen templates and runtime consumers pick up the updated query typing from QueryParamType.
  • The generated parsers for int/bool (and any other generator logic) stay in sync with QueryParamType.
playground-experimental/src/pages/users/[userId=int].vue (1)

5-15: LGTM: query param typing example.

Good showcase of optional int query param via definePage.

playground/src/params/number.ts (1)

5-11: LGTM: finite check covers NaN/±Infinity.

Behavior aligns with expectations for numeric params.

playground-experimental/package.json (1)

18-22: Confirm availability of private vue-router URL in all environments.

"https://pkg.pr.new/vue-router@2d79fe7" is ephemeral. Ensure CI/dev can resolve it or document a fallback/pin for local work.

src/core/tree.ts (4)

299-309: LGTM!

The score calculation correctly walks up the tree to find the minimum score, properly excluding the root node.


311-320: LGTM!

The matcherParams getter correctly aggregates parameters from the node's param list.


322-332: LGTM!

The matcherParts implementation correctly builds the path representation with parameter placeholders.


334-338: LGTM!

The isMatchable predicate correctly identifies nodes with components and valid names.

src/core/treeNodeValue.ts (2)

516-541: LGTM!

The parameter parser state machine correctly handles the [param=type] syntax with proper state transitions.


285-298: LGTM!

The toString implementation with the 𝑥 notation provides clear parameter visualization including modifiers and parsers.

src/core/extendRoutes.spec.ts (1)

72-81: Test assertion change from toEqual to toMatchObject

The change from toEqual to toMatchObject makes the test less strict. This could hide unintended properties being added to params.

Are there additional properties on params that necessitate using toMatchObject instead of toEqual? If not, consider keeping the stricter assertion.

src/core/tree.spec.ts (5)

46-62: LGTM!

The test correctly validates custom parameter type parsing with the [id=int] syntax.


64-82: LGTM!

The test properly verifies repeatable custom param type behavior.


84-102: LGTM!

The test correctly validates optional custom param type parsing.


104-123: LGTM!

The test properly verifies the combination of repeatable and optional modifiers.


125-141: LGTM!

The test correctly validates custom param types within compound segments.

playground-experimental/tsconfig.config.json (1)

1-8: LGTM

Config-only TS project looks correct for Vite/vitest/cypress configs. The narrowed include and Node types are appropriate.

src/index.ts (3)

13-14: Exported resolver path import looks good.


70-71: Resolver virtual id wiring is correct.

Including MODULE_RESOLVER_PATH in resolve and handling it alongside routes/auto is sound.

Also applies to: 79-82


93-95: Async buildStart is fine.

await ctx.scanPages(options.watch) on buildStart avoids race conditions before loads.

playground-experimental/src/main.ts (1)

13-14: Fix the DataLoaderPlugin type mismatch
Remove the @ts-expect-error and update the call at playground-experimental/src/main.ts:13–14 to match the actual signature exported by unplugin-vue-router/data-loaders.

• Verify the plugin’s type in node_modules/unplugin-vue-router/data-loaders/index.d.ts (or its source) and adjust your call accordingly (e.g. passing arguments in the form it expects).

src/core/moduleConstants.ts (1)

31-35: LGTM!

The new resolver module constant and updated list are correctly integrated.

playground-experimental/tsconfig.json (1)

1-47: LGTM!

The TypeScript configuration properly extends Vue's config and sets up path mappings for the experimental playground.

src/options.ts (5)

236-243: LGTM!

The interface definition for experimental param matchers is well-structured with clear documentation.


245-247: LGTM!

The default configuration with ['src/params'] follows the established convention.


324-332: LGTM!

The normalization logic correctly handles boolean and object values for paramMatchers.


334-340: LGTM!

Directory resolution correctly handles both string and array inputs with proper path resolution.


397-400: LGTM!

The type alias provides convenient access to resolved options.

playground-experimental/auto-imports.d.ts (1)

1-14: LGTM!

Generated ambient declarations are properly configured for auto-imports.

src/codegen/generateRouteResolver.spec.ts (4)

1-22: LGTM!

Test setup is properly structured with appropriate imports and state initialization.


24-66: LGTM!

Tests for static path generation are comprehensive with clear snapshots.


68-133: LGTM!

The resolver generation test correctly validates parent-child relationships and internal naming.


135-173: LGTM!

Test correctly validates route ordering based on path specificity.

src/codegen/generateDTS.ts (2)

22-27: LGTM!

The parameter change from vueRouterModule to paramsTypesDeclaration aligns with the new param matcher feature.


37-45: LGTM!

Conditional injection of param parsers declarations is properly implemented.

playground-experimental/src/router.ts (2)

9-12: Router setup with experimental resolver looks correct.
Using experimental_createRouter with a resolver is consistent with the new virtual module and experimental API.


15-32: Verify runtime parity with augmented types.
You’re extending RouteNamedMap with two dynamic routes. Ensure the resolver (and/or runtime code) actually registers routes with names 'custom-dynamic-name' and 'custom-dynamic-child-name' to avoid type/runtime drift.

src/codegen/generateRouteMap.ts (1)

9-19: Signature threading (options, paramParsersMap) through recursion is sound.
Call sites in core/context.ts are updated; recursion passes the new args consistently.

src/codegen/generateRouteResolver.ts (2)

154-168: Non-matchable nodes emit records without a path — confirm normalizeRouteRecord supports this.
For non-matchable nodes, path is omitted. Ensure normalizeRouteRecord tolerates records lacking a path (group-only nodes), or guard generation accordingly.


160-164: Confirm availability and safety of node.regexp and node.matcherParts.
These symbols must be valid code (RegExp literal/string) and JSON-safe respectively. If either can be undefined, guard or sanitize to prevent broken output.

playground-experimental/typed-router.d.ts (4)

24-33: Typed params mapping looks coherent with param matchers/resolvers.

Good propagation of typed params (e.g., number for [userId=int], Param_date for [when=date]) through raw/parsed generics.


90-94: Conditional type for _RouteNamesForFilePath is standard and precise.

The Record-based narrowing to infer route unions per file path is correct.


9-9: TS configs already allow importing “.ts” in types via moduleResolution: "Bundler". No changes needed.


45-82: Default view mapping is correct
Only src/pages/[name].vue defines a nested route and renders <RouterView />, so it’s the only entry with views: 'default'. All other pages have no child routes and correctly show views: never.

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

♻️ Duplicate comments (1)
playground-experimental/package.json (1)

9-16: Add nodemon to devDependencies to satisfy the dev script.

The dev script uses nodemon but it isn’t declared here, causing runtime failure in a workspace install.

   "devDependencies": {
+    "nodemon": "^3.1.10",
     "@vitejs/plugin-vue": "^6.0.1",
     "@vue/compiler-sfc": "^3.5.18",
     "@vue/tsconfig": "^0.7.0",
     "json-server": "^0.17.4",
     "unplugin-vue-router": "workspace:*",
     "vite": "^7.1.2"
   },
🧹 Nitpick comments (3)
playground-experimental/package.json (1)

5-5: Use nodemon’s -e without the dot for clarity.

Minor: nodemon docs use extensions without a leading dot. Current flag likely works but this is clearer.

-    "dev": "nodemon -w '../src/**/*.ts' -e .ts -x vite",
+    "dev": "nodemon -w '../src/**/*.ts' -e ts -x vite",
package.json (2)

121-122: Include playground-experimental in lint coverage.

Currently excluded from the prettier check.

-    "lint": "prettier -c '{src,test,e2e,examples,playground}/**/*.{ts,vue}'",
+    "lint": "prettier -c '{src,test,e2e,examples,playground,playground-experimental}/**/*.{ts,vue}'",

121-128: Add scripts to run the experimental playground from the repo root.

Improves DX and mirrors existing playground commands.

   "scripts": {
     "build": "pnpm run --stream '/^build:/'",
     "build:core": "tsdown",
     "build:runtime": "tsdown --config tsdown-runtime.config.ts",
     "dev": "pnpm run vitest --ui",
     "vitest": "vitest --typecheck",
     "docs": "vitepress dev docs",
     "docs:build": "vitepress build docs",
     "docs:preview": "vitepress preview docs",
     "lint": "prettier -c '{src,test,e2e,examples,playground,playground-experimental}/**/*.{ts,vue}'",
     "play": "npm -C playground run dev",
+    "play:experimental": "npm -C playground-experimental run dev",
     "play:build": "npm -C playground run build",
+    "play:experimental:build": "npm -C playground-experimental run playground:build",
     "release": "node scripts/release.mjs",
     "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1",
     "pretest": "pnpm run lint",
     "test": "pnpm run build && vitest --typecheck --coverage run && pnpm run docs:build"
   },
📜 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 57c91c0 and d1c3038.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • examples/nuxt/package.json (1 hunks)
  • package.json (5 hunks)
  • playground-experimental/package.json (1 hunks)
  • playground/package.json (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • examples/nuxt/package.json
  • playground/package.json
⏰ 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). (1)
  • GitHub Check: Socket Security: Pull Request Alerts
🔇 Additional comments (3)
playground-experimental/package.json (1)

21-21: Confirm CI/network access and reproducibility for private vue-router tarball URL.

Ensure CI and contributors can fetch https://pkg.pr.new/vue-router@2d79fe7 and that your lockfile is committed to guarantee reproducible installs. If this is temporary, consider a comment or tracking issue.

package.json (2)

206-206: Verify the private vue-router devDependency is acceptable for builds/tests.

Using a tarball URL changes provenance and can break offline or air-gapped builds. Confirm CI/registry access and that the tarball’s internal version still satisfies peer constraints during local dev.


141-158: Version bumps look good.

Patch/minor bumps across deps are reasonable and low risk.

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

🧹 Nitpick comments (6)
src/codegen/generateParamParsers.ts (5)

34-38: Fix JSDoc param description

The JSDoc refers to paramParserPath but the function receives a TreeRouteParam. Update the comment to avoid confusion.


68-82: Rename parparsersMap to paramParsersMap for clarity

Typo in the parameter name; use a consistent name across the codebase.

Apply this diff:

-export function generateParamsTypes(
-  params: TreeRouteParam[],
-  parparsersMap: ParamParsersMap
-): Array<string | null> {
+export function generateParamsTypes(
+  params: TreeRouteParam[],
+  paramParsersMap: ParamParsersMap
+): Array<string | null> {
   return params.map((param) => {
     if (param.parser) {
-      if (parparsersMap.has(param.parser)) {
-        return parparsersMap.get(param.parser)!.typeName
+      if (paramParsersMap.has(param.parser)) {
+        return paramParsersMap.get(param.parser)!.typeName
       } else if (param.parser === 'int') {
         return 'number'
       }
     }
     return null
   })
 }

84-101: Use relativePath for generated imports; sanitize built-in checks

Absolute file-system import specifiers can break bundlers. Prefer the already-available relativePath. Also, see next comment to make the built-in “int” check consistent.

Apply this diff:

   if (param.parser && paramParsers.has(param.parser)) {
-    const { name, absolutePath } = paramParsers.get(param.parser)!
+    const { name, relativePath } = paramParsers.get(param.parser)!
     const varName = `PARAM_PARSER__${name}`
-    importsMap.add(absolutePath, { name: 'parser', as: varName })
+    importsMap.add(`./${relativePath}`, { name: 'parser', as: varName })
     return ` ...${varName}, `
   } else if (param.parser === 'int') {
     importsMap.add('vue-router/experimental', `PARAM_PARSER_INT`)
     return ` ...PARAM_PARSER_INT, `
   }

95-98: Normalize built-in “int” detection

Elsewhere you already camelCase the parser name; do the same here to accept “Int”, “INT”, etc.

Apply this diff:

-  } else if (param.parser === 'int') {
+  } else if (param.parser && camelCase(param.parser) === 'int') {
     importsMap.add('vue-router/experimental', `PARAM_PARSER_INT`)
     return ` ...PARAM_PARSER_INT, `
   }

102-110: Remove or improve the throw message

Throwing Error('what') is unhelpful and the param is non-optional by type. Either remove the check or provide a descriptive error.

Apply this diff to remove the check:

 export function generateParamsOptions(
   params: TreeRouteParam[],
   importsMap: ImportsMap,
   paramParsers: ParamParsersMap
 ) {
-  if (!paramParsers) {
-    throw new Error('what')
-  }
src/codegen/generateRouteResolver.ts (1)

35-40: Drop unused MatcherPatternPathStar import

It's not referenced here. Remove to avoid unused import noise in generated output.

Apply this diff:

   importsMap.add('vue-router/experimental', 'MatcherPatternPathStatic')
   importsMap.add('vue-router/experimental', 'MatcherPatternPathCustomParams')
-  importsMap.add('vue-router/experimental', 'MatcherPatternPathStar')
   importsMap.add('vue-router/experimental', 'normalizeRouteRecord')
📜 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 d1c3038 and ba8727f.

📒 Files selected for processing (5)
  • src/codegen/generateParamParsers.ts (1 hunks)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/codegen/generateRouteResolver.ts (1 hunks)
  • src/core/tree.ts (2 hunks)
  • src/core/treeNodeValue.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/core/tree.ts
  • src/codegen/generateRouteResolver.spec.ts
  • src/core/treeNodeValue.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/codegen/generateParamParsers.ts (4)
src/core/tree.ts (3)
  • PrefixTree (384-418)
  • name (242-249)
  • params (272-284)
src/core/treeNodeValue.ts (1)
  • TreeRouteParam (226-233)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/utils/index.ts (1)
  • ts (18-18)
src/codegen/generateRouteResolver.ts (4)
src/core/tree.ts (3)
  • PrefixTree (384-418)
  • TreeNode (19-378)
  • params (272-284)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/codegen/generateParamParsers.ts (2)
  • ParamParsersMap (7-15)
  • generateParamsOptions (102-120)
src/utils/index.ts (1)
  • ts (18-18)
⏰ 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). (1)
  • GitHub Check: Socket Security: Pull Request Alerts
🔇 Additional comments (3)
src/codegen/generateParamParsers.ts (2)

57-66: Verify parser.get shape and path normalization

The generated type alias assumes a named export parser with a 'get' property. Confirm that custom parsers follow this contract and that relativePath is normalized to POSIX separators in emitted code.

Would you like a small utility to normalize paths before they’re stored in ParamParsersMap?


122-133: LGTM on TypesConfig augmentation

The ambient union for ParamParsers is clear and only emitted when needed.

src/codegen/generateRouteResolver.ts (1)

149-174: LGTM on path serialization

Static vs. custom-params paths are emitted correctly, and params options integrate with generateParamsOptions.

@posva posva force-pushed the feat/experimental-router branch from e8c15b3 to e5861d4 Compare August 15, 2025 13:51
Copy link

codecov bot commented Aug 15, 2025

Codecov Report

❌ Patch coverage is 72.28916% with 253 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.14%. Comparing base (8d4b387) to head (8b22754).

Files with missing lines Patch % Lines
src/core/context.ts 0.00% 104 Missing ⚠️
src/core/treeNodeValue.ts 73.13% 36 Missing ⚠️
src/codegen/generateRouteParams.ts 9.67% 28 Missing ⚠️
src/core/definePage.ts 78.70% 23 Missing ⚠️
src/options.ts 48.38% 16 Missing ⚠️
src/core/vite/index.ts 0.00% 13 Missing ⚠️
src/utils/index.ts 11.11% 8 Missing ⚠️
src/codegen/generateDTS.ts 0.00% 7 Missing ⚠️
src/codegen/generateRouteResolver.ts 97.51% 6 Missing ⚠️
src/core/moduleConstants.ts 0.00% 6 Missing ⚠️
... and 3 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #700      +/-   ##
==========================================
+ Coverage   60.90%   64.14%   +3.23%     
==========================================
  Files          36       38       +2     
  Lines        3379     4203     +824     
  Branches      618      801     +183     
==========================================
+ Hits         2058     2696     +638     
- Misses       1314     1500     +186     
  Partials        7        7              

☔ 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.

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

♻️ Duplicate comments (6)
src/utils/index.ts (1)

1-1: Bug: constants isn’t exported from fs/promises; fileExists() always returns false

constants import is invalid; accessing constants.F_OK throws and the catch returns false even for existing files.

Apply this diff:

-import { access, constants } from 'fs/promises'
+import { access } from 'fs/promises'
@@
-export async function fileExists(filePath: string) {
+export async function fileExists(filePath: string): Promise<boolean> {
   try {
-    await access(filePath, constants.F_OK)
+    await access(filePath)
     return true
   } catch {
     return false
   }
 }

Also applies to: 20-27

src/core/tree.ts (1)

290-295: Fix regex escaping for static segments.

Static segments containing regex metacharacters (e.g., "users.list") will create invalid regex patterns. Apply proper escaping:

       if (node.value.isParam() && node.value.re) {
         re = node.value.re + (re ? '\\/' : '') + re
       } else {
-        re = node.value.pathSegment + (re ? '\\/' : '') + re
+        // Escape special regex characters in static segments
+        const escaped = node.value.pathSegment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+        re = escaped + (re ? '\\/' : '') + re
       }
src/codegen/generateParamParsers.ts (1)

17-32: Remove unnecessary async modifier.

The function doesn't use await:

-export async function warnMissingParamParsers(
+export function warnMissingParamParsers(
   tree: PrefixTree,
   paramParsers: ParamParsersMap
 ) {
src/codegen/generateRouteResolver.spec.ts (1)

126-126: Filter glob pattern to prevent non-code files.

Line 126 uses glob('*') which can include non-code files like README.md, causing invalid type imports.

Apply this diff:

-        return glob('*', {
+        return glob('*.{ts,js}', {
src/core/context.ts (1)

126-126: Filter glob pattern to code files only.

Using glob('*') can match non-code files like README.md, causing invalid imports.

-        return glob('*', {
+        return glob('*.{ts,js}', {
src/codegen/generateRouteResolver.ts (1)

93-93: Fix potential negative repeat count.

The padding calculation can produce negative values when varName is longer than expected.

-      `  ${varName},  ${' '.repeat(String(state.id).length - varName.length + 2)}// ${path}`
+      `  ${varName},  ${' '.repeat(Math.max(0, String(state.id).length - varName.length + 2))}// ${path}`
🧹 Nitpick comments (2)
client.d.ts (1)

7-7: Readonly routes is a (minor) TS breaking change; align callback type for consistency

Some users may have been mutating routes. Typing as readonly is good; consider making handleHotUpdate’s newRoutes readonly too for consistency.

Apply this diff:

-  export function handleHotUpdate(
+  export function handleHotUpdate(
     router: Router,
-    hotUpdateCallback?: (newRoutes: RouteRecordRaw[]) => void
+    hotUpdateCallback?: (newRoutes: readonly RouteRecordRaw[]) => void
   ): void
src/codegen/generateRouteMap.ts (1)

42-49: Inefficient array initialization and type generation.

Creating arrays and conditionally assigning paramType is unnecessarily complex.

-  const params = node.params
-  let paramParsers: Array<string | null> = []
-  let paramType: string = ''
-
-  if (options.experimental.paramMatchers) {
-    paramParsers = generateParamsTypes(params, paramParsersMap)
-    paramType = EXPERIMENTAL_generateRouteParams(node, paramParsers)
-  }
+  const paramType = options.experimental.paramMatchers
+    ? EXPERIMENTAL_generateRouteParams(node, generateParamsTypes(node.params, paramParsersMap))
+    : ''
📜 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 e8c15b3 and e5861d4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (55)
  • client.d.ts (1 hunks)
  • examples/nuxt/package.json (1 hunks)
  • package.json (2 hunks)
  • playground-experimental/.gitignore (1 hunks)
  • playground-experimental/auto-imports.d.ts (1 hunks)
  • playground-experimental/db.json (1 hunks)
  • playground-experimental/env.d.ts (1 hunks)
  • playground-experimental/index.html (1 hunks)
  • playground-experimental/package.json (1 hunks)
  • playground-experimental/src/App.vue (1 hunks)
  • playground-experimental/src/main.ts (1 hunks)
  • playground-experimental/src/pages/(home).vue (1 hunks)
  • playground-experimental/src/pages/[name].vue (1 hunks)
  • playground-experimental/src/pages/[name]/24.vue (1 hunks)
  • playground-experimental/src/pages/[name]/[userId=int].vue (1 hunks)
  • playground-experimental/src/pages/a.[b].c.[d].vue (1 hunks)
  • playground-experimental/src/pages/b.vue (1 hunks)
  • playground-experimental/src/pages/events/[when=date].vue (1 hunks)
  • playground-experimental/src/pages/users/[userId=int].vue (1 hunks)
  • playground-experimental/src/pages/users/sub-[first]-[second].vue (1 hunks)
  • playground-experimental/src/params/date.ts (1 hunks)
  • playground-experimental/src/router.ts (1 hunks)
  • playground-experimental/src/utils.ts (1 hunks)
  • playground-experimental/tsconfig.config.json (1 hunks)
  • playground-experimental/tsconfig.json (1 hunks)
  • playground-experimental/typed-router.d.ts (1 hunks)
  • playground-experimental/vite.config.ts (1 hunks)
  • playground/package.json (1 hunks)
  • playground/src/pages/(some-layout).vue (1 hunks)
  • playground/src/params/date.ts (1 hunks)
  • playground/src/params/number.ts (1 hunks)
  • playground/vite.config.ts (1 hunks)
  • pnpm-workspace.yaml (1 hunks)
  • src/codegen/generateDTS.ts (2 hunks)
  • src/codegen/generateParamParsers.ts (1 hunks)
  • src/codegen/generateRouteMap.spec.ts (20 hunks)
  • src/codegen/generateRouteMap.ts (1 hunks)
  • src/codegen/generateRouteParams.ts (1 hunks)
  • src/codegen/generateRouteRecords.ts (1 hunks)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/codegen/generateRouteResolver.ts (1 hunks)
  • src/core/context.ts (10 hunks)
  • src/core/extendRoutes.spec.ts (9 hunks)
  • src/core/moduleConstants.ts (2 hunks)
  • src/core/tree.spec.ts (2 hunks)
  • src/core/tree.ts (2 hunks)
  • src/core/treeNodeValue.ts (11 hunks)
  • src/data-loaders/meta-extensions.ts (1 hunks)
  • src/index.ts (5 hunks)
  • src/options.ts (3 hunks)
  • src/runtime.ts (1 hunks)
  • src/utils/index.ts (2 hunks)
  • tsconfig.json (2 hunks)
  • vitest.config.ts (1 hunks)
  • vitest.workspace.js (0 hunks)
💤 Files with no reviewable changes (1)
  • vitest.workspace.js
✅ Files skipped from review due to trivial changes (2)
  • playground-experimental/tsconfig.config.json
  • examples/nuxt/package.json
🚧 Files skipped from review as they are similar to previous changes (37)
  • playground-experimental/src/pages/events/[when=date].vue
  • playground-experimental/index.html
  • playground-experimental/src/pages/(home).vue
  • playground-experimental/src/pages/[name]/24.vue
  • playground-experimental/src/pages/[name]/[userId=int].vue
  • playground-experimental/src/pages/b.vue
  • playground-experimental/src/pages/users/[userId=int].vue
  • playground-experimental/env.d.ts
  • playground-experimental/.gitignore
  • src/core/extendRoutes.spec.ts
  • vitest.config.ts
  • src/codegen/generateRouteRecords.ts
  • package.json
  • src/core/tree.spec.ts
  • playground/vite.config.ts
  • playground/src/params/number.ts
  • src/index.ts
  • playground-experimental/src/pages/users/sub-[first]-[second].vue
  • playground-experimental/src/params/date.ts
  • playground/package.json
  • src/data-loaders/meta-extensions.ts
  • playground-experimental/src/main.ts
  • playground-experimental/db.json
  • playground-experimental/tsconfig.json
  • pnpm-workspace.yaml
  • playground-experimental/package.json
  • playground-experimental/auto-imports.d.ts
  • src/runtime.ts
  • src/codegen/generateDTS.ts
  • playground-experimental/src/router.ts
  • playground/src/params/date.ts
  • playground-experimental/src/pages/[name].vue
  • src/codegen/generateRouteParams.ts
  • playground-experimental/src/App.vue
  • playground-experimental/vite.config.ts
  • playground-experimental/src/utils.ts
  • tsconfig.json
🧰 Additional context used
🧬 Code Graph Analysis (10)
src/codegen/generateRouteMap.spec.ts (3)
src/codegen/generateRouteMap.ts (1)
  • generateRouteNamedMap (9-35)
src/options.ts (1)
  • DEFAULT_OPTIONS (249-266)
src/index.ts (1)
  • DEFAULT_OPTIONS (29-29)
src/codegen/generateRouteResolver.ts (8)
src/core/tree.ts (4)
  • PrefixTree (384-418)
  • path (221-226)
  • TreeNode (19-378)
  • params (272-284)
src/options.ts (1)
  • ResolvedOptions (400-400)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/codegen/generateParamParsers.ts (2)
  • ParamParsersMap (7-15)
  • generateParamsOptions (102-120)
src/codegen/generateRouteRecords.ts (2)
  • generateRouteRecord (15-113)
  • generatePageImport (148-169)
src/utils/index.ts (1)
  • ts (18-18)
src/core/treeNodeValue.ts (1)
  • path (74-76)
src/core/extendRoutes.ts (1)
  • children (190-194)
src/codegen/generateRouteMap.ts (4)
src/core/tree.ts (2)
  • TreeNode (19-378)
  • params (272-284)
src/options.ts (1)
  • ResolvedOptions (400-400)
src/codegen/generateParamParsers.ts (2)
  • ParamParsersMap (7-15)
  • generateParamsTypes (68-82)
src/codegen/generateRouteParams.ts (2)
  • EXPERIMENTAL_generateRouteParams (24-50)
  • generateRouteParams (3-22)
src/codegen/generateParamParsers.ts (4)
src/core/tree.ts (2)
  • PrefixTree (384-418)
  • name (242-249)
src/core/treeNodeValue.ts (1)
  • TreeRouteParam (226-233)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/utils/index.ts (1)
  • ts (18-18)
src/core/tree.ts (2)
src/core/treeNodeValue.ts (1)
  • re (273-286)
src/core/extendRoutes.ts (1)
  • params (175-177)
src/codegen/generateRouteResolver.spec.ts (6)
src/options.ts (2)
  • DEFAULT_OPTIONS (249-266)
  • resolveOptions (313-395)
src/codegen/generateRouteResolver.ts (3)
  • generateRouteRecord (103-170)
  • generateRouteRecordPath (196-218)
  • generateRouteResolver (61-98)
src/codegen/generateRouteRecords.ts (1)
  • generateRouteRecord (15-113)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/core/tree.ts (1)
  • PrefixTree (384-418)
src/codegen/generateParamParsers.ts (1)
  • ParamParsersMap (7-15)
src/core/treeNodeValue.ts (2)
src/core/tree.ts (1)
  • params (272-284)
src/core/extendRoutes.ts (1)
  • params (175-177)
src/options.ts (1)
src/core/utils.ts (1)
  • isArray (65-66)
src/core/context.ts (10)
src/core/RoutesFolderWatcher.ts (1)
  • RoutesFolderWatcher (15-84)
src/codegen/generateParamParsers.ts (3)
  • ParamParsersMap (7-15)
  • warnMissingParamParsers (17-32)
  • generateParamParsersTypesDeclarations (57-66)
src/core/tree.ts (1)
  • name (242-249)
src/core/extendRoutes.ts (2)
  • name (88-90)
  • name (95-97)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/codegen/generateRouteResolver.ts (1)
  • generateRouteResolver (61-98)
src/codegen/generateDTS.ts (1)
  • generateDTS (18-83)
src/core/moduleConstants.ts (1)
  • MODULE_ROUTES_PATH (6-6)
src/codegen/generateRouteMap.ts (1)
  • generateRouteNamedMap (9-35)
src/codegen/generateRouteFileInfoMap.ts (1)
  • generateRouteFileInfoMap (4-54)
playground-experimental/typed-router.d.ts (2)
playground-experimental/src/router.ts (1)
  • RouteNamedMap (16-31)
src/codegen/generateRouteParams.ts (1)
  • ParamValue (83-85)
🔇 Additional comments (18)
playground/src/pages/(some-layout).vue (1)

3-3: Confirm name: false is intended and supported by definePage

If the intent is to opt-out of a route name, ensure no navigation relies on this page’s name and that types/runtime accept false here.

playground-experimental/src/pages/a.[b].c.[d].vue (1)

1-5: LGTM

Minimal page to surface name/path works for the experimental routes.

src/core/moduleConstants.ts (1)

7-7: LGTM: new resolver virtual module constant and registration

MODULE_RESOLVER_PATH and its inclusion in MODULES_ID_LIST look correct.

Also applies to: 31-35

src/options.ts (4)

324-333: LGTM! Clean ternary logic for paramMatchers normalization.

The logic correctly handles boolean true by using defaults, merges object configs with defaults, and returns undefined for falsy values.


334-340: LGTM! Proper array normalization for paramMatchersDir.

The code correctly normalizes dir to an array and resolves paths relative to root.


342-347: LGTM! Consistent path resolution for autoExportsDataLoaders.

The refactored code maintains the same logic while improving readability.


349-357: LGTM! Proper experimental options restructuring.

The code correctly preserves undefined for paramMatchers when not set and replaces dir with resolved paths when present.

src/core/tree.ts (2)

326-348: LGTM! Well-structured matcher parts generation.

The recursive traversal correctly builds the matcher parts array, handling both single and multiple sub-segments appropriately.


350-354: LGTM! Clear matchability check.

The type guard correctly ensures a node has components and a truthy name.

playground-experimental/typed-router.d.ts (1)

1-133: LGTM! Well-structured type definitions.

The auto-generated TypeScript declarations properly define custom param parsers, route maps, and file info mappings for type-safe routing.

src/codegen/generateRouteMap.spec.ts (1)

22-24: LGTM! Test updated for new API signature.

All test calls correctly pass the required three arguments to generateRouteNamedMap.

src/codegen/generateRouteResolver.spec.ts (2)

18-23: Good test structure with proper state reset.

The beforeEach hook correctly resets DEFAULT_STATE to prevent test interference.


309-320: Clean helper function for extracting route order.

The getRouteOrderFromResolver utility effectively parses resolver output for testing route prioritization.

src/core/treeNodeValue.ts (3)

257-271: Well-structured score calculation for route prioritization.

The score getter properly differentiates static (300) from param segments with appropriate malus values.


274-286: Good regex escaping implementation.

Properly escapes regex special characters using REGEX_CHARS_RE pattern.


522-547: Parser state handling looks correct.

The paramParser state properly handles '=' delimiter and accumulates parser type in paramParserBuffer.

src/core/context.ts (1)

252-273: Clean resolver generation implementation.

The generateResolver function properly assembles imports and resolver code.

src/codegen/generateRouteResolver.ts (1)

12-50: Robust route scoring algorithm.

The compareRouteScore function implements comprehensive priority comparison with minimum score, average score, segment count, and path length.

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)
src/codegen/generateRouteResolver.ts (1)

90-96: Fix padding logic to avoid negative repeat while keeping alignment

Clamp the repeat count to non-negative to prevent runtime errors.

Apply this diff:

-  .map(
-    ({ varName, path }) =>
-      `  ${varName},  ${' '.repeat(String(state.id).length - varName.length + 2)}// ${path}`
-  )
+  .map(
+    ({ varName, path }) =>
+      `  ${varName},${' '.repeat(Math.max(0, String(state.id).length - varName.length + 2))}// ${path}`
+  )
🧹 Nitpick comments (10)
playground-experimental/src/pages/u[name]/24.vue (2)

5-5: Fix typo in copy

"Too test" -> "To test".

Apply:

-  <p>Too test specificity in routes</p>
+  <p>To test specificity in routes.</p>

1-1: Remove empty script block

No script logic is used. Dropping it keeps the SFC lean.

Apply:

-<script lang="ts" setup></script>
playground-experimental/src/pages/u[name]/[userId=int].vue (2)

4-4: Clarify the intent of the heading

This route will be visible except when the path matches the more specific static child (…/24). The current message can mislead.

Apply:

-  <h2>This page should never be visible</h2>
+  <h2>Dynamic userId route (hidden when the path is .../24)</h2>

6-6: Drop RouterView if there are no nested children

If no routes exist under /u[name]/[userId=int]/..., the RouterView is unnecessary.

Apply:

-  <RouterView />

If you plan to add children later, keeping it is fine.

playground-experimental/src/pages/[...404].vue (1)

21-23: Prefer meta keys without spaces

Accessing with dot notation becomes impossible with spaces. Consider camelCase.

Apply:

-  "meta": {
-    "from block": true
-  }
+  "meta": {
+    "fromBlock": true
+  }

Note: Update any code that reads $route.meta['from block'] accordingly.

src/runtime.ts (4)

119-129: Consider preserving alias concatenation like _mergeRouteRecord

The experimental merge overwrites alias instead of concatenating. If aliases matter in the experimental resolver, mirror the behavior of _mergeRouteRecord.

Apply this diff:

-      // Merge meta properties specially
+      // Merge meta properties specially
       const meta = Object.assign({}, acc.meta, routeRecord.meta)
 
-      // Merge all other properties directly
+      // Merge all other properties directly
       Object.assign(acc, routeRecord)
 
+      // Concatenate aliases if present
+      const alias = ([] as string[]).concat(acc.alias || [], routeRecord.alias || [])
+      if (alias.length) acc.alias = alias
+
       // Set the merged meta

73-84: Make QueryParamType extensible via module augmentation

A type alias cannot be augmented by users. Expose a map interface and derive the union from its keys to allow extension.

Apply this diff:

-export type QueryParamType = 'int' | 'bool'
+export interface QueryParamTypeMap {
+  int: true
+  bool: true
+}
+export type QueryParamType = keyof QueryParamTypeMap

81-82: Fix broken JSDoc link

https://uvr.esm.is/TODO is a placeholder and will confuse users. Replace with a neutral note until docs exist.

Apply this diff:

-   * and any parser in the {@link https://uvr.esm.is/TODO | params folder }.
+   * and any parser available in the params directory (documentation pending).

62-65: Track the query serialization TODO

The TODO implies non-final behavior. Please open an issue to scope the custom serialization design and extraction rules.

I can open a tracking issue with a short proposal if you want.

src/codegen/generateRouteResolver.ts (1)

80-85: Avoid emitting unused imports for the star matcher

MatcherPatternPathStar is added unconditionally but never used here. Emit it only when needed to keep generated code clean and avoid noUnusedLocals issues.

Apply this diff to remove it for now:

   importsMap.add('vue-router/experimental', 'createStaticResolver')
   importsMap.add('vue-router/experimental', 'MatcherPatternPathStatic')
   importsMap.add('vue-router/experimental', 'MatcherPatternPathCustomParams')
-  importsMap.add('vue-router/experimental', 'MatcherPatternPathStar')
   importsMap.add('vue-router/experimental', 'normalizeRouteRecord')

Optionally, add it conditionally when a star segment is detected once that case is generated.

📜 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 e5861d4 and d847e06.

⛔ Files ignored due to path filters (1)
  • src/codegen/__snapshots__/generateRouteRecords.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (10)
  • playground-experimental/src/pages/[...404].vue (1 hunks)
  • playground-experimental/src/pages/u[name].vue (1 hunks)
  • playground-experimental/src/pages/u[name]/24.vue (1 hunks)
  • playground-experimental/src/pages/u[name]/[userId=int].vue (1 hunks)
  • playground-experimental/typed-router.d.ts (1 hunks)
  • src/codegen/generateRouteRecords.ts (2 hunks)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/codegen/generateRouteResolver.ts (1 hunks)
  • src/core/treeNodeValue.ts (11 hunks)
  • src/runtime.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/codegen/generateRouteResolver.spec.ts
  • src/codegen/generateRouteRecords.ts
  • src/core/treeNodeValue.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-15T16:11:02.610Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.610Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/codegen/generateRouteResolver.ts
🧬 Code Graph Analysis (3)
src/runtime.ts (2)
src/core/tree.ts (1)
  • meta (264-270)
src/core/extendRoutes.ts (2)
  • meta (110-112)
  • meta (119-122)
src/codegen/generateRouteResolver.ts (7)
src/core/tree.ts (6)
  • PrefixTree (384-418)
  • path (221-226)
  • TreeNode (19-378)
  • isMatchable (350-354)
  • name (242-249)
  • params (272-284)
src/options.ts (1)
  • ResolvedOptions (400-400)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/codegen/generateParamParsers.ts (2)
  • ParamParsersMap (7-15)
  • generateParamsOptions (102-120)
src/codegen/generateRouteRecords.ts (3)
  • generateRouteRecord (15-110)
  • formatMeta (168-178)
  • generatePageImport (145-166)
src/utils/index.ts (1)
  • ts (18-18)
src/core/treeNodeValue.ts (1)
  • path (74-76)
playground-experimental/typed-router.d.ts (2)
playground-experimental/src/router.ts (1)
  • RouteNamedMap (16-31)
src/codegen/generateRouteParams.ts (1)
  • ParamValue (83-85)
🔇 Additional comments (5)
playground-experimental/src/pages/u[name].vue (1)

3-8: LGTM

Parent displays params and hosts children correctly. Works well for testing specificity vs. static child (/24) and dynamic child ([userId=int]).

playground-experimental/typed-router.d.ts (2)

24-34: Typed routes for u[name] and children look correct

  • '/u[name]' -> '/u:name' with children union of '/u[name]/24' and '/u[name]/[userId=int]'.
  • Static '/24' outranks the dynamic int segment, which matches the intent of the playground.

46-78: typed-router.d.ts is already included in tsconfig.json
Verified that playground-experimental/tsconfig.json lists ./typed-router.d.ts under include, so typed routes are available in IDEs.

playground-experimental/src/pages/[...404].vue (1)

2-7: LGTM

Meta defined via definePage is fine and complements the route block.

src/codegen/generateRouteResolver.ts (1)

128-141: DefinePage import breadth can yield undefined defaults

You import definePage data for every component when node.hasDefinePage is true. Some components may not export a definePage default, making their variables undefined. With the runtime merge fix, this is safe, but confirm your loader always defines a default (even {}) or rely on the new guard.

If the loader cannot guarantee a default, no action in codegen is needed after adopting the runtime guard; otherwise, filter to known components if that metadata becomes available.

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

♻️ Duplicate comments (2)
src/codegen/generateParamParsers.ts (2)

17-32: Async removal applied correctly

Previous feedback about removing unnecessary async was addressed; the function is now synchronous and iterates deep-sorted nodes.


102-109: Replace placeholder error message

Use a descriptive error to aid debugging.

   if (!paramParsers) {
-    throw new Error('what')
+    throw new Error('paramParsers is required for generateParamsOptions')
   }
🧹 Nitpick comments (8)
playground-experimental/src/page-outside.vue (1)

1-2: Remove empty script block

No script logic is present; dropping it reduces noise.

-<script lang="ts" setup></script>
-
playground-experimental/src/App.vue (3)

6-6: Gate console log to dev builds.

Avoid noisy logs in prod.

-console.log(`We have ${routes.length} routes.`)
+if (import.meta.env.DEV) console.log(`We have ${routes.length} routes.`)

34-37: Pretty-print route params for readability.

Raw objects render as [object Object].

-      <pre>{{ $route.params }}</pre>
+      <pre>{{ JSON.stringify($route.params, null, 2) }}</pre>

2-2: Remove unused import.

useRoute isn’t used in this component.

-import { useRoute } from 'vue-router'
src/core/context.ts (1)

216-224: Normalize relativePath consistently (avoid leading './').

Initial scan stores relativePath without './', watcher path adds it, leading to inconsistent imports like import('././path').

         paramParsers.set(name, {
           name,
           typeName: `Param_${name}`,
           absolutePath,
-          relativePath: './' + relative(options.root, absolutePath),
+          relativePath: relative(options.root, absolutePath),
         })
src/codegen/generateParamParsers.ts (3)

25-27: Enrich warn message with param name

Including the param name improves debuggability without adding noise.

-          console.warn(
-            `Parameter parser "${param.parser}" not found for route "${node.fullPath}".`
-          )
+          console.warn(
+            `Parameter parser "${param.parser}" not found for route "${node.fullPath}" (param "${param.paramName}").`
+          )

39-55: Prefer early return for native parser

Minor: check for 'int' before computing camelCase.

 export function generateParamParserType(param: TreeRouteParam): string | null {
   if (!param.parser) {
     return null
   }
-  // TODO: actualpath
-  // const nameWithExtension = basename(param.paramName)
-  const name = camelCase(param.parser)
-
-  // TODO: treat custom parsers first
-
-  // native parsers
-  if (name === 'int') {
-    return 'number'
-  }
-
-  return `Param_${name}`
+  // native parsers
+  if (param.parser === 'int') {
+    return 'number'
+  }
+  // TODO: actualpath
+  // const nameWithExtension = basename(param.paramName)
+  const name = camelCase(param.parser)
+  return `Param_${name}`
 }

68-82: Rename parparsersMapparamParsersMap for consistency

Update the parameter name and its usages in src/codegen/generateParamParsers.ts:

 export function generateParamsTypes(
   params: TreeRouteParam[],
-  parparsersMap: ParamParsersMap
+  paramParsersMap: ParamParsersMap
 ): Array<string | null> {
   return params.map((param) => {
     if (param.parser) {
-      if (parparsersMap.has(param.parser)) {
-        return parparsersMap.get(param.parser)!.typeName
+      if (paramParsersMap.has(param.parser)) {
+        return paramParsersMap.get(param.parser)!.typeName
       } else if (param.parser === 'int') {
         return 'number'
       }
     }
     return null
   })
 }

[optional_refactors_recommended]

📜 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 d847e06 and 8c028f8.

📒 Files selected for processing (10)
  • playground-experimental/src/App.vue (1 hunks)
  • playground-experimental/src/page-outside.vue (1 hunks)
  • playground-experimental/typed-router.d.ts (1 hunks)
  • playground-experimental/vite.config.ts (1 hunks)
  • src/codegen/generateParamParsers.ts (1 hunks)
  • src/core/context.ts (10 hunks)
  • src/core/extendRoutes.ts (1 hunks)
  • src/core/tree.ts (3 hunks)
  • src/core/treeNodeValue.ts (11 hunks)
  • src/utils/index.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/core/extendRoutes.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/utils/index.ts
  • playground-experimental/typed-router.d.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-15T16:11:02.610Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.610Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/core/treeNodeValue.ts
  • src/core/context.ts
🧬 Code Graph Analysis (5)
playground-experimental/vite.config.ts (2)
scripts/release.mjs (1)
  • __dirname (13-13)
src/index.ts (1)
  • VueRouterAutoImports (240-253)
src/codegen/generateParamParsers.ts (4)
src/core/tree.ts (3)
  • PrefixTree (385-419)
  • name (243-250)
  • params (273-285)
src/core/treeNodeValue.ts (1)
  • TreeRouteParam (226-233)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/utils/index.ts (1)
  • ts (18-18)
src/core/tree.ts (2)
src/core/treeNodeValue.ts (2)
  • re (282-295)
  • escapeRegex (247-248)
src/core/extendRoutes.ts (1)
  • params (177-179)
src/core/treeNodeValue.ts (2)
src/core/extendRoutes.ts (1)
  • params (177-179)
src/core/tree.ts (1)
  • params (273-285)
src/core/context.ts (10)
src/core/RoutesFolderWatcher.ts (1)
  • RoutesFolderWatcher (15-84)
src/codegen/generateParamParsers.ts (3)
  • ParamParsersMap (7-15)
  • warnMissingParamParsers (17-32)
  • generateParamParsersTypesDeclarations (57-66)
src/core/extendRoutes.ts (2)
  • name (90-92)
  • name (97-99)
src/core/tree.ts (1)
  • name (243-250)
src/codegen/generateRouteResolver.ts (1)
  • generateRouteResolver (62-99)
src/utils/index.ts (1)
  • ts (18-18)
src/codegen/generateDTS.ts (1)
  • generateDTS (18-83)
src/core/moduleConstants.ts (1)
  • MODULE_ROUTES_PATH (6-6)
src/codegen/generateRouteMap.ts (1)
  • generateRouteNamedMap (9-35)
src/codegen/generateRouteFileInfoMap.ts (1)
  • generateRouteFileInfoMap (4-54)
⏰ 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: test (lts/*, ubuntu-latest)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (9)
playground-experimental/src/page-outside.vue (1)

3-5: LGTM

Template is clear and minimal.

src/core/treeNodeValue.ts (3)

235-249: Proper regex escaping utility added.

Good call extracting and reusing escapeRegex.


282-295: Correct use of escapeRegex in param regex builder.

Static segments are safely escaped; param patterns look solid.


497-503: Avoid pushing leading empty static subsegment.

This prevents bogus “” entries when a segment starts with a param.

src/core/tree.ts (2)

287-303: Static segment escaping in regexp is correct.

Using escapeRegex fixes meta-character issues in route regex construction.


301-302: Confirm case-insensitive matching is intended.

/i changes default Vue Router behavior (case-sensitive). Validate this aligns with resolver expectations.

Would you like me to scan for consumers that rely on case-sensitive matching before changing it?

src/core/context.ts (1)

71-73: Good: param parser scan restricted to code files.

Filtering to *.{ts,js} prevents spurious imports.

src/codegen/generateParamParsers.ts (2)

57-66: Type declarations generation looks good

The emitted ReturnType over typeof import(...).parser['get'] is consistent with how options import parser objects elsewhere.


122-133: LGTM on ambient module augmentation

The union emission and empty-case guard are appropriate.

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 (2)
src/runtime.ts (1)

114-124: Guard against nullish records and undefined meta in experimental merge

Accessing routeRecord.meta or spreading acc.meta when either is undefined will throw at runtime. Also, Object.assign with an undefined routeRecord will throw. Add a nullish guard and safe meta merge.

Apply:

 export function _mergeRouteRecordExperimental(
   main: EXPERIMENTAL_RouteRecordRaw,
   ...routeRecords: Partial<DefinePage>[]
 ): EXPERIMENTAL_RouteRecordRaw {
-  return routeRecords.reduce((acc, routeRecord) => {
-    Object.assign(acc, routeRecord)
-    acc.meta = { ...acc.meta, ...routeRecord.meta }
-
-    return acc
-  }, main)
+  return routeRecords.reduce((acc, routeRecord) => {
+    if (!routeRecord) return acc
+    const meta = Object.assign({}, acc.meta ?? {}, routeRecord.meta ?? {})
+    Object.assign(acc, routeRecord)
+    if (Object.keys(meta).length) acc.meta = meta
+    return acc
+  }, main)
 }
src/codegen/generateParamParsers.ts (1)

91-94: Normalize absolute import path (cross-OS safety)

Backslashes in Windows paths break import specifiers. Normalize before adding.

-    const { name, absolutePath } = paramParsers.get(param.parser)!
-    const varName = `PARAM_PARSER__${name}`
-    importsMap.add(absolutePath, { name: 'parser', as: varName })
+    const { name, absolutePath } = paramParsers.get(param.parser)!
+    const varName = `PARAM_PARSER__${name}`
+    const importPath = absolutePath.replace(/\\/g, '/')
+    importsMap.add(importPath, { name: 'parser', as: varName })
🧹 Nitpick comments (6)
src/runtime.ts (2)

74-75: Allow custom param parser names beyond 'int' | 'bool'

If users can add parsers (e.g., from a params folder), this alias should be open-ended. Otherwise, using custom parser names will not type-check.

Two options:

  • Minimal change (keeps suggestions, allows any string):
-export type QueryParamType = 'int' | 'bool'
+export type QueryParamType = 'int' | 'bool' | (string & {})
  • Augmentable pattern:
// in this file
export interface _RouterParamParsers {} // to be augmented elsewhere
export type QueryParamType = keyof ({ int: unknown; bool: unknown } & _RouterParamParsers)

123-123: Optional: avoid mutating the input “main”

If you prefer immutability, seed the reducer with a shallow clone.

-  }, main)
+  }, { ...main })
src/codegen/generateParamParsers.ts (4)

43-48: Clarify or remove TODOs

The TODOs are vague (“actualpath”, “treat custom parsers first”). Either address them or remove to avoid confusion.


60-66: Normalize Windows paths in generated type imports

relativePath may contain backslashes on Windows, breaking import specifiers. Normalize to POSIX-style slashes before embedding.

   return Array.from(paramParsers.values())
-    .map(
-      ({ typeName, relativePath }) =>
-        `type ${typeName} = ReturnType<NonNullable<typeof import('./${relativePath}').parser['get']>>`
-    )
+    .map(({ typeName, relativePath }) => {
+      const importPath = relativePath.replace(/\\/g, '/')
+      return `type ${typeName} = ReturnType<NonNullable<typeof import('./${importPath}').parser['get']>>`
+    })
     .join('\n')

68-76: Fix param name typo for readability

Rename parparsersMap → paramParsersMap for consistency.

-export function generateParamsTypes(
-  params: TreeRouteParam[],
-  parparsersMap: ParamParsersMap
-): Array<string | null> {
+export function generateParamsTypes(
+  params: TreeRouteParam[],
+  paramParsersMap: ParamParsersMap
+): Array<string | null> {
   return params.map((param) => {
     if (param.parser) {
-      if (parparsersMap.has(param.parser)) {
-        return parparsersMap.get(param.parser)!.typeName
+      if (paramParsersMap.has(param.parser)) {
+        return paramParsersMap.get(param.parser)!.typeName
       } else if (param.parser === 'int') {
         return 'number'
       }

This will require updating call sites accordingly.


91-94: Consider using relativePath instead of absolutePath for ESM imports

Absolute file paths can be problematic in some bundlers and TS transpilation setups. Using the computed relativePath improves portability (normalize slashes as above).

-    const { name, absolutePath } = paramParsers.get(param.parser)!
+    const { name, relativePath } = paramParsers.get(param.parser)!
     const varName = `PARAM_PARSER__${name}`
-    const importPath = absolutePath.replace(/\\/g, '/')
+    const importPath = relativePath.replace(/\\/g, '/')
     importsMap.add(importPath, { name: 'parser', as: varName })

Please confirm that ImportsMap resolves specifiers relative to the generated file location.

📜 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 8c028f8 and 5e2450f.

📒 Files selected for processing (2)
  • src/codegen/generateParamParsers.ts (1 hunks)
  • src/runtime.ts (2 hunks)
🔇 Additional comments (7)
src/runtime.ts (3)

2-2: Import of experimental types is fine

Type-only import from vue-router/experimental is appropriate.


62-66: DefinePage.query addition looks good

Shape is reasonable and future-proof for normalizers/codegen.


68-73: No action on TODO comment

Acknowledged. Nothing to change here right now.

src/codegen/generateParamParsers.ts (4)

7-15: ParamParsersMap shape looks solid

Covers the essential metadata (names and paths) for codegen and typing.


17-32: Synchronous warnMissingParamParsers is correct

No async work; iteration over getChildrenDeepSorted() is appropriate. Log message is clear and scoped.


119-130: Types augmentation looks good

Conditional emission and union literal listing are correct.


95-97: No action needed: ImportsMap.add supports both overloads
Verified that ImportsMap.add accepts both a string and an ImportEntry object, so the import emission is correct.

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)
playground/vite.config.ts (1)

12-13: Keep path resolution consistent; avoid introducing a custom __dirname.

To match the rest of the file (which uses fileURLToPath(new URL(...))), inline the path resolution in beforeWriteFiles and drop the extra variable. No functional change; just consistency and one less global.

Apply this diff to remove the variable:

-const __dirname = fileURLToPath(new URL('.', import.meta.url))
-

Then update the usage in beforeWriteFiles (outside the selected range) to:

root.insert('/from-root', fileURLToPath(new URL('./src/pages/index.vue', import.meta.url)))
📜 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 5e2450f and fe0f80d.

📒 Files selected for processing (2)
  • playground/vite.config.ts (2 hunks)
  • src/runtime.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/runtime.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-16T13:01:42.699Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateParamParsers.ts:91-94
Timestamp: 2025-08-16T13:01:42.699Z
Learning: In the unplugin-vue-router codebase, path normalization for import specifiers is handled elsewhere in the system (not at individual call sites like generateParamParsers.ts). Individual functions should not normalize paths themselves as it violates separation of concerns.

Applied to files:

  • playground/vite.config.ts
📚 Learning: 2025-08-16T13:00:51.231Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-147
Timestamp: 2025-08-16T13:00:51.231Z
Learning: In src/core/context.ts, the options parameter is already resolved, meaning options.experimental.paramMatchers is either an object with a dir property or undefined - never a boolean. The boolean normalization happens earlier in the options resolution phase.

Applied to files:

  • playground/vite.config.ts
🧬 Code Graph Analysis (1)
playground/vite.config.ts (1)
scripts/release.mjs (1)
  • __dirname (13-13)
⏰ 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). (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (1)
playground/vite.config.ts (1)

55-56: Confirm: experimental.paramMatchers is explicitly disabled.

Setting paramMatchers: false will disable the new param-matcher flow in the playground. If that’s intentional, all good. If the goal is to exercise the feature here, switch to an object (e.g., { dir: 'src/param-matchers' }) or remove the option to rely on defaults. Based on the retrieved learnings, booleans are normalized earlier in options resolution, so this is valid at the plugin boundary.

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 (2)
playground-experimental/index.html (2)

3-11: Move the <style> block into and add a <title>.

The style element is outside of head/body, which is invalid HTML. Also, a title improves accessibility and DX.

Apply:

   <head>
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  </head>
-  <style>
-    :root {
-      color-scheme: light dark;
-    }
-  </style>
+    <title>Playground (experimental)</title>
+    <style>
+      :root {
+        color-scheme: light dark;
+      }
+    </style>
+  </head>

13-18: Add rel="noopener noreferrer" to the external link.

Prevents reverse tabnabbing when using target="_blank".

Apply:

-      target="_blank"
+      target="_blank" rel="noopener noreferrer"
📜 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 fe0f80d and 2b2fdfe.

📒 Files selected for processing (1)
  • playground-experimental/index.html (1 hunks)

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

♻️ Duplicate comments (5)
src/runtime-experimental.ts (1)

86-101: Guard merge against nullish definePage records

The reducer assumes each routeRecord is defined. If any imported default is undefined, accessing routeRecord.meta will throw.

 export function _mergeRouteRecord(
@@
 ) {
-  return routeRecords.reduce((acc, routeRecord) => {
-    Object.assign(acc, routeRecord)
-    acc.meta = { ...acc.meta, ...routeRecord.meta }
-
-    return acc
-  }, main)
+  return routeRecords.reduce((acc, routeRecord) => {
+    if (!routeRecord) return acc
+    Object.assign(acc, routeRecord)
+    if (routeRecord.meta) {
+      acc.meta = { ...acc.meta, ...routeRecord.meta }
+    }
+    return acc
+  }, main)
 }
src/runtime.ts (1)

74-89: Harden experimental merge to skip nullish records

Same issue as flagged previously: reduce should skip falsy routeRecord and guard meta.

 export function _mergeRouteRecordExperimental(
@@
 ) {
-  return routeRecords.reduce((acc, routeRecord) => {
-    Object.assign(acc, routeRecord)
-    acc.meta = { ...acc.meta, ...routeRecord.meta }
-
-    return acc
-  }, main)
+  return routeRecords.reduce((acc, routeRecord) => {
+    if (!routeRecord) return acc
+    Object.assign(acc, routeRecord)
+    if (routeRecord.meta) {
+      acc.meta = { ...acc.meta, ...routeRecord.meta }
+    }
+    return acc
+  }, main)
 }
src/codegen/generateRouteResolver.spec.ts (1)

205-205: Typo in test data: wrong file path for '/b/e/f'

File path should match the route.

-    tree.insert('b/e/f', 'b/c/f.vue')
+    tree.insert('b/e/f', 'b/e/f.vue')

After changing, update the inline snapshots (e.g., run tests with -u) to reflect import('b/e/f.vue').

src/core/context.ts (1)

336-343: Guard DTS warning against missing param parsers

Use optional chaining to avoid accessing .length on undefined.

-    if (
-      options.experimental.paramMatchers &&
-      options.experimental.paramMatchers.dir.length > 0
-    ) {
+    if (options.experimental.paramMatchers?.dir?.length) {
       warnMissingParamParsers(routeTree, paramParsers)
     }
src/codegen/generateRouteResolver.ts (1)

89-96: Guard the repeat count while preserving comment alignment

Wrap the repeat count with Math.max(0, …) to avoid runtime throws if the computed value is ever negative. This keeps alignment intact (as requested) and is future-proof.

-  .map(
-    ({ varName, path }) =>
-      `  ${varName},  ${' '.repeat(String(state.id).length - varName.length + 2)}// ${path}`
-  )
+  .map(
+    ({ varName, path }) =>
+      `  ${varName},  ${' '.repeat(Math.max(0, String(state.id).length - varName.length + 2))}// ${path}`
+  )
🧹 Nitpick comments (7)
src/runtime.ts (1)

63-89: Avoid code duplication between runtime and runtime-experimental

There are two implementations of the experimental merge helper now (here and in src/runtime-experimental.ts via alias). This risks divergence.

Consider a single implementation source (keeping type-only imports to avoid the children typing issue noted in comments) or add a minimal test to ensure both stay functionally identical. Do you want me to draft such a test?

src/core/context.ts (1)

71-73: Broaden param parser glob to common script extensions

Support tsx/jsx/mjs/cjs too.

-    const PARAM_PARSER_GLOB = '*.{ts,js}'
+    const PARAM_PARSER_GLOB = '*.{ts,tsx,js,jsx,mjs,cjs}'
src/codegen/generateRouteResolver.ts (5)

89-96: Add deterministic tie-breakers to the resolver ordering

When scores are equal, add secondary sort keys (path length, then lexicographic) for deterministic emission across environments.

-  .sort((a, b) => compareRouteScore(a.score, b.score))
+  .sort((a, b) => {
+    const byScore = compareRouteScore(a.score, b.score)
+    if (byScore) return byScore
+    return a.path.length - b.path.length || a.path.localeCompare(b.path)
+  })

149-162: Escape route names to prevent invalid string literals

Using raw interpolation can break if a name contains quotes or special chars. Emit a properly escaped string literal.

-      recordName = `name: '${node.name}',`
+      // ensure proper escaping of the route name
+      recordName = `name: ${JSON.stringify(node.name)},`

173-188: Normalize whitespace formatting consistently for both branches

The trimming/filtering chain only applies to the else branch due to operator precedence, causing inconsistent output. Apply it to the whole ternary.

-    recordDeclaration =
-      definePageDataList.length > 0
-        ? `
-const ${varName} = normalizeRouteRecord(
-  ${generateRouteRecordMerge(routeRecordObject, definePageDataList, importsMap)}
-)
-`
-        : `
-const ${varName} = normalizeRouteRecord(${routeRecordObject})
-`
-            .trim()
-            .split('\n')
-            // remove empty lines
-            .filter((l) => l.trimStart().length > 0)
-            .join('\n')
+    recordDeclaration = (
+      definePageDataList.length > 0
+        ? `
+const ${varName} = normalizeRouteRecord(
+  ${generateRouteRecordMerge(routeRecordObject, definePageDataList, importsMap)}
+)
+`
+        : `
+const ${varName} = normalizeRouteRecord(${routeRecordObject})
+`
+    )
+      .trim()
+      .split('\n')
+      // remove empty lines
+      .filter((l) => l.trimStart().length > 0)
+      .join('\n')

127-141: Handle unknown SFC lang defensively when building definePage imports

If getLang(filePath) returns undefined or an unexpected value, the query suffix becomes invalid. Provide a sane default.

-      const lang = getLang(filePath)
+      const lang = getLang(filePath) ?? 'ts'

Please verify getLang() cannot return undefined for your inputs; otherwise, the default avoids malformed queries like lang.undefined.


254-255: Escape static paths safely

Use JSON.stringify to emit valid string literals even if a path contains quotes.

-    return `path: new MatcherPatternPathStatic('${node.fullPath}'),`
+    return `path: new MatcherPatternPathStatic(${JSON.stringify(node.fullPath)}),`
📜 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 2b2fdfe and e7aafc7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • package.json (2 hunks)
  • playground-experimental/package.json (1 hunks)
  • playground-experimental/src/pages/users/[userId=int].vue (1 hunks)
  • playground-experimental/src/params/date.ts (1 hunks)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/codegen/generateRouteResolver.ts (1 hunks)
  • src/core/context.ts (10 hunks)
  • src/runtime-experimental.ts (1 hunks)
  • src/runtime.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • playground-experimental/src/params/date.ts
  • playground-experimental/package.json
  • package.json
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-15T16:11:02.610Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.610Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/codegen/generateRouteResolver.spec.ts
  • src/codegen/generateRouteResolver.ts
  • src/core/context.ts
📚 Learning: 2025-08-16T13:00:51.231Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-147
Timestamp: 2025-08-16T13:00:51.231Z
Learning: In src/core/context.ts, the options parameter is already resolved, meaning options.experimental.paramMatchers is either an object with a dir property or undefined - never a boolean. The boolean normalization happens earlier in the options resolution phase.

Applied to files:

  • src/core/context.ts
🧬 Code Graph Analysis (5)
src/runtime-experimental.ts (1)
src/runtime.ts (5)
  • _definePage (9-9)
  • DefinePage (52-61)
  • definePage (16-16)
  • _mergeRouteRecord (27-47)
  • _mergeRouteRecordExperimental (74-89)
src/codegen/generateRouteResolver.spec.ts (7)
src/options.ts (2)
  • DEFAULT_OPTIONS (249-266)
  • resolveOptions (313-395)
src/index.ts (1)
  • DEFAULT_OPTIONS (29-29)
src/codegen/generateRouteResolver.ts (3)
  • generateRouteRecord (103-208)
  • generateRouteRecordPath (234-256)
  • generateRouteResolver (62-98)
src/codegen/generateRouteRecords.ts (1)
  • generateRouteRecord (15-110)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/core/tree.ts (1)
  • PrefixTree (385-419)
src/codegen/generateParamParsers.ts (1)
  • ParamParsersMap (7-15)
src/codegen/generateRouteResolver.ts (7)
src/core/tree.ts (5)
  • path (222-227)
  • TreeNode (20-379)
  • isMatchable (351-355)
  • name (243-250)
  • params (273-285)
src/options.ts (1)
  • ResolvedOptions (400-400)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/codegen/generateParamParsers.ts (2)
  • ParamParsersMap (7-15)
  • generateParamsOptions (102-117)
src/codegen/generateRouteRecords.ts (3)
  • generateRouteRecord (15-110)
  • formatMeta (168-178)
  • generatePageImport (145-166)
src/utils/index.ts (1)
  • ts (18-18)
src/core/treeNodeValue.ts (1)
  • path (74-76)
src/core/context.ts (8)
src/core/RoutesFolderWatcher.ts (1)
  • RoutesFolderWatcher (15-84)
src/codegen/generateParamParsers.ts (3)
  • ParamParsersMap (7-15)
  • warnMissingParamParsers (17-32)
  • generateParamParsersTypesDeclarations (57-66)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/codegen/generateRouteResolver.ts (1)
  • generateRouteResolver (62-98)
src/codegen/generateDTS.ts (1)
  • generateDTS (18-83)
src/core/moduleConstants.ts (1)
  • MODULE_ROUTES_PATH (6-6)
src/codegen/generateRouteMap.ts (1)
  • generateRouteNamedMap (9-35)
src/codegen/generateRouteFileInfoMap.ts (1)
  • generateRouteFileInfoMap (4-54)
src/runtime.ts (1)
src/runtime-experimental.ts (2)
  • _mergeRouteRecordExperimental (104-104)
  • DefinePage (21-38)
🔇 Additional comments (1)
src/codegen/generateRouteResolver.ts (1)

62-68: Imports are correctly emitted in the caller
In src/core/context.ts’s generateResolver() (lines 267–270 and 324–327), you already call importsMap.toString(), append a newline if needed, and prepend it to the resolver code. No changes required.

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 (3)
src/runtime.ts (1)

83-85: Fix runtime error when meta/records are undefined

Spreading main.meta throws if it's undefined, and record.meta access throws if any record is nullish. Guard and use Object.assign to safely merge.

-  for (const record of routeRecords) {
-    main.meta = { ...main.meta, ...record.meta }
-  }
+  for (const record of routeRecords) {
+    if (!record) continue
+    main.meta = Object.assign({}, main.meta, record.meta)
+  }
src/codegen/generateRouteResolver.spec.ts (2)

239-243: Fix mismatched file path for route '/b/e/f' (typo reintroduced).

Route path and file path disagree. This was flagged before; please correct both the fixture and the inline snapshot.

Apply:

-    tree.insert('b/e/f', 'b/c/f.vue')
+    tree.insert('b/e/f', 'b/e/f.vue')

And in the snapshot:

-          'default': () => import('b/c/f.vue')
+          'default': () => import('b/e/f.vue')

Also applies to: 276-281


336-341: Same path/file mismatch in “strips off empty parent records”.

Ensure the file path matches the route and update the snapshot import accordingly.

-    tree.insert('b/e/f', 'b/c/f.vue')
+    tree.insert('b/e/f', 'b/e/f.vue')
-          'default': () => import('b/c/f.vue')
+          'default': () => import('b/e/f.vue')

Also applies to: 372-378

🧹 Nitpick comments (5)
src/runtime.ts (1)

65-73: Document that only meta is merged (intentional behavior?)

If only meta is meant to be merged (and other DefinePage fields ignored), spell it out to avoid future regressions. If not, we should revisit the merge semantics.

  /**
   * Merges route record objects for the experimental resolver format.
   * This function is specifically designed to work with objects that will be passed to normalizeRouteRecord().
   *
   * @internal
   *
   * @param main - main route record object
   * @param routeRecords - route records to merge (from definePage imports)
   * @returns merged route record object
+  * Note: Only the `meta` field is merged; other DefinePage fields are intentionally ignored
+  * to avoid leaking DefinePage-only constructs (e.g. `name: false`) into runtime records.
   */
src/codegen/generateRouteResolver.spec.ts (4)

9-11: Use a type-only import for ParamParsersMap.

It’s only used as a type in tests; avoid a runtime import.

-import { ParamParsersMap } from './generateParamParsers'
+import type { ParamParsersMap } from './generateParamParsers'

507-545: Assert definePage helper imports are generated.

The snapshot references _definePage_default_0 and _mergeRouteRecordExperimental. Add assertions to ensure the corresponding import lines exist to prevent silent regressions.

Example assertions to insert before the snapshot expect:

-    expect(resolver).toMatchInlineSnapshot(`
+    // ensure helper imports for definePage are present
+    expect(resolver).toMatch(/^\s*import .*_definePage_default_0/m)
+    expect(resolver).toMatch(/^\s*import .*_mergeRouteRecordExperimental\b/m)
+    expect(resolver).toMatchInlineSnapshot(`

293-306: Align fixture file paths with routes for readability.

Many inserts here reuse 'b/c/d.vue' for unrelated routes. It doesn’t break these order-only tests, but aligning file paths with routes makes snapshots and future diffs easier to reason about.


25-188: Add coverage for param matchers ([param=type]) in path/codegen.

Given this PR introduces typed param matchers, include tests that assert the generated param config reflects the declared types (e.g., 'a/[id=int]') and that ParamParsersMap integration shows up in the output.

I can draft targeted tests once you confirm the expected serialized shape for parser metadata in the generated code.

📜 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 e7aafc7 and 1a186c2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • package.json (2 hunks)
  • playground-experimental/package.json (1 hunks)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/core/tree.ts (3 hunks)
  • src/runtime.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • package.json
  • playground-experimental/package.json
  • src/core/tree.ts
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-147
Timestamp: 2025-08-16T13:00:51.231Z
Learning: In src/core/context.ts, the options parameter is already resolved, meaning options.experimental.paramMatchers is either an object with a dir property or undefined - never a boolean. The boolean normalization happens earlier in the options resolution phase.
📚 Learning: 2025-08-15T16:11:02.610Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.610Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/codegen/generateRouteResolver.spec.ts
🔇 Additional comments (1)
src/codegen/generateRouteResolver.spec.ts (1)

548-560: Nice, robust order extraction.

Parsing the comment markers to derive ordering keeps the test resilient to alignment changes elsewhere.

@posva posva force-pushed the feat/experimental-router branch from 43e7dfb to 0846945 Compare August 18, 2025 11:28
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

♻️ Duplicate comments (3)
src/codegen/generateRouteResolver.spec.ts (2)

242-242: Fix mismatched test fixture path for '/b/e/f'.

The route '/b/e/f' should point to 'b/e/f.vue', not 'b/c/f.vue'. This looks like a regression of the earlier fix.

-    tree.insert('b/e/f', 'b/c/f.vue')
+    tree.insert('b/e/f', 'b/e/f.vue')

Also applies to: 339-339


279-279: Update inline snapshots to match the corrected path for '/b/e/f'.

Adjust the import path in snapshots after fixing the fixture path.

-          'default': () => import('b/c/f.vue')
+          'default': () => import('b/e/f.vue')

Also applies to: 376-376

src/core/context.ts (1)

216-227: Don’t access watcher.options; use the passed cwd. Also drop the './' prefix from relativePath.

  • FSWatcher.options isn’t a public API; avoid relying on it.
  • relativePath should be clean (codegen already prepends './' in imports), otherwise you’ll get '././...' in generated DTS.
-  function setupParamParserWatcher(watcher: FSWatcher, cwd: string) {
-    logger.log(`🤖 Scanning param parsers in ${watcher.options.cwd}`)
+  function setupParamParserWatcher(watcher: FSWatcher, cwd: string) {
+    logger.log(`🤖 Scanning param parsers in ${cwd}`)
     return watcher
       .on('add', (file) => {
         const name = parsePathe(file).name
-        const absolutePath = resolve(cwd, file)
+        const absolutePath = resolve(cwd, file)
         paramParsers.set(name, {
           name,
           typeName: `Param_${name}`,
           absolutePath,
-          relativePath: './' + relative(options.root, absolutePath),
+          relativePath: relative(options.root, absolutePath),
         })
         writeConfigFiles()
       })
🧹 Nitpick comments (2)
src/core/context.ts (2)

71-73: Broaden param parser glob (optional).

If you plan to support common JS/TS variants, consider matching tsx/jsx/mjs/cjs as well.

-    const PARAM_PARSER_GLOB = '*.{ts,js}'
+    const PARAM_PARSER_GLOB = '*.{ts,tsx,js,jsx,mjs,cjs}'

355-356: Typo in TODO.

Fix “copmlete” → “complete”.

-    // TODO: parser auto copmlete for definePage
+    // TODO: parser auto complete for definePage
📜 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 1a186c2 and 43e7dfb.

📒 Files selected for processing (3)
  • playground/tsconfig.json (2 hunks)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/core/context.ts (10 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-15T16:11:02.610Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.610Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/codegen/generateRouteResolver.spec.ts
  • src/core/context.ts
📚 Learning: 2025-08-16T13:00:51.231Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-147
Timestamp: 2025-08-16T13:00:51.231Z
Learning: In src/core/context.ts, the options parameter is already resolved, meaning options.experimental.paramMatchers is either an object with a dir property or undefined - never a boolean. The boolean normalization happens earlier in the options resolution phase.

Applied to files:

  • src/core/context.ts
🧬 Code Graph Analysis (2)
src/codegen/generateRouteResolver.spec.ts (7)
src/options.ts (2)
  • DEFAULT_OPTIONS (249-266)
  • resolveOptions (313-395)
src/index.ts (1)
  • DEFAULT_OPTIONS (29-29)
src/codegen/generateRouteResolver.ts (3)
  • generateRouteRecord (103-208)
  • generateRouteRecordPath (234-256)
  • generateRouteResolver (62-98)
src/codegen/generateRouteRecords.ts (1)
  • generateRouteRecord (15-110)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/core/tree.ts (1)
  • PrefixTree (390-424)
src/codegen/generateParamParsers.ts (1)
  • ParamParsersMap (7-15)
src/core/context.ts (10)
src/core/RoutesFolderWatcher.ts (1)
  • RoutesFolderWatcher (15-84)
src/codegen/generateParamParsers.ts (3)
  • ParamParsersMap (7-15)
  • warnMissingParamParsers (17-32)
  • generateParamParsersTypesDeclarations (57-66)
src/core/tree.ts (1)
  • name (243-250)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/codegen/generateRouteResolver.ts (1)
  • generateRouteResolver (62-98)
src/utils/index.ts (1)
  • ts (18-18)
src/codegen/generateDTS.ts (1)
  • generateDTS (18-83)
src/core/moduleConstants.ts (1)
  • MODULE_ROUTES_PATH (6-6)
src/codegen/generateRouteMap.ts (1)
  • generateRouteNamedMap (9-35)
src/codegen/generateRouteFileInfoMap.ts (1)
  • generateRouteFileInfoMap (4-54)
⏰ 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: Socket Security: Pull Request Alerts
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (7)
playground/tsconfig.json (4)

15-15: Path alias @/ looks good*

Standard Vue alias to src. No concerns.


26-27: No-op formatting change in paths block

Closing bracket/comma adjustments only. Safe.


8-8: AutoImport dts path correct

  • playground/auto-imports.d.ts exists.
  • No custom dts option in vite.config.ts → plugin uses default auto-imports.d.ts.

TS will resolve ambient types as expected.


16-17: Add or align Vite resolve.alias mappings for TS path overrides

I didn’t find matching resolve.alias entries in playground/vite.config.(js|ts), so your TS paths:

  • unplugin-vue-router/runtime../src/runtime.ts
  • unplugin-vue-router/types../src/types.ts

won’t apply at runtime.

Action items:

  • In playground/vite.config.ts, under resolve.alias, add:
    import { resolve } from 'path'
    export default defineConfig({
      // …
      resolve: {
        alias: {
          'unplugin-vue-router/runtime': resolve(__dirname, '../src/runtime.ts'),
          'unplugin-vue-router/types':   resolve(__dirname, '../src/types.ts'),
        }
      }
    })
  • Or remove/limit these TS path overrides if you intend to use the published package entry points.
  • Double-check the published unplugin-vue-router exports (e.g. via npm view unplugin-vue-router exports) to confirm your aliases match the official entry-points.
src/codegen/generateRouteResolver.spec.ts (2)

548-679: Route prioritization coverage looks solid.

Good breadth across static, dynamic, optional, repeatable, catch-all, nested, and complex subsegments. This should guard regressions in scoring and resolver ordering.


523-537: No action needed: merge helper import is verified

The _mergeRouteRecordExperimental helper is already added via importsMap.add('unplugin-vue-router/runtime', '_mergeRouteRecordExperimental') in src/codegen/generateRouteResolver.ts (line 270), alongside the other experimental helpers. The generated resolver will compile as expected.

src/core/context.ts (1)

257-278: New generateResolver entrypoint: LGTM.

Clean integration with ImportsMap and paramParsers. The separation from generateRoutes is clear.

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

♻️ Duplicate comments (5)
src/utils/index.ts (1)

1-1: Confirm Node engine; consider dropping constants import for wider compatibility

constants from node:fs/promises requires Node ≥ 18.4. If engines allow earlier 18.x, either import constants from node:fs or avoid passing F_OK (default), which lets you remove the constants import entirely.

Apply this import-only simplification:

-import { access, constants } from 'node:fs/promises'
+import { access } from 'node:fs/promises'

Run to verify engines:

#!/bin/bash
set -euo pipefail

echo "Scanning package.json files for engines.node…"
fd -a package.json -E node_modules | while read -r f; do
  echo "== $f =="
  jq -r '.name // "(no name)"' "$f"
  jq -r '.engines?.node // "(no engines.node)"' "$f"
  echo
done

echo "Checking optional Node version files…"
fd -a '.nvmrc' '.node-version' -E node_modules | while read -r f; do
  echo "== $f =="
  cat "$f"
  echo
done
src/codegen/generateRouteResolver.spec.ts (2)

242-242: Fix test fixture path inconsistency.

The route path b/e/f should use matching file path b/e/f.vue instead of b/c/f.vue.

-    tree.insert('b/e/f', 'b/c/f.vue')
+    tree.insert('b/e/f', 'b/e/f.vue')

649-649: Fix nested route fixture path.

The nested route api/v1/[type]/c should point to its own component file.

-    tree.insert('api/v1/[type]/c', 'api/v1/[type]/c.vue')
+    tree.insert('api/v1/[type]/c', 'api/v1/[type]/c.vue')
src/codegen/generateRouteResolver.ts (1)

93-93: Prevent negative repeat count error.

The padding calculation can produce negative values when varName.length exceeds String(state.id).length + 2.

-      `  ${varName},  ${' '.repeat(String(state.id).length - varName.length + 2)}// ${path}`
+      `  ${varName},${' '.repeat(Math.max(0, String(state.id).length - varName.length + 2))}// ${path}`
playground-experimental/src/pages/users/[userId=int].vue (1)

5-16: Remove unsupported path in experimental definePage

The experimental definePage only accepts name, params, meta, and redirect. Drop path to avoid TS errors and confusion.

Apply:

 definePage({
-  path: '/users/:userId',
   // query: {
   //   page: {
   //     type: 'int',
   //     optional: true,
   //   },
   // },
   meta: {
     // te: 3, 2
   },
 })
🧹 Nitpick comments (7)
pnpm-workspace.yaml (1)

2-3: Optional: collapse explicit entries into a single glob

Using a glob reduces future edits if more playground variants are added.

Apply this diff:

-  - playground
-  - playground-experimental
+  - playground*
src/utils/index.ts (1)

20-27: Make fileExists portable and explicit (type + default F_OK)

No behavior change, but this removes an unnecessary flag and improves typing.

-export async function fileExists(filePath: string) {
+export async function fileExists(
+  filePath: import('node:fs').PathLike
+): Promise<boolean> {
   try {
-    await access(filePath, constants.F_OK)
+    // F_OK is the default
+    await access(filePath)
     return true
   } catch {
     return false
   }
 }
playground-experimental/src/pages/users/[userId=int].vue (1)

2-3: Remove or use the unused route expression

route is declared but not used; the standalone route.params.userId expression has no effect. Either remove both lines and rely on $route in the template, or bind a value you actually render.

Apply (minimal cleanup):

-const route = useRoute()
-route.params.userId

Optionally, if you prefer using useRoute() in the template:

+const route = useRoute()

and switch the template to {{ String(route.name) }} - {{ route.path }}.

src/codegen/generateRouteMap.spec.ts (2)

22-24: Type the paramParsersMap and avoid repeating new Map() everywhere.

Use a tiny helper returning a properly typed map to improve readability and type-safety.

Apply this change to each occurrence within the selected ranges:

- formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ formatExports(
+   generateRouteNamedMap(tree, DEFAULT_OPTIONS, createParamParsersMap())
+ )

Add this once near the top of the file:

import type { ParamParsersMap } from './generateParamParsers'

const createParamParsersMap = (): ParamParsersMap => new Map()

Also applies to: 45-47, 68-70, 84-86, 104-106, 128-130, 149-151, 171-173, 189-191, 209-211, 226-228, 240-242, 257-259, 281-283, 297-299, 311-313, 325-327, 345-347, 379-381, 400-402


128-130: Pass the same resolved options used to build the tree.

These two tests build the tree with custom options but call the generator with DEFAULT_OPTIONS. Pass the same options to avoid future drift if generation logic depends on options.

Apply this diff at the call sites:

- formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ formatExports(generateRouteNamedMap(tree, options, new Map()))

And adjust the setup in each test to keep a reference to the resolved options (outside the selected lines; example):

// before
const tree = new PrefixTree(
  resolveOptions({
    routesFolder: [{ src: 'src/pages', path: ':lang/' }],
  })
)

// after
const options = resolveOptions({
  routesFolder: [{ src: 'src/pages', path: ':lang/' }],
})
const tree = new PrefixTree(options)

Same pattern for the test using [lang]/.

Also applies to: 281-283

src/codegen/generateRouteParams.ts (2)

24-27: Add explicit return type and tighten the types param.

Make the API clearer and immutable at the boundary.

-export function EXPERIMENTAL_generateRouteParams(
-  node: TreeNode,
-  types: Array<string | null>
-) {
+export function EXPERIMENTAL_generateRouteParams(
+  node: TreeNode,
+  types: ReadonlyArray<string | null>
+): string {

34-34: Minor: avoid re-declaring isRaw inside the map.

Hoist const isRaw = false outside the .map() to avoid per-iteration redeclaration. Tiny win, clearer intent.

📜 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 43e7dfb and 0846945.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • src/codegen/__snapshots__/generateRouteRecords.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (57)
  • client.d.ts (1 hunks)
  • examples/nuxt/package.json (1 hunks)
  • package.json (2 hunks)
  • playground-experimental/.gitignore (1 hunks)
  • playground-experimental/auto-imports.d.ts (1 hunks)
  • playground-experimental/db.json (1 hunks)
  • playground-experimental/env.d.ts (1 hunks)
  • playground-experimental/index.html (1 hunks)
  • playground-experimental/package.json (1 hunks)
  • playground-experimental/src/App.vue (1 hunks)
  • playground-experimental/src/main.ts (1 hunks)
  • playground-experimental/src/page-outside.vue (1 hunks)
  • playground-experimental/src/pages/(home).vue (1 hunks)
  • playground-experimental/src/pages/[...404].vue (1 hunks)
  • playground-experimental/src/pages/a.[b].c.[d].vue (1 hunks)
  • playground-experimental/src/pages/b.vue (1 hunks)
  • playground-experimental/src/pages/events/[when=date].vue (1 hunks)
  • playground-experimental/src/pages/u[name].vue (1 hunks)
  • playground-experimental/src/pages/u[name]/24.vue (1 hunks)
  • playground-experimental/src/pages/u[name]/[userId=int].vue (1 hunks)
  • playground-experimental/src/pages/users/[userId=int].vue (1 hunks)
  • playground-experimental/src/pages/users/sub-[first]-[second].vue (1 hunks)
  • playground-experimental/src/params/date.ts (1 hunks)
  • playground-experimental/src/router.ts (1 hunks)
  • playground-experimental/tsconfig.config.json (1 hunks)
  • playground-experimental/tsconfig.json (1 hunks)
  • playground-experimental/typed-router.d.ts (1 hunks)
  • playground-experimental/vite.config.ts (1 hunks)
  • playground/src/pages/(some-layout).vue (1 hunks)
  • playground/src/params/date.ts (1 hunks)
  • playground/src/params/number.ts (1 hunks)
  • playground/tsconfig.json (2 hunks)
  • playground/vite.config.ts (2 hunks)
  • pnpm-workspace.yaml (1 hunks)
  • src/codegen/generateDTS.ts (2 hunks)
  • src/codegen/generateParamParsers.ts (1 hunks)
  • src/codegen/generateRouteMap.spec.ts (20 hunks)
  • src/codegen/generateRouteMap.ts (1 hunks)
  • src/codegen/generateRouteParams.ts (1 hunks)
  • src/codegen/generateRouteRecords.ts (2 hunks)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/codegen/generateRouteResolver.ts (1 hunks)
  • src/core/context.ts (10 hunks)
  • src/core/extendRoutes.spec.ts (9 hunks)
  • src/core/extendRoutes.ts (1 hunks)
  • src/core/moduleConstants.ts (2 hunks)
  • src/core/tree.spec.ts (2 hunks)
  • src/core/tree.ts (3 hunks)
  • src/core/treeNodeValue.ts (11 hunks)
  • src/data-loaders/meta-extensions.ts (1 hunks)
  • src/index.ts (5 hunks)
  • src/options.ts (3 hunks)
  • src/runtime.ts (1 hunks)
  • src/utils/index.ts (2 hunks)
  • tsconfig.json (2 hunks)
  • vitest.config.ts (1 hunks)
  • vitest.workspace.js (0 hunks)
💤 Files with no reviewable changes (1)
  • vitest.workspace.js
✅ Files skipped from review due to trivial changes (2)
  • playground-experimental/db.json
  • playground-experimental/index.html
🚧 Files skipped from review as they are similar to previous changes (46)
  • playground-experimental/src/pages/b.vue
  • playground-experimental/.gitignore
  • src/core/extendRoutes.ts
  • playground-experimental/src/pages/users/sub-[first]-[second].vue
  • playground-experimental/env.d.ts
  • playground-experimental/src/page-outside.vue
  • playground/vite.config.ts
  • client.d.ts
  • playground-experimental/src/pages/u[name].vue
  • playground-experimental/src/main.ts
  • playground/src/params/number.ts
  • playground-experimental/src/pages/[...404].vue
  • playground-experimental/src/router.ts
  • playground-experimental/src/pages/events/[when=date].vue
  • src/data-loaders/meta-extensions.ts
  • examples/nuxt/package.json
  • src/codegen/generateRouteRecords.ts
  • playground-experimental/tsconfig.json
  • playground-experimental/package.json
  • playground-experimental/src/pages/u[name]/24.vue
  • playground-experimental/auto-imports.d.ts
  • playground/src/pages/(some-layout).vue
  • src/core/tree.ts
  • playground-experimental/src/pages/u[name]/[userId=int].vue
  • src/core/tree.spec.ts
  • playground/src/params/date.ts
  • playground-experimental/src/pages/(home).vue
  • src/core/context.ts
  • playground-experimental/src/App.vue
  • playground-experimental/src/pages/a.[b].c.[d].vue
  • playground-experimental/tsconfig.config.json
  • src/runtime.ts
  • playground-experimental/src/params/date.ts
  • src/options.ts
  • tsconfig.json
  • src/core/extendRoutes.spec.ts
  • package.json
  • src/index.ts
  • vitest.config.ts
  • src/core/treeNodeValue.ts
  • src/codegen/generateParamParsers.ts
  • src/core/moduleConstants.ts
  • src/codegen/generateDTS.ts
  • playground/tsconfig.json
  • src/codegen/generateRouteMap.ts
  • playground-experimental/vite.config.ts
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-147
Timestamp: 2025-08-16T13:00:51.231Z
Learning: In src/core/context.ts, the options parameter is already resolved, meaning options.experimental.paramMatchers is either an object with a dir property or undefined - never a boolean. The boolean normalization happens earlier in the options resolution phase.
📚 Learning: 2025-08-16T12:19:37.722Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/utils/index.ts:1-1
Timestamp: 2025-08-16T12:19:37.722Z
Learning: In Node.js, the `fs/promises` module exports `constants` which contains file system constants like `F_OK`, `R_OK`, `W_OK`, and `X_OK`. The import `import { access, constants } from 'fs/promises'` is valid and `constants.F_OK` can be used with the `access()` function.

Applied to files:

  • src/utils/index.ts
📚 Learning: 2025-08-16T12:19:37.722Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/utils/index.ts:1-1
Timestamp: 2025-08-16T12:19:37.722Z
Learning: The `constants` export from Node.js `fs/promises` module is version-dependent: it's available in Node.js 18.4.0 and above, but not in earlier versions. In older Node.js versions, `constants` must be imported from `node:fs` instead. When reviewing Node.js fs API usage, always consider the project's minimum Node.js version requirements.

Applied to files:

  • src/utils/index.ts
📚 Learning: 2025-08-15T16:11:02.610Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.610Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/codegen/generateRouteResolver.ts
  • src/codegen/generateRouteResolver.spec.ts
📚 Learning: 2025-08-16T13:01:42.699Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateParamParsers.ts:91-94
Timestamp: 2025-08-16T13:01:42.699Z
Learning: In the unplugin-vue-router codebase, path normalization for import specifiers is handled elsewhere in the system (not at individual call sites like generateParamParsers.ts). Individual functions should not normalize paths themselves as it violates separation of concerns.

Applied to files:

  • playground-experimental/typed-router.d.ts
🧬 Code Graph Analysis (5)
src/codegen/generateRouteMap.spec.ts (2)
src/codegen/generateRouteMap.ts (1)
  • generateRouteNamedMap (9-35)
src/options.ts (1)
  • DEFAULT_OPTIONS (249-266)
src/codegen/generateRouteResolver.ts (8)
src/core/tree.ts (5)
  • PrefixTree (390-424)
  • path (222-227)
  • TreeNode (20-384)
  • name (243-250)
  • params (273-285)
src/options.ts (1)
  • ResolvedOptions (400-400)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/codegen/generateParamParsers.ts (2)
  • ParamParsersMap (7-15)
  • generateParamsOptions (102-117)
src/codegen/generateRouteRecords.ts (3)
  • generateRouteRecord (15-110)
  • formatMeta (168-178)
  • generatePageImport (145-166)
src/utils/index.ts (1)
  • ts (18-18)
src/core/treeNodeValue.ts (1)
  • path (74-76)
src/core/extendRoutes.ts (1)
  • children (192-196)
src/codegen/generateRouteResolver.spec.ts (6)
src/options.ts (2)
  • DEFAULT_OPTIONS (249-266)
  • resolveOptions (313-395)
src/index.ts (1)
  • DEFAULT_OPTIONS (29-29)
src/codegen/generateRouteResolver.ts (3)
  • generateRouteRecord (103-208)
  • generateRouteRecordPath (234-256)
  • generateRouteResolver (62-98)
src/codegen/generateRouteRecords.ts (1)
  • generateRouteRecord (15-110)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/core/tree.ts (1)
  • PrefixTree (390-424)
src/codegen/generateRouteParams.ts (1)
src/core/tree.ts (1)
  • TreeNode (20-384)
playground-experimental/typed-router.d.ts (2)
playground-experimental/src/router.ts (1)
  • RouteNamedMap (16-31)
src/codegen/generateRouteParams.ts (1)
  • ParamValue (83-85)
🔇 Additional comments (5)
pnpm-workspace.yaml (1)

3-3: LGTM: workspace includes the new playground-experimental package

The addition correctly brings the new playground into the monorepo.

playground-experimental/typed-router.d.ts (3)

8-10: Param_date type derivation is correct

Deriving the param type from the parser.get return type is sound and resilient to parser changes. This matches the param-parsers pipeline. Based on the retrieved learning, keeping the relative import path here (without extra normalization) is the right call since normalization is handled elsewhere.


11-35: RouteNamedMap entries look consistent with file-based routes and param matchers

Static entries and the typed int/date params line up (e.g., number for userId, Param_date for when). Interface merging with dynamic routes declared in src/router.ts should work as intended.


1-7: playground-experimental/typed-router.d.ts is already included in tsconfig

The include array in playground-experimental/tsconfig.json already lists ./typed-router.d.ts, so no changes are needed.

src/codegen/generateRouteMap.spec.ts (1)

22-24: API migration to the new signature looks good.

Calls now pass options and a fresh Map; snapshots remain stable. No issues spotted here.

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

♻️ Duplicate comments (1)
src/codegen/generateRouteResolver.ts (1)

92-94: Prevent negative repeat while keeping aligned comments

Wrap the repeat count to avoid a potential runtime RangeError if the naming scheme ever changes. Alignment is preserved.

-    ({ varName, path }) =>
-      `  ${varName},  ${' '.repeat(String(state.id).length - varName.length + 2)}// ${path}`
+    ({ varName, path }) =>
+      `  ${varName},  ${' '.repeat(Math.max(0, String(state.id).length - varName.length + 2))}// ${path}`
🧹 Nitpick comments (3)
src/codegen/generateRouteResolver.ts (3)

88-96: Nit: precompute max digits once

Micro-optimization: compute const maxDigits = String(state.id).length once before the template and reuse it in the mapper to avoid recomputing per item.


216-229: Ensure deterministic components order

Sort component entries for stable codegen across environments.

-  const files = Array.from(node.value.components)
+  const files = Array.from(node.value.components).sort(([a], [b]) =>
+    a.localeCompare(b)
+  )

246-256: Remove unused local and inline the check

Minor cleanup; params local is unused.

-  const params = node.params
-  if (params.length > 0) {
+  if (node.params.length > 0) {
📜 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 0846945 and d864f4b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • package.json (2 hunks)
  • playground-experimental/package.json (1 hunks)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/codegen/generateRouteResolver.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • playground-experimental/package.json
  • src/codegen/generateRouteResolver.spec.ts
  • package.json
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-15T16:11:02.610Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.610Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/codegen/generateRouteResolver.ts
🔇 Additional comments (3)
src/codegen/generateRouteResolver.ts (3)

13-51: LGTM: route scoring comparator is clear and deterministic

The tie-breakers (min, avg, subsegment count, then segment count) make sense and yield a stable ordering.


127-141: Verify definePage import query string

I didn’t find any existing loader logic for ?definePage&vue&lang.tsx. Please confirm that your bundler/parser handles ?definePage&vue&lang.tsx as intended (versus using a simpler query like ?definePage&lang.tsx or ?definePage&lang=ts).

Location to check:

  • src/codegen/generateRouteResolver.ts (lines 127–141)

120-124: No action needed: definePage-only nodes aren’t skipped
The skip guard only excludes nodes that have neither a component nor meta. Every page node—including those with only definePage—always has at least one component (isMatchable() === true), so shouldSkipNode stays false.

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

♻️ Duplicate comments (4)
playground-experimental/vite.config.ts (2)

78-82: __dirname is undefined in ESM Vite config; use import.meta.url.

Replace usage to avoid runtime error.

-        root.insert(
-          '/manually-added',
-          join(__dirname, './src/page-outside.vue')
-        )
+        root.insert(
+          '/manually-added',
+          fileURLToPath(new URL('./src/page-outside.vue', import.meta.url))
+        )

96-102: Fix features path transform; current replace doesn’t remove “pages/”.

The relative path starts with "/pages/…", so ^pages/ never matches. Normalize separators and drop the intermediate "pages/" segment.

         {
           src: 'src/features',
           filePatterns: '*/pages/**/*',
           path: (file) => {
-            return relative('src/features', file).replace(/^pages\//, '')
+            const rel = relative('src/features', file).replace(/\\/g, '/')
+            // keep "<feature>/" and drop the intermediate "pages/"
+            return rel.replace(/^([^/]+)\/pages\//, '$1/')
           },
         },
src/runtime.ts (1)

90-94: Remove placeholder URL from public TSDoc.

Avoid shipping a broken link in typings.

-   * The type of the query parameter. Allowed values are native param parsers
-   * and any parser in the {@link https://uvr.esm.is/TODO | params folder }. If
+   * The type of the query parameter. Allowed values are native param parsers
+   * and any parser in the params folder. If
src/core/context.ts (1)

216-218: Avoid accessing private watcher.options; use passed cwd.

FSWatcher doesn’t expose .options in its public type. This is a TS error and a runtime hazard.

Apply this diff:

-  function setupParamParserWatcher(watcher: FSWatcher, cwd: string) {
-    logger.log(`🤖 Scanning param parsers in ${watcher.options.cwd}`)
+  function setupParamParserWatcher(watcher: FSWatcher, cwd: string) {
+    logger.log(`🤖 Scanning param parsers in ${cwd}`)
🧹 Nitpick comments (8)
playground-experimental/src/pages/[...path].vue (1)

4-6: Remove unused route access.

This no-op access to route params isn’t used and may trip linters.

-const route = useRoute()
-route.params.path
+
src/codegen/generateRouteMap.ts (2)

3-7: Type-only import for ParamParsersMap.

Prevents bundling type metadata.

-import { generateParamsTypes, ParamParsersMap } from './generateParamParsers'
+import { generateParamsTypes } from './generateParamParsers'
+import type { ParamParsersMap } from './generateParamParsers'

46-49: Optional: defensive check on experimental flag.

Safer if callers pass a partially constructed options object.

-  if (options.experimental.paramParsers) {
+  if (options.experimental?.paramParsers) {
     paramParsers = generateParamsTypes(params, paramParsersMap)
     paramType = EXPERIMENTAL_generateRouteParams(node, paramParsers)
   }
src/core/context.ts (5)

71-73: Broaden glob to support nested folders and common JS/TS variants.

Current pattern only matches top-level .ts/.js files. Consider supporting nested dirs and TS/JS variants to reduce surprises.

Apply this diff:

-    const PARAM_PARSER_GLOB = '*.{ts,js}'
+    const PARAM_PARSER_GLOB = '**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}'
     const isParamParserMatch = picomatch(PARAM_PARSER_GLOB)

133-143: Warn on duplicate parser names to avoid silent overwrites.

Two files named the same (e.g., index.ts in different dirs) will collide on name and silently overwrite in the map.

Apply this diff:

           for (const file of paramParserFiles) {
             const name = parsePathe(file).name
             // TODO: could be simplified to only one import that starts with / for vite
             const absolutePath = resolve(folder, file)
+            if (paramParsers.has(name)) {
+              logger.warn(
+                `Duplicate param parser "${name}" at ${absolutePath} overwrites ${paramParsers.get(name)!.absolutePath}`
+              )
+            }
             paramParsers.set(name, {
               name,
               typeName: `Param_${name}`,
               absolutePath,
               relativePath: relative(options.root, absolutePath),
             })
           }

219-228: Normalize relativePath (remove redundant './') and guard duplicates on add.

generateParamParsersTypesDeclarations already prepends './' in imports; adding it here leads to '././...' paths. Also mirror duplicate-name warning on add.

Apply this diff:

       .on('add', (file) => {
         const name = parsePathe(file).name
         const absolutePath = resolve(cwd, file)
+        if (paramParsers.has(name)) {
+          logger.warn(
+            `Duplicate param parser "${name}" at ${absolutePath} overwrites ${paramParsers.get(name)!.absolutePath}`
+          )
+        }
         paramParsers.set(name, {
           name,
           typeName: `Param_${name}`,
           absolutePath,
-          relativePath: './' + relative(options.root, absolutePath),
+          relativePath: relative(options.root, absolutePath),
         })
         writeConfigFiles()
       })

337-344: Safer guard before warning about missing param parsers.

Avoid assuming dir exists. Optional-chain to prevent runtime errors.

Apply this diff:

-    if (
-      options.experimental.paramParsers &&
-      options.experimental.paramParsers.dir.length > 0
-    ) {
+    if (options.experimental.paramParsers?.dir?.length) {
       warnMissingParamParsers(routeTree, paramParsers)
     }

355-357: Fix typo in TODO.

Small spelling fix.

Apply this diff:

-    // TODO: parser auto copmlete for definePage
+    // TODO: parser autocomplete for definePage
📜 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 d864f4b and a1d0628.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • package.json (2 hunks)
  • playground-experimental/package.json (1 hunks)
  • playground-experimental/src/pages/[...path].vue (1 hunks)
  • playground-experimental/src/pages/b.vue (1 hunks)
  • playground-experimental/src/pages/users/[userId=int].vue (1 hunks)
  • playground-experimental/typed-router.d.ts (1 hunks)
  • playground-experimental/vite.config.ts (1 hunks)
  • playground/vite.config.ts (2 hunks)
  • src/codegen/generateRouteMap.ts (1 hunks)
  • src/core/context.ts (10 hunks)
  • src/core/extendRoutes.ts (1 hunks)
  • src/options.ts (3 hunks)
  • src/runtime.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/core/extendRoutes.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • playground-experimental/src/pages/users/[userId=int].vue
  • playground-experimental/src/pages/b.vue
  • playground-experimental/package.json
  • playground/vite.config.ts
  • playground-experimental/typed-router.d.ts
  • package.json
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-16T13:00:51.231Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-147
Timestamp: 2025-08-16T13:00:51.231Z
Learning: In src/core/context.ts, the options parameter is already resolved, meaning options.experimental.paramMatchers is either an object with a dir property or undefined - never a boolean. The boolean normalization happens earlier in the options resolution phase.

Applied to files:

  • playground-experimental/vite.config.ts
  • src/options.ts
  • src/core/context.ts
📚 Learning: 2025-08-15T16:11:02.610Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.610Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/core/context.ts
🧬 Code Graph Analysis (5)
src/codegen/generateRouteMap.ts (4)
src/core/tree.ts (2)
  • TreeNode (20-384)
  • params (273-285)
src/options.ts (1)
  • ResolvedOptions (400-400)
src/codegen/generateParamParsers.ts (2)
  • ParamParsersMap (7-15)
  • generateParamsTypes (68-82)
src/codegen/generateRouteParams.ts (2)
  • EXPERIMENTAL_generateRouteParams (24-50)
  • generateRouteParams (3-22)
playground-experimental/vite.config.ts (2)
scripts/release.mjs (1)
  • __dirname (13-13)
src/index.ts (1)
  • VueRouterAutoImports (240-253)
src/runtime.ts (1)
client.d.ts (1)
  • TypesConfig (35-37)
src/options.ts (1)
src/core/utils.ts (1)
  • isArray (65-66)
src/core/context.ts (8)
src/core/RoutesFolderWatcher.ts (1)
  • RoutesFolderWatcher (15-84)
src/codegen/generateParamParsers.ts (3)
  • ParamParsersMap (7-15)
  • warnMissingParamParsers (17-32)
  • generateParamParsersTypesDeclarations (57-66)
src/core/utils.ts (1)
  • ImportsMap (299-368)
src/codegen/generateRouteResolver.ts (1)
  • generateRouteResolver (62-98)
src/utils/index.ts (1)
  • ts (18-18)
src/codegen/generateDTS.ts (1)
  • generateDTS (18-83)
src/codegen/generateRouteMap.ts (1)
  • generateRouteNamedMap (9-35)
src/codegen/generateRouteFileInfoMap.ts (1)
  • generateRouteFileInfoMap (4-54)
🔇 Additional comments (8)
playground-experimental/src/pages/[...path].vue (1)

7-22: LGTM: definePage meta and query param schema align with experimental param parsers.

The query param config (parser/default/format) is consistent with the new types.

src/core/context.ts (7)

3-11: LGTM on new imports.

Using node:fs promises, pathe utilities, and MODULE_ROUTES_PATH is appropriate here.


24-31: LGTM on resolver and param-parser related imports.

Bringing in generateRouteResolver, chokidar FSWatcher, param parser helpers, and picomatch is consistent with the new functionality introduced.


56-57: LGTM on watcher and paramParsers state.

Union-typing watchers and introducing ParamParsersMap is clear and maintainable.


257-278: LGTM: clean resolver generation entrypoint.

Nice separation using ImportsMap + generateRouteResolver and returning a self-contained module string.


289-289: LGTM on tagged template for HMR block.

Using ts keeps indentation and avoids unintended escapes.


345-353: LGTM: DTS now includes param parser types.

Passing paramsTypesDeclaration and a paramParsers-aware RouteNamedMap looks consistent with the new feature.


416-418: LGTM: expose generateResolver in public API.

Matches the new experimental pathway.

Comment on lines +349 to 357
const experimental = {
...options.experimental,
autoExportsDataLoaders,
// keep undefined if paramParsers is not set
paramParsers: paramParsers && {
...paramParsers,
dir: paramParsersDir,
},
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard object spread when experimental is undefined.

{ ...options.experimental } throws if experimental isn’t provided.

-  const experimental = {
-    ...options.experimental,
+  const experimental = {
+    ...(options.experimental || {}),
     autoExportsDataLoaders,
     // keep undefined if paramParsers is not set
     paramParsers: paramParsers && {
       ...paramParsers,
       dir: paramParsersDir,
     },
   }
🤖 Prompt for AI Agents
In src/options.ts around lines 349 to 357, the spread of options.experimental
can throw when options.experimental is undefined; change the construction to
spread a safe default (e.g., ...(options.experimental ?? {}) or
...(options?.experimental || {})) so the object spread never receives undefined,
then merge autoExportsDataLoaders and keep the existing conditional paramParsers
logic (paramParsers && { ...paramParsers, dir: paramParsersDir }) so
paramParsers remains undefined when not provided.

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

♻️ Duplicate comments (4)
src/codegen/generateRouteResolver.ts (1)

89-97: Guard repeat() and keep alignment (can throw at runtime)

The padding can go negative when varName is longer than expected, causing ' '.repeat() to throw. Compute max varName length and clamp.

-export const resolver = createFixedResolver([
-${state.matchableRecords
-  .sort((a, b) => compareRouteScore(a.score, b.score))
-  .map(
-    ({ varName, path }) =>
-      `  ${varName},  ${' '.repeat(String(state.id).length - varName.length + 2)}// ${path}`
-  )
-  .join('\n')}
-])
+const maxVarNameLength = state.matchableRecords.reduce(
+  (m, r) => Math.max(m, r.varName.length),
+  0
+)
+export const resolver = createFixedResolver([
+${state.matchableRecords
+  .sort((a, b) => compareRouteScore(a.score, b.score))
+  .map(({ varName, path }) => {
+    const pad = Math.max(0, maxVarNameLength - varName.length + 2)
+    return `  ${varName},${' '.repeat(pad)}// ${path}`
+  })
+  .join('\n')}
+])
src/core/tree.ts (1)

318-319: Resolved: static segments are now escaped

Using escapeRegex fixes the prior “regex metacharacters in static segments” issue. ✅

src/core/context.ts (2)

208-209: Don’t read private watcher.options; use the cwd you already pass.

FSWatcher#options isn’t part of the public API and isn’t typed. Log the explicit cwd param.

-    logger.log(`🤖 Scanning param parsers in ${watcher.options.cwd}`)
+    logger.log(`🤖 Scanning param parsers in ${cwd}`)

109-150: Fix crash: .map on possibly undefined paramParsers.dir.

If experimental.paramParsers is undefined, paramParsers?.dir.map(...) still attempts .map on undefined (because the optional chaining only applies to paramParsers). Guard with a fallback array.

-      ...(options.experimental.paramParsers?.dir.map((folder) => {
+      ...((options.experimental.paramParsers?.dir ?? []).map((folder) => {
@@
-        })
-      }) || []),
+        })
+      })),
🧹 Nitpick comments (13)
src/core/definePage.ts (3)

320-333: Support array defaults for query params

Default values often need arrays (e.g., checkboxes). Add ArrayExpression handling to avoid silently ignoring valid configs.

-            } else if (paramProp.key.name === 'default') {
+            } else if (paramProp.key.name === 'default') {
               // Handle different literal types for default values
               if (paramProp.value.type === 'NumericLiteral') {
                 paramInfo.default = paramProp.value.value
               } else if (paramProp.value.type === 'StringLiteral') {
                 paramInfo.default = paramProp.value.value
               } else if (paramProp.value.type === 'BooleanLiteral') {
                 paramInfo.default = paramProp.value.value
               } else if (paramProp.value.type === 'NullLiteral') {
                 paramInfo.default = null
+              } else if (paramProp.value.type === 'ArrayExpression') {
+                // Accept arrays of literals (numbers, strings, booleans, null)
+                paramInfo.default = paramProp.value.elements
+                  .filter((el): el is any => !!el && el.type !== 'SpreadElement')
+                  .map((el: any) => (el.type === 'NullLiteral' ? null : el.value))
               }
               // TODO: handle function expressions for default values
             }

369-377: Clarify alias warning message

The validator accepts a string or an array of strings, but the warning mentions only “string literal”. Align the message with behavior.

-    warn(`route alias must be a string literal. Found in "${id}".`)
+    warn(
+      `route alias must be a string literal or an array of string literals. Found in "${id}".`
+    )

406-413: Fix typo in JSDoc

“filtere” → “filtered”. Minor readability polish.

-/**
- * Generate a filtere import statement based on a set of identifiers that should be kept.
+/**
+ * Generate a filtered import statement based on a set of identifiers to keep.
src/codegen/generateRouteResolver.ts (1)

246-255: Prefer pathParams for clarity

Use the dedicated path params accessor; avoids confusion with any future query params handling.

-  const params = node.params
+  const params = node.pathParams
 ...
-    ${generateParamsOptions(node.params, importsMap, paramParsersMap)},
+    ${generateParamsOptions(params, importsMap, paramParsersMap)},

Also applies to: 250-251

src/core/utils.ts (1)

202-212: Optionally omit empty params blocks

If both path and query end up empty, consider skipping params entirely to reduce noise in overrides.

-      merged[key] = { path, query }
+      merged[key] =
+        Object.keys(path).length || Object.keys(query).length
+          ? { path, query }
+          : merged[key]
src/core/vite/index.ts (2)

10-17: Normalize return type of invalidate to void

Current implementation returns Promise<void> | boolean. ServerContext.invalidate is typed as () => void. Don’t return values; fire-and-forget the reload.

-  function invalidate(path: string) {
+  function invalidate(path: string): void {
     const foundModule = server.moduleGraph.getModuleById(path)
     // console.log(`🟣 Invalidating module: ${path}, found: ${!!foundModule}`)
-    if (foundModule) {
-      return server.reloadModule(foundModule)
-    }
-    return !!foundModule
+    if (foundModule) {
+      void server.reloadModule(foundModule)
+    }
   }

26-31: Docstring: covers both routes and resolver

Reflects actual behavior of reloading two virtual modules.

-  /**
-   * Triggers HMR for the vue-router/auto-routes module.
-   */
+  /**
+   * Triggers HMR for the routes and resolver virtual modules.
+   */
src/core/tree.ts (1)

293-305: pathParams duplicates params logic

Currently identical to params. Either delegate to params for now or document planned divergence to avoid future drift.

Apply this diff:

-  get pathParams(): TreeRouteParam[] {
-    const params = this.value.isParam() ? [...this.value.params] : []
-    let node = this.parent
-    // add all the params from the parents
-    while (node) {
-      if (node.value.isParam()) {
-        params.unshift(...node.value.params)
-      }
-      node = node.parent
-    }
-
-    return params
-  }
+  get pathParams(): TreeRouteParam[] {
+    return this.params
+  }
src/core/treeNodeValue.ts (1)

241-255: Broaden escaping to include '|'

escapeRegex misses '|', a regex metacharacter. Add it to REGEX_CHARS_RE (standard pattern).

Apply this diff:

-const REGEX_CHARS_RE = /[.+*?^${}()[\]/\\]/g
+const REGEX_CHARS_RE = /[.*+?^${}()|[\]\\]/g
src/core/context.ts (4)

71-73: Broaden the glob to cover common module extensions.

Support TSX/JSX and node ESM/CJS variants for param parsers.

-    const PARAM_PARSER_GLOB = '*.{ts,js}'
+    const PARAM_PARSER_GLOB = '*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}'

274-301: Remove unreachable HMR code (early invalidate() + return).

Everything after the return is dead. Keep a minimal accept that forces reload until live swapping is implemented.

-if (import.meta.hot) {
-  import.meta.hot.accept((mod) => {
-    console.log('🔥 HMRRRR')
-    import.meta.hot.invalidate('[unplugin-vue-router:HMR] reloading resolver')
-    return
-    const router = import.meta.hot.data.router
-    if (!router) {
-      import.meta.hot.invalidate('[unplugin-vue-router:HMR] Cannot replace the resolver because there is no active router. Reloading.')
-      return
-    }
-    // TODO:
-    router._hmrReplaceResolver(mod.resolver)
-    // call the hotUpdateCallback for custom updates
-    import.meta.hot.data.router_hotUpdateCallback?.(mod.routes)
-    const route = router.currentRoute.value
-    router.replace({
-      ...route,
-      // NOTE: we should be able to just do ...route but the router
-      // currently skips resolving and can give errors with renamed routes
-      // so we explicitly set remove matched and name
-      name: undefined,
-      matched: undefined,
-      params: {},
-      force: true
-    })
-  })
-}
+if (import.meta.hot) {
+  import.meta.hot.accept(() => {
+    import.meta.hot.invalidate('[unplugin-vue-router:HMR] reloading resolver')
+  })
+}

133-145: Detect duplicate param-parser names during initial scan.

Basename collisions across dir[] silently override previous entries. Warn to aid debugging.

           for (const file of paramParserFiles) {
             const name = parsePathe(file).name
             // TODO: could be simplified to only one import that starts with / for vite
             const absolutePath = resolve(folder, file)
+            if (paramParsers.has(name)) {
+              logger.warn(
+                `Duplicate param parser "${name}" at ${absolutePath} overrides ${paramParsers.get(name)!.absolutePath}.`
+              )
+            }
             paramParsers.set(name, {
               name,
               typeName: `Param_${name}`,
               absolutePath,
               relativePath: relative(dts ? dirname(dts) : options.root, absolutePath),
             })
           }

211-221: Mirror duplicate-name detection in the watcher.

Prevents silent overrides on file adds after startup.

       .on('add', (file) => {
         const name = parsePathe(file).name
         const absolutePath = resolve(cwd, file)
+        if (paramParsers.has(name) && paramParsers.get(name)!.absolutePath !== absolutePath) {
+          logger.warn(
+            `Duplicate param parser "${name}" at ${absolutePath} overrides ${paramParsers.get(name)!.absolutePath}.`
+          )
+        }
         paramParsers.set(name, {
           name,
           typeName: `Param_${name}`,
           absolutePath,
-          relativePath: './' + relative(options.root, absolutePath),
+          relativePath: relative(dts ? dirname(dts) : options.root, absolutePath),
         })
         writeConfigFiles()
       })
📜 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 a1d0628 and ec8b991.

⛔ Files ignored due to path filters (1)
  • src/core/__snapshots__/definePage.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (16)
  • playground-experimental/src/App.vue (1 hunks)
  • playground-experimental/src/pages/(home).vue (1 hunks)
  • playground-experimental/src/pages/[...path].vue (1 hunks)
  • playground-experimental/typed-router.d.ts (1 hunks)
  • src/codegen/generateRouteMap.ts (1 hunks)
  • src/codegen/generateRouteResolver.ts (1 hunks)
  • src/core/context.ts (12 hunks)
  • src/core/customBlock.ts (2 hunks)
  • src/core/definePage.spec.ts (10 hunks)
  • src/core/definePage.ts (4 hunks)
  • src/core/tree.ts (4 hunks)
  • src/core/treeNodeValue.ts (13 hunks)
  • src/core/utils.ts (1 hunks)
  • src/core/vite/index.ts (2 hunks)
  • src/index.ts (6 hunks)
  • src/runtime.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • playground-experimental/src/App.vue
  • playground-experimental/src/pages/[...path].vue
  • src/runtime.ts
  • playground-experimental/typed-router.d.ts
  • playground-experimental/src/pages/(home).vue
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-16T13:01:42.709Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateParamParsers.ts:91-94
Timestamp: 2025-08-16T13:01:42.709Z
Learning: In the unplugin-vue-router codebase, path normalization for import specifiers is handled elsewhere in the system (not at individual call sites like generateParamParsers.ts). Individual functions should not normalize paths themselves as it violates separation of concerns.

Applied to files:

  • src/index.ts
  • src/core/treeNodeValue.ts
📚 Learning: 2025-08-15T16:11:02.627Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.627Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/codegen/generateRouteResolver.ts
  • src/core/context.ts
  • src/codegen/generateRouteMap.ts
  • src/core/treeNodeValue.ts
📚 Learning: 2025-08-16T13:00:51.271Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-147
Timestamp: 2025-08-16T13:00:51.271Z
Learning: In src/core/context.ts, the options parameter is already resolved, meaning options.experimental.paramMatchers is either an object with a dir property or undefined - never a boolean. The boolean normalization happens earlier in the options resolution phase.

Applied to files:

  • src/core/context.ts
🧬 Code graph analysis (10)
src/core/customBlock.ts (1)
src/runtime.ts (1)
  • DefinePage (52-75)
src/core/vite/index.ts (2)
src/options.ts (1)
  • ServerContext (268-272)
src/core/moduleConstants.ts (3)
  • asVirtualId (43-45)
  • MODULE_ROUTES_PATH (6-6)
  • MODULE_RESOLVER_PATH (7-7)
src/core/definePage.spec.ts (3)
src/core/context.ts (1)
  • definePageTransform (452-457)
src/core/definePage.ts (2)
  • definePageTransform (65-188)
  • extractDefinePageInfo (198-259)
src/utils/index.ts (1)
  • ts (18-18)
src/index.ts (1)
src/core/moduleConstants.ts (6)
  • MODULE_RESOLVER_PATH (7-7)
  • routeBlockQueryRE (41-41)
  • MODULE_ROUTES_PATH (6-6)
  • MODULE_VUE_ROUTER_AUTO (4-4)
  • VIRTUAL_PREFIX (26-26)
  • ROUTES_LAST_LOAD_TIME (14-21)
src/codegen/generateRouteResolver.ts (6)
src/core/tree.ts (6)
  • PrefixTree (405-439)
  • path (222-227)
  • TreeNode (20-399)
  • isMatchable (371-375)
  • name (243-250)
  • params (276-288)
src/options.ts (1)
  • ResolvedOptions (400-400)
src/core/utils.ts (1)
  • ImportsMap (310-379)
src/codegen/generateParamParsers.ts (2)
  • ParamParsersMap (7-15)
  • generateParamsOptions (102-117)
src/codegen/generateRouteRecords.ts (3)
  • generateRouteRecord (15-110)
  • formatMeta (168-178)
  • generatePageImport (145-166)
src/utils/index.ts (1)
  • ts (18-18)
src/core/definePage.ts (3)
src/runtime.ts (2)
  • DefinePage (52-75)
  • ParamParserType (79-83)
src/core/tree.ts (2)
  • params (276-288)
  • pathParams (293-305)
src/core/extendRoutes.ts (1)
  • params (177-179)
src/core/context.ts (9)
src/core/RoutesFolderWatcher.ts (1)
  • RoutesFolderWatcher (15-84)
src/codegen/generateParamParsers.ts (3)
  • ParamParsersMap (7-15)
  • warnMissingParamParsers (17-32)
  • generateParamParsersTypesDeclarations (57-66)
src/core/definePage.ts (1)
  • extractDefinePageInfo (198-259)
src/codegen/generateRouteResolver.ts (1)
  • generateRouteResolver (62-98)
src/utils/index.ts (1)
  • ts (18-18)
src/codegen/generateDTS.ts (1)
  • generateDTS (18-83)
src/core/moduleConstants.ts (1)
  • MODULE_ROUTES_PATH (6-6)
src/codegen/generateRouteMap.ts (1)
  • generateRouteNamedMap (9-35)
src/codegen/generateRouteFileInfoMap.ts (1)
  • generateRouteFileInfoMap (4-54)
src/codegen/generateRouteMap.ts (4)
src/core/tree.ts (1)
  • TreeNode (20-399)
src/options.ts (1)
  • ResolvedOptions (400-400)
src/codegen/generateParamParsers.ts (2)
  • ParamParsersMap (7-15)
  • generateParamsTypes (68-82)
src/codegen/generateRouteParams.ts (2)
  • EXPERIMENTAL_generateRouteParams (24-50)
  • generateRouteParams (3-22)
src/core/treeNodeValue.ts (3)
src/runtime.ts (1)
  • DefinePage (52-75)
src/core/tree.ts (1)
  • params (276-288)
src/core/extendRoutes.ts (1)
  • params (177-179)
src/core/tree.ts (2)
src/core/treeNodeValue.ts (3)
  • TreeRouteParam (232-239)
  • re (288-301)
  • escapeRegex (253-254)
src/core/extendRoutes.ts (1)
  • params (177-179)
⏰ 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). (1)
  • GitHub Check: test (20.x, windows-latest)
🔇 Additional comments (14)
src/core/customBlock.ts (1)

28-29: LGTM: params typing wired to DefinePage

Typing CustomRouteBlock.params as DefinePage['params'] keeps schema consistent with runtime types.

src/index.ts (3)

63-85: Resolver virtual module wiring looks correct

Including MODULE_RESOLVER_PATH in resolveId’s filter and returning a virtual id alongside MODULE_ROUTES_PATH/MODULE_VUE_ROUTER_AUTO is consistent with existing behavior.


92-95: Async buildStart is fine

Awaiting ctx.scanPages(options.watch) prevents unhandled promise cases and aligns with Vite hooks.


113-151: Load branch for vue-router/auto-resolver is properly integrated

Adding the VIRTUAL_PREFIX filter and delegating to ctx.generateResolver() while updating ROUTES_LAST_LOAD_TIME mirrors the routes path.

src/codegen/generateRouteMap.ts (1)

45-54: Param-parsers integration and fallback logic are sound

paramType overriding generateRouteParams when experimental.paramParsers is enabled is clear and preserves backward compatibility when disabled.

src/core/tree.ts (2)

310-325: Confirm behavior with overridden paths

If a node’s path is overridden (absolute/relative), regexp uses pathSegment/re only, not value.path. That may desync matching vs actual route path. Confirm intended behavior; adjust to honor overrides if needed.


327-337: Score aggregation and isMatchable guard look good

score builds a top-down 2D matrix; isMatchable reliably narrows types.

Also applies to: 371-375

src/core/treeNodeValue.ts (2)

271-301: Param regex and scoring per subsegment are sensible

Using non-greedy groups and malus-based scoring will help deterministic matching across mixed static/param segments.


420-421: Param parser ([param=type]) integration is tight

New parser state, buffer handling, and consumption into currentTreeRouteParam.parser are consistent; createEmptyRouteParam sets parser: null to keep types stable.

Also applies to: 451-563, 731-740

src/core/definePage.spec.ts (2)

3-7: Test refactor and coverage improvements look good

Renaming to extractDefinePageInfo, adding ts/vue wrappers, and expanding params extraction tests improve clarity and coverage.


75-87: Import pruning tests are valuable

The “remove default/star unused imports” cases catch common regressions in definePageTransform.

Also applies to: 89-101, 103-119

src/core/context.ts (3)

166-174: LGTM: extractDefinePageInfo() merge.

Merging extracted definePage metadata into the custom route block is correct and side-effect free.


249-258: LGTM: New generateResolver() public API.

The resolver pipeline and exposure look coherent and match the new codegen flow.

Also applies to: 449-450


415-419: LGTM: Refresh routes after DTS changes.

Calling server.updateRoutes() post-DTS write keeps editor integrations in sync.

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

♻️ Duplicate comments (9)
src/codegen/generateRouteResolver.spec.ts (2)

439-439: Wrong fixture path for route '/b/e/f'.

The inserted file path should match the route. Use 'b/e/f.vue' instead of 'b/c/f.vue'. Inline snapshots will update accordingly after fixing.

-    tree.insert('b/e/f', 'b/c/f.vue')
+    tree.insert('b/e/f', 'b/e/f.vue')
#!/bin/bash
# Find any remaining mismatches for '/b/e/f' pointing to b/c/f.vue
rg -nP "insert\\('b/e/f'\\s*,\\s*'b/c/f\\.vue'\\)" src

536-536: Same path mismatch repeated.

Duplicate of the previous issue: fix the file path for '/b/e/f'.

-    tree.insert('b/e/f', 'b/c/f.vue')
+    tree.insert('b/e/f', 'b/e/f.vue')
src/core/utils.ts (1)

202-212: Object spread on possibly undefined will throw at runtime.

{ ...a[key]?.path } (and similar) throws “Cannot convert undefined or null to object” when absent. Default to {} before spreading.

-    } else if (key === 'params') {
-      merged[key] = {
-        path: {
-          ...a[key]?.path,
-          ...b[key]?.path,
-        },
-        query: {
-          ...a[key]?.query,
-          ...b[key]?.query,
-        },
-      }
+    } else if (key === 'params') {
+      const path = {
+        ...(a[key]?.path ?? {}),
+        ...(b[key]?.path ?? {}),
+      }
+      const query = {
+        ...(a[key]?.query ?? {}),
+        ...(b[key]?.query ?? {}),
+      }
+      merged[key] = { path, query }
src/codegen/generateRouteMap.ts (1)

16-20: Fix extra closing brace in generated RouteNamedMap

The template closes the interface twice, producing invalid TS.

-    return `export interface RouteNamedMap {
-${node
-  .getChildrenSorted()
-  .map((n) => generateRouteNamedMap(n, options, paramParsersMap))
-  .join('')}}`
+    return `export interface RouteNamedMap {
+${node
+  .getChildrenSorted()
+  .map((n) => generateRouteNamedMap(n, options, paramParsersMap))
+  .join('')}
+}`
src/core/tree.ts (1)

316-331: Regex builder adds extra slashes for groups/root; build parts and join

Including root and empty segments injects redundant '/', yielding patterns like /^//users$/i. Build an array of non-empty parts and join once.

-  get regexp(): string {
-    let re = ''
-    let node: TreeNode | undefined = this
-
-    while (node) {
-      if (node.value.isParam() && node.value.re) {
-        re = node.value.re + (re ? '\\/' : '') + re
-      } else {
-        re = escapeRegex(node.value.pathSegment) + (re ? '\\/' : '') + re
-      }
-
-      node = node.parent
-    }
-
-    return '/^' + re + '$/i'
-  }
+  get regexp(): string {
+    const parts: string[] = []
+    let node: TreeNode | undefined = this
+    while (node && !node.isRoot()) {
+      if (node.value.isParam() && node.value.re) {
+        parts.unshift(node.value.re)
+      } else if (node.value.pathSegment) {
+        parts.unshift(escapeRegex(node.value.pathSegment))
+      }
+      node = node.parent
+    }
+    const body = parts.length ? '\\/' + parts.join('\\/') : '\\/'
+    return '/^' + body + '$/i'
+  }
src/codegen/generateRouteParams.ts (2)

40-48: Missing cardinality wrapping for overridden types

Custom parser types aren't wrapped with ParamValueOneOrMore/ParamValueZeroOrMore/ParamValueZeroOrOne based on param.modifier, breaking type consistency for repeatable/optional params.


31-49: Bug: override logic never executes fallback path

The conditional type ? type || '/* INVALID */ unknown' is faulty. When type is truthy, the entire ternary evaluates to type, ignoring the || '/* INVALID */ unknown' part. Empty strings slip through as valid types.

Apply this diff to correctly handle null/empty overrides:

-        .map((param, i) => {
-          const type = types[i]
-          return `${param.paramName}${
-            isRaw && isTreeParamOptional(param) ? '?' : ''
-          }: ${
-            'modifier' in param
-              ? param.repeatable
-                ? param.optional || isRaw // in raw mode, the tuple version is annoying to pass
-                  ? 'string[]'
-                  : '[string, ...string[]]'
-                : param.optional
-                  ? 'string | null'
-                  : 'string'
-              : type
-          }`
-        })
+        .map((param, i) => {
+          const hasOverride = i < types.length
+          const type = types[i]
+          return `${param.paramName}${
+            isRaw && isTreeParamOptional(param) ? '?' : ''
+          }: ${
+            'modifier' in param
+              ? param.repeatable
+                ? param.optional || isRaw // in raw mode, the tuple version is annoying to pass
+                  ? 'string[]'
+                  : '[string, ...string[]]'
+                : param.optional
+                  ? 'string | null'
+                  : 'string'
+              : hasOverride
+                ? (type && type.trim() ? type : '/* INVALID */ unknown')
+                : 'string'
+          }`
+        })
src/codegen/generateRouteResolver.ts (1)

95-98: Guard against negative repeat count

When varName.length exceeds String(state.id).length + 2, the repeat count becomes negative and throws.

Apply this diff:

-      `  ${varName},  ${' '.repeat(String(state.id).length - varName.length + 2)}// ${path}`
+      `  ${varName},${' '.repeat(Math.max(1, String(state.id).length - varName.length + 3))}// ${path}`
src/core/context.ts (1)

218-218: Remove manual './' prefix for relative paths

The codegen already adds the './' prefix when needed. Adding it here causes double prefixing.

-          relativePath: './' + relative(options.root, absolutePath),
+          relativePath: relative(options.root, absolutePath),
🧹 Nitpick comments (13)
playground-experimental/src/pages/blog/[[slugOptional]]+.vue (1)

6-6: Render params as readable JSON instead of [object Object].

Stringify the params so the playground shows useful output.

-  <pre>{{ $route.params }}</pre>
+  <pre v-text="JSON.stringify($route.params, null, 2)" />
playground-experimental/src/pages/blog/[slug]+.vue (1)

6-6: Render params as readable JSON instead of [object Object].

Same tweak here for clearer debugging output.

-  <pre>{{ $route.params }}</pre>
+  <pre v-text="JSON.stringify($route.params, null, 2)" />
playground-experimental/src/pages/blog/info/[[section]].vue (1)

6-6: Render params as readable JSON instead of [object Object].

Helps verify optional param behavior in the playground.

-  <pre>{{ $route.params }}</pre>
+  <pre v-text="JSON.stringify($route.params, null, 2)" />
playground-experimental/src/pages/blog/info/(info).vue (1)

9-14: Passing null as a param may turn into the literal 'null' segment; use undefined/omit if the goal is to remove the optional segment.

Depending on resolver/encoder, null can stringify. Recommend undefined (or omitting the key) for “absent” to avoid unexpected URLs.

       <RouterLink
         :to="{
           name: '/blog/info/[[section]]',
           params: {
-            section: null,
+            section: undefined,
           },
         }"
       >
@@
       <RouterLink
         :to="{
           name: '/blog/info/[[section]]',
           params: {
             section: '1',
           },
         }"
       >

If the intent is to explicitly test null handling, ignore this suggestion.

Also applies to: 20-25

src/codegen/generateRouteResolver.spec.ts (2)

222-222: Grammar nit: test title.

Prefer “does not include query params from parent nodes”.

-  it('does not includes query params from parent nodes', () => {
+  it('does not include query params from parent nodes', () => {

480-485: Guard String.repeat to prevent negative counts

The only repeat( usage in src/codegen/generateRouteResolver.ts (line 97) should be wrapped with Math.max(0, …):

• File: src/codegen/generateRouteResolver.ts
Line: ~97

Suggested diff:

   .map(({ varName, path }) => {
-    return `  ${varName},  ${' '.repeat(String(state.id).length - varName.length + 2)}// ${path}`
+    const pad = Math.max(0, String(state.id).length - varName.length + 2)
+    return `  ${varName},  ${' '.repeat(pad)}// ${path}`
   })

This preserves comment alignment in snapshots while ensuring repeat() never receives a negative count.

playground-experimental/typed-router.d.ts (1)

8-10: Param_date is defined but not used.

Route '/events/[when=date]' still types when as string. If parser-aware types are intended, wire Param_date here (via codegen) to reflect the custom parser.

-    '/events/[when=date]': RouteRecordInfo<'/events/[when=date]', '/events/:when', { when: string }, { when: string }>,
+    '/events/[when=date]': RouteRecordInfo<'/events/[when=date]', '/events/:when', { when: Param_date }, { when: Param_date }>,

If this file is auto-generated (as noted), make the change in the generator rather than editing this file directly. Do you want a patch against the generator to produce Param_* types for parser-backed params?

src/core/definePage.ts (2)

314-321: Validate format and warn on invalid values

Currently any string is accepted; constrain to 'value' | 'array' | 'both' with a fallback.

-            } else if (
-              paramProp.key.name === 'format' &&
-              paramProp.value.type === 'StringLiteral'
-            ) {
-              paramInfo.format = paramProp.value.value as
-                | 'value'
-                | 'array'
-                | 'both'
+            } else if (
+              paramProp.key.name === 'format' &&
+              paramProp.value.type === 'StringLiteral'
+            ) {
+              const v = paramProp.value.value
+              if (v === 'value' || v === 'array' || v === 'both') {
+                paramInfo.format = v
+              } else {
+                warn(
+                  `Unrecognized format "${v}" in definePage() for query param "${paramName}" in "${_id}". Falling back to "both".`
+                )
+                paramInfo.format = 'both'
+              }

357-371: Path param parser validation is TODO; consider basic guard + warn

The TODO notes missing validation of parser values. At minimum, warn when the value is empty or obviously invalid; custom parsers will still be handled later by paramParsersMap.

I can add a minimal guard that warns on empty strings and leaves custom values untouched. Want a patch?

src/codegen/generateParamParsers.ts (1)

15-30: Use unified warn helper for missing parsers

Prefer the project’s warn() to keep log format consistent.

-import { ImportsMap } from '../core/utils'
+import { ImportsMap, warn } from '../core/utils'
@@
-          console.warn(
-            `Parameter parser "${param.parser}" not found for route "${node.fullPath}".`
-          )
+          warn(
+            `Parameter parser "${param.parser}" not found for route "${node.fullPath}".`
+          )
src/core/context.ts (2)

109-149: Unnecessary fallback || [] after optional chaining

The || [] at line 149 is redundant since .map() returns an array. The spread operator handles undefined gracefully.

-      }) || []),
+      }),

357-363: Simplify directory length check

Use optional chaining for cleaner code.

-    if (
-      options.experimental.paramParsers &&
-      options.experimental.paramParsers.dir.length > 0
-    ) {
+    if (options.experimental.paramParsers?.dir?.length) {
src/core/treeNodeValue.ts (1)

119-120: Avoid using var declarations

Use const or let instead of var for block scoping.

-    for (var paramName in paramsQuery) {
-      var config = paramsQuery[paramName]
+    for (const paramName in paramsQuery) {
+      const config = paramsQuery[paramName]
📜 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 ec8b991 and 8480c1b.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • src/core/__snapshots__/definePage.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (27)
  • client.d.ts (2 hunks)
  • package.json (2 hunks)
  • playground-experimental/package.json (1 hunks)
  • playground-experimental/src/pages/[...path].vue (1 hunks)
  • playground-experimental/src/pages/blog/[[slugOptional]]+.vue (1 hunks)
  • playground-experimental/src/pages/blog/[slug]+.vue (1 hunks)
  • playground-experimental/src/pages/blog/info/(info).vue (1 hunks)
  • playground-experimental/src/pages/blog/info/[[section]].vue (1 hunks)
  • playground-experimental/src/pages/tests/[[optional]]/end.vue (1 hunks)
  • playground-experimental/src/router.ts (1 hunks)
  • playground-experimental/typed-router.d.ts (1 hunks)
  • src/codegen/generateParamParsers.ts (1 hunks)
  • src/codegen/generateRouteMap.ts (1 hunks)
  • src/codegen/generateRouteParams.ts (2 hunks)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/codegen/generateRouteResolver.ts (1 hunks)
  • src/core/context.ts (12 hunks)
  • src/core/customBlock.ts (2 hunks)
  • src/core/definePage.spec.ts (10 hunks)
  • src/core/definePage.ts (4 hunks)
  • src/core/extendRoutes.ts (2 hunks)
  • src/core/tree.spec.ts (4 hunks)
  • src/core/tree.ts (3 hunks)
  • src/core/treeNodeValue.ts (16 hunks)
  • src/core/utils.ts (3 hunks)
  • src/runtime.ts (2 hunks)
  • src/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • playground-experimental/src/router.ts
  • playground-experimental/package.json
  • client.d.ts
  • package.json
  • playground-experimental/src/pages/[...path].vue
  • src/core/extendRoutes.ts
  • src/core/customBlock.ts
  • src/runtime.ts
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-08-15T16:11:02.627Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.627Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/codegen/generateRouteResolver.spec.ts
  • src/codegen/generateRouteMap.ts
  • src/core/treeNodeValue.ts
  • src/core/tree.ts
  • src/core/context.ts
  • src/codegen/generateRouteResolver.ts
📚 Learning: 2025-08-16T13:01:42.709Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateParamParsers.ts:91-94
Timestamp: 2025-08-16T13:01:42.709Z
Learning: In the unplugin-vue-router codebase, path normalization for import specifiers is handled elsewhere in the system (not at individual call sites like generateParamParsers.ts). Individual functions should not normalize paths themselves as it violates separation of concerns.

Applied to files:

  • src/codegen/generateParamParsers.ts
  • src/core/treeNodeValue.ts
  • playground-experimental/typed-router.d.ts
📚 Learning: 2025-08-23T12:44:23.804Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/utils.ts:202-212
Timestamp: 2025-08-23T12:44:23.804Z
Learning: In JavaScript, spreading undefined or null values in object literals is safe and does not throw errors. `{ ...undefined }` and `{ ...null }` both result in empty objects `{}`. The spread operator ignores falsy values like undefined and null.

Applied to files:

  • src/core/utils.ts
📚 Learning: 2025-08-16T13:00:51.271Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-147
Timestamp: 2025-08-16T13:00:51.271Z
Learning: In src/core/context.ts, the options parameter is already resolved, meaning options.experimental.paramMatchers is either an object with a dir property or undefined - never a boolean. The boolean normalization happens earlier in the options resolution phase.

Applied to files:

  • src/core/context.ts
🧬 Code graph analysis (13)
src/core/definePage.spec.ts (3)
src/core/context.ts (1)
  • definePageTransform (444-449)
src/core/definePage.ts (2)
  • definePageTransform (66-189)
  • extractDefinePageInfo (199-260)
src/utils/index.ts (1)
  • ts (18-18)
src/core/tree.spec.ts (2)
src/core/tree.ts (1)
  • PrefixTree (411-445)
src/core/treeNodeValue.ts (1)
  • TreePathParam (287-294)
src/codegen/generateRouteResolver.spec.ts (6)
src/index.ts (1)
  • DEFAULT_OPTIONS (28-28)
src/options.ts (2)
  • DEFAULT_OPTIONS (249-266)
  • resolveOptions (313-395)
src/codegen/generateRouteResolver.ts (4)
  • generateRouteRecord (107-219)
  • generateRouteRecordPath (245-267)
  • generateRouteRecordQuery (272-316)
  • generateRouteResolver (66-102)
src/core/utils.ts (1)
  • ImportsMap (310-379)
src/core/tree.ts (1)
  • PrefixTree (411-445)
src/codegen/generateParamParsers.ts (1)
  • ParamParsersMap (5-13)
src/codegen/generateParamParsers.ts (3)
src/core/tree.ts (3)
  • PrefixTree (411-445)
  • params (277-287)
  • name (244-251)
src/core/treeNodeValue.ts (3)
  • params (148-150)
  • TreePathParam (287-294)
  • TreeQueryParam (296-309)
src/core/utils.ts (1)
  • ImportsMap (310-379)
src/core/utils.ts (1)
src/core/treeNodeValue.ts (1)
  • TreePathParam (287-294)
src/core/definePage.ts (4)
src/core/customBlock.ts (1)
  • CustomRouteBlock (20-34)
src/core/treeNodeValue.ts (2)
  • params (148-150)
  • queryParams (111-142)
src/runtime.ts (1)
  • ParamParserType (79-83)
src/core/utils.ts (1)
  • warn (10-15)
src/codegen/generateRouteMap.ts (4)
src/core/tree.ts (1)
  • TreeNode (21-405)
src/options.ts (1)
  • ResolvedOptions (400-400)
src/codegen/generateParamParsers.ts (2)
  • ParamParsersMap (5-13)
  • generateParamsTypes (43-57)
src/codegen/generateRouteParams.ts (2)
  • EXPERIMENTAL_generateRouteParams (26-54)
  • generateRouteParams (4-24)
src/core/treeNodeValue.ts (2)
src/core/customBlock.ts (1)
  • CustomRouteBlockQueryParamOptions (36-41)
src/core/tree.ts (3)
  • queryParams (309-311)
  • pathParams (292-304)
  • params (277-287)
playground-experimental/typed-router.d.ts (1)
playground-experimental/src/router.ts (1)
  • RouteNamedMap (21-36)
src/core/tree.ts (2)
src/core/treeNodeValue.ts (5)
  • TreePathParam (287-294)
  • TreeQueryParam (296-309)
  • params (148-150)
  • re (365-378)
  • escapeRegex (332-333)
src/core/extendRoutes.ts (1)
  • params (177-179)
src/core/context.ts (5)
src/core/RoutesFolderWatcher.ts (1)
  • RoutesFolderWatcher (15-84)
src/codegen/generateParamParsers.ts (3)
  • ParamParsersMap (5-13)
  • warnMissingParamParsers (15-30)
  • generateParamParsersTypesDeclarations (32-41)
src/core/definePage.ts (1)
  • extractDefinePageInfo (199-260)
src/codegen/generateRouteResolver.ts (1)
  • generateRouteResolver (66-102)
src/codegen/generateDTS.ts (1)
  • generateDTS (18-83)
src/codegen/generateRouteParams.ts (1)
src/core/treeNodeValue.ts (1)
  • isTreeParamOptional (311-318)
src/codegen/generateRouteResolver.ts (4)
src/core/tree.ts (7)
  • PrefixTree (411-445)
  • path (223-228)
  • TreeNode (21-405)
  • isMatchable (377-381)
  • name (244-251)
  • params (277-287)
  • queryParams (309-311)
src/core/utils.ts (1)
  • ImportsMap (310-379)
src/codegen/generateParamParsers.ts (3)
  • ParamParsersMap (5-13)
  • generatePathParamsOptions (78-93)
  • generateParamParserOptions (59-76)
src/codegen/generateRouteRecords.ts (3)
  • generateRouteRecord (15-110)
  • formatMeta (168-178)
  • generatePageImport (145-166)
🔇 Additional comments (10)
playground-experimental/src/pages/tests/[[optional]]/end.vue (1)

3-7: LGTM for showcasing fullPath.

This is fine for validating optional-in-the-middle routing.

src/codegen/generateRouteResolver.spec.ts (1)

885-888: Thanks for correcting the nested fixture path.

The child route now correctly points to 'api/v1/[type]/c.vue'.

src/types.ts (1)

9-13: Re-exports look consistent with core type changes.

Public type surface aligns with TreeNodeValue/Param/Group updates.

src/core/tree.spec.ts (3)

46-62: Good coverage for custom param parser variants.

Solid assertions for typed parsers across plain, repeatable, optional, repeatable-optional, and mixed subsegment cases.

Also applies to: 64-82, 84-102, 104-122, 125-141


552-559: Type-checked expectation using satisfies is a nice touch.

Keeps the runtime check minimal while enforcing the shape at compile time.


691-772: Query params extraction tests read well.

Clear separation between path and query params, defaults, and parser hints.

src/codegen/generateRouteMap.ts (1)

9-13: All call sites updated to use 3-arg signatures
Verified that every invocation of generateRouteNamedMap and generateRouteRecordInfo now includes paramParsersMap as the third argument (in src/codegen/generateRouteMap.ts, its spec file, and src/core/context.ts).

src/core/definePage.spec.ts (1)

184-239: Nice coverage for params extraction

Good end‑to‑end check of path/query params, parser/default/format handling.

src/core/tree.ts (1)

324-324: Good use of escapeRegex for static segments

Escaping static segments avoids regex metacharacter issues.

src/core/treeNodeValue.ts (1)

324-333: LGTM! Proper regex escaping implementation

The regex escaping now correctly handles all special characters, not just forward slashes. Good fix for the previously identified issue.

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

♻️ Duplicate comments (5)
src/runtime.ts (1)

90-94: Broken TSDoc link: replace or remove

Replace the placeholder URL with plain text to avoid leaking an invalid link into public typings.

-   * and any parser in the {@link https://uvr.esm.is/TODO | params folder }. If
+   * and any parser in the params folder. If
src/codegen/generateRouteResolver.spec.ts (1)

439-439: Typo in fixture path for route '/b/e/f'

The inserted file path should match the route.

-    tree.insert('b/e/f', 'b/c/f.vue')
+    tree.insert('b/e/f', 'b/e/f.vue')
src/core/context.ts (3)

208-210: Don’t access private watcher.options; use the provided cwd

FSWatcher doesn’t expose options in its public API. Use the cwd argument you already pass.

-  function setupParamParserWatcher(watcher: FSWatcher, cwd: string) {
-    logger.log(`🤖 Scanning param parsers in ${watcher.options.cwd}`)
+  function setupParamParserWatcher(watcher: FSWatcher, cwd: string) {
+    logger.log(`🤖 Scanning param parsers in ${cwd}`)

358-361: Guard .dir.length with optional chaining

Avoid reading .length on undefined when paramParsers or dir is not set.

-    if (options.experimental.paramParsers?.dir.length) {
+    if (options.experimental.paramParsers?.dir?.length) {
       warnMissingParamParsers(routeTree, paramParsers)
     }

109-150: Fix potential crash when paramParsers is undefined

options.experimental.paramParsers?.dir.map(...) calls .map on undefined. Guard the mapping.

-      ...(options.experimental.paramParsers?.dir.map((folder) => {
+      ...((options.experimental.paramParsers?.dir ?? []).map((folder) => {
         watchers.push(
           setupParamParserWatcher(
             fsWatch('.', {
               cwd: folder,
               ignoreInitial: true,
               ignorePermissionErrors: true,
               ignored: (filePath, stats) => {
                 // let folders pass, they are ignored by the glob pattern
                 if (!stats || stats.isDirectory()) {
                   return false
                 }
 
                 return !isParamParserMatch(relative(folder, filePath))
               },
             }),
             folder
           )
         )
         return glob(PARAM_PARSER_GLOB, {
           cwd: folder,
           onlyFiles: true,
           expandDirectories: false,
         }).then((paramParserFiles) => {
           for (const file of paramParserFiles) {
             const name = parsePathe(file).name
             // TODO: could be simplified to only one import that starts with / for vite
             const absolutePath = resolve(folder, file)
             paramParsers.set(name, {
               name,
               typeName: `Param_${name}`,
               absolutePath,
               relativePath: relative(options.root, absolutePath),
             })
           }
           logger.log(
             'Parsed param parsers',
             [...paramParsers].map((p) => p[0])
           )
         })
-      }) || []),
+      })),
🧹 Nitpick comments (8)
src/core/definePage.ts (5)

199-205: Narrow the return type of extractDefinePageInfo

The function never returns null, only undefined or an object. Simplify the signature for clarity.

-export function extractDefinePageInfo(
+export function extractDefinePageInfo(
   sfcCode: string,
   id: string
-): DefinePageInfo | null | undefined {
+): DefinePageInfo | undefined {

251-255: Warn on non-object params value

If params is present but not an object, silently ignoring it hides misconfigurations. Emit a warning.

-      } else if (prop.key.name === 'params') {
-        if (prop.value.type === 'ObjectExpression') {
-          routeInfo.params = extractParamsInfo(prop.value, id)
-        }
-      }
+      } else if (prop.key.name === 'params') {
+        if (prop.value.type === 'ObjectExpression') {
+          routeInfo.params = extractParamsInfo(prop.value, id)
+        } else {
+          warn(`"params" must be an object. Found in "${id}".`)
+        }
+      }

314-318: Validate format option instead of trusting any string

Casting to 'value' | 'array' without runtime validation lets invalid formats slip through. Default to 'value' and warn on unknowns.

-            } else if (
-              paramProp.key.name === 'format' &&
-              paramProp.value.type === 'StringLiteral'
-            ) {
-              paramInfo.format = paramProp.value.value as 'value' | 'array'
+            } else if (
+              paramProp.key.name === 'format' &&
+              paramProp.value.type === 'StringLiteral'
+            ) {
+              const fmt = paramProp.value.value
+              if (fmt === 'value' || fmt === 'array') {
+                paramInfo.format = fmt
+              } else {
+                warn(
+                  `Unknown "format" "${fmt}" in definePage() for query param "${paramName}". Falling back to "value".`
+                )
+                paramInfo.format = 'value'
+              }
             }

319-341: Support negative numeric and template literal defaults

Defaults like -1 (UnaryExpression) and simple template literals currently fall into the “Unrecognized” branch. Handle common cases to reduce noise.

-            } else if (paramProp.key.name === 'default') {
+            } else if (paramProp.key.name === 'default') {
               if (
                 typeof paramProp.value.extra?.raw === 'string' &&
                 paramProp.value.extra
               ) {
                 paramInfo.default = paramProp.value.extra.raw
               } else {
                 warn(
                   `No raw value parsed in definePage() for query param "${paramName}". This is a bug, open an issue on https://github.com/posva/unplugin-vue-router and provide the definePage() code.`
                 )
-                if (paramProp.value.type === 'NumericLiteral') {
+                if (paramProp.value.type === 'NumericLiteral') {
                   paramInfo.default = String(paramProp.value.value)
+                } else if (
+                  paramProp.value.type === 'UnaryExpression' &&
+                  paramProp.value.operator === '-' &&
+                  paramProp.value.argument.type === 'NumericLiteral'
+                ) {
+                  // e.g. -1
+                  paramInfo.default = `-${String(paramProp.value.argument.value)}`
                 } else if (paramProp.value.type === 'StringLiteral') {
                   paramInfo.default = JSON.stringify(paramProp.value.value)
+                } else if (
+                  paramProp.value.type === 'TemplateLiteral' &&
+                  paramProp.value.expressions.length === 0
+                ) {
+                  // simple template literal without expressions: `foo`
+                  paramInfo.default = JSON.stringify(
+                    paramProp.value.quasis.map((q) => q.value.cooked || '').join('')
+                  )
                 } else if (paramProp.value.type === 'BooleanLiteral') {
                   paramInfo.default = String(paramProp.value.value)
                 } else if (paramProp.value.type === 'NullLiteral') {
                   paramInfo.default = 'null'
                 } else {
                   warn(
                     `Unrecognized default value in definePage() for query param "${paramName}". Typeof value: ${paramProp.value.type}. This is a bug, open an issue on https://github.com/posva/unplugin-vue-router and provide the definePage() code.`
                   )
                 }
               }
             }

354-373: Optional: warn on non-string path param values

Path params silently ignore non-string literal values. Add a warning to aid users.

   for (const prop of pathObj.properties) {
     if (
       prop.type === 'ObjectProperty' &&
       prop.key.type === 'Identifier' &&
       prop.value.type === 'StringLiteral'
     ) {
       // TODO: we should check if the value is a valid parser type
       pathParams[prop.key.name] = prop.value.value as ParamParserType
+    } else if (
+      prop.type === 'ObjectProperty' &&
+      prop.key.type === 'Identifier'
+    ) {
+      warn(
+        `Path param "${prop.key.name}" must be a string literal parser.`
+      )
     }
   }
src/runtime.ts (2)

31-47: Make _mergeRouteRecord resilient to nullish entries

Guard against accidental undefined or null route records to avoid runtime errors when merging.

 export function _mergeRouteRecord(
   main: RouteRecordRaw,
   ...routeRecords: Partial<RouteRecordRaw>[]
 ): RouteRecordRaw {
   // @ts-expect-error: complicated types
-  return routeRecords.reduce((acc, routeRecord) => {
+  return routeRecords.reduce((acc, routeRecord) => {
+    if (!routeRecord) return acc
     const meta = Object.assign({}, acc.meta, routeRecord.meta)
     const alias: string[] = ([] as string[]).concat(
       acc.alias || [],
       routeRecord.alias || []
     )

99-103: TSDoc grammar nits

Small wording fixes improve generated docs.

-   * (e.g. a invalid number is passed to the int param parser). If not provided
+   * (e.g. an invalid number is passed to the int param parser). If not provided
-   * not provided, the value will kept as is.
+   * not provided, the value will be kept as is.
src/core/context.ts (1)

218-219: Unify relativePath shape (drop extra './' here)

Initial scan stores relativePath without a './' prefix, but the watcher adds it, leading to inconsistent imports ('././...'). Let codegen add the './' prefix.

-          relativePath: './' + relative(options.root, absolutePath),
+          relativePath: relative(options.root, absolutePath),
📜 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 8480c1b and d715c3f.

📒 Files selected for processing (6)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/core/context.ts (12 hunks)
  • src/core/definePage.ts (4 hunks)
  • src/core/tree.spec.ts (4 hunks)
  • src/core/treeNodeValue.ts (16 hunks)
  • src/runtime.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/core/tree.spec.ts
  • src/core/treeNodeValue.ts
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:137-142
Timestamp: 2025-08-23T12:50:36.588Z
Learning: In src/core/context.ts, param parser relative paths must be computed relative to options.root (not relative to DTS file location) and the './' prefix is required for proper path resolution in the generated code.
📚 Learning: 2025-08-15T16:11:02.627Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.627Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/codegen/generateRouteResolver.spec.ts
  • src/core/context.ts
📚 Learning: 2025-08-23T12:50:36.588Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:137-142
Timestamp: 2025-08-23T12:50:36.588Z
Learning: In src/core/context.ts, param parser relative paths must be computed relative to options.root (not relative to DTS file location) and the './' prefix is required for proper path resolution in the generated code.

Applied to files:

  • src/codegen/generateRouteResolver.spec.ts
  • src/core/context.ts
📚 Learning: 2025-08-23T12:54:44.896Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-150
Timestamp: 2025-08-23T12:54:44.896Z
Learning: In src/core/context.ts, when paramParsers is defined in resolved options, the dir property is guaranteed to be an array, so optional chaining on options.experimental.paramParsers?.dir.map() is sufficient protection against undefined values.

Applied to files:

  • src/runtime.ts
  • src/core/context.ts
📚 Learning: 2025-08-16T13:00:51.271Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-147
Timestamp: 2025-08-16T13:00:51.271Z
Learning: In src/core/context.ts, the options parameter is already resolved, meaning options.experimental.paramMatchers is either an object with a dir property or undefined - never a boolean. The boolean normalization happens earlier in the options resolution phase.

Applied to files:

  • src/core/context.ts
📚 Learning: 2025-08-16T13:01:42.709Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateParamParsers.ts:91-94
Timestamp: 2025-08-16T13:01:42.709Z
Learning: In the unplugin-vue-router codebase, path normalization for import specifiers is handled elsewhere in the system (not at individual call sites like generateParamParsers.ts). Individual functions should not normalize paths themselves as it violates separation of concerns.

Applied to files:

  • src/core/context.ts
🧬 Code graph analysis (4)
src/codegen/generateRouteResolver.spec.ts (6)
src/index.ts (1)
  • DEFAULT_OPTIONS (28-28)
src/options.ts (2)
  • DEFAULT_OPTIONS (249-266)
  • resolveOptions (313-395)
src/codegen/generateRouteResolver.ts (4)
  • generateRouteRecord (107-219)
  • generateRouteRecordPath (245-267)
  • generateRouteRecordQuery (272-316)
  • generateRouteResolver (66-102)
src/core/utils.ts (1)
  • ImportsMap (310-379)
src/core/tree.ts (1)
  • PrefixTree (411-445)
src/codegen/generateParamParsers.ts (1)
  • ParamParsersMap (5-13)
src/core/definePage.ts (5)
src/core/customBlock.ts (1)
  • CustomRouteBlock (20-34)
src/core/treeNodeValue.ts (2)
  • params (146-148)
  • queryParams (111-140)
src/core/tree.ts (3)
  • params (277-287)
  • queryParams (309-311)
  • pathParams (292-304)
src/runtime.ts (1)
  • ParamParserType (79-83)
src/core/utils.ts (1)
  • warn (10-15)
src/runtime.ts (2)
client.d.ts (1)
  • TypesConfig (38-40)
src/data-loaders/entries/index.ts (1)
  • TypesConfig (41-41)
src/core/context.ts (8)
src/core/RoutesFolderWatcher.ts (1)
  • RoutesFolderWatcher (15-84)
src/codegen/generateParamParsers.ts (3)
  • ParamParsersMap (5-13)
  • warnMissingParamParsers (15-30)
  • generateParamParsersTypesDeclarations (32-41)
src/core/definePage.ts (1)
  • extractDefinePageInfo (199-260)
src/core/utils.ts (1)
  • ImportsMap (310-379)
src/codegen/generateRouteResolver.ts (1)
  • generateRouteResolver (66-102)
src/codegen/generateDTS.ts (1)
  • generateDTS (18-83)
src/codegen/generateRouteMap.ts (1)
  • generateRouteNamedMap (9-35)
src/codegen/generateRouteFileInfoMap.ts (1)
  • generateRouteFileInfoMap (4-54)
🔇 Additional comments (1)
src/core/context.ts (1)

171-175: Avoid spreading possibly undefined objects

routeBlock and definedPageInfo can be undefined. Spreading them throws at runtime. Use nullish fallbacks.

-    node.setCustomRouteBlock(filePath, {
-      ...routeBlock,
-      ...definedPageInfo,
-    })
+    node.setCustomRouteBlock(filePath, {
+      ...(routeBlock || {}),
+      ...(definedPageInfo || {}),
+    })
⛔ Skipped due to learnings
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/utils.ts:202-212
Timestamp: 2025-08-23T12:44:23.804Z
Learning: In JavaScript, spreading undefined or null values in object literals is safe and does not throw errors. `{ ...undefined }` and `{ ...null }` both result in empty objects `{}`. The spread operator ignores falsy values like undefined and null.

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)
src/core/context.ts (1)

357-361: Use optional chaining on .dir?.length (defensive, low impact).

Resolved options make dir an array when defined, but this guard is harmless and future-proof.

-    if (options.experimental.paramParsers?.dir.length) {
+    if (options.experimental.paramParsers?.dir?.length) {
🧹 Nitpick comments (5)
src/core/context.ts (5)

71-73: Broaden matcher to nested dirs and common JS/TS module extensions.

Your current *.{ts,js} only matches files at the root of each folder. Consider supporting subfolders and Node variants.

-    const PARAM_PARSER_GLOB = '*.{ts,js}'
+    const PARAM_PARSER_GLOB = '**/*.{ts,js,mts,mjs,cjs,cts}'

137-142: Normalize param-parser relativePath: relative to root + './' prefix (consistency).

Initial scan stores relativePath without './', while the watcher adds it. Based on your earlier clarification, paths should be relative to options.root and include './'. Make both code paths consistent.

-              relativePath: relative(options.root, absolutePath),
+              relativePath: './' + relative(options.root, absolutePath),

Note: With this change, both initial scan and watcher add './'. See the follow-up on generateParamParsers for avoiding a double './' in imports.


208-219: Avoid double './' in generated imports; keep normalization in one place.

Currently, watcher adds './' and generateParamParsersTypesDeclarations() also prefixes './', leading to import('././...'). While it usually works, it’s noisy. Since we normalize in context (per your guidance: relative to root + './'), drop the extra './' in the generator.

Proposed change in src/codegen/generateParamParsers.ts (outside this diff):

-        `type ${typeName} = ReturnType<NonNullable<typeof import('./${relativePath}').parser['get']>>`
+        `type ${typeName} = ReturnType<NonNullable<typeof import('${relativePath}').parser['get']>>`

This aligns with the learning that path normalization belongs outside individual generators and keeps imports clean.


160-175: Protect the scan from crashing on malformed definePage().

A syntax error in definePage() currently throws and can abort the scan. Catch and warn instead.

   const content = await fs.readFile(filePath, 'utf8')
@@
-    const definedPageInfo = extractDefinePageInfo(content, filePath)
+    let definedPageInfo: ReturnType<typeof extractDefinePageInfo>
+    try {
+      definedPageInfo = extractDefinePageInfo(content, filePath)
+    } catch (err) {
+      logger.warn(
+        `[definePage] Failed to parse definePage() in "${filePath}": ${String(
+          (err as Error)?.message || err
+        )}`
+      )
+      definedPageInfo = undefined
+    }
@@
     node.setCustomRouteBlock(filePath, {
       ...routeBlock,
       ...definedPageInfo,
     })

418-423: Reset watchers list after closing to allow rescans in the same context.

Without clearing, subsequent scanPages() calls early-return forever.

     if (watchers.length) {
       logger.log('🛑 stopping watcher')
-      watchers.forEach((watcher) => watcher.close())
+      watchers.forEach((watcher) => watcher.close())
+      watchers.length = 0
     }
📜 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 d715c3f and 7072b6d.

📒 Files selected for processing (2)
  • src/core/context.ts (12 hunks)
  • src/core/tree.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/tree.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-08-23T12:50:36.588Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:137-142
Timestamp: 2025-08-23T12:50:36.588Z
Learning: In src/core/context.ts, param parser relative paths must be computed relative to options.root (not relative to DTS file location) and the './' prefix is required for proper path resolution in the generated code.

Applied to files:

  • src/core/context.ts
📚 Learning: 2025-08-23T12:54:44.896Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-150
Timestamp: 2025-08-23T12:54:44.896Z
Learning: In src/core/context.ts, when paramParsers is defined in resolved options, the dir property is guaranteed to be an array, so optional chaining on options.experimental.paramParsers?.dir.map() is sufficient protection against undefined values.

Applied to files:

  • src/core/context.ts
📚 Learning: 2025-08-16T13:00:51.271Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-147
Timestamp: 2025-08-16T13:00:51.271Z
Learning: In src/core/context.ts, the options parameter is already resolved, meaning options.experimental.paramMatchers is either an object with a dir property or undefined - never a boolean. The boolean normalization happens earlier in the options resolution phase.

Applied to files:

  • src/core/context.ts
📚 Learning: 2025-08-15T16:11:02.627Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.627Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/core/context.ts
📚 Learning: 2025-08-16T13:01:42.709Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateParamParsers.ts:91-94
Timestamp: 2025-08-16T13:01:42.709Z
Learning: In the unplugin-vue-router codebase, path normalization for import specifiers is handled elsewhere in the system (not at individual call sites like generateParamParsers.ts). Individual functions should not normalize paths themselves as it violates separation of concerns.

Applied to files:

  • src/core/context.ts
🧬 Code graph analysis (1)
src/core/context.ts (10)
src/core/RoutesFolderWatcher.ts (1)
  • RoutesFolderWatcher (15-84)
src/codegen/generateParamParsers.ts (3)
  • ParamParsersMap (5-13)
  • warnMissingParamParsers (15-30)
  • generateParamParsersTypesDeclarations (32-41)
src/core/definePage.ts (1)
  • extractDefinePageInfo (199-260)
src/core/utils.ts (1)
  • ImportsMap (310-379)
src/codegen/generateRouteResolver.ts (1)
  • generateRouteResolver (66-102)
src/utils/index.ts (1)
  • ts (18-18)
src/codegen/generateDTS.ts (1)
  • generateDTS (18-83)
src/core/moduleConstants.ts (1)
  • MODULE_ROUTES_PATH (6-6)
src/codegen/generateRouteMap.ts (1)
  • generateRouteNamedMap (9-35)
src/codegen/generateRouteFileInfoMap.ts (1)
  • generateRouteFileInfoMap (4-54)
🔇 Additional comments (2)
src/core/context.ts (2)

249-298: Resolver generation + HMR wrapper looks solid.

ImportsMap orchestration and HMR flow are coherent. No issues spotted.


71-73: One more check: param-parser behavior summary.

  • Pattern: consider **/* + common extensions.
  • Watchers: gate param-parser watchers on startWatchers.
  • Paths: make both initial scan and watcher store './' + relative(root, abs).
  • Codegen: remove extra './' in generateParamParsersTypesDeclarations() to avoid ././.

These keep behavior consistent with your earlier guidance (root-relative + './' prefix).

Also applies to: 109-150, 208-219, 357-361

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

♻️ Duplicate comments (2)
src/core/tree.ts (2)

4-8: Escaping static segments: good fix.

Importing and using escapeRegex addresses the previously flagged risk of unescaped static segments in regex construction.


313-331: Avoid extra slashes with empty segments (e.g., groups); keep root-leading slash behavior.

Current loop inserts a separator even when pathSegment is empty (group nodes), yielding double slashes like ^\\/\\/users$. Build parts and join; prefix once. This preserves the (previously validated) leading slash from the root.

Apply this diff:

   get regexp(): string {
-    let re = ''
-    let node: TreeNode | undefined = this
-
-    while (node) {
-      if (node.value.isParam() && node.value.re) {
-        re = node.value.re + (re ? '\\/' : '') + re
-      } else {
-        re = escapeRegex(node.value.pathSegment) + (re ? '\\/' : '') + re
-      }
-
-      node = node.parent
-    }
-
-    return '/^' + re + '$/i'
+    const parts: string[] = []
+    let node: TreeNode | undefined = this
+    while (node && !node.isRoot()) {
+      if (node.value.isParam() && node.value.re) {
+        parts.unshift(node.value.re)
+      } else if (node.value.pathSegment) {
+        parts.unshift(escapeRegex(node.value.pathSegment))
+      }
+      node = node.parent
+    }
+    const body = parts.length ? '\\/' + parts.join('\\/') : '\\/'
+    return '/^' + body + '$/i'
   }
🧹 Nitpick comments (1)
src/core/tree.ts (1)

17-19: Constrain matcher token type to avoid accidental numbers.

Only 0 | 1 are meaningful numeric tokens; tighten the type for clarity and safety.

Apply this diff:

-export type TreeNodeValueMatcherPart = Array<
-  string | number | Array<string | number>
->
+export type MatcherToken = string | 0 | 1
+export type TreeNodeValueMatcherPart = Array<
+  MatcherToken | Array<MatcherToken>
+>
📜 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 7072b6d and fd62e04.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • package.json (2 hunks)
  • playground-experimental/package.json (1 hunks)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/core/tree.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/codegen/generateRouteResolver.spec.ts
  • package.json
  • playground-experimental/package.json
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-23T12:49:31.712Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/tree.ts:313-331
Timestamp: 2025-08-23T12:49:31.712Z
Learning: The regexp getter in src/core/tree.ts correctly processes the root node to add the leading slash. The empty pathSegment from root becomes an empty string when escaped, but the concatenation logic `"" + (re ? '\\/' : '') + re` properly adds the leading slash that routes require.

Applied to files:

  • src/core/tree.ts
📚 Learning: 2025-08-15T16:11:02.627Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.627Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/core/tree.ts
🧬 Code graph analysis (1)
src/core/tree.ts (3)
src/core/treeNodeValue.ts (5)
  • TreePathParam (285-292)
  • TreeQueryParam (294-307)
  • params (146-148)
  • re (363-376)
  • escapeRegex (330-331)
src/core/extendRoutes.ts (1)
  • params (177-179)
src/types.ts (1)
  • TreeNode (7-7)
🔇 Additional comments (6)
src/core/tree.ts (6)

274-287: Params aggregation looks correct.

Properly aggregates both path and query params from ancestors as documented.


292-304: Path params aggregation looks correct.

Collects only path params and includes parents’ param segments. Behavior aligns with the JSDoc.


306-312: Local query params getter is clear and correct.

Returns only this node’s query params as intended.


333-343: Score path is fine.

Skips root and preserves order from root→leaf via unshift; matches the intended semantics.


377-381: Matchability guard is correct.

Accurately narrows when there is at least one component and name !== false.


345-375: Unify param token encoding for multi-subsegments

In the multi-subsegment branch, params are always mapped to 0, discarding the splat (0) vs non-splat (1) distinction used elsewhere, and duplicating the mapping logic. Refactor to reuse the already-computed subSegments:

@@ src/core/tree.ts:352
-      if (subSegments.length > 1) {
-        parts.unshift(
-          node.value.subSegments.map(
-            segment => typeof segment === 'string' ? segment : 0
-          )
-        )
+      if (subSegments.length > 1) {
+        parts.unshift(subSegments)
      } else if (subSegments.length === 1) {
        parts.unshift(subSegments[0]!)
      }

This change:

  • Preserves the correct 0 (splat) vs 1 (non-splat) semantics in all cases
  • Removes duplicated mapping logic

Next steps:

  • Apply the diff above
  • Manually verify in vue-router/experimental how MatcherPatternPathDynamic interprets numeric tokens (0 vs 1) to ensure the consumer handles both correctly

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

🧹 Nitpick comments (13)
src/codegen/generateParamParsers.spec.ts (3)

73-127: Type declarations tests are correct; consider asserting deterministic ordering

Inline snapshot for multiple parsers is fine. For long-term stability across platforms/sources, consider asserting a sorted output (e.g., by typeName) if the generator is updated to sort.

Would you like me to add a test that builds the map in reverse insertion order and still matches a sorted snapshot?


129-241: Params-to-types mapping tests are comprehensive

Covers no parser, custom, native, and mixed. Consider adding a small case with TreeQueryParam to ensure parity for query params.

If helpful, I can draft a minimal test for a query param with parser 'bool'.


243-342: Parser options import tests are clear; add override test

Great checks for custom and native imports. Add a test to verify that a custom parser named 'int' overrides the native one (as intended by the implementation).

I can provide the exact test snippet if you want it in this file.

src/codegen/generateParamParsers.ts (2)

40-49: Deterministic order for generated type declarations

To avoid order drift across environments, sort entries before mapping.

Apply:

 export function generateParamParsersTypesDeclarations(
   paramParsers: ParamParsersMap
 ) {
-  return Array.from(paramParsers.values())
+  return Array.from(paramParsers.values())
+    .sort((a, b) => a.typeName.localeCompare(b.typeName))
     .map(
       ({ typeName, relativePath }) =>
         `type ${typeName} = ReturnType<NonNullable<typeof import('./${relativePath}').parser['get']>>`
     )
     .join('\n')
 }

90-120: Path params options are lean and readable

Conditional inclusion of repeatable/optional keeps output minimal. If you prefer explicitness, you could always emit repeatable: false, but current behavior is fine.

playground-experimental/src/pages/[...path].vue (3)

21-31: Align default with format: 'array' for page.

default: 1 conflicts with format: 'array' (type should be number[]). Either drop format or make default an array.

       page: {
         parser: 'int',
-        default: 1,
+        default: [1],
         format: 'array',
       },

41-43: Fix duplicate output; show query once.

     <pre>{{ $route.params }}</pre>
-    <pre>{{ $route.params }}</pre>
+    <pre>{{ $route.query }}</pre>

5-5: Remove no-op property access (or comment intent).

This reads the value without using it and may trigger no-unused-expression lint.

-route.params.path
src/core/definePage.ts (5)

24-24: Use a type-only import to avoid runtime dependency/cycles.

CustomRouteBlock is used purely for typing here; mark it as a type-only import.

-import { CustomRouteBlock } from './customBlock'
+import type { CustomRouteBlock } from './customBlock'

251-254: Add a warning when params is not an object literal.

Right now non-object params is silently ignored. Emit a warning for better DX.

-      } else if (prop.key.name === 'params') {
-        if (prop.value.type === 'ObjectExpression') {
-          routeInfo.params = extractParamsInfo(prop.value, id)
-        }
+      } else if (prop.key.name === 'params') {
+        if (prop.value.type === 'ObjectExpression') {
+          routeInfo.params = extractParamsInfo(prop.value, id)
+        } else {
+          warn(`route params must be an object literal. Found in "${id}".`)
+        }
       }

262-283: Warn on unknown properties inside params.

Only query and path are supported. Unknown keys should emit a warning to catch typos.

   for (const prop of paramsObj.properties) {
     if (prop.type === 'ObjectProperty' && prop.key.type === 'Identifier') {
       if (prop.key.name === 'query' && prop.value.type === 'ObjectExpression') {
         params.query = extractQueryParams(prop.value, id)
       } else if (
         prop.key.name === 'path' &&
         prop.value.type === 'ObjectExpression'
       ) {
         params.path = extractPathParams(prop.value, id)
+      } else {
+        warn(`Unknown params key "${prop.key.name}" in definePage() of "${id}".`)
       }
     }
   }

351-370: Guard against empty parser names in path params.

Cheap validation to catch '' by mistake.

     ) {
-      // TODO: we should check if the value is a valid parser type
-      pathParams[prop.key.name] = prop.value.value as ParamParserType
+      // TODO: we should check if the value is a valid parser type
+      const parser = (prop.value.value as string) || ''
+      if (!parser.trim()) {
+        warn(`Empty parser for path param "${prop.key.name}" in "${_id}".`)
+        continue
+      }
+      pathParams[prop.key.name] = parser as ParamParserType
     }

371-386: Improve alias diagnostics and preserve only string literals.

Message should mention arrays; warn when non-string elements are dropped.

   if (
     aliasValue.type !== 'StringLiteral' &&
     aliasValue.type !== 'ArrayExpression'
   ) {
-    warn(`route alias must be a string literal. Found in "${id}".`)
+    warn(`route alias must be a string literal or an array of string literals. Found in "${id}".`)
   } else {
-    return aliasValue.type === 'StringLiteral'
-      ? [aliasValue.value]
-      : aliasValue.elements.filter(isStringLiteral).map((el) => el.value)
+    if (aliasValue.type === 'StringLiteral') return [aliasValue.value]
+    const literals = aliasValue.elements.filter(isStringLiteral)
+    if (literals.length !== aliasValue.elements.length) {
+      warn(`Some non-string entries in route alias were ignored in "${id}".`)
+    }
+    return literals.map((el) => el.value)
   }
📜 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 fd62e04 and e1712af.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • package.json (2 hunks)
  • playground-experimental/package.json (1 hunks)
  • playground-experimental/src/pages/[...path].vue (1 hunks)
  • playground-experimental/typed-router.d.ts (1 hunks)
  • src/codegen/generateParamParsers.spec.ts (1 hunks)
  • src/codegen/generateParamParsers.ts (1 hunks)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/core/definePage.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • package.json
  • playground-experimental/package.json
  • src/codegen/generateRouteResolver.spec.ts
  • playground-experimental/typed-router.d.ts
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-16T13:01:42.709Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateParamParsers.ts:91-94
Timestamp: 2025-08-16T13:01:42.709Z
Learning: In the unplugin-vue-router codebase, path normalization for import specifiers is handled elsewhere in the system (not at individual call sites like generateParamParsers.ts). Individual functions should not normalize paths themselves as it violates separation of concerns.

Applied to files:

  • src/codegen/generateParamParsers.spec.ts
  • src/codegen/generateParamParsers.ts
📚 Learning: 2025-08-23T12:54:44.918Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-150
Timestamp: 2025-08-23T12:54:44.918Z
Learning: In src/core/context.ts, when paramParsers is defined in resolved options, the dir property is guaranteed to be an array, so optional chaining on options.experimental.paramParsers?.dir.map() is sufficient protection against undefined values.

Applied to files:

  • src/codegen/generateParamParsers.spec.ts
  • src/codegen/generateParamParsers.ts
📚 Learning: 2025-08-23T12:50:36.614Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:137-142
Timestamp: 2025-08-23T12:50:36.614Z
Learning: In src/core/context.ts, param parser relative paths must be computed relative to options.root (not relative to DTS file location) and the './' prefix is required for proper path resolution in the generated code.

Applied to files:

  • src/codegen/generateParamParsers.ts
🧬 Code graph analysis (3)
src/codegen/generateParamParsers.spec.ts (6)
src/options.ts (1)
  • resolveOptions (313-395)
tests/vitest-mock-warn.ts (1)
  • mockWarn (5-114)
src/core/tree.ts (1)
  • PrefixTree (411-445)
src/codegen/generateParamParsers.ts (6)
  • ParamParsersMap (5-13)
  • warnMissingParamParsers (23-38)
  • generateParamParsersTypesDeclarations (40-49)
  • generateParamsTypes (51-67)
  • generateParamParserOptions (69-88)
  • generatePathParamsOptions (90-120)
src/core/treeNodeValue.ts (1)
  • TreePathParam (285-292)
src/core/utils.ts (1)
  • ImportsMap (310-379)
src/core/definePage.ts (4)
src/core/customBlock.ts (1)
  • CustomRouteBlock (20-34)
src/core/treeNodeValue.ts (2)
  • params (146-148)
  • queryParams (111-140)
src/runtime.ts (1)
  • ParamParserType (79-83)
src/core/utils.ts (1)
  • warn (10-15)
src/codegen/generateParamParsers.ts (3)
src/core/tree.ts (3)
  • PrefixTree (411-445)
  • params (277-287)
  • name (244-251)
src/core/treeNodeValue.ts (3)
  • params (146-148)
  • TreePathParam (285-292)
  • TreeQueryParam (294-307)
src/core/utils.ts (1)
  • ImportsMap (310-379)
⏰ 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). (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (8)
src/codegen/generateParamParsers.spec.ts (2)

18-71: Tests for warnMissingParamParsers look solid

Good coverage of no-parser, native parsers, missing custom, and existing custom cases. The mockWarn guard also prevents silent warnings.


344-466: Path params options generation reads well

Covers empty, parser/no-parser, flags, and mixed scenarios. Nice lean output with comment hints.

src/codegen/generateParamParsers.ts (4)

16-22: Native parsers/types block is well-typed

The const assertions and satisfies guarantee the mapping stays in sync.


23-38: Warning logic is correct and minimal

Traversal + native filter is right. Keeping console.warn is consistent with the test harness that spies on it.


69-88: Import selection logic is correct; no path normalization here

Custom overrides native and aliases are stable. Per retrieved learnings, path normalization is handled elsewhere, so using absolutePath directly here is appropriate.


51-67: Rename parparsersMapparamParsersMap

The only remaining occurrences are in src/codegen/generateParamParsers.ts:

  • Line 53: parameter declaration
  • Line 57: if (parparsersMap.has(param.parser))
  • Line 58: return parparsersMap.get(param.parser)!.typeName

After renaming these three references, the typo is fully resolved and no other files need changes.

src/core/definePage.ts (2)

295-299: Good: normalize query shorthand to full form.

Normalizing 'int'{ parser: 'int' } simplifies consumers and matches treeNodeValue.ts expectations.


229-260: Ensure empty DefinePageInfo is handled as “no info” downstream

After auditing, extractDefinePageInfo() always returns a (possibly empty) object and never null/undefined once invoked. Downstream checks in src/core/context.ts (around line 165) do not guard against an empty object’s truthiness, e.g.:

const definedPageInfo = extractDefinePageInfo(content, filePath)
// …
if (node.hasDefinePage || definedPageInfo) {
  // This block will run even when definedPageInfo is {}
}

To avoid treating {} as valid info:

  • Update downstream logic to explicitly check for needed keys, e.g.
    if (definedPageInfo.name || definedPageInfo.path || definedPageInfo.params) { … }
  • Or have extractDefinePageInfo() return null when routeInfo is empty.

Please revise the checks in src/core/context.ts (around line 165) to distinguish an empty info object from real route data.

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

♻️ Duplicate comments (1)
playground-experimental/src/pages/[...path].vue (1)

8-11: Use route.query for query fields and guard logs

These are query params; also avoid prod logs. (Repeat of a prior comment.)

-console.log('typeof', typeof route.params.active)
-console.log('value', route.params.active)
-console.log('typeof', typeof route.params.other)
-console.log('value', route.params.other)
+if (import.meta.env.DEV) {
+  console.log('typeof', typeof route.query.active)
+  console.log('value', route.query.active)
+  console.log('typeof', typeof route.query.other)
+  console.log('value', route.query.other)
+}
🧹 Nitpick comments (11)
playground-experimental/src/pages/events/repeat/[when=date]+.vue (1)

3-3: Guard console logs for dev only

Avoid noisy logs in prod; keep them in DEV.

-console.log(route.params.when)
+if (import.meta.env.DEV) console.log(route.params.when)
playground-experimental/src/params/date.ts (2)

12-19: Anchor the ISO-midnight replace

Use a regex with end anchor to prevent accidental mid-string replacements.

-      .replace('T00:00:00.000Z', '')
+      .replace(/T00:00:00\.000Z$/, '')

22-31: Optional: make parser generics explicit

Helps readability; no behavior change.

-export const parser = defineParamParser({
+export const parser = defineParamParser<Date, string>({
playground-experimental/src/pages/[...path].vue (1)

5-7: Remove unused variable and no-op expression

Avoid lint noise and dead code.

-const router = useRouter()
-route.params.path
+// Keep if you need to touch types; otherwise remove:
+// route.params.path
src/core/context.ts (4)

71-73: Exclude declaration files from param-parser matching.

Avoid picking up .d.ts which won’t export runtime parsers.

Apply:

-    const PARAM_PARSER_GLOB = '*.{ts,js}'
-    const isParamParserMatch = picomatch(PARAM_PARSER_GLOB)
+    const PARAM_PARSER_GLOB = '*.{ts,js}'
+    const isParamParserMatch = picomatch([PARAM_PARSER_GLOB, '!*.d.ts'])

131-151: Filter .d.ts and warn on duplicates during initial scan.

Prevents invalid entries and makes overwrites explicit.

-        return glob(PARAM_PARSER_GLOB, {
+        return glob(PARAM_PARSER_GLOB, {
           cwd: folder,
           onlyFiles: true,
           expandDirectories: false,
+          ignore: ['**/*.d.ts'],
         }).then((paramParserFiles) => {
           for (const file of paramParserFiles) {
             const name = parsePathe(file).name
             // TODO: could be simplified to only one import that starts with / for vite
             const absolutePath = resolve(folder, file)
+            const previous = paramParsersMap.get(name)
+            if (previous) {
+              logger.warn(
+                `Duplicate param parser "${name}": "${absolutePath}" overwrites "${previous.absolutePath}"`
+              )
+            }
             paramParsersMap.set(name, {
               name,
               typeName: `Param_${name}`,
               absolutePath,
               relativePath: relative(options.root, absolutePath),
             })
           }

375-377: Typo in TODO comment.

“copmlete” → “complete”.

-    // TODO: parser auto copmlete for definePage
+    // TODO: parser auto complete for definePage

211-229: Unify relativePath storage and add duplicate-warning in watcher

The code generator already prefixes ./ in imports:

`import('./${relativePath}')` 
// ⇒ import('./parsers/foo')

Storing relativePath with its own ./ causes import('././parsers/foo'). Remove the prefix in the watcher and log a warning if a parser name is re-added.

– In setupParamParserWatcher (src/core/context.ts):

  1. Add a warning when paramParsersMap already has the same name.
  2. Store relativePath without the leading ./.
   .on('add', (file) => {
     const name = parsePathe(file).name
     const absolutePath = resolve(cwd, file)
+    const previous = paramParsersMap.get(name)
+    if (previous) {
+      logger.warn(
+        `Duplicate param parser "${name}": "${absolutePath}" overwrites "${previous.absolutePath}"`
+      )
+    }
     paramParsersMap.set(name, {
       name,
       typeName: `Param_${name}`,
       absolutePath,
-      relativePath: './' + relative(options.root, absolutePath),
+      relativePath: relative(options.root, absolutePath),
     })
     writeConfigFiles()
   })
src/core/tree.ts (2)

17-20: Type alias is fine; consider a brief doc.

Optional: add a one-liner describing the encoding (strings for static, 0 splat, 1 param, arrays for multi-subsegments).


356-383: Unify numeric encoding for matcher parts (avoid 0-only in multi-segment case).

Single-subsegment uses 0 (splat) and 1 (param), but multi-subsegment maps any param to 0, losing the 1 distinction. If consumers rely on 1 vs 0, this is inconsistent. Reuse the already computed subSegments array in both branches.

Apply this diff:

       const subSegments = node.value.subSegments.map((segment) =>
         typeof segment === 'string'
           ? segment
           : // param
             segment.isSplat
             ? 0
             : 1
       )

       if (subSegments.length > 1) {
-        parts.unshift(
-          node.value.subSegments.map(
-            (segment) => (typeof segment === 'string' ? segment : 0) /* param */
-          )
-        )
+        parts.unshift(subSegments as Array<string | number>)
       } else if (subSegments.length === 1) {
         parts.unshift(subSegments[0]!)
       }
src/codegen/generateRouteParams.ts (1)

30-65: Optional query params in experimental path: align undefined/null semantics with non-experimental.

Current logic adds “| null” for optional non-repeatable path params, but optional query params never include undefined (or null) in the type when not raw. To mirror ParamValue* behavior:

  • For optional query params: add “| undefined” (and “| null” when isRaw).
  • Keep existing property “?” for isRaw.

Apply this diff:

-          extractedType +=
-            isTreePathParam(param) && isOptional && !isRepeatable
-              ? ' | null'
-              : ''
+          if (isOptional) {
+            if (isTreePathParam(param)) {
+              if (!isRepeatable) extractedType += ' | null'
+            } else {
+              // query param
+              extractedType += isRaw ? ' | undefined | null' : ' | undefined'
+            }
+          }
📜 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 e1712af and abf692e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • package.json (3 hunks)
  • playground-experimental/package.json (1 hunks)
  • playground-experimental/src/pages/[...path].vue (1 hunks)
  • playground-experimental/src/pages/events/repeat/[when=date]+.vue (1 hunks)
  • playground-experimental/src/pages/users/[userId=int].vue (1 hunks)
  • playground-experimental/src/params/date.ts (1 hunks)
  • playground-experimental/typed-router.d.ts (1 hunks)
  • src/codegen/generateRouteParams.ts (2 hunks)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/codegen/generateRouteResolver.ts (1 hunks)
  • src/core/context.ts (12 hunks)
  • src/core/definePage.ts (4 hunks)
  • src/core/tree.ts (3 hunks)
  • src/core/treeNodeValue.ts (16 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/codegen/generateRouteResolver.ts
  • playground-experimental/src/pages/users/[userId=int].vue
  • playground-experimental/package.json
  • src/core/treeNodeValue.ts
  • src/core/definePage.ts
  • src/codegen/generateRouteResolver.spec.ts
  • package.json
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:137-142
Timestamp: 2025-08-23T12:50:36.614Z
Learning: In src/core/context.ts, param parser relative paths must be computed relative to options.root (not relative to DTS file location) and the './' prefix is required for proper path resolution in the generated code.
📚 Learning: 2025-08-23T12:49:31.747Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/tree.ts:313-331
Timestamp: 2025-08-23T12:49:31.747Z
Learning: The regexp getter in src/core/tree.ts correctly processes the root node to add the leading slash. The empty pathSegment from root becomes an empty string when escaped, but the concatenation logic `"" + (re ? '\\/' : '') + re` properly adds the leading slash that routes require.

Applied to files:

  • src/core/tree.ts
📚 Learning: 2025-08-15T16:11:02.627Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.627Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/core/tree.ts
  • src/core/context.ts
📚 Learning: 2025-08-23T12:50:36.614Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:137-142
Timestamp: 2025-08-23T12:50:36.614Z
Learning: In src/core/context.ts, param parser relative paths must be computed relative to options.root (not relative to DTS file location) and the './' prefix is required for proper path resolution in the generated code.

Applied to files:

  • src/core/context.ts
📚 Learning: 2025-08-23T12:54:44.918Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-150
Timestamp: 2025-08-23T12:54:44.918Z
Learning: In src/core/context.ts, when paramParsers is defined in resolved options, the dir property is guaranteed to be an array, so optional chaining on options.experimental.paramParsers?.dir.map() is sufficient protection against undefined values.

Applied to files:

  • src/core/context.ts
📚 Learning: 2025-08-16T13:00:51.271Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-147
Timestamp: 2025-08-16T13:00:51.271Z
Learning: In src/core/context.ts, the options parameter is already resolved, meaning options.experimental.paramMatchers is either an object with a dir property or undefined - never a boolean. The boolean normalization happens earlier in the options resolution phase.

Applied to files:

  • src/core/context.ts
📚 Learning: 2025-08-16T13:01:42.709Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateParamParsers.ts:91-94
Timestamp: 2025-08-16T13:01:42.709Z
Learning: In the unplugin-vue-router codebase, path normalization for import specifiers is handled elsewhere in the system (not at individual call sites like generateParamParsers.ts). Individual functions should not normalize paths themselves as it violates separation of concerns.

Applied to files:

  • src/core/context.ts
  • playground-experimental/typed-router.d.ts
🧬 Code graph analysis (4)
src/core/tree.ts (2)
src/core/treeNodeValue.ts (5)
  • TreePathParam (285-292)
  • TreeQueryParam (294-307)
  • params (146-148)
  • re (393-406)
  • escapeRegex (360-361)
src/core/extendRoutes.ts (1)
  • params (177-179)
src/core/context.ts (7)
src/core/RoutesFolderWatcher.ts (1)
  • RoutesFolderWatcher (15-84)
src/codegen/generateParamParsers.ts (3)
  • ParamParsersMap (5-13)
  • warnMissingParamParsers (23-38)
  • generateParamParsersTypesDeclarations (40-49)
src/core/definePage.ts (1)
  • extractDefinePageInfo (200-261)
src/core/utils.ts (1)
  • ImportsMap (310-379)
src/codegen/generateRouteResolver.ts (1)
  • generateRouteResolver (66-102)
src/codegen/generateDTS.ts (1)
  • generateDTS (18-83)
src/codegen/generateRouteMap.ts (1)
  • generateRouteNamedMap (9-35)
src/codegen/generateRouteParams.ts (1)
src/core/treeNodeValue.ts (3)
  • isTreeParamOptional (314-321)
  • isTreeParamRepeatable (328-335)
  • isTreePathParam (342-346)
playground-experimental/typed-router.d.ts (1)
playground-experimental/src/router.ts (1)
  • RouteNamedMap (21-36)
🔇 Additional comments (14)
playground-experimental/typed-router.d.ts (2)

8-10: Param_date import path looks correct

Root-relative with './' prefix matches the generator’s expectations.


31-33: Correct array vs single param typing

Using Extract/Exclude for repeat vs single params is spot on.

src/core/context.ts (6)

3-32: Imports look consistent and scoped.

New imports align with usage below; no issues.


56-58: Watcher union type is fine; map init OK.

The union supports both chokidar and custom watcher; close() exists on both.


109-129: Good: param-parser watchers respect startWatchers.

Conditional registration matches routes’ behavior.


168-177: Switch to extractDefinePageInfo is correct.

Merged info into custom route block cleanly.


252-301: Resolver generator + HMR wrapper LGTM.

API surface and hot-reload flow are coherent with routes HMR.


360-373: DTS: guarded warning and param-parsers integration are correct.

Optional chaining avoids crashes; wiring to routeNamedMap/types is sound.

src/core/tree.ts (4)

4-8: Using escapeRegex for static segments is correct.

This resolves prior concerns about unescaped metacharacters in static path segments.


274-287: Param order is root-to-leaf; confirm caller alignment.

node.params/pathParams build root→leaf order. Please verify generators using index-based mapping (e.g., experimental param types) assume the same order and inclusion (query vs path).

Would you like a quick grep script to check call sites that construct the parallel types array?

Also applies to: 292-312


313-331: Regexp builder LGTM; keep root processing.

Root’s empty segment is needed to add the leading slash. Current logic is correct and matches the resolved guidance.


333-343: Score/isSplat getters read well.

Excludes root consistently; splat detection is straightforward.

Also applies to: 348-350

src/codegen/generateRouteParams.ts (2)

8-28: Path-only route params generation reads clean.

Using node.pathParams and the ParamValue* wrappers keeps raw/non-raw semantics consistent.


30-65: No mismatches in types ordering

The types array is produced by generateParamsTypes(node.params, …), and EXPERIMENTAL_generateRouteParams iterates over the same node.params array. This ensures each types[i] aligns with node.params[i], including path→query ordering.

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

♻️ Duplicate comments (1)
src/codegen/generateRouteResolver.spec.ts (1)

770-770: Fix wrong fixture path for '/b/e/f' (update snapshots too).

Use 'b/e/f.vue' to match the inserted route; snapshots should import the same file.

-  tree.insert('b/e/f', 'b/c/f.vue')
+  tree.insert('b/e/f', 'b/e/f.vue')

Also update inline snapshots within this file:

-          'default': () => import('b/c/f.vue')
+          'default': () => import('b/e/f.vue')

Run the quick fix:

#!/bin/bash
rg -n "b/c/f\.vue" src/codegen/generateRouteResolver.spec.ts
sed -i "s|b/c/f.vue|b/e/f.vue|g" src/codegen/generateRouteResolver.spec.ts

Also applies to: 1015-1015

🧹 Nitpick comments (5)
src/codegen/generateRouteResolver.spec.ts (3)

560-560: Grammar: “does not includes” → “does not include”.

Keeps test descriptions clean.

-it('does not includes query params from parent nodes', () => {
+it('does not include query params from parent nodes', () => {

839-843: Fix typos in FIXME comment.

“appeand” → “append”; tighten phrasing.

-// it should appeand a trailing slash to the children route or the parent
+// it should append a trailing slash to the child route or the parent
-// I think it's better to not have a stable routing to preserve stable trailing slash
+// Better to preserve trailing-slash behavior without forcing a “stable” routing order

916-927: Make route-order extraction resilient to spacing/alignment.

Avoid brittle dependency on exact spaces before //.

-      // Extract the order from the resolver output
-      const lines = resolver.split('\n').filter((line) => line.includes('// /'))
-      return lines.map((line) => line.split('// ')[1] || '')
+      // Extract the order from the resolver output (robust to spacing)
+      return Array.from(
+        resolver.matchAll(/\/\/\s*(\/[^\s]+)\s*$/gm),
+        (m) => m[1]
+      )
src/core/utils.ts (1)

333-342: Clarify has() doc: parameter is the alias/key, not the imported name

Internally the map key is the local alias (the “as” identifier). Consider adjusting the JSDoc to avoid confusion.

-  /**
-   * Check if the given path has the given import name.
+  /**
+   * Check if the given path has the given import alias (the local identifier).
   *
   * @param path - the path to check
-  * @param name - the import name to check
+  * @param name - the local alias to check (e.g. 'useRouter' or 'MyUseRouter')
   */
src/core/tree.ts (1)

316-363: Regex builder handles optional-first segments and separators correctly; minor modernizations

Logic for optional single-param segments and leading slash is sound. Prefer let/const over var for clarity.

-    let re = ''
-    for (var i = 0; i < nodeList.length; i++) {
+    let re = ''
+    for (let i = 0; i < nodeList.length; i++) {
       node = nodeList[i]!
-      if (node.value.isParam()) {
-        var nodeRe = node.value.re
+      if (node.value.isParam()) {
+        const nodeRe = node.value.re
📜 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 abf692e and b2f6061.

📒 Files selected for processing (4)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/core/tree.ts (3 hunks)
  • src/core/treeNodeValue.ts (16 hunks)
  • src/core/utils.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/treeNodeValue.ts
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-08-15T16:11:02.627Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.627Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/codegen/generateRouteResolver.spec.ts
  • src/core/tree.ts
📚 Learning: 2025-08-23T12:50:36.614Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:137-142
Timestamp: 2025-08-23T12:50:36.614Z
Learning: In src/core/context.ts, param parser relative paths must be computed relative to options.root (not relative to DTS file location) and the './' prefix is required for proper path resolution in the generated code.

Applied to files:

  • src/codegen/generateRouteResolver.spec.ts
📚 Learning: 2025-08-16T13:01:42.709Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateParamParsers.ts:91-94
Timestamp: 2025-08-16T13:01:42.709Z
Learning: In the unplugin-vue-router codebase, path normalization for import specifiers is handled elsewhere in the system (not at individual call sites like generateParamParsers.ts). Individual functions should not normalize paths themselves as it violates separation of concerns.

Applied to files:

  • src/codegen/generateRouteResolver.spec.ts
📚 Learning: 2025-08-23T12:44:23.819Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/utils.ts:202-212
Timestamp: 2025-08-23T12:44:23.819Z
Learning: In JavaScript, spreading undefined or null values in object literals is safe and does not throw errors. `{ ...undefined }` and `{ ...null }` both result in empty objects `{}`. The spread operator ignores falsy values like undefined and null.

Applied to files:

  • src/core/utils.ts
📚 Learning: 2025-08-23T12:54:44.918Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-150
Timestamp: 2025-08-23T12:54:44.918Z
Learning: In src/core/context.ts, when paramParsers is defined in resolved options, the dir property is guaranteed to be an array, so optional chaining on options.experimental.paramParsers?.dir.map() is sufficient protection against undefined values.

Applied to files:

  • src/core/utils.ts
📚 Learning: 2025-08-23T12:49:31.747Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/tree.ts:313-331
Timestamp: 2025-08-23T12:49:31.747Z
Learning: The regexp getter in src/core/tree.ts correctly processes the root node to add the leading slash. The empty pathSegment from root becomes an empty string when escaped, but the concatenation logic `"" + (re ? '\\/' : '') + re` properly adds the leading slash that routes require.

Applied to files:

  • src/core/tree.ts
🧬 Code graph analysis (3)
src/codegen/generateRouteResolver.spec.ts (6)
src/index.ts (1)
  • DEFAULT_OPTIONS (28-28)
src/options.ts (2)
  • DEFAULT_OPTIONS (249-266)
  • resolveOptions (313-395)
src/codegen/generateRouteResolver.ts (4)
  • generateRouteRecord (107-219)
  • generateRouteRecordPath (245-268)
  • generateRouteRecordQuery (273-317)
  • generateRouteResolver (66-102)
src/core/utils.ts (1)
  • ImportsMap (310-395)
src/core/tree.ts (1)
  • PrefixTree (455-489)
src/codegen/generateParamParsers.ts (1)
  • ParamParsersMap (5-13)
src/core/utils.ts (3)
src/core/treeNodeValue.ts (2)
  • TreePathParam (285-292)
  • path (91-93)
src/core/tree.ts (2)
  • path (223-228)
  • name (244-251)
src/core/extendRoutes.ts (4)
  • path (138-140)
  • path (145-155)
  • name (90-92)
  • name (97-99)
src/core/tree.ts (2)
src/core/treeNodeValue.ts (5)
  • TreePathParam (285-292)
  • TreeQueryParam (294-307)
  • params (146-148)
  • re (393-416)
  • escapeRegex (360-361)
src/core/extendRoutes.ts (1)
  • params (177-179)
⏰ 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: Socket Security: Pull Request Alerts
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (11)
src/core/utils.ts (3)

2-2: Type import switch to TreePathParam looks correct

Matches the updated interfaces in treeNodeValue.ts.


122-134: paramToName implementation is consistent with new TreePathParam

No issues spotted.


202-212: Confirm intended shape change for params override

This branch always creates both params.path and params.query objects when params exists on either side. Previously, the merged value could omit one (or both) keys entirely. Verify downstream code doesn’t treat an empty object as “explicit override present”.

src/core/tree.ts (8)

4-8: New imports align with added features

Importing escapeRegex and the param types fits the new getters and regex builder.


17-19: TreeNodeValueMatcherPart alias is fine

Signature matches usage in matcherPatternPathDynamicParts.


274-287: params aggregation (path + query) across ancestors

Order (ancestors first) looks intentional and consistent. LGTM.


292-304: pathParams aggregation is correct

Only path params, with ancestors first. LGTM.


306-312: queryParams getter scope is clear

Returns only this node’s query params as documented. LGTM.


365-375: score aggregation looks good

Ancestor-to-leaf ordering preserved. LGTM.


380-382: isSplat is correct and cheap

Directly checks current node params. LGTM.


417-425: isMatchable guard is appropriate

Matches intended semantics (has component and a non-false name).

@posva posva force-pushed the feat/experimental-router branch from dc3f8ef to e5a73fc Compare September 18, 2025 12:15
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: 3

♻️ Duplicate comments (4)
src/runtime.ts (1)

89-96: Broken TSDoc link.

Replace placeholder URL.

-   * and any parser in the {@link https://uvr.esm.is/TODO | params folder }. If
+   * and any parser in the params folder. If
src/core/context.ts (3)

71-73: Good: narrowed param-parser glob to code files.

This addresses the earlier glob('*') issue; combined with picomatch it avoids non-code.


109-129: Watcher setup gated by startWatchers and cwd passed explicitly.

Matches prior guidance; avoids accessing watcher.options and respects startWatchers.


211-229: Watcher helper refactor looks solid.

Passing cwd explicitly and dropping watcher.options access removes a TS/runtime hazard.

🧹 Nitpick comments (28)
playground-experimental/tsconfig.config.json (1)

4-7: Consider enforcing TS 5/Vite-friendly module resolution (if not inherited).

If @vue/tsconfig doesn’t already set it, add moduleResolution: "Bundler" to match Vite/Vitest setups.

Apply if needed:

   "compilerOptions": {
     "composite": true,
-    "types": ["node"]
+    "types": ["node"],
+    "moduleResolution": "Bundler"
   }
playground-experimental/src/params/date.ts (4)

3-10: Stabilize YYYY-MM-DD parsing across environments.

Avoid engine quirks by normalizing bare dates to UTC midnight before fallback parsing.

Apply:

-function toDate(value: string): Date {
-  const asDate = new Date(value)
+function toDate(value: string): Date {
+  const v = value.trim()
+  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v)
+  const asDate = m ? new Date(Date.UTC(+m[1], +m[2] - 1, +m[3])) : new Date(v)
   if (Number.isNaN(asDate.getTime())) {
     throw miss(`Invalid date: "${value}"`)
   }
-
-  return asDate
+  return asDate
 }

12-20: Minor nit: cheaper string op for midnight dates.

endsWith + slice avoids a generic replace and is a touch clearer.

-function toString(value: Date): string {
-  return (
-    value
-      .toISOString()
-      // allows keeping simple dates like 2023-10-01 without time
-      // while still being able to parse full dates like 2023-10-01T12:00:00.000Z
-      .replace('T00:00:00.000Z', '')
-  )
-}
+function toString(value: Date): string {
+  const iso = value.toISOString()
+  return iso.endsWith('T00:00:00.000Z') ? iso.slice(0, 10) : iso
+}

23-26: Don’t treat empty string as “missing”.

Use a nullish check so '' surfaces as an invalid date (with message) instead of a generic miss().

-    if (!value) {
+    if (value == null) {
       throw miss()
     }

1-1: Optional: lock the public type.

Annotating parser with ParamParser<Date, string> makes intent explicit and guards future API drift.

-import { defineParamParser, miss } from 'vue-router/experimental'
+import { defineParamParser, miss } from 'vue-router/experimental'
+import type { ParamParser } from 'vue-router/experimental'
@@
-export const parser = defineParamParser({
+export const parser: ParamParser<Date, string> = defineParamParser({

Also applies to: 22-22

package.json (2)

142-142: Keep @babel/generator in dependencies — runtime import found; consider version alignment

generate is imported in src/core/definePage.ts:20, so keep it in dependencies. Optionally align @babel/generator's minor with @babel/types in package.json for consistency.


175-176: Add "engines.node": ">=18.18.0" to top-level package.json (align workspace packages).

Verification: no package.json contains an engines.node field — package.json, docs/package.json, examples/nuxt/package.json, playground/package.json, playground-experimental/package.json.

Add (top-level package.json) — outside this hunk:

   "license": "MIT",
+  "engines": {
+    "node": ">=18.18.0"
+  },

Optionally add the same engines field to the workspace package.json files for consistency.

playground-experimental/src/pages/a.[b].c.[d].vue (1)

1-1: Remove empty script block.

Not needed; drop to reduce noise.

-<script lang="ts" setup></script>
+
playground-experimental/src/pages/blog/info/(info).vue (1)

1-1: Remove empty script block.

No script logic; safe to remove.

-<script lang="ts" setup></script>
+
playground-experimental/src/router.ts (2)

11-13: Honor Vite base URL.

Use createWebHistory(import.meta.env.BASE_URL) for subpath deployments.

-  history: createWebHistory(),
+  history: createWebHistory(import.meta.env.BASE_URL),

20-37: Consider moving type augmentation to a .d.ts.

Keep runtime and types separate; place this declare module in a dedicated router-augment.d.ts.

src/core/extendRoutes.spec.ts (9)

72-79: toMatchObject is fine; add a length guard.

Looser matching can hide extra params. Add an explicit length check.

-    expect(child.params).toMatchObject([
+    expect(child.params).toHaveLength(1)
+    expect(child.params).toMatchObject([
       {
         paramName: 'id',
         modifier: '',
         optional: false,
         repeatable: false,
         parser: null,
         isSplat: false,
       },
-    ])
+    ])

93-101: Also assert length when using toMatchObject.

Prevents accidental extra entries.

-    expect(child.params).toMatchObject([
+    expect(child.params).toHaveLength(1)
+    expect(child.params).toMatchObject([
       {
         paramName: 'id',
         parser: null,
         modifier: '+',
         optional: false,
         repeatable: true,
         isSplat: false,
       },
-    ])
+    ])

114-129: Guard array size for multi-param case.

Ensure exactly two params.

-    expect(node.params).toMatchObject([
+    expect(node.params).toHaveLength(2)
+    expect(node.params).toMatchObject([

141-163: Multi‑param with modifiers: assert length, too.

Same rationale.

-    expect(node.params).toMatchObject([
+    expect(node.params).toHaveLength(3)
+    expect(node.params).toMatchObject([

174-182: Custom regex case: add length assertion.

-    expect(node.params).toMatchObject([
+    expect(node.params).toHaveLength(1)
+    expect(node.params).toMatchObject([

193-201: Empty regex case: add length assertion.

-    expect(node.params).toMatchObject([
+    expect(node.params).toHaveLength(1)
+    expect(node.params).toMatchObject([

212-220: Modifier + custom regex: assert length.

-    expect(node.params).toMatchObject([
+    expect(node.params).toHaveLength(1)
+    expect(node.params).toMatchObject([

231-239: Modifier + empty regex: assert length.

-    expect(node.params).toMatchObject([
+    expect(node.params).toHaveLength(1)
+    expect(node.params).toMatchObject([

251-259: Splat case: assert length.

-    expect(child.params).toMatchObject([
+    expect(child.params).toHaveLength(1)
+    expect(child.params).toMatchObject([
src/core/definePage.ts (3)

200-261: Return type includes null but never returned.

Tighten to DefinePageInfo | undefined.

-export function extractDefinePageInfo(
+export function extractDefinePageInfo(
   sfcCode: string,
   id: string
-): DefinePageInfo | null | undefined {
+): DefinePageInfo | undefined {

428-465: Typo: “filtere” → “filtered”.

Minor doc polish.

- * Generate a filtere import statement based on a set of identifiers that should be kept.
+ * Generate a filtered import statement based on a set of identifiers that should be kept.

359-377: Validate path parser values against the project's ParamParsers and warn on unknowns.

Check prop.value.value against the native parser names and the project's ParamParsersMap (see src/codegen/generateParamParsers.ts → ParamParsersMap and src/core/context.ts → warnMissingParamParsers); emit a warning when it's not found.

src/core/tree.ts (1)

17-20: Document numeric markers.

0 = splat, 1 = param is implicit. Consider named enum/const for clarity.

const MATCH_SPLAT = 0 as const
const MATCH_PARAM = 1 as const
src/core/context.ts (4)

136-146: Warn on duplicate param-parser names to avoid silent overrides.

If two folders contain the same basename, the later entry overwrites the former. Emit a warning before set().

-          for (const file of paramParserFiles) {
+          for (const file of paramParserFiles) {
             const name = parsePathe(file).name
             // TODO: could be simplified to only one import that starts with / for vite
             const absolutePath = resolve(folder, file)
+            if (paramParsersMap.has(name)) {
+              logger.warn(`Duplicate param parser "${name}" from ${absolutePath} (overwriting).`)
+            }
             paramParsersMap.set(name, {
               name,
               typeName: `Param_${name}`,
               absolutePath,
-              relativePath: relative(options.root, absolutePath),
+              relativePath: './' + relative(options.root, absolutePath),
             })
           }

141-145: Align relativePath format with watcher “add” for consistency.

Initial scan stores relativePath without './' while watcher adds it with './'. Pick one consistently. Given prior decision to include './' relative to options.root, update the initial scan.

-              relativePath: relative(options.root, absolutePath),
+              relativePath: './' + relative(options.root, absolutePath),

Note: applying this keeps both paths consistent and avoids flip-flopping in generated DTS. [retrieved_learnings used]


215-223: Mirror duplicate-name warning on HMR “add”.

Add the same overwrite warning here to keep runtime behavior transparent.

       .on('add', (file) => {
         const name = parsePathe(file).name
         const absolutePath = resolve(cwd, file)
+        if (paramParsersMap.has(name)) {
+          logger.warn(`Duplicate param parser "${name}" from ${absolutePath} (overwriting).`)
+        }
         paramParsersMap.set(name, {
           name,
           typeName: `Param_${name}`,
           absolutePath,
           relativePath: './' + relative(options.root, absolutePath),
         })
         writeConfigFiles()
       })

421-426: Clear watchers array after closing to allow re-scan.

Without clearing, a later scanPages() won’t run after stopWatcher().

   function stopWatcher() {
     if (watchers.length) {
       logger.log('🛑 stopping watcher')
-      watchers.forEach((watcher) => watcher.close())
+      watchers.forEach((watcher) => watcher.close())
+      watchers.length = 0
     }
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc3f8ef and e5a73fc.

⛔ Files ignored due to path filters (3)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • src/codegen/__snapshots__/generateRouteRecords.spec.ts.snap is excluded by !**/*.snap
  • src/core/__snapshots__/definePage.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (70)
  • client.d.ts (2 hunks)
  • examples/nuxt/package.json (1 hunks)
  • package.json (3 hunks)
  • playground-experimental/.gitignore (1 hunks)
  • playground-experimental/auto-imports.d.ts (1 hunks)
  • playground-experimental/db.json (1 hunks)
  • playground-experimental/env.d.ts (1 hunks)
  • playground-experimental/index.html (1 hunks)
  • playground-experimental/package.json (1 hunks)
  • playground-experimental/src/App.vue (1 hunks)
  • playground-experimental/src/main.ts (1 hunks)
  • playground-experimental/src/page-outside.vue (1 hunks)
  • playground-experimental/src/pages/(home).vue (1 hunks)
  • playground-experimental/src/pages/[...path].vue (1 hunks)
  • playground-experimental/src/pages/a.[b].c.[d].vue (1 hunks)
  • playground-experimental/src/pages/b.vue (1 hunks)
  • playground-experimental/src/pages/blog/[[slugOptional]]+.vue (1 hunks)
  • playground-experimental/src/pages/blog/[slug]+.vue (1 hunks)
  • playground-experimental/src/pages/blog/info/(info).vue (1 hunks)
  • playground-experimental/src/pages/blog/info/[[section]].vue (1 hunks)
  • playground-experimental/src/pages/events/[when=date].vue (1 hunks)
  • playground-experimental/src/pages/events/repeat/[when=date]+.vue (1 hunks)
  • playground-experimental/src/pages/tests/[[optional]]/end.vue (1 hunks)
  • playground-experimental/src/pages/u[name].vue (1 hunks)
  • playground-experimental/src/pages/u[name]/24.vue (1 hunks)
  • playground-experimental/src/pages/u[name]/[userId=int].vue (1 hunks)
  • playground-experimental/src/pages/users/[userId=int].vue (1 hunks)
  • playground-experimental/src/pages/users/sub-[first]-[second].vue (1 hunks)
  • playground-experimental/src/params/date.ts (1 hunks)
  • playground-experimental/src/router.ts (1 hunks)
  • playground-experimental/tsconfig.config.json (1 hunks)
  • playground-experimental/tsconfig.json (1 hunks)
  • playground-experimental/typed-router.d.ts (1 hunks)
  • playground-experimental/vite.config.ts (1 hunks)
  • playground/src/pages/(some-layout).vue (1 hunks)
  • playground/src/params/date.ts (1 hunks)
  • playground/src/params/number.ts (1 hunks)
  • playground/tsconfig.json (2 hunks)
  • playground/vite.config.ts (2 hunks)
  • pnpm-workspace.yaml (1 hunks)
  • src/codegen/generateDTS.ts (2 hunks)
  • src/codegen/generateParamParsers.spec.ts (1 hunks)
  • src/codegen/generateParamParsers.ts (1 hunks)
  • src/codegen/generateRouteMap.spec.ts (20 hunks)
  • src/codegen/generateRouteMap.ts (1 hunks)
  • src/codegen/generateRouteParams.ts (2 hunks)
  • src/codegen/generateRouteRecords.ts (2 hunks)
  • src/codegen/generateRouteResolver.spec.ts (1 hunks)
  • src/codegen/generateRouteResolver.ts (1 hunks)
  • src/core/context.ts (12 hunks)
  • src/core/customBlock.ts (2 hunks)
  • src/core/definePage.spec.ts (10 hunks)
  • src/core/definePage.ts (4 hunks)
  • src/core/extendRoutes.spec.ts (9 hunks)
  • src/core/extendRoutes.ts (2 hunks)
  • src/core/moduleConstants.ts (2 hunks)
  • src/core/tree.spec.ts (5 hunks)
  • src/core/tree.ts (3 hunks)
  • src/core/treeNodeValue.ts (16 hunks)
  • src/core/utils.ts (4 hunks)
  • src/core/vite/index.ts (2 hunks)
  • src/data-loaders/meta-extensions.ts (1 hunks)
  • src/index.ts (6 hunks)
  • src/options.ts (3 hunks)
  • src/runtime.ts (2 hunks)
  • src/types.ts (1 hunks)
  • src/utils/index.ts (2 hunks)
  • tsconfig.json (2 hunks)
  • vitest.config.ts (1 hunks)
  • vitest.workspace.js (0 hunks)
💤 Files with no reviewable changes (1)
  • vitest.workspace.js
✅ Files skipped from review due to trivial changes (3)
  • playground-experimental/.gitignore
  • playground-experimental/db.json
  • examples/nuxt/package.json
🚧 Files skipped from review as they are similar to previous changes (53)
  • playground-experimental/src/pages/blog/info/[[section]].vue
  • playground-experimental/src/pages/u[name]/[userId=int].vue
  • playground-experimental/src/pages/(home).vue
  • playground-experimental/src/main.ts
  • src/utils/index.ts
  • playground-experimental/src/pages/u[name]/24.vue
  • playground-experimental/auto-imports.d.ts
  • playground-experimental/src/pages/tests/[[optional]]/end.vue
  • vitest.config.ts
  • playground/src/params/number.ts
  • src/codegen/generateRouteResolver.spec.ts
  • playground-experimental/src/pages/blog/[slug]+.vue
  • playground-experimental/src/pages/users/sub-[first]-[second].vue
  • pnpm-workspace.yaml
  • src/core/vite/index.ts
  • playground-experimental/src/pages/events/[when=date].vue
  • playground/src/params/date.ts
  • src/core/tree.spec.ts
  • tsconfig.json
  • src/core/customBlock.ts
  • src/core/extendRoutes.ts
  • src/core/moduleConstants.ts
  • src/codegen/generateRouteMap.ts
  • src/codegen/generateRouteRecords.ts
  • playground-experimental/package.json
  • playground-experimental/src/page-outside.vue
  • src/codegen/generateDTS.ts
  • playground-experimental/tsconfig.json
  • playground-experimental/src/App.vue
  • playground-experimental/env.d.ts
  • playground-experimental/src/pages/[...path].vue
  • playground-experimental/src/pages/u[name].vue
  • src/options.ts
  • playground-experimental/src/pages/users/[userId=int].vue
  • src/codegen/generateParamParsers.spec.ts
  • client.d.ts
  • playground-experimental/vite.config.ts
  • src/data-loaders/meta-extensions.ts
  • playground-experimental/src/pages/blog/[[slugOptional]]+.vue
  • src/core/utils.ts
  • playground/tsconfig.json
  • src/codegen/generateRouteResolver.ts
  • src/codegen/generateParamParsers.ts
  • playground/vite.config.ts
  • src/core/definePage.spec.ts
  • src/codegen/generateRouteMap.spec.ts
  • playground/src/pages/(some-layout).vue
  • playground-experimental/src/pages/events/repeat/[when=date]+.vue
  • playground-experimental/src/pages/b.vue
  • playground-experimental/index.html
  • src/types.ts
  • playground-experimental/typed-router.d.ts
  • src/codegen/generateRouteParams.ts
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-08-23T12:54:44.918Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-150
Timestamp: 2025-08-23T12:54:44.918Z
Learning: In src/core/context.ts, when paramParsers is defined in resolved options, the dir property is guaranteed to be an array, so optional chaining on options.experimental.paramParsers?.dir.map() is sufficient protection against undefined values.

Applied to files:

  • src/runtime.ts
  • src/core/context.ts
📚 Learning: 2025-08-23T12:49:31.747Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/tree.ts:313-331
Timestamp: 2025-08-23T12:49:31.747Z
Learning: The regexp getter in src/core/tree.ts correctly processes the root node to add the leading slash. The empty pathSegment from root becomes an empty string when escaped, but the concatenation logic `"" + (re ? '\\/' : '') + re` properly adds the leading slash that routes require.

Applied to files:

  • src/core/tree.ts
  • src/core/treeNodeValue.ts
📚 Learning: 2025-08-15T16:11:02.627Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateRouteResolver.ts:0-0
Timestamp: 2025-08-15T16:11:02.627Z
Learning: In src/codegen/generateRouteResolver.ts, the user wants comment alignment preserved in the generated resolver code, even when fixing potential runtime errors with String.repeat().

Applied to files:

  • src/core/tree.ts
  • src/core/treeNodeValue.ts
  • src/core/context.ts
📚 Learning: 2025-08-16T13:01:42.709Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/codegen/generateParamParsers.ts:91-94
Timestamp: 2025-08-16T13:01:42.709Z
Learning: In the unplugin-vue-router codebase, path normalization for import specifiers is handled elsewhere in the system (not at individual call sites like generateParamParsers.ts). Individual functions should not normalize paths themselves as it violates separation of concerns.

Applied to files:

  • src/core/treeNodeValue.ts
  • src/index.ts
  • src/core/context.ts
📚 Learning: 2025-08-23T12:50:36.614Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:137-142
Timestamp: 2025-08-23T12:50:36.614Z
Learning: In src/core/context.ts, param parser relative paths must be computed relative to options.root (not relative to DTS file location) and the './' prefix is required for proper path resolution in the generated code.

Applied to files:

  • src/core/context.ts
📚 Learning: 2025-08-16T13:00:51.271Z
Learnt from: posva
PR: posva/unplugin-vue-router#700
File: src/core/context.ts:109-147
Timestamp: 2025-08-16T13:00:51.271Z
Learning: In src/core/context.ts, the options parameter is already resolved, meaning options.experimental.paramMatchers is either an object with a dir property or undefined - never a boolean. The boolean normalization happens earlier in the options resolution phase.

Applied to files:

  • src/core/context.ts
🧬 Code graph analysis (7)
playground-experimental/src/router.ts (2)
playground-experimental/typed-router.d.ts (1)
  • RouteNamedMap (23-41)
src/codegen/generateRouteParams.ts (1)
  • ParamValue (98-100)
src/runtime.ts (2)
client.d.ts (1)
  • TypesConfig (38-40)
src/data-loaders/entries/index.ts (1)
  • TypesConfig (41-41)
src/core/tree.ts (2)
src/core/treeNodeValue.ts (5)
  • TreePathParam (285-292)
  • TreeQueryParam (294-307)
  • params (146-148)
  • re (393-416)
  • escapeRegex (360-361)
src/core/extendRoutes.ts (1)
  • params (177-179)
src/core/treeNodeValue.ts (3)
src/core/customBlock.ts (1)
  • CustomRouteBlockQueryParamOptions (36-41)
src/core/tree.ts (3)
  • queryParams (309-311)
  • pathParams (292-304)
  • params (277-287)
src/core/extendRoutes.ts (2)
  • parent (64-66)
  • params (177-179)
src/core/definePage.ts (5)
src/core/customBlock.ts (1)
  • CustomRouteBlock (20-34)
src/core/tree.ts (3)
  • params (277-287)
  • queryParams (309-311)
  • pathParams (292-304)
src/core/treeNodeValue.ts (2)
  • params (146-148)
  • queryParams (111-140)
src/runtime.ts (1)
  • ParamParserType (79-83)
src/core/utils.ts (1)
  • warn (10-15)
src/index.ts (1)
src/core/moduleConstants.ts (6)
  • MODULE_RESOLVER_PATH (7-7)
  • routeBlockQueryRE (41-41)
  • MODULE_ROUTES_PATH (6-6)
  • MODULE_VUE_ROUTER_AUTO (4-4)
  • VIRTUAL_PREFIX (26-26)
  • ROUTES_LAST_LOAD_TIME (14-21)
src/core/context.ts (8)
src/core/RoutesFolderWatcher.ts (1)
  • RoutesFolderWatcher (15-84)
src/codegen/generateParamParsers.ts (3)
  • ParamParsersMap (5-13)
  • warnMissingParamParsers (23-38)
  • generateParamParsersTypesDeclarations (40-49)
src/core/definePage.ts (1)
  • extractDefinePageInfo (200-261)
src/core/utils.ts (1)
  • ImportsMap (310-395)
src/codegen/generateRouteResolver.ts (1)
  • generateRouteResolver (66-102)
src/codegen/generateDTS.ts (1)
  • generateDTS (18-83)
src/codegen/generateRouteMap.ts (1)
  • generateRouteNamedMap (9-35)
src/codegen/generateRouteFileInfoMap.ts (1)
  • generateRouteFileInfoMap (4-54)
🔇 Additional comments (32)
playground-experimental/tsconfig.config.json (2)

1-8: LGTM for a config-only tsconfig.

Extends Vue base, scopes to tool configs, and sets composite for project refs. Looks correct.


6-6: Resolve — @types/node present

package.json includes "@types/node": "^24.3.0", so Node types are available for playground-experimental/tsconfig.config.json.

playground-experimental/src/params/date.ts (2)

22-31: LGTM overall.

Clean parser, good use of miss(), and array handling is straightforward.


23-31: Confirm empty-array semantics for repeatable params.

Should [] be accepted (round-trip to []) or trigger miss()? If miss() is desired, add a guard:

     if (value == null) {
       throw miss()
     }
+    if (Array.isArray(value) && value.length === 0) {
+      throw miss()
+    }
package.json (1)

208-208: PR tarball for vue-router in devDependencies: ensure reproducibility and revert before release.

File: package.json (lines 208)

    "vue-router": "https://pkg.pr.new/vue-router@7d6164a",

Before merging/releasing, revert to a reproducible source (commit tarball or semver):

-    "vue-router": "https://pkg.pr.new/vue-router@7d6164a",
+    "vue-router": "^4.5.1",

Repo search in the verification environment returned "No files were searched" — cannot confirm no other pkg.pr.new usages. Please verify locally:

#!/bin/bash
rg -n --hidden --no-ignore "pkg\.pr\.new" || git grep -n "pkg.pr.new" || true
playground-experimental/src/pages/blog/info/(info).vue (1)

9-15: Verify null param behavior with the experimental resolver.

Confirm that section: null produces /blog/info (not /blog/info/null). If unsure, prefer omitting section or using undefined.

-          params: {
-            section: null,
-          },
+          // if the resolver doesn't strip null, omit the param:
+          params: {},
playground-experimental/src/router.ts (2)

15-17: HMR hook looks correct.

Guarded by import.meta.hot; no runtime impact in prod.


1-3: Verify vue-router exposes experimental imports

package.json pins vue-router to "https://pkg.pr.new/vue-router@7d6164a" — confirm this build exports "vue-router/experimental" and "vue-router/auto-resolver" (inspect node_modules/vue-router/package.json "exports" or run node -e "require.resolve('vue-router/experimental')"). Location: playground-experimental/src/router.ts:1-2

src/index.ts (3)

69-85: Virtual resolver wiring looks correct.

Including MODULE_RESOLVER_PATH in resolveId and returning a virtual id is consistent with routes/auto handling.


92-94: Async buildStart is good; ensures initial scan completes.

Awaiting scanPages avoids race conditions on first load.


121-149: Load branch for auto‑resolver: update time + generateResolver().
Verified — ctx.generateResolver is defined (src/core/context.ts:252).

src/core/definePage.ts (2)

192-199: New DefinePageInfo API: good shape.

Lightweight container with optional fields fits downstream merges.


263-284: Param extractor is fine.

Structure mirrors CustomRouteBlock['params'] cleanly.

src/core/tree.ts (8)

4-8: Importing escapeRegex and param types: good.

Keeps concerns in treeNodeValue and avoids duplication.


223-228: Path getter logic LGTM.

Root-children get leading slash; overrides respected.


274-287: Params aggregation LGTM.

Includes ancestors; matches downstream expectations.


292-304: Path params aggregation LGTM.

Filters strictly to path params.


309-311: Query params passthrough LGTM.

Scoped to current node only as intended.


388-411: Dynamic parts preserve splat markers.

Shape matches TreeNodeValueMatcherPart.


417-421: isMatchable guard LGTM.

Simple and effective.


316-363: Add tests for optional-first segments and verify the regexp behaviour

No matching tests found in the repo; add unit tests for:

  • optional-first only segment
  • optional-first + following static segment
  • optional optional-in-the-middle segment
src/runtime.ts (2)

62-75: Params field shape LGTM.

Experimental gating is clear.


77-84: Param parser types look good.

Union with TypesConfig ParamParsers + native set is flexible.

src/core/context.ts (2)

252-301: Resolver + HMR integration: LGTM.

API surface and hot replacement flow are coherent with the new resolver.


360-373: DTS generation hooks in param-parsers correctly.

Guard + warnMissingParamParsers + type declarations wiring look correct.

src/core/treeNodeValue.ts (7)

18-26: RouteRecordOverride.params surface: LGTM.

Shape for path/query parsers is clear and extensible.


108-140: Prefer Object.keys to avoid inherited keys in query params loop.

Safer enumeration; keeps behavior stable if the object prototype is altered.

[ suggest_nitpick_refactor ]

-    for (var paramName in paramsQuery) {
-      var config = paramsQuery[paramName]
+    for (const paramName of Object.keys(paramsQuery)) {
+      const config = paramsQuery[paramName]!

257-258: Static/group scores: LGTM.

Setting a fixed [300] baseline aligns with sorting expectations.

Also applies to: 272-273


352-362: Regex escaping utility: LGTM.

Covers all needed special chars for path literals.


376-391: Param subsegment scoring: LGTM.

Clear malus model; negative scores for splats are fine given comparator semantics.


393-416: Param regex builder: LGTM.

Escapes statics and handles splat/optional/repeatable correctly.


563-700: File-segment parser: solid state machine.

Handles optional, splat, repeatable, and parser syntax [param=type] cleanly with good errors.

@@ -1,4 +1,4 @@
import type { RouteRecordRaw } from 'vue-router'
import type { RouteRecordRaw, TypesConfig } from 'vue-router'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

TypesConfig import path likely wrong.

TypesConfig is defined in this package’s client types, not exported by vue-router. Import from unplugin-vue-router’s client types to prevent TS errors.

-import type { RouteRecordRaw, TypesConfig } from 'vue-router'
+import type { RouteRecordRaw } from 'vue-router'
+import type { TypesConfig } from '../client' // or 'unplugin-vue-router/client'

🏁 Script executed:

#!/usr/bin/env bash
# Confirm where TypesConfig is declared
rg -n "export interface TypesConfig" -S
rg -n "declare module ['\"]vue-router['\"]" -S client.d.ts

Length of output: 147


Import TypesConfig from this package's client types, not from 'vue-router'.

client.d.ts and src/data-loaders/types-config.ts declare/export TypesConfig; adjust import in src/runtime.ts:

-import type { RouteRecordRaw, TypesConfig } from 'vue-router'
+import type { RouteRecordRaw } from 'vue-router'
+import type { TypesConfig } from '../client' // or 'unplugin-vue-router/client'
📝 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
import type { RouteRecordRaw, TypesConfig } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
import type { TypesConfig } from '../client' // or 'unplugin-vue-router/client'
🤖 Prompt for AI Agents
In src/runtime.ts around line 1, the file currently imports TypesConfig from
'vue-router'; replace that import so TypesConfig is imported from this package's
client types (either the generated client.d.ts export or from
src/data-loaders/types-config.ts). Update the import line to import type {
TypesConfig } from the package's client types file (or from
'./src/data-loaders/types-config' relative path) and remove TypesConfig from the
'vue-router' import so the type comes from the correct local/client declaration.

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.

1 participant