next-vite-router 1.2.2 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,33 +6,19 @@ Define routes with folders and let the package generate the
6
6
  [React Router](https://reactrouter.com/) `RouteObject[]`. It does not replace
7
7
  React Router, navigation, history, loaders, or application state.
8
8
 
9
- ## Release notes
10
-
11
- ### Plugin import
12
-
13
- The Vite plugin is no longer exported from the main
14
- `next-vite-router` entry point. Existing imports must be migrated:
15
-
16
- ```ts
17
- // Previous import
18
- import { nextViteRouter } from "next-vite-router";
19
-
20
- // Current import
21
- import { nextViteRouter } from "next-vite-router/plugin";
22
- ```
23
-
24
- The plugin remains available through `next-vite-router/plugin`. This separation
25
- prevents the plugin's Node dependencies from being included in the application's
26
- browser dependency graph.
27
-
28
9
  ## Start here
29
10
 
30
11
  ```bash
31
12
  npm create vite@latest my-app -- --template react-ts
32
13
  cd my-app
33
- npm install next-vite-router react-router-dom
14
+ npm install next-vite-router
34
15
  ```
35
16
 
17
+ `next-vite-router` declares `react-router-dom` as a peer dependency. Recent
18
+ versions of npm install this dependency automatically; if your package manager
19
+ does not resolve peer dependencies automatically, install
20
+ `react-router-dom` explicitly.
21
+
36
22
  Add the plugin to `vite.config.ts`:
37
23
 
38
24
  ```ts
@@ -147,13 +133,17 @@ create competing navigation or data-loading APIs.
147
133
  | `(admin)` | Route group omitted from the URL |
148
134
 
149
135
  Catch-all pages and `not-found.tsx` at the same level both use React Router's
150
- `*` path and should not be declared together if their fallback behavior must
151
- be distinct. The catch-all parameter provider wraps the page route, so a
152
- layout cannot read that parameter through the package `useParams` hook.
136
+ `*` path and are rejected during route validation because their fallback
137
+ behavior would be ambiguous. The catch-all parameter provider wraps the page
138
+ route, so a layout cannot read that parameter through the package `useParams`
139
+ hook.
153
140
 
154
141
  The Vite plugin accepts custom `pageFile`, `layoutFile`, and `notFoundFile`
155
142
  names. `createRouteGenerator` accepts the same options, plus `pagesDir`, as
156
143
  its optional fourth argument when custom glob paths or extensions are used.
144
+ For the Vite plugin, `pagesDir` is relative to the Vite project root (the
145
+ default is `src/app`). The lower-level route path utility also accepts an
146
+ absolute `pagesDir`, which is useful for custom route generators.
157
147
 
158
148
  Layouts are the recommended way to add route-scoped navigation, providers, or
159
149
  authorization:
@@ -203,8 +193,14 @@ No manual declaration for the virtual module is required.
203
193
 
204
194
  Pages and `not-found.tsx` files use Vite's non-eager `import.meta.glob`, so
205
195
  Vite can emit separate chunks for pages. Layouts are eager because they wrap
206
- the generated route tree; changing a layout can therefore cause a full
207
- development reload. Lazy loading is enabled by default:
196
+ the generated route tree. Vite handles ordinary content changes to pages,
197
+ layouts, and `not-found.tsx` through its normal HMR mechanism. Creating,
198
+ deleting, or renaming a route-related file also refreshes the generated route
199
+ module and route declarations. Because Vite cannot reliably preserve a cached
200
+ route tree when an `import.meta.glob` entry is added or removed, these
201
+ structural changes trigger a full-page reload as a fallback. Lazy-loaded route
202
+ components remain lazy-loaded during normal navigation. Lazy loading is
203
+ enabled by default:
208
204
 
209
205
  ```tsx
210
206
  import { configureRouter } from "next-vite-router";
@@ -310,7 +306,12 @@ pnpm exec tsc -p test/fixture/tsconfig.json --noEmit
310
306
  pnpm run test:local
311
307
  ```
312
308
 
313
- The route matrix lives in [`test/routes.test.js`](./test/routes.test.js).
309
+ The route matrix lives in [`test/routes.test.js`](./test/routes.test.js) and
310
+ includes React Router `matchRoutes` assertions for dynamic parameters, route
311
+ groups, nested `not-found.tsx` fallbacks, and catch-all pages under layouts.
312
+ `test/test-route-types.js` covers generated TypeScript declarations and HMR
313
+ updates, while `test/test-compatibility.js` checks the supported dependency
314
+ ranges and generated production bootstrap.
314
315
 
315
316
  ## License
316
317
 
package/dist/cli.cjs CHANGED
@@ -548,7 +548,7 @@ const init = new commander.Command().name("init").description(
548
548
  Project "${projectName}" is ready!
549
549
  `);
550
550
  });
551
- const version = "1.2.2";
551
+ const version = "1.2.4";
552
552
  const pkg = {
553
553
  version
554
554
  };
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ import { RouteObject } from 'react-router-dom';
5
5
 
6
6
  export declare function assertNoDuplicateRoutes(filePaths: string[], fileOptions?: RouteFileOptions): void;
7
7
 
8
+ export declare function assertNoRouteConflicts(pageFiles: string[], notFoundFiles: string[], fileOptions?: RouteFileOptions): void;
9
+
8
10
  export declare function buildRouteTree(pages: Record<string, () => Promise<any>>, layouts: Record<string, any>, notFounds: Record<string, () => Promise<any>>, fileOptions?: RouteFileOptions): RouteNode;
9
11
 
