next-vite-router 1.1.1 → 1.2.1

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,6 +6,25 @@ 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
+
9
28
  ## Start here
10
29
 
11
30
  ```bash
@@ -83,16 +102,19 @@ npm run dev
83
102
 
84
103
  ## Use React Router directly
85
104
 
86
- `Router` is only a convenience wrapper around
87
- `useRoutes(generateRoutes())`. You can keep using React Router APIs:
105
+ `Router` is only a convenience wrapper around `useRoutes(routes)`. Routes are
106
+ generated lazily on the first render, so calls to `configureRouter` and
107
+ `registerMiddleware` made during application setup are applied before route
108
+ elements are created. You can keep using React Router APIs:
88
109
 
89
110
  ```tsx
90
111
  import {
91
112
  Link,
92
113
  generateRoutes,
93
114
  useRoutes,
115
+ useParams,
94
116
  } from "virtual:next-vite-router";
95
- import { useNavigate, useParams } from "react-router-dom";
117
+ import { useNavigate } from "react-router-dom";
96
118
 
97
119
  const routes = generateRoutes();
98
120
 
@@ -107,9 +129,10 @@ export default function App() {
107
129
  }
108
130
  ```
109
131
 
110
- Use `useNavigate`, `useParams`, loaders, actions, and other React Router APIs
111
- from `react-router-dom`. The package intentionally does not create competing
112
- navigation or data-loading APIs.
132
+ Use `useNavigate`, loaders, actions, and other React Router APIs from
133
+ `react-router-dom`. The package `useParams` hook is also available from the
134
+ virtual module and maps catch-all names such as `slug`. The package does not
135
+ create competing navigation or data-loading APIs.
113
136
 
114
137
  ## File conventions
115
138
 
@@ -119,10 +142,19 @@ navigation or data-loading APIs.
119
142
  | `layout.tsx` | Nested layout rendered with React Router's `Outlet` |
120
143
  | `not-found.tsx` | `*` route for that subtree |
121
144
  | `[id]` | Dynamic segment `:id` |
122
- | `[...slug]` | Catch-all `*` segment |
123
- | `[[...slug]]` | Optional catch-all `*` segment |
145
+ | `[...slug]` | Catch-all `*` segment; package `useParams` exposes it as `slug` |
146
+ | `[[...slug]]` | Optional catch-all `*` segment; package `useParams` exposes it as `slug` |
124
147
  | `(admin)` | Route group omitted from the URL |
125
148
 
149
+ 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.
153
+
154
+ The Vite plugin accepts custom `pageFile`, `layoutFile`, and `notFoundFile`
155
+ names. `createRouteGenerator` accepts the same options, plus `pagesDir`, as
156
+ its optional fourth argument when custom glob paths or extensions are used.
157
+
126
158
  Layouts are the recommended way to add route-scoped navigation, providers, or
127
159
  authorization:
128
160
 
@@ -146,6 +178,10 @@ export default function DashboardLayout() {
146
178
  }
147
179
  ```
148
180
 
181
+ The Vite plugin is a Node/Vite integration and should be imported from
182
+ `next-vite-router/plugin`; the main `next-vite-router` entry remains safe for
183
+ browser application code.
184
+
149
185
  ## Type-safe paths
150
186
 
151
187
  The Vite plugin generates `virtual-next-vite-router.d.ts` automatically:
@@ -167,7 +203,8 @@ No manual declaration for the virtual module is required.
167
203
 
168
204
  Pages and `not-found.tsx` files use Vite's non-eager `import.meta.glob`, so
169
205
  Vite can emit separate chunks for pages. Layouts are eager because they wrap
170
- the generated route tree. Lazy loading is enabled by default:
206
+ the generated route tree; changing a layout can therefore cause a full
207
+ development reload. Lazy loading is enabled by default:
171
208
 
172
209
  ```tsx
173
210
  import { configureRouter } from "next-vite-router";
@@ -201,12 +238,13 @@ replace React Router.
201
238
  For an interactive setup:
202
239
 
203
240
  ```bash
204
- npx create-next-vite-router init
241
+ npx next-vite-router init
205
242
  ```
206
243
 
207
244
  The CLI creates a Vite React project, installs the package and
208
245
  `react-router-dom@^7.0.0`, configures the plugin, and adds an example route
209
- tree.
246
+ tree. The CLI is published as part of `next-vite-router`; `create-next-vite-router`
247
+ is its executable name, not a separate npm package.
210
248
 
211
249
  ## Compatibility
212
250
 
package/dist/cli.cjs CHANGED
@@ -218,10 +218,19 @@ function createVirtualDTS(projectDir, useSrc) {
218
218
 
219
219
  export const generateRoutes: () => RouteObject[];
220
220
  export function Router(): ReactElement | null;
221
+ export function useParams<T extends Record<string, string | undefined> = Record<string, string | undefined>>(): T;
221
222
  export { Link, useRoutes } from 'react-router-dom';
222
223
  }
223
224
  `;
224
225
  fs.writeFileSync(filePath, content, "utf-8");
226
+ const gitignorePath = path.join(projectDir, ".gitignore");
227
+ const entry = useSrc ? "src/virtual-next-vite-router.d.ts" : "virtual-next-vite-router.d.ts";
228
+ const currentGitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf-8") : "";
229
+ if (!currentGitignore.split(/\r?\n/).includes(entry)) {
230
+ const prefix = currentGitignore && !currentGitignore.endsWith("\n") ? "\n" : "";
231
+ fs.writeFileSync(gitignorePath, `${currentGitignore}${prefix}${entry}
232
+ `, "utf-8");
233
+ }
225
234
  }
226
235
  function createMainTSX(projectDir, useSrc) {
227
236
  const targetDir = useSrc ? path.join(projectDir, "src") : projectDir;
@@ -535,7 +544,7 @@ const init = new commander.Command().name("init").description(
535
544
  Project "${projectName}" is ready!
536
545
  `);
537
546
  });
538
- const version = "1.1.1";
547
+ const version = "1.2.1";
539
548
  const pkg = {
540
549
  version
541
550
  };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,14 @@
1
+ import { ComponentType } from 'react';
1
2
  import { default as default_2 } from 'react';
2
- import { Plugin as Plugin_2 } from 'vite';
3
3
  import { ReactElement } from 'react';
4
4
  import { RouteObject } from 'react-router-dom';
5
5
 
6
- export declare function buildRouteTree(pages: Record<string, () => Promise<any>>, layouts: Record<string, any>, notFounds: Record<string, () => Promise<any>>): RouteNode;
6
+ export declare function buildRouteTree(pages: Record<string, () => Promise<any>>, layouts: Record<string, any>, notFounds: Record<string, () => Promise<any>>, fileOptions?: RouteFileOptions): RouteNode;
7
+
8
+ export declare function CatchAllParamProvider({ name, children, }: {
9
+ name: string;
10
+ children: default_2.ReactElement;
11
+ }): default_2.ReactElement;
7
12
 
8
13
  export declare function clearMiddlewares(): void;
9
14
 
@@ -34,23 +39,14 @@ export declare function createLazyElement(loader: () => Promise<any>, cacheKey:
34
39
  * // Then use it
35
40
  * const routes = generateCustomRoutes();
36
41
  */
37
- export declare function createRouteGenerator(pages: Record<string, () => Promise<any>>, layouts: Record<string, any>, notFounds: Record<string, () => Promise<any>>): () => RouteObject[];
42
+ export declare function createRouteGenerator(pages: Record<string, () => Promise<any>>, layouts: Record<string, any>, notFounds: Record<string, () => Promise<any>>, fileOptions?: RouteFileOptions): () => RouteObject[];
38
43
 
39
44
  /**
40
45
  * Extract the route path from a file path
41
46
  * @example "../../app/blog/[id]/page.tsx" -> "blog/[id]"
42
47
  * @example "../../app/page.tsx" -> ""
43
48
  */
44
- export declare function extractRoutePath(filePath: string): string;
45
-
46
- export declare function nextViteRouter(options?: NextViteRouterPluginOptions): Plugin_2;
47
-
48
- export declare type NextViteRouterPluginOptions = {
49
- pagesDir?: string;
50
- pageFile?: string;
51
- layoutFile?: string;
52
- notFoundFile?: string;
53
- };
49
+ export declare function extractRoutePath(filePath: string, { pagesDir, pageFile, layoutFile, notFoundFile }?: RouteFileOptions): string;
54
50
 
55
51
  /**
56
52
  * Normalize a Next.js segment to a React Router segment
@@ -62,14 +58,25 @@ export declare function normalizeSegment(segment: string): string;
62
58
 
63
59
  export declare function registerMiddleware(pattern: RegExp, middleware: RouteMiddleware): void;
64
60
 
61
+ export declare type RouteFileOptions = {
62
+ pagesDir?: string;
63
+ pageFile?: string;
64
+ layoutFile?: string;
65
+ notFoundFile?: string;
66
+ };
67
+
65
68
  export declare type RouteMiddleware = (element: ReactElement) => ReactElement;
66
69
 
70
+ export declare type RouteModuleLoader = () => Promise<{
71
+ default: ComponentType;
72
+ }>;
73
+
67
74
  export declare type RouteNode = {
68
75
  segment: string;
69
76
  fullPath: string;
70
- page?: any;
71
- layout?: any;
72
- notFound?: any;
77
+ page?: RouteModuleLoader;
78
+ layout?: ComponentType;
79
+ notFound?: RouteModuleLoader;
73
80
  children: Map<string, RouteNode>;
74
81
  };
75
82
 
@@ -81,4 +88,6 @@ export declare type RouterConfig = {
81
88
 
82
89
  export declare function treeToRoutes(node: RouteNode, isRoot?: boolean, currentPath?: string): RouteObject[];
83
90
 
91
+ export declare function useParams<T extends Record<string, string | undefined> = Record<string, string | undefined>>(): T;
92
+
84
93
  export { }
package/dist/index.js CHANGED
@@ -1,156 +1,208 @@
1
- import { e as C, n as f } from "./index.plugin--ecyiAem.js";
2
- import { a as G } from "./index.plugin--ecyiAem.js";
3
- import i, { Suspense as y, lazy as F } from "react";
4
- const L = {
1
+ import { e as z, n as g } from "./path-BodMUTCM.js";
2
+ import i, { Suspense as C, lazy as x, createContext as L, useContext as M } from "react";
3
+ import { useParams as A } from "react-router-dom";
4
+ const k = {
5
5
  enableLazyLoading: !0
6
6
  };
7
- let d = { ...L };
8
- function x(e) {
7
+ let d = { ...k };
8
+ function B(e) {
9
9
  d = { ...d, ...e };
10
10
  }
11
- function $() {
11
+ function S() {
12
12
  return d;
13
13
  }
14
14
  let E = [];
15
- function T(e, r) {
16
- E.push({ pattern: e, middleware: r });
15
+ function H(e, t) {
16
+ E.push({ pattern: e, middleware: t });
17
17
  }
18
- function j() {
18
+ function J() {
19
19
  E = [];
20
20
  }
21
- function z(e, r) {
22
- let n = r;
23
- for (const t of E)
24
- t.pattern.test(e) && (n = t.middleware(n));
21
+ function p(e, t) {
22
+ let n = t;
23
+ for (const r of E)
24
+ r.pattern.lastIndex = 0, r.pattern.test(e) && (n = r.middleware(n)), r.pattern.lastIndex = 0;
25
25
  return n;
26
26
  }
27
- const M = () => i.createElement("div"), b = /* @__PURE__ */ new Map();
28
- function R(e, r, n) {
29
- const t = $();
30
- if (t.enableLazyLoading === !1) {
31
- const c = i.lazy(e), u = i.createElement(
32
- y,
27
+ const T = () => i.createElement("div"), y = /* @__PURE__ */ new Map();
28
+ function R(e, t, n) {
29
+ const r = S();
30
+ if (r.enableLazyLoading === !1) {
31
+ const u = i.lazy(e), s = i.createElement(
32
+ C,
33
33
  { fallback: null },
34
34
  // No loading fallback when disabled
35
- i.createElement(c)
35
+ i.createElement(u)
36
36
  );
37
- return n ? z(n, u) : u;
37
+ return n ? p(n, s) : s;
38
38
  }
39
- let l = b.get(r);
40
- l || (l = F(e), b.set(r, l));
41
- const o = t.loadingFallback || M, a = i.createElement(
42
- y,
43
- { fallback: i.createElement(o) },
44
- i.createElement(l)
39
+ let a = y.get(t);
40
+ a || (a = x(e), y.set(t, a));
41
+ const l = r.loadingFallback || T, o = i.createElement(
42
+ C,
43
+ { fallback: i.createElement(l) },
44
+ i.createElement(a)
45
45
  );
46
- return n ? z(n, a) : a;
47
- }
48
- function p(e, r, n, t) {
49
- const l = C(r), o = l ? l.split("/") : [];
50
- let a = e, c = "";
51
- o.forEach((u, h) => {
52
- c = c ? `${c}/${u}` : u, a.children.has(u) || a.children.set(u, {
53
- segment: u,
54
- fullPath: c,
46
+ return n ? p(n, o) : o;
47
+ }
48
+ function m(e, t, n, r, a) {
49
+ const l = z(t, a), o = l ? l.split("/") : [];
50
+ let u = e, s = "";
51
+ o.forEach((c, h) => {
52
+ s = s ? `${s}/${c}` : c, u.children.has(c) || u.children.set(c, {
53
+ segment: c,
54
+ fullPath: s,
55
55
  children: /* @__PURE__ */ new Map()
56
- }), a = a.children.get(u), h === o.length - 1 && (a[t] = n);
57
- }), o.length === 0 && (e[t] = n);
56
+ }), u = u.children.get(c), h === o.length - 1 && (u[r] = n);
57
+ }), o.length === 0 && (e[r] = n);
58
58
  }
59
- function S(e, r, n) {
60
- const t = {
59
+ function j(e, t, n, r) {
60
+ const a = {
61
61
  segment: "",
62
62
  fullPath: "",
63
63
  children: /* @__PURE__ */ new Map()
64
64
  };
65
- return Object.entries(r).forEach(([l, o]) => {
66
- p(t, l, o.default, "layout");
65
+ return Object.entries(t).forEach(([l, o]) => {
66
+ m(a, l, o.default, "layout", r);
67
67
  }), Object.entries(e).forEach(([l, o]) => {
68
- p(t, l, o, "page");
68
+ m(a, l, o, "page", r);
69
69
  }), Object.entries(n).forEach(([l, o]) => {
70
- p(t, l, o, "notFound");
71
- }), t;
72
- }
73
- function m(e, r, n = !1) {
74
- const t = R(
75
- async () => ({ default: (await e.page()).default }),
76
- `page:${e.fullPath || r}`,
77
- r
70
+ m(a, l, o, "notFound", r);
71
+ }), a;
72
+ }
73
+ const F = L(void 0);
74
+ function N({
75
+ name: e,
76
+ children: t
77
+ }) {
78
+ return i.createElement(
79
+ F.Provider,
80
+ { value: e },
81
+ t
78
82
  );
79
- return n ? { index: !0, element: t } : { path: f(e.segment), element: t };
80
83
  }
81
- function g(e) {
84
+ function D() {
85
+ return M(F);
86
+ }
87
+ function P(e, t) {
88
+ return p(e, i.createElement(t));
89
+ }
90
+ function I(e) {
91
+ return /^\[\[\.\.\..+\]\]$/.test(e);
92
+ }
93
+ function f(e, t, n = !1) {
94
+ const r = e.page;
95
+ if (!r)
96
+ throw new Error(`Route node "${e.fullPath}" has no page loader`);
97
+ const a = R(
98
+ async () => ({ default: (await r()).default }),
99
+ `page:${e.fullPath || t}`,
100
+ t
101
+ ), l = e.segment.match(/^\[\[?\.\.\.(.+?)\]\]?$/), o = l ? i.createElement(N, {
102
+ name: l[1],
103
+ children: a
104
+ }) : a;
105
+ return n ? { index: !0, element: o } : { path: g(e.segment), element: o };
106
+ }
107
+ function $(e) {
108
+ const t = e.notFound;
109
+ if (!t)
110
+ throw new Error(`Route node "${e.fullPath}" has no not-found loader`);
111
+ const n = e.fullPath ? `/${e.fullPath}` : "/";
82
112
  return {
83
113
  path: "*",
84
114
  element: R(
85
- async () => ({ default: (await e.notFound()).default }),
86
- `notfound:${e.fullPath}`
115
+ async () => ({ default: (await t()).default }),
116
+ `notfound:${e.fullPath}`,
117
+ n
87
118
  )
88
119
  };
89
120
  }
90
- function s(e, r) {
121
+ function b(e, t) {
91
122
  const n = [];
92
- return [...e.children.entries()].sort(([t], [l]) => t.localeCompare(l)).forEach(([t, l]) => {
93
- const o = f(t), a = o ? r ? `${r}/${o}` : `/${o}` : r;
94
- n.push(...w(l, !1, a));
123
+ return [...e.children.entries()].sort(([r], [a]) => r.localeCompare(a)).forEach(([r, a]) => {
124
+ const l = g(r), o = l ? t ? `${t}/${l}` : `/${l}` : t;
125
+ n.push(...v(a, !1, o));
95
126
  }), n;
96
127
  }
97
- function w(e, r = !1, n = "") {
98
- const t = [];
99
- if (r && e.layout) {
100
- const a = [];
101
- return e.page && a.push(m(e, "/", !0)), a.push(...s(e, "")), e.notFound && a.push(g(e)), t.push({
128
+ function w(e, t) {
129
+ const n = [];
130
+ return e.page && n.push(f(e, t || "/", !0)), n.push(...b(e, t)), e.notFound && n.push($(e)), n;
131
+ }
132
+ function v(e, t = !1, n = "") {
133
+ const r = [];
134
+ if (t) {
135
+ const c = w(e, "");
136
+ return e.layout ? (r.push({
137
+ path: "/",
138
+ element: P("/", e.layout),
139
+ children: c
140
+ }), r) : e.page ? (r.push({
102
141
  path: "/",
103
- element: i.createElement(e.layout),
104
- children: a
105
- }), t;
142
+ element: f(e, "/", !1).element,
143
+ children: c.slice(1).length > 0 ? c.slice(1) : void 0
144
+ }), r) : e.notFound ? (r.push({
145
+ path: "/",
146
+ children: c
147
+ }), r) : c;
106
148
  }
149
+ const a = g(e.segment), l = !a, o = l ? n : n + "/" + a, u = b(e, o), s = e.notFound ? [$(e)] : [];
107
150
  if (e.layout) {
108
- const a = f(e.segment);
109
- if (!a)
110
- return s(e, n);
111
- const c = n + "/" + a, u = [];
112
- return e.page && u.push(m(e, c, !0)), u.push(...s(e, c)), e.notFound && u.push(g(e)), t.push({
151
+ const c = w(e, o);
152
+ return [{
153
+ ...l ? {} : { path: a },
154
+ element: P(o, e.layout),
155
+ children: c.length > 0 ? c : void 0
156
+ }];
157
+ }
158
+ if (e.page && !l) {
159
+ if (I(e.segment)) {
160
+ const c = f(e, n, !0), h = {
161
+ path: a,
162
+ element: f(e, o).element,
163
+ children: [...u, ...s].length > 0 ? [...u, ...s] : void 0
164
+ };
165
+ return [c, h];
166
+ }
167
+ return [{
113
168
  path: a,
114
- element: i.createElement(e.layout),
115
- children: u.length > 0 ? u : void 0
116
- }), t;
169
+ element: f(e, o).element,
170
+ children: [...u, ...s].length > 0 ? [...u, ...s] : void 0
171
+ }];
117
172
  }
118
- if (e.page) {
119
- const a = f(e.segment);
120
- if (!a)
121
- return r ? (t.push({
122
- path: "/",
123
- element: m(e, "/", !0).element,
124
- children: s(e, n).length > 0 ? s(e, n) : void 0
125
- }), e.notFound && t.push(g(e)), t) : s(e, n);
126
- const c = n + "/" + a, u = s(e, c), h = m(e, c, !1);
127
- return t.push({
128
- path: h.path,
129
- element: h.element,
130
- children: u.length > 0 ? u : void 0
131
- }), t;
173
+ if (e.page || e.notFound || !l) {
174
+ const c = [
175
+ ...e.page ? [f(e, o, !0)] : [],
176
+ ...u,
177
+ ...s
178
+ ];
179
+ return [{
180
+ ...l ? {} : { path: a },
181
+ children: c.length > 0 ? c : void 0
182
+ }];
132
183
  }
133
- const l = f(e.segment), o = s(e, n);
134
- return l ? [{
135
- path: l,
136
- children: o.length > 0 ? o : void 0
137
- }] : o;
184
+ return u;
138
185
  }
139
- function O(e, r, n) {
186
+ function Q(e, t, n, r) {
140
187
  return () => {
141
- const t = S(e, r, n);
142
- return w(t, !0);
188
+ const a = j(e, t, n, r);
189
+ return v(a, !0);
143
190
  };
144
191
  }
192
+ function U() {
193
+ const e = A(), t = D();
194
+ return typeof t == "string" && e["*"] !== void 0 ? { ...e, [t]: e["*"] } : e;
195
+ }
145
196
  export {
146
- S as buildRouteTree,
147
- j as clearMiddlewares,
148
- x as configureRouter,
197
+ N as CatchAllParamProvider,
198
+ j as buildRouteTree,
199
+ J as clearMiddlewares,
200
+ B as configureRouter,
149
201
  R as createLazyElement,
150
- O as createRouteGenerator,
151
- C as extractRoutePath,
152
- G as nextViteRouter,
153
- f as normalizeSegment,
154
- T as registerMiddleware,
155
- w as treeToRoutes
202
+ Q as createRouteGenerator,
203
+ z as extractRoutePath,
204
+ g as normalizeSegment,
205
+ H as registerMiddleware,
206
+ v as treeToRoutes,
207
+ U as useParams
156
208
  };
@@ -0,0 +1,14 @@
1
+ function $(e) {
2
+ return /^\([^)]+\)$/.test(e) ? "" : /^\[\[\.\.\.(.+)\]\]$/.test(e) || /^\[\.\.\.(.+)\]$/.test(e) ? "*" : /^\[([^\]]+)\]$/.test(e) ? e.replace(/^\[([^\]]+)\]$/, ":$1") : e;
3
+ }
4
+ function u(e, { pagesDir: t, pageFile: a = "page.tsx", layoutFile: n = "layout.tsx", notFoundFile: c = "not-found.tsx" } = {}) {
5
+ const o = e.replace(/\\/g, "/"), r = t == null ? void 0 : t.replace(/\\/g, "/").replace(/^\/|\/$/g, ""), l = [a, n, c].map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
6
+ return o.replace(
7
+ r ? new RegExp(`^.*(?:^|/)${r}/`) : /^.*(?:^|\/)(?:app|pages)\//,
8
+ ""
9
+ ).replace(/\\/g, "/").replace(new RegExp(`/?(?:${l})$`), "");
10
+ }
11
+ export {
12
+ u as e,
13
+ $ as n
14
+ };
package/dist/plugin.js CHANGED
@@ -1,6 +1,145 @@
1
- import "node:fs";
2
- import "node:path";
3
- import { a as i } from "./index.plugin--ecyiAem.js";
1
+ import d from "node:fs";
2
+ import u from "node:path";
3
+ import { n as R } from "./path-BodMUTCM.js";
4
+ function g(r) {
5
+ const e = r.split(u.sep).filter(Boolean).map(R).filter(Boolean);
6
+ return e.length > 0 ? `/${e.join("/")}` : "/";
7
+ }
8
+ function v(r, e) {
9
+ const i = /* @__PURE__ */ new Map();
10
+ function a(n) {
11
+ for (const s of d.readdirSync(n, { withFileTypes: !0 })) {
12
+ const m = u.join(n, s.name);
13
+ if (s.isDirectory())
14
+ a(m);
15
+ else if (s.isFile() && s.name === e) {
16
+ const o = u.relative(r, n), f = o.split(u.sep).filter(Boolean).map((c) => {
17
+ const t = c.match(/^\[\[\.\.\.(.+)\]\]$/);
18
+ if (t)
19
+ return { name: t[1], optional: !0 };
20
+ const p = c.match(/^\[\.\.\.(.+)\]$/);
21
+ return p ? { name: p[1], optional: !1 } : void 0;
22
+ }).filter((c) => !!c);
23
+ i.set(g(o), {
24
+ path: g(o),
25
+ params: f
26
+ });
27
+ }
28
+ }
29
+ }
30
+ return d.existsSync(r) && a(r), [...i.values()].sort((n, s) => n.path.localeCompare(s.path));
31
+ }
32
+ function x(r) {
33
+ const e = r.match(/^\/(?:\[\.\.\.|:)([^/\]]+)\]?$/);
34
+ return e == null ? void 0 : e[1];
35
+ }
36
+ function P(r) {
37
+ const e = r.map(({ path: a }) => ` | "${a}"`).join(`
38
+ `), i = r.map(({ path: a, params: n }) => {
39
+ const s = a.split("/").map((o) => x(`/${o}`)).filter((o) => !!o), m = s.length > 0 || n.length > 0 ? [
40
+ ...s.map((o) => ` ${o}: string;`),
41
+ ...n.map(({ name: o, optional: l }) => ` ${o}${l ? "?" : ""}: string;`)
42
+ ].join(`
43
+ `) : " [key: string]: never;";
44
+ return ` "${a}": {
45
+ ${m}
46
+ };`;
47
+ }).join(`
48
+ `);
49
+ return `declare module 'virtual:next-vite-router' {
50
+ import type { ReactElement } from 'react';
51
+ import type { RouteObject } from 'react-router-dom';
52
+
53
+ export type RoutePath =
54
+ ${e || ' | "/"'};
55
+
56
+ export type RouteParams<P extends RoutePath> = P extends keyof RoutePathParams
57
+ ? RoutePathParams[P]
58
+ : never;
59
+
60
+ export interface RoutePathParams {
61
+ ${i}
62
+ }
63
+
64
+ export const generateRoutes: () => RouteObject[];
65
+ export function Router(): ReactElement | null;
66
+ export function useParams<T extends Record<string, string | undefined> = Record<string, string | undefined>>(): T;
67
+ export { Link, useRoutes } from 'react-router-dom';
68
+ }
69
+ `;
70
+ }
71
+ function k(r = {}) {
72
+ const {
73
+ pagesDir: e = "src/app",
74
+ pageFile: i = "page.tsx",
75
+ layoutFile: a = "layout.tsx",
76
+ notFoundFile: n = "not-found.tsx"
77
+ } = r, s = { pagesDir: e, pageFile: i, layoutFile: a, notFoundFile: n }, m = "virtual:next-vite-router", o = "\0" + m;
78
+ let l = "", f = "";
79
+ function c() {
80
+ const t = P(v(l, i));
81
+ d.writeFileSync(f, t, "utf-8");
82
+ }
83
+ return {
84
+ name: "next-vite-router",
85
+ configResolved(t) {
86
+ l = u.resolve(t.root, e), f = u.join(u.dirname(l), "virtual-next-vite-router.d.ts"), c();
87
+ },
88
+ configureServer(t) {
89
+ const p = (h) => {
90
+ if (u.relative(l, h).startsWith("..") || ![i, a, n].includes(u.basename(h)))
91
+ return;
92
+ c();
93
+ const y = t.moduleGraph.getModuleById(o);
94
+ y && (t.moduleGraph.invalidateModule(y), t.ws.send({ type: "full-reload", path: "*" }));
95
+ };
96
+ t.watcher.on("add", p), t.watcher.on("unlink", p);
97
+ },
98
+ resolveId(t) {
99
+ if (t === m)
100
+ return o;
101
+ },
102
+ load(t) {
103
+ if (t === o)
104
+ return `
105
+ import { buildRouteTree, treeToRoutes, normalizeSegment, createLazyElement, useParams } from 'next-vite-router';
106
+ import { Link, useRoutes } from 'react-router-dom';
107
+
108
+ const pagesGlob = import.meta.glob('/${e}/**/${i}');
109
+ const layoutsGlob = import.meta.glob('/${e}/**/${a}', { eager: true });
110
+ const notFoundsGlob = import.meta.glob('/${e}/**/${n}');
111
+
112
+ const transformPath = (path) => path.replace('/${e}/', '');
113
+
114
+ const pages = Object.fromEntries(
115
+ Object.entries(pagesGlob).map(([key, value]) => [transformPath(key), value])
116
+ );
117
+ const layouts = Object.fromEntries(
118
+ Object.entries(layoutsGlob).map(([key, value]) => [transformPath(key), value])
119
+ );
120
+ const notFounds = Object.fromEntries(
121
+ Object.entries(notFoundsGlob).map(([key, value]) => [transformPath(key), value])
122
+ );
123
+
124
+ export function generateRoutes() {
125
+ const tree = buildRouteTree(pages, layouts, notFounds, ${JSON.stringify(s)});
126
+ const routes = treeToRoutes(tree, true);
127
+ return routes;
128
+ }
129
+
130
+ export function Router() {
131
+ routes ??= generateRoutes();
132
+ return useRoutes(routes);
133
+ }
134
+
135
+ let routes;
136
+
137
+ export { Link, useRoutes };
138
+ export { useParams };
139
+ `;
140
+ }
141
+ };
142
+ }
4
143
  export {
5
- i as nextViteRouter
144
+ k as nextViteRouter
6
145
  };
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "next-vite-router",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "Next.js-like file-based routing for React + Vite",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
+ "sideEffects": false,
8
9
  "bin": {
9
10
  "create-next-vite-router": "./dist/cli.cjs"
10
11
  },
@@ -65,6 +66,7 @@
65
66
  "test:local": "node test/test-package.js",
66
67
  "test:routes": "node test/test-route-types.js",
67
68
  "test:generator": "node --test test/routes.test.js",
69
+ "test:params": "node --test test/params.test.js",
68
70
  "test:compatibility": "node test/test-compatibility.js",
69
71
  "preview": "pnpm pack --dry-run",
70
72
  "cli": "cd test && node ../dist/cli.cjs"
@@ -1,132 +0,0 @@
1
- import p from "node:fs";
2
- import i from "node:path";
3
- function y(e) {
4
- return /^\([^)]+\)$/.test(e) ? "" : /^\[\[\.\.\.(.+)\]\]$/.test(e) || /^\[\.\.\.(.+)\]$/.test(e) ? "*" : /^\[([^\]]+)\]$/.test(e) ? e.replace(/^\[([^\]]+)\]$/, ":$1") : e;
5
- }
6
- function b(e) {
7
- return e.replace(/\\/g, "/").replace("../../app/", "").replace(/\/?(page|layout|not-found)\.tsx$/, "");
8
- }
9
- function R(e) {
10
- const t = e.split(i.sep).filter(Boolean).map(y).filter(Boolean);
11
- return t.length > 0 ? `/${t.join("/")}` : "/";
12
- }
13
- function h(e, t) {
14
- const s = /* @__PURE__ */ new Set();
15
- function n(a) {
16
- for (const u of p.readdirSync(a, { withFileTypes: !0 })) {
17
- const r = i.join(a, u.name);
18
- u.isDirectory() ? n(r) : u.isFile() && u.name === t && s.add(R(i.relative(e, a)));
19
- }
20
- }
21
- return p.existsSync(e) && n(e), [...s].sort();
22
- }
23
- function x(e) {
24
- const t = e.match(/^\/(?:\[\.\.\.|:)([^/\]]+)\]?$/);
25
- return t == null ? void 0 : t[1];
26
- }
27
- function $(e) {
28
- const t = e.map((n) => ` | "${n}"`).join(`
29
- `), s = e.map((n) => {
30
- const a = n.split("/").map((r) => x(`/${r}`)).filter((r) => !!r), u = a.length > 0 ? a.map((r) => ` ${r}: string;`).join(`
31
- `) : " [key: string]: never;";
32
- return ` "${n}": {
33
- ${u}
34
- };`;
35
- }).join(`
36
- `);
37
- return `declare module 'virtual:next-vite-router' {
38
- import type { ReactElement } from 'react';
39
- import type { RouteObject } from 'react-router-dom';
40
-
41
- export type RoutePath =
42
- ${t || ' | "/"'};
43
-
44
- export type RouteParams<P extends RoutePath> = P extends keyof RoutePathParams
45
- ? RoutePathParams[P]
46
- : never;
47
-
48
- export interface RoutePathParams {
49
- ${s}
50
- }
51
-
52
- export const generateRoutes: () => RouteObject[];
53
- export function Router(): ReactElement | null;
54
- export { Link, useRoutes } from 'react-router-dom';
55
- }
56
- `;
57
- }
58
- function g(e = {}) {
59
- const {
60
- pagesDir: t = "src/app",
61
- pageFile: s = "page.tsx",
62
- layoutFile: n = "layout.tsx",
63
- notFoundFile: a = "not-found.tsx"
64
- } = e, u = "virtual:next-vite-router", r = "\0" + u;
65
- let l = "", m = "";
66
- function f() {
67
- const o = $(h(l, s));
68
- p.writeFileSync(m, o, "utf-8");
69
- }
70
- return {
71
- name: "next-vite-router",
72
- configResolved(o) {
73
- l = i.resolve(o.root, t), m = i.join(i.dirname(l), "virtual-next-vite-router.d.ts"), f();
74
- },
75
- resolveId(o) {
76
- if (o === u)
77
- return r;
78
- },
79
- load(o) {
80
- if (o === r)
81
- return `
82
- import { buildRouteTree, treeToRoutes, normalizeSegment, createLazyElement } from 'next-vite-router';
83
- import { Link, useRoutes } from 'react-router-dom';
84
-
85
- const pagesGlob = import.meta.glob('/${t}/**/${s}');
86
- const layoutsGlob = import.meta.glob('/${t}/**/${n}', { eager: true });
87
- const notFoundsGlob = import.meta.glob('/${t}/**/${a}');
88
-
89
- const transformPath = (path) => path.replace('/${t}/', '');
90
-
91
- const pages = Object.fromEntries(
92
- Object.entries(pagesGlob).map(([key, value]) => [transformPath(key), value])
93
- );
94
- const layouts = Object.fromEntries(
95
- Object.entries(layoutsGlob).map(([key, value]) => [transformPath(key), value])
96
- );
97
- const notFounds = Object.fromEntries(
98
- Object.entries(notFoundsGlob).map(([key, value]) => [transformPath(key), value])
99
- );
100
-
101
- export function generateRoutes() {
102
- const tree = buildRouteTree(pages, layouts, notFounds);
103
- const routes = treeToRoutes(tree, true);
104
- return routes;
105
- }
106
-
107
- export function Router() {
108
- return useRoutes(generateRoutes());
109
- }
110
-
111
- export { Link, useRoutes };
112
- `;
113
- },
114
- handleHotUpdate({ file: o, server: c }) {
115
- if (o.includes(t) && (o.endsWith(s) || o.endsWith(n) || o.endsWith(a))) {
116
- f();
117
- const d = c.moduleGraph.getModuleById(
118
- r
119
- );
120
- d && (c.moduleGraph.invalidateModule(d), c.ws.send({
121
- type: "full-reload",
122
- path: "*"
123
- }));
124
- }
125
- }
126
- };
127
- }
128
- export {
129
- g as a,
130
- b as e,
131
- y as n
132
- };