10
12
  export declare function CatchAllParamProvider({ name, children, }: {
@@ -47,6 +49,9 @@ export declare function createRouteGenerator(pages: Record<string, () => Promise
47
49
  * Extract the route path from a file path
48
50
  * @example "../../app/blog/[id]/page.tsx" -> "blog/[id]"
49
51
  * @example "../../app/page.tsx" -> ""
52
+ *
53
+ * `pagesDir` may be relative or absolute. The plugin resolves it relative to
54
+ * the Vite project root before passing it here.
50
55
  */
51
56
  export declare function extractRoutePath(filePath: string, { pagesDir, pageFile, layoutFile, notFoundFile }?: RouteFileOptions): string;
52
57
 
@@ -58,6 +63,8 @@ export declare function extractRoutePath(filePath: string, { pagesDir, pageFile,
58
63
  */
59
64
  export declare function normalizeSegment(segment: string): string;
60
65
 
66
+ export declare function parseRouteSegments(routePath: string): RouteSegment[];
67
+
61
68
  export declare function registerMiddleware(pattern: RegExp, middleware: RouteMiddleware): void;
62
69
 
63
70
  export declare type RouteFileOptions = {
@@ -84,10 +91,26 @@ export declare type RouteNode = {
84
91
 
85
92
  export declare type RouterConfig = {
86
93
  loadingFallback?: React.ComponentType;
87
- pagesDir?: string;
88
94
  enableLazyLoading?: boolean;
89
95
  };
90
96
 
97
+ export declare type RouteSegment = {
98
+ kind: "group";
99
+ name: string;
100
+ } | {
101
+ kind: "static";
102
+ name: string;
103
+ } | {
104
+ kind: "dynamic";
105
+ name: string;
106
+ } | {
107
+ kind: "catchAll";
108
+ name: string;
109
+ optional: boolean;
110
+ };
111
+
112
+ export declare function routeSegmentToPath(segment: RouteSegment): string;
113
+
91
114
  export declare function treeToRoutes(node: RouteNode, isRoot?: boolean, currentPath?: string): RouteObject[];
92
115
 
93
116
  export declare function useParams<T extends Record<string, string | undefined> = Record<string, string | undefined>>(): T;
package/dist/index.js CHANGED
@@ -1,193 +1,222 @@
1
- import { n as p, b as z } from "./tree-HwBRWJNI.js";
2
- import { a as V, e as W } from "./tree-HwBRWJNI.js";
3
- import c, { Suspense as d, lazy as b, createContext as v, useContext as L } from "react";
4
- import { useParams as A } from "react-router-dom";
5
- const M = {
1
+ import { p, r as v, b as z } from "./tree-DW5A1s6W.js";
2
+ import { a as K, c as tt, e as et, n as nt } from "./tree-DW5A1s6W.js";
3
+ import u, { Suspense as R, lazy as F, createContext as $, useContext as A } from "react";
4
+ import { useParams as k } from "react-router-dom";
5
+ const N = {
6
6
  enableLazyLoading: !0
7
7
  };
8
- let m = { ...M };
9
- function j(e) {
10
- m = { ...m, ...e };
8
+ let f = { ...N };
9
+ function U(t) {
10
+ f = { ...f, ...t };
11
11
  }
12
- function k() {
13
- return m;
12
+ function M() {
13
+ return f;
14
14
  }
15
- let g = [];
16
- function q(e, t) {
17
- g.push({ pattern: e, middleware: t });
15
+ let h = [];
16
+ function V(t, e) {
17
+ h.push({ pattern: t, middleware: e });
18
18
  }
19
- function B() {
20
- g = [];
19
+ function W() {
20
+ h = [];
21
21
  }
22
- function h(e, t) {
23
- let n = t;
24
- for (const r of g)
25
- r.pattern.lastIndex = 0, r.pattern.test(e) && (n = r.middleware(n)), r.pattern.lastIndex = 0;
22
+ function m(t, e) {
23
+ let n = e;
24
+ for (const r of h)
25
+ r.pattern.lastIndex = 0, r.pattern.test(t) && (n = r.middleware(n)), r.pattern.lastIndex = 0;
26
26
  return n;
27
27
  }
28
- const S = () => c.createElement("div"), C = /* @__PURE__ */ new Map();
29
- function R(e, t, n) {
30
- const r = k();
28
+ const T = () => u.createElement("div"), C = /* @__PURE__ */ new Map();
29
+ function y(t, e, n) {
30
+ const r = M();
31
31
  if (r.enableLazyLoading === !1) {
32
- const s = c.lazy(e), i = c.createElement(
33
- d,
32
+ const c = u.lazy(t), d = u.createElement(
33
+ R,
34
34
  { fallback: null },
35
35
  // No loading fallback when disabled
36
- c.createElement(s)
36
+ u.createElement(c)
37
37
  );
38
- return n ? h(n, i) : i;
38
+ return n ? m(n, d) : d;
39
39
  }
40
- let a = C.get(t);
41
- a || (a = b(e), C.set(t, a));
42
- const l = r.loadingFallback || S, o = c.createElement(
43
- d,
44
- { fallback: c.createElement(l) },
45
- c.createElement(a)
40
+ let a = C.get(e);
41
+ a || (a = F(t), C.set(e, a));
42
+ const o = r.loadingFallback || T, l = u.createElement(
43
+ R,
44
+ { fallback: u.createElement(o) },
45
+ u.createElement(a)
46
46
  );
47
- return n ? h(n, o) : o;
47
+ return n ? m(n, l) : l;
48
48
  }
49
- const w = v(void 0);
49
+ const E = $(void 0);
50
50
  function D({
51
- name: e,
52
- children: t
51
+ name: t,
52
+ children: e
53
53
  }) {
54
- return c.createElement(
55
- w.Provider,
56
- { value: e },
57
- t
54
+ return u.createElement(
55
+ E.Provider,
56
+ { value: t },
57
+ e
58
58
  );
59
59
  }
60
- function N() {
61
- return L(w);
60
+ function I() {
61
+ return A(E);
62
62
  }
63
- function y(e, t) {
64
- return h(e, c.createElement(t));
63
+ function w(t, e) {
64
+ return m(t, u.createElement(e));
65
65
  }
66
- function T(e) {
67
- return /^\[\[\.\.\..+\]\]$/.test(e);
66
+ function S(t) {
67
+ const e = p(t)[0];
68
+ return (e == null ? void 0 : e.kind) === "catchAll" && e.optional;
68
69
  }
69
- function f(e, t, n = !1) {
70
- const r = e.page;
70
+ function g(t) {
71
+ const e = p(t)[0];
72
+ return e ? v(e) : "";
73
+ }
74
+ function s(t, e) {
75
+ return i(t, e, !0);
76
+ }
77
+ function i(t, e, n = !1) {
78
+ const r = t.page;
71
79
  if (!r)
72
- throw new Error(`Route node "${e.fullPath}" has no page loader`);
73
- const a = R(
80
+ throw new Error(`Route node "${t.fullPath}" has no page loader`);
81
+ const a = y(
74
82
  async () => ({ default: (await r()).default }),
75
- `page:${e.fullPath || t}`,
76
- t
77
- ), l = e.segment.match(/^\[\[?\.\.\.(.+?)\]\]?$/), o = l ? c.createElement(D, {
78
- name: l[1],
83
+ `page:${t.fullPath || e}`,
84
+ e
85
+ ), o = p(t.segment)[0], l = (o == null ? void 0 : o.kind) === "catchAll" ? u.createElement(D, {
86
+ name: o.name,
79
87
  children: a
80
88
  }) : a;
81
- return n ? { index: !0, element: o } : { path: p(e.segment), element: o };
89
+ return n ? { index: !0, element: l } : { path: g(t.segment), element: l };
82
90
  }
83
- function P(e) {
84
- const t = e.notFound;
85
- if (!t)
86
- throw new Error(`Route node "${e.fullPath}" has no not-found loader`);
87
- const n = e.fullPath ? `/${e.fullPath}` : "/";
91
+ function G(t) {
92
+ const e = t.notFound;
93
+ if (!e)
94
+ throw new Error(`Route node "${t.fullPath}" has no not-found loader`);
95
+ const n = t.fullPath ? `/${t.fullPath}` : "/";
88
96
  return {
89
97
  path: "*",
90
- element: R(
91
- async () => ({ default: (await t()).default }),
92
- `notfound:${e.fullPath}`,
98
+ element: y(
99
+ async () => ({ default: (await e()).default }),
100
+ `notfound:${t.fullPath}`,
93
101
  n
94
102
  )
95
103
  };
96
104
  }
97
- function F(e, t) {
105
+ function P(t) {
106
+ return t.notFound ? [G(t)] : [];
107
+ }
108
+ function O(t, e, n, r) {
109
+ return {
110
+ ...e ? { path: e } : {},
111
+ element: w(t, n),
112
+ children: r.length > 0 ? r : void 0
113
+ };
114
+ }
115
+ function x(t, e) {
98
116
  const n = [];
99
- return [...e.children.entries()].sort(([r], [a]) => r.localeCompare(a)).forEach(([r, a]) => {
100
- const l = p(r), o = l ? t ? `${t}/${l}` : `/${l}` : t;
101
- n.push(...$(a, !1, o));
117
+ return [...t.children.entries()].sort(([r], [a]) => r.localeCompare(a)).forEach(([r, a]) => {
118
+ const o = g(r), l = o ? e ? `${e}/${o}` : `/${o}` : e;
119
+ n.push(...b(a, !1, l));
102
120
  }), n;
103
121
  }
104
- function E(e, t) {
122
+ function L(t, e) {
105
123
  const n = [];
106
- return e.page && n.push(f(e, t || "/", !0)), n.push(...F(e, t)), e.notFound && n.push(P(e)), n;
107
- }
108
- function $(e, t = !1, n = "") {
109
- const r = [];
110
- if (t) {
111
- const u = E(e, "");
112
- return e.layout ? (r.push({
113
- path: "/",
114
- element: y("/", e.layout),
115
- children: u
116
- }), r) : e.page ? u.length > 1 ? [{
117
- path: "/",
118
- children: u
119
- }] : [{
120
- path: "/",
121
- element: f(e, "/", !1).element
122
- }] : e.notFound ? (r.push({
123
- path: "/",
124
- children: u
125
- }), r) : u;
126
- }
127
- const a = p(e.segment), l = !a, o = l ? n : n + "/" + a, s = F(e, o), i = e.notFound ? [P(e)] : [];
128
- if (e.layout) {
129
- const u = E(e, o);
130
- return [{
131
- ...l ? {} : { path: a },
132
- element: y(o, e.layout),
133
- children: u.length > 0 ? u : void 0
134
- }];
135
- }
136
- if (e.page && !l) {
137
- if (T(e.segment)) {
138
- const u = f(e, n, !0), x = {
139
- path: a,
140
- element: f(e, o).element,
141
- children: [...s, ...i].length > 0 ? [...s, ...i] : void 0
142
- };
143
- return [u, x];
144
- }
145
- return s.length > 0 || i.length > 0 ? [{
146
- path: a,
147
- children: [
148
- f(e, o, !0),
149
- ...s,
150
- ...i
151
- ]
152
- }] : [{
153
- path: a,
154
- element: f(e, o).element
155
- }];
156
- }
157
- if (e.page || e.notFound || !l) {
158
- const u = [
159
- ...e.page ? [f(e, o, !0)] : [],
160
- ...s,
161
- ...i
124
+ return t.page && n.push(s(t, e || "/")), n.push(...x(t, e)), n.push(...P(t)), n;
125
+ }
126
+ function j(t, e, n, r, a, o) {
127
+ if (S(t.segment)) {
128
+ const l = [...a, ...o];
129
+ return [
130
+ s(t, e),
131
+ {
132
+ path: r,
133
+ element: i(t, n).element,
134
+ children: l.length > 0 ? l : void 0
135
+ }
162
136
  ];
163
- return [{
164
- ...l ? {} : { path: a },
165
- children: u.length > 0 ? u : void 0
166
- }];
167
137
  }
168
- return s;
169
- }
170
- function H(e, t, n, r) {
138
+ return a.length > 0 || o.length > 0 ? [{
139
+ path: r,
140
+ children: [
141
+ s(t, n),
142
+ ...a,
143
+ ...o
144
+ ]
145
+ }] : [{
146
+ path: r,
147
+ element: i(t, n).element
148
+ }];
149
+ }
150
+ function q(t, e, n, r) {
151
+ const a = [
152
+ ...t.page ? [s(t, e)] : [],
153
+ ...n,
154
+ ...r
155
+ ];
156
+ return [{
157
+ children: a.length > 0 ? a : void 0
158
+ }];
159
+ }
160
+ function B(t) {
161
+ const e = L(t, "");
162
+ return t.layout ? [{
163
+ path: "/",
164
+ element: w("/", t.layout),
165
+ children: e
166
+ }] : t.page ? e.length > 1 ? [{ path: "/", children: e }] : [{
167
+ path: "/",
168
+ element: i(t, "/", !1).element
169
+ }] : t.notFound ? [{ path: "/", children: e }] : e;
170
+ }
171
+ function b(t, e = !1, n = "") {
172
+ if (e)
173
+ return B(t);
174
+ const r = g(t.segment), a = !r, o = a ? n : n + "/" + r, l = x(t, o), c = P(t);
175
+ return t.layout ? [O(
176
+ o,
177
+ a ? "" : r,
178
+ t.layout,
179
+ L(t, o)
180
+ )] : t.page && !a ? j(
181
+ t,
182
+ n,
183
+ o,
184
+ r,
185
+ l,
186
+ c
187
+ ) : t.page || t.notFound || !a ? a ? q(t, o, l, c) : [{
188
+ path: r,
189
+ children: [
190
+ ...t.page ? [s(t, o)] : [],
191
+ ...l,
192
+ ...c
193
+ ]
194
+ }] : l;
195
+ }
196
+ function X(t, e, n, r) {
171
197
  return () => {
172
- const a = z(e, t, n, r);
173
- return $(a, !0);
198
+ const a = z(t, e, n, r);
199
+ return b(a, !0);
174
200
  };
175
201
  }
176
- function J() {
177
- const e = A(), t = N();
178
- return typeof t == "string" && e["*"] !== void 0 ? { ...e, [t]: e["*"] } : e;
202
+ function Y() {
203
+ const t = k(), e = I();
204
+ return typeof e == "string" && t["*"] !== void 0 ? { ...t, [e]: t["*"] } : t;
179
205
  }
180
206
  export {
181
207
  D as CatchAllParamProvider,
182
- V as assertNoDuplicateRoutes,
208
+ K as assertNoDuplicateRoutes,
209
+ tt as assertNoRouteConflicts,
183
210
  z as buildRouteTree,
184
- B as clearMiddlewares,
185
- j as configureRouter,
186
- R as createLazyElement,
187
- H as createRouteGenerator,
188
- W as extractRoutePath,
189
- p as normalizeSegment,
190
- q as registerMiddleware,
191
- $ as treeToRoutes,
192
- J as useParams
211
+ W as clearMiddlewares,
212
+ U as configureRouter,
213
+ y as createLazyElement,
214
+ X as createRouteGenerator,
215
+ et as extractRoutePath,
216
+ nt as normalizeSegment,
217
+ p as parseRouteSegments,
218
+ V as registerMiddleware,
219
+ v as routeSegmentToPath,
220
+ b as treeToRoutes,
221
+ Y as useParams
193
222
  };
package/dist/plugin.js CHANGED
@@ -1,60 +1,77 @@
1
- import h from "node:fs";
1
+ import P from "node:fs";
2
2
  import l from "node:path";
3
- import { a as v, n as b } from "./tree-HwBRWJNI.js";
4
- function x(r) {
5
- const e = r.split(l.sep).filter(Boolean).map(b).filter(Boolean);
6
- return e.length > 0 ? `/${e.join("/")}` : "/";
3
+ import { c as $, p as S, r as k } from "./tree-DW5A1s6W.js";
4
+ function j(n) {
5
+ const a = n.map(k).filter(Boolean);
6
+ return a.length > 0 ? `/${a.join("/")}` : "/";
7
7
  }
8
- function $(r, e) {
9
- const n = /* @__PURE__ */ new Map();
10
- function s(a) {
11
- for (const t of h.readdirSync(a, { withFileTypes: !0 })) {
12
- const c = l.join(a, t.name);
13
- if (t.isDirectory())
14
- s(c);
15
- else if (t.isFile() && t.name === e) {
16
- const i = l.relative(r, a), u = i.split(l.sep).filter(Boolean), y = u.map((p) => {
17
- const m = p.match(/^\[\[\.\.\.(.+)\]\]$/);
18
- if (m)
19
- return { name: m[1], optional: !0 };
20
- const d = p.match(/^\[\.\.\.(.+)\]$/);
21
- return d ? { name: d[1], optional: !1 } : void 0;
22
- }).filter((p) => !!p), f = x(i);
23
- n.set(f, { path: f, params: y });
24
- const o = u[u.length - 1];
25
- if (o == null ? void 0 : o.match(/^\[\[\.\.\.(.+)\]\]$/)) {
26
- const p = u.slice(0, -1).join(l.sep), m = x(p);
27
- n.has(m) || n.set(m, { path: m, params: [] });
8
+ function w(n) {
9
+ const a = [
10
+ ["pagesDir", n.pagesDir],
11
+ ["pageFile", n.pageFile],
12
+ ["layoutFile", n.layoutFile],
13
+ ["notFoundFile", n.notFoundFile]
14
+ ];
15
+ for (const [e, o] of a)
16
+ if (!o.trim())
17
+ throw new Error(`nextViteRouter option "${e}" must not be empty.`);
18
+ const r = a.slice(1), i = r.find(
19
+ ([, e], o) => r.slice(o + 1).some(([, u]) => u === e)
20
+ );
21
+ if (i)
22
+ throw new Error(
23
+ `nextViteRouter file options must be distinct; "${i[0]}" is duplicated.`
24
+ );
25
+ }
26
+ function O(n) {
27
+ return n.replace(/\\/g, "/").replace(/^\/|\/$/g, "");
28
+ }
29
+ function T(n, a) {
30
+ const r = /* @__PURE__ */ new Map();
31
+ function i(e) {
32
+ for (const o of P.readdirSync(e, { withFileTypes: !0 })) {
33
+ const u = l.join(e, o.name);
34
+ if (o.isDirectory())
35
+ i(u);
36
+ else if (o.isFile() && o.name === a) {
37
+ const d = l.relative(n, e).split(l.sep).filter(Boolean), s = S(d.join("/")), R = s.filter(
38
+ (c) => c.kind === "dynamic" || c.kind === "catchAll"
39
+ ).map((c) => ({
40
+ name: c.name,
41
+ optional: c.kind === "catchAll" && c.optional
42
+ })), m = j(s);
43
+ r.set(m, { path: m, params: R });
44
+ const f = s[s.length - 1];
45
+ if ((f == null ? void 0 : f.kind) === "catchAll" && f.optional) {
46
+ const c = d.slice(0, -1).join(l.sep), t = j(
47
+ S(c.split(l.sep).join("/"))
48
+ );
49
+ r.has(t) || r.set(t, { path: t, params: [] });
28
50
  }
29
51
  }
30
52
  }
31
53
  }
32
- return h.existsSync(r) && s(r), [...n.values()].sort((a, t) => a.path.localeCompare(t.path));
54
+ return P.existsSync(n) && i(n), [...r.values()].sort((e, o) => e.path.localeCompare(o.path));
33
55
  }
34
- function P(r, e) {
35
- const n = [];
36
- function s(a) {
37
- for (const t of h.readdirSync(a, { withFileTypes: !0 })) {
38
- const c = l.join(a, t.name);
39
- t.isDirectory() ? s(c) : t.isFile() && t.name === e && n.push(l.relative(r, c));
56
+ function b(n, a) {
57
+ const r = [];
58
+ function i(e) {
59
+ for (const o of P.readdirSync(e, { withFileTypes: !0 })) {
60
+ const u = l.join(e, o.name);
61
+ o.isDirectory() ? i(u) : o.isFile() && o.name === a && r.push(l.relative(n, u));
40
62
  }
41
63
  }
42
- return h.existsSync(r) && s(r), n;
64
+ return P.existsSync(n) && i(n), r;
43
65
  }
44
- function F(r) {
45
- const e = r.match(/^\/(?:\[\.\.\.|:)([^/\]]+)\]?$/);
46
- return e == null ? void 0 : e[1];
47
- }
48
- function j(r) {
49
- const e = r.map(({ path: s }) => ` | "${s}"`).join(`
50
- `), n = r.map(({ path: s, params: a }) => {
51
- const t = s.split("/").map((i) => F(`/${i}`)).filter((i) => !!i), c = t.length > 0 || a.length > 0 ? [
52
- ...t.map((i) => ` ${i}: string;`),
53
- ...a.map(({ name: i, optional: u }) => ` ${i}${u ? "?" : ""}: string;`)
66
+ function G(n) {
67
+ const a = n.map(({ path: i }) => ` | ${JSON.stringify(i)}`).join(`
68
+ `), r = n.map(({ path: i, params: e }) => {
69
+ const o = e.length > 0 ? [
70
+ ...e.map(({ name: u, optional: y }) => ` ${u}${y ? "?" : ""}: string;`)
54
71
  ].join(`
55
72
  `) : " [key: string]: never;";
56
- return ` "${s}": {
57
- ${c}
73
+ return ` ${JSON.stringify(i)}: {
74
+ ${o}
58
75
  };`;
59
76
  }).join(`
60
77
  `);
@@ -63,14 +80,14 @@ ${c}
63
80
  import type { RouteObject } from 'react-router-dom';
64
81
 
65
82
  export type RoutePath =
66
- ${e || ' | "/"'};
83
+ ${a || ' | "/"'};
67
84
 
68
85
  export type RouteParams<P extends RoutePath> = P extends keyof RoutePathParams
69
86
  ? RoutePathParams[P]
70
87
  : never;
71
88
 
72
89
  export interface RoutePathParams {
73
- ${n}
90
+ ${r}
74
91
  }
75
92
 
76
93
  export const generateRoutes: () => RouteObject[];
@@ -80,56 +97,76 @@ ${n}
80
97
  }
81
98
  `;
82
99
  }
83
- function S(r = {}) {
100
+ function M(n = {}) {
84
101
  const {
85
- pagesDir: e = "src/app",
86
- pageFile: n = "page.tsx",
87
- layoutFile: s = "layout.tsx",
88
- notFoundFile: a = "not-found.tsx"
89
- } = r, t = { pagesDir: e, pageFile: n, layoutFile: s, notFoundFile: a }, c = "virtual:next-vite-router", i = "\0" + c;
90
- let u = "", y = "";
91
- function f() {
92
- const o = j($(u, n));
93
- h.writeFileSync(y, o, "utf-8");
102
+ pagesDir: a = "src/app",
103
+ pageFile: r = "page.tsx",
104
+ layoutFile: i = "layout.tsx",
105
+ notFoundFile: e = "not-found.tsx"
106
+ } = n;
107
+ w({ pagesDir: a, pageFile: r, layoutFile: i, notFoundFile: e });
108
+ const o = O(a), u = { pagesDir: a, pageFile: r, layoutFile: i, notFoundFile: e }, y = "virtual:next-vite-router", d = "\0" + y;
109
+ let s = "", R = "", m, f;
110
+ function c() {
111
+ const t = G(T(s, r));
112
+ P.writeFileSync(R, t, "utf-8");
94
113
  }
95
114
  return {
96
115
  name: "next-vite-router",
97
- configResolved(o) {
98
- u = l.resolve(o.root, e), y = l.join(l.dirname(u), "virtual-next-vite-router.d.ts"), v(P(u, n), t), f();
116
+ configResolved(t) {
117
+ s = l.resolve(t.root, a), R = l.join(l.dirname(s), "virtual-next-vite-router.d.ts"), $(
118
+ b(s, r),
119
+ b(s, e),
120
+ u
121
+ ), c();
99
122
  },
100
- configureServer(o) {
101
- const g = (p) => {
102
- if (l.relative(u, p).startsWith("..") || ![n, s, a].includes(l.basename(p)))
123
+ configureServer(t) {
124
+ if (m === t)
125
+ return;
126
+ f == null || f(), m = t;
127
+ const h = (x) => {
128
+ if (l.relative(s, x).startsWith("..") || ![r, i, e].includes(l.basename(x)))
103
129
  return;
104
130
  try {
105
- v(P(u, n), t);
106
- } catch (R) {
107
- o.config.logger.error(
108
- R instanceof Error ? R.message : String(R)
131
+ $(
132
+ b(s, r),
133
+ b(s, e),
134
+ u
135
+ );
136
+ } catch (v) {
137
+ t.config.logger.error(
138
+ v instanceof Error ? v.message : String(v)
109
139
  );
110
140
  return;
111
141
  }
112
- f();
113
- const d = o.moduleGraph.getModuleById(i);
114
- d && (o.moduleGraph.invalidateModule(d), o.ws.send({ type: "full-reload", path: "*" }));
142
+ c();
143
+ const F = t.moduleGraph.getModuleById(d);
144
+ F && (t.moduleGraph.invalidateModule(F), p && clearTimeout(p), p = setTimeout(() => {
145
+ p = void 0, t.ws.send({ type: "full-reload", path: "*" });
146
+ }, 0));
115
147
  };
116
- o.watcher.on("add", g), o.watcher.on("unlink", g);
148
+ let p;
149
+ const g = () => {
150
+ p && (clearTimeout(p), p = void 0), t.watcher.off("add", h), t.watcher.off("unlink", h), t.watcher.off("close", g), m === t && (m = void 0, f = void 0);
151
+ };
152
+ t.watcher.on("add", h), t.watcher.on("unlink", h), t.watcher.once("close", g), f = g;
117
153
  },
118
- resolveId(o) {
119
- if (o === c)
120
- return i;
154
+ resolveId(t) {
155
+ if (t === y)
156
+ return d;
121
157
  },
122
- load(o) {
123
- if (o === i)
158
+ load(t) {
159
+ if (t === d) {
160
+ const h = `/${o}/**/${r}`, p = `/${o}/**/${i}`, g = `/${o}/**/${e}`, x = `/${o}/`;
124
161
  return `
125
162
  import { buildRouteTree, treeToRoutes, normalizeSegment, createLazyElement, useParams } from 'next-vite-router';
126
163
  import { Link, useRoutes } from 'react-router-dom';
127
164
 
128
- const pagesGlob = import.meta.glob('/${e}/**/${n}');
129
- const layoutsGlob = import.meta.glob('/${e}/**/${s}', { eager: true });
130
- const notFoundsGlob = import.meta.glob('/${e}/**/${a}');
165
+ const pagesGlob = import.meta.glob(${JSON.stringify(h)});
166
+ const layoutsGlob = import.meta.glob(${JSON.stringify(p)}, { eager: true });
167
+ const notFoundsGlob = import.meta.glob(${JSON.stringify(g)});
131
168
 
132
- const transformPath = (path) => path.replace('/${e}/', '');
169
+ const transformPath = (path) => path.replace(${JSON.stringify(x)}, '');
133
170
 
134
171
  const pages = Object.fromEntries(
135
172
  Object.entries(pagesGlob).map(([key, value]) => [transformPath(key), value])
@@ -142,7 +179,7 @@ const notFounds = Object.fromEntries(
142
179
  );
143
180
 
144
181
  export function generateRoutes() {
145
- const tree = buildRouteTree(pages, layouts, notFounds, ${JSON.stringify(t)});
182
+ const tree = buildRouteTree(pages, layouts, notFounds, ${JSON.stringify(u)});
146
183
  const routes = treeToRoutes(tree, true);
147
184
  return routes;
148
185
  }
@@ -154,12 +191,19 @@ export function Router() {
154
191
 
155
192
  let routes;
156
193
 
194
+ if (import.meta.hot) {
195
+ import.meta.hot.accept(() => {
196
+ routes = undefined;
197
+ });
198
+ }
199
+
157
200
  export { Link, useRoutes };
158
201
  export { useParams };
159
202
  `;
203
+ }
160
204
  }
161
205
  };
162
206
  }
163
207
  export {
164
- S as nextViteRouter
208
+ M as nextViteRouter
165
209
  };
@@ -0,0 +1,143 @@
1
+ function R(t) {
2
+ return /^\([^)]+\)$/.test(t) ? "" : /^\[\[\.\.\.(.+)\]\]$/.test(t) || /^\[\.\.\.(.+)\]$/.test(t) ? "*" : /^\[([^\]]+)\]$/.test(t) ? t.replace(/^\[([^\]]+)\]$/, ":$1") : t;
3
+ }
4
+ const P = /^[A-Za-z_][A-Za-z0-9_]*$/;
5
+ function A(t) {
6
+ return t.kind === "group" ? "" : t.kind === "dynamic" ? `:${t.name}` : t.kind === "catchAll" ? "*" : t.name;
7
+ }
8
+ function m(t) {
9
+ const s = t ? t.split("/").filter(Boolean) : [], o = [], c = /* @__PURE__ */ new Set();
10
+ for (const a of s) {
11
+ const n = a.match(/^\(([^)]+)\)$/), e = a.match(/^\[\[\.\.\.(.+)\]\]$/), r = a.match(/^\[\.\.\.(.+)\]$/), i = a.match(/^\[([^\]]+)\]$/), l = e ? { name: e[1], optional: !0, kind: "catchAll" } : r ? { name: r[1], optional: !1, kind: "catchAll" } : i ? { name: i[1], optional: !1, kind: "dynamic" } : void 0;
12
+ if (n)
13
+ o.push({ kind: "group", name: n[1] });
14
+ else if (l) {
15
+ if (!P.test(l.name))
16
+ throw new Error(
17
+ `Invalid route parameter name "${l.name}" in segment "${a}". Parameter names must start with a letter or underscore and contain only letters, numbers, and underscores.`
18
+ );
19
+ if (c.has(l.name))
20
+ throw new Error(
21
+ `Duplicate route parameter "${l.name}" in route "${t || "/"}".`
22
+ );
23
+ c.add(l.name), o.push(l);
24
+ } else
25
+ o.push({ kind: "static", name: a });
26
+ }
27
+ return o;
28
+ }
29
+ function f(t, { pagesDir: s, pageFile: o = "page.tsx", layoutFile: c = "layout.tsx", notFoundFile: a = "not-found.tsx" } = {}) {
30
+ const n = t.replace(/\\/g, "/"), e = s == null ? void 0 : s.replace(/\\/g, "/").replace(/^\/|\/$/g, ""), r = e == null ? void 0 : e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), i = [o, c, a].map((l) => l.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
31
+ return n.replace(
32
+ e ? new RegExp(`^.*(?:^|/)${r}/`) : /^.*(?:^|\/)(?:app|pages)\//,
33
+ ""
34
+ ).replace(/\\/g, "/").replace(new RegExp(`/?(?:${i})$`), "");
35
+ }
36
+ function $(t) {
37
+ return t.kind === "group" ? "" : t.kind === "catchAll" ? "*" : t.kind === "dynamic" ? ":param" : A(t);
38
+ }
39
+ function g(t, s) {
40
+ const o = f(t, s), c = m(o), a = c.map($).filter(Boolean), n = [
41
+ a.length > 0 ? `/${a.join("/")}` : "/"
42
+ ], e = c[c.length - 1];
43
+ if ((e == null ? void 0 : e.kind) === "catchAll" && e.optional) {
44
+ const r = a.slice(0, -1);
45
+ n.push(r.length > 0 ? `/${r.join("/")}` : "/");
46
+ }
47
+ return n;
48
+ }
49
+ function w(t) {
50
+ return /(^|\/)(:param|\*)($|\/)/.test(t) ? "pattern" : "literal";
51
+ }
52
+ function j(t, s, o) {
53
+ const c = t.flatMap((n) => {
54
+ const e = f(n, o), r = m(e), i = r[r.length - 1];
55
+ if ((i == null ? void 0 : i.kind) !== "catchAll")
56
+ return [];
57
+ const l = r.map($).filter(Boolean), u = l.length > 0 ? `/${l.join("/")}` : "/", p = l.slice(0, -1), k = p.length > 0 ? `/${p.join("/")}` : "/";
58
+ return [{ filePath: n, paths: /* @__PURE__ */ new Set([u, k]) }];
59
+ }), a = /* @__PURE__ */ new Map();
60
+ for (const n of s)
61
+ for (const e of g(n, o))
62
+ for (const r of c)
63
+ if (r.paths.has(e)) {
64
+ const i = `${e}\0${r.filePath}\0${n}`;
65
+ a.set(i, {
66
+ routePath: e,
67
+ pageFile: r.filePath,
68
+ notFoundFile: n
69
+ });
70
+ }
71
+ if (a.size > 0)
72
+ throw new Error(
73
+ [...a.values()].sort((n, e) => n.routePath.localeCompare(e.routePath)).map(
74
+ ({ routePath: n, pageFile: e, notFoundFile: r }) => `Conflicting fallback routes "${n}" detected:
75
+ catch-all page: ${e}
76
+ not-found file: ${r}
77
+ A not-found route and a catch-all page cannot coexist at the same route level.`
78
+ ).join(`
79
+
80
+ `)
81
+ );
82
+ }
83
+ function d(t, s) {
84
+ const o = /* @__PURE__ */ new Map();
85
+ for (const a of t)
86
+ for (const n of g(a, s)) {
87
+ const e = o.get(n) ?? [];
88
+ e.push({ kind: w(n), filePath: a }), o.set(n, e);
89
+ }
90
+ const c = [...o.entries()].filter(([, a]) => a.length > 1).sort(([a], [n]) => a.localeCompare(n));
91
+ if (c.length > 0)
92
+ throw new Error(
93
+ c.map(
94
+ ([a, n]) => `${n[0].kind === "pattern" ? "Ambiguous route pattern" : "Duplicate route"} "${a}" detected:
95
+ ${n.sort((e, r) => e.filePath.localeCompare(r.filePath)).map((e) => ` ${e.filePath}`).join(`
96
+ `)}`
97
+ ).join(`
98
+
99
+ `)
100
+ );
101
+ }
102
+ function E(t, s, o) {
103
+ d(t, o), d(s, o), j(t, s, o);
104
+ }
105
+ function h(t, s, o, c, a) {
106
+ const n = f(s, a), e = n ? n.split("/") : [];
107
+ let r = t, i = "";
108
+ e.forEach((l, u) => {
109
+ i = i ? `${i}/${l}` : l, r.children.has(l) || r.children.set(l, {
110
+ segment: l,
111
+ fullPath: i,
112
+ children: /* @__PURE__ */ new Map()
113
+ }), r = r.children.get(l), u === e.length - 1 && (r[c] = o);
114
+ }), e.length === 0 && (t[c] = o);
115
+ }
116
+ function b(t, s, o, c) {
117
+ E(
118
+ Object.keys(t),
119
+ Object.keys(o),
120
+ c
121
+ );
122
+ const a = {
123
+ segment: "",
124
+ fullPath: "",
125
+ children: /* @__PURE__ */ new Map()
126
+ };
127
+ return Object.entries(s).forEach(([n, e]) => {
128
+ h(a, n, e.default, "layout", c);
129
+ }), Object.entries(t).forEach(([n, e]) => {
130
+ h(a, n, e, "page", c);
131
+ }), Object.entries(o).forEach(([n, e]) => {
132
+ h(a, n, e, "notFound", c);
133
+ }), a;
134
+ }
135
+ export {
136
+ d as a,
137
+ b,
138
+ E as c,
139
+ f as e,
140
+ R as n,
141
+ m as p,
142
+ A as r
143
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-vite-router",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "Next.js-like file-based routing for React + Vite",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,64 +0,0 @@
1
- function p(t) {
2
- return /^\([^)]+\)$/.test(t) ? "" : /^\[\[\.\.\.(.+)\]\]$/.test(t) || /^\[\.\.\.(.+)\]$/.test(t) ? "*" : /^\[([^\]]+)\]$/.test(t) ? t.replace(/^\[([^\]]+)\]$/, ":$1") : t;
3
- }
4
- function h(t, { pagesDir: c, pageFile: a = "page.tsx", layoutFile: o = "layout.tsx", notFoundFile: r = "not-found.tsx" } = {}) {
5
- const n = t.replace(/\\/g, "/"), e = c == null ? void 0 : c.replace(/\\/g, "/").replace(/^\/|\/$/g, ""), l = [a, o, r].map((u) => u.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
6
- return n.replace(
7
- e ? new RegExp(`^.*(?:^|/)${e}/`) : /^.*(?:^|\/)(?:app|pages)\//,
8
- ""
9
- ).replace(/\\/g, "/").replace(new RegExp(`/?(?:${l})$`), "");
10
- }
11
- function $(t, c) {
12
- const a = h(t, c), o = a ? a.split("/").map(p).filter(Boolean) : [];
13
- return o.length > 0 ? `/${o.join("/")}` : "/";
14
- }
15
- function d(t, c) {
16
- const a = /* @__PURE__ */ new Map();
17
- for (const r of t) {
18
- const n = $(r, c), e = a.get(n) ?? [];
19
- e.push(r), a.set(n, e);
20
- }
21
- const o = [...a.entries()].filter(([, r]) => r.length > 1).sort(([r], [n]) => r.localeCompare(n));
22
- if (o.length > 0)
23
- throw new Error(
24
- o.map(
25
- ([r, n]) => `Duplicate route "${r}" detected:
26
- ${n.sort((e, l) => e.localeCompare(l)).map((e) => ` ${e}`).join(`
27
- `)}`
28
- ).join(`
29
-
30
- `)
31
- );
32
- }
33
- function i(t, c, a, o, r) {
34
- const n = h(c, r), e = n ? n.split("/") : [];
35
- let l = t, u = "";
36
- e.forEach((s, f) => {
37
- u = u ? `${u}/${s}` : s, l.children.has(s) || l.children.set(s, {
38
- segment: s,
39
- fullPath: u,
40
- children: /* @__PURE__ */ new Map()
41
- }), l = l.children.get(s), f === e.length - 1 && (l[o] = a);
42
- }), e.length === 0 && (t[o] = a);
43
- }
44
- function P(t, c, a, o) {
45
- d(Object.keys(t), o);
46
- const r = {
47
- segment: "",
48
- fullPath: "",
49
- children: /* @__PURE__ */ new Map()
50
- };
51
- return Object.entries(c).forEach(([n, e]) => {
52
- i(r, n, e.default, "layout", o);
53
- }), Object.entries(t).forEach(([n, e]) => {
54
- i(r, n, e, "page", o);
55
- }), Object.entries(a).forEach(([n, e]) => {
56
- i(r, n, e, "notFound", o);
57
- }), r;
58
- }
59
- export {
60
- d as a,
61
- P as b,
62
- h as e,
63
- p as n
64
- };