next-vite-router 1.1.0 → 1.2.0

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";
package/dist/cli.cjs CHANGED
@@ -47,7 +47,7 @@ function detectPackageManager() {
47
47
  case "pnpm":
48
48
  return {
49
49
  name: "pnpm",
50
- createProjectCommand: (projectName, template) => `pnpm create vite@latest ${projectName} --template ${template} --no-rolldown`,
50
+ createProjectCommand: (projectName, template) => `pnpm create vite@latest ${projectName} --template ${template} --no-interactive`,
51
51
  addDepsCommand: (deps) => `pnpm add ${deps.join(" ")}`,
52
52
  addDevDepsCommand: (deps) => `pnpm add -D ${deps.join(" ")}`,
53
53
  installCommand: "pnpm install"
@@ -55,7 +55,7 @@ function detectPackageManager() {
55
55
  case "bun":
56
56
  return {
57
57
  name: "bun",
58
- createProjectCommand: (projectName, template) => `bun create vite ${projectName} --template=${template} --no-rolldown`,
58
+ createProjectCommand: (projectName, template) => `bun create vite ${projectName} --template=${template} --no-interactive`,
59
59
  addDepsCommand: (deps) => `bun add ${deps.join(" ")}`,
60
60
  addDevDepsCommand: (deps) => `bun add -d ${deps.join(" ")}`,
61
61
  installCommand: "bun install"
@@ -63,7 +63,7 @@ function detectPackageManager() {
63
63
  case "yarn":
64
64
  return {
65
65
  name: "yarn",
66
- createProjectCommand: (projectName, template) => `yarn create vite ${projectName} --template=${template} --no-rolldown`,
66
+ createProjectCommand: (projectName, template) => `yarn create vite ${projectName} --template=${template} --no-interactive`,
67
67
  addDepsCommand: (deps) => `yarn add ${deps.join(" ")}`,
68
68
  addDevDepsCommand: (deps) => `yarn add -D ${deps.join(" ")}`,
69
69
  installCommand: "yarn install"
@@ -72,7 +72,7 @@ function detectPackageManager() {
72
72
  default:
73
73
  return {
74
74
  name: "npm",
75
- createProjectCommand: (projectName, template) => `npm create vite@latest ${projectName} -- --template ${template} --no-rolldown`,
75
+ createProjectCommand: (projectName, template) => `npm create vite@latest ${projectName} -- --template ${template} --no-interactive`,
76
76
  addDepsCommand: (deps) => `npm install ${deps.join(" ")}`,
77
77
  addDevDepsCommand: (deps) => `npm install -D ${deps.join(" ")}`,
78
78
  installCommand: "npm install"
@@ -126,10 +126,17 @@ const logger = {
126
126
  }
127
127
  };
128
128
  function getViteConfigPath(projectDir) {
129
- const tsPath = path.join(projectDir, "vite.config.ts");
130
- const jsPath = path.join(projectDir, "vite.config.js");
131
- if (fs.existsSync(tsPath)) return tsPath;
132
- if (fs.existsSync(jsPath)) return jsPath;
129
+ for (const fileName of [
130
+ "vite.config.ts",
131
+ "vite.config.mts",
132
+ "vite.config.cts",
133
+ "vite.config.js",
134
+ "vite.config.mjs",
135
+ "vite.config.cjs"
136
+ ]) {
137
+ const configPath = path.join(projectDir, fileName);
138
+ if (fs.existsSync(configPath)) return configPath;
139
+ }
133
140
  return null;
134
141
  }
135
142
  function readViteConfig(projectDir) {
@@ -211,10 +218,19 @@ function createVirtualDTS(projectDir, useSrc) {
211
218
 
212
219
  export const generateRoutes: () => RouteObject[];
213
220
  export function Router(): ReactElement | null;
221
+ export function useParams<T extends Record<string, string | undefined> = Record<string, string | undefined>>(): T;
214
222
  export { Link, useRoutes } from 'react-router-dom';
215
223
  }
216
224
  `;
217
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
+ }
218
234
  }
219
235
  function createMainTSX(projectDir, useSrc) {
220
236
  const targetDir = useSrc ? path.join(projectDir, "src") : projectDir;
@@ -225,19 +241,12 @@ function createMainTSX(projectDir, useSrc) {
225
241
  const content = `import { StrictMode } from 'react';
226
242
  import { createRoot } from 'react-dom/client';
227
243
  import { BrowserRouter } from "react-router-dom";
228
- import { generateRoutes, useRoutes } from 'virtual:next-vite-router';
229
-
230
- const routes = generateRoutes();
231
-
232
- export default function App() {
233
- const element = useRoutes(routes);
234
- return element;
235
- }
244
+ import { Router } from 'virtual:next-vite-router';
236
245
 
237
246
  createRoot(document.getElementById('root')!).render(
238
247
  <StrictMode>
239
248
  <BrowserRouter>
240
- <App />
249
+ <Router />
241
250
  </BrowserRouter>
242
251
  </StrictMode>,
243
252
  );
@@ -463,12 +472,11 @@ const init = new commander.Command().name("init").description(
463
472
  name: "template",
464
473
  message: "Select a template:",
465
474
  choices: [
466
- { title: "React + TypeScript", value: "react-ts" },
475
+ { title: "React + TypeScript (Recommended)", value: "react-ts" },
467
476
  {
468
477
  title: "React + TypeScript (React Compiler)",
469
478
  value: "react-compiler-ts"
470
- },
471
- { title: "React + SWC (Recommended)", value: "react-swc-ts" }
479
+ }
472
480
  ],
473
481
  initial: 0
474
482
  },
@@ -512,13 +520,10 @@ const init = new commander.Command().name("init").description(
512
520
  const useTailwind = tailwindResponse.useTailwind;
513
521
  const createCommand = pm.createProjectCommand(projectName, variant);
514
522
  logger.info(`Creating project "${projectName}" using ${pm.name}...`);
515
- try {
516
- child_process.execSync(createCommand, {
517
- stdio: ["pipe", "inherit", "inherit"],
518
- input: "n\n"
519
- });
520
- } catch (error) {
521
- }
523
+ child_process.execSync(createCommand, {
524
+ stdio: ["pipe", "inherit", "inherit"],
525
+ input: "n\n"
526
+ });
522
527
  logger.info("Installing dependencies...");
523
528
  child_process.execSync(pm.installCommand, { stdio: "inherit", cwd: projectName });
524
529
  logger.info(
@@ -539,7 +544,7 @@ const init = new commander.Command().name("init").description(
539
544
  Project "${projectName}" is ready!
540
545
  `);
541
546
  });
542
- const version = "1.1.0";
547
+ const version = "1.2.0";
543
548
  const pkg = {
544
549
  version
545
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,196 @@
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 p } from "./path-BodMUTCM.js";
2
+ import i, { Suspense as E, lazy as L, createContext as M, useContext as x } from "react";
3
+ import { useParams as k } from "react-router-dom";
4
+ const A = {
5
5
  enableLazyLoading: !0
6
6
  };
7
- let d = { ...L };
8
- function x(e) {
9
- d = { ...d, ...e };
7
+ let m = { ...A };
8
+ function B(e) {
9
+ m = { ...m, ...e };
10
10
  }
11
- function $() {
12
- return d;
11
+ function S() {
12
+ return m;
13
13
  }
14
- let E = [];
15
- function T(e, r) {
16
- E.push({ pattern: e, middleware: r });
14
+ let g = [];
15
+ function H(e, t) {
16
+ g.push({ pattern: e, middleware: t });
17
17
  }
18
- function j() {
19
- E = [];
18
+ function I() {
19
+ g = [];
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 d(e, t) {
22
+ let n = t;
23
+ for (const r of g)
24
+ r.pattern.test(e) && (n = r.middleware(n));
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"), C = /* @__PURE__ */ new Map();
28
+ function w(e, t, n) {
29
+ const r = S();
30
+ if (r.enableLazyLoading === !1) {
31
+ const u = i.lazy(e), s = i.createElement(
32
+ E,
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 ? d(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,
39
+ let a = C.get(t);
40
+ a || (a = L(e), C.set(t, a));
41
+ const o = r.loadingFallback || T, l = i.createElement(
42
+ E,
43
43
  { fallback: i.createElement(o) },
44
- 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 ? d(n, l) : l;
47
+ }
48
+ function h(e, t, n, r, a) {
49
+ const o = z(t, a), l = o ? o.split("/") : [];
50
+ let u = e, s = "";
51
+ l.forEach((c, v) => {
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), v === l.length - 1 && (u[r] = n);
57
+ }), l.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");
67
- }), Object.entries(e).forEach(([l, o]) => {
68
- p(t, l, o, "page");
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
65
+ return Object.entries(t).forEach(([o, l]) => {
66
+ h(a, o, l.default, "layout", r);
67
+ }), Object.entries(e).forEach(([o, l]) => {
68
+ h(a, o, l, "page", r);
69
+ }), Object.entries(n).forEach(([o, l]) => {
70
+ h(a, o, l, "notFound", r);
71
+ }), a;
72
+ }
73
+ const F = M(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 x(F);
86
+ }
87
+ function P(e, t) {
88
+ return d(e, i.createElement(t));
89
+ }
90
+ function f(e, t, n = !1) {
91
+ const r = e.page;
92
+ if (!r)
93
+ throw new Error(`Route node "${e.fullPath}" has no page loader`);
94
+ const a = w(
95
+ async () => ({ default: (await r()).default }),
96
+ `page:${e.fullPath || t}`,
97
+ t
98
+ ), o = e.segment.match(/^\[\[?\.\.\.(.+?)\]\]?$/), l = o ? i.createElement(N, {
99
+ name: o[1],
100
+ children: a
101
+ }) : a;
102
+ return n ? { index: !0, element: l } : { path: p(e.segment), element: l };
103
+ }
104
+ function R(e) {
105
+ const t = e.notFound;
106
+ if (!t)
107
+ throw new Error(`Route node "${e.fullPath}" has no not-found loader`);
108
+ const n = e.fullPath ? `/${e.fullPath}` : "/";
82
109
  return {
83
110
  path: "*",
84
- element: R(
85
- async () => ({ default: (await e.notFound()).default }),
86
- `notfound:${e.fullPath}`
111
+ element: w(
112
+ async () => ({ default: (await t()).default }),
113
+ `notfound:${e.fullPath}`,
114
+ n
87
115
  )
88
116
  };
89
117
  }
90
- function s(e, r) {
118
+ function $(e, t) {
91
119
  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));
120
+ return [...e.children.entries()].sort(([r], [a]) => r.localeCompare(a)).forEach(([r, a]) => {
121
+ const o = p(r), l = o ? t ? `${t}/${o}` : `/${o}` : t;
122
+ n.push(...b(a, !1, l));
95
123
  }), n;
96
124
  }
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({
125
+ function y(e, t) {
126
+ const n = [];
127
+ return e.page && n.push(f(e, t || "/", !0)), n.push(...$(e, t)), e.notFound && n.push(R(e)), n;
128
+ }
129
+ function b(e, t = !1, n = "") {
130
+ const r = [];
131
+ if (t) {
132
+ const c = y(e, "");
133
+ return e.layout ? (r.push({
134
+ path: "/",
135
+ element: P("/", e.layout),
136
+ children: c
137
+ }), r) : e.page ? (r.push({
138
+ path: "/",
139
+ element: f(e, "/", !1).element,
140
+ children: c.slice(1).length > 0 ? c.slice(1) : void 0
141
+ }), r) : e.notFound ? (r.push({
102
142
  path: "/",
103
- element: i.createElement(e.layout),
104
- children: a
105
- }), t;
143
+ children: c
144
+ }), r) : c;
106
145
  }
146
+ const a = p(e.segment), o = !a, l = o ? n : n + "/" + a, u = $(e, l), s = e.notFound ? [R(e)] : [];
107
147
  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({
113
- path: a,
114
- element: i.createElement(e.layout),
115
- children: u.length > 0 ? u : void 0
116
- }), t;
148
+ const c = y(e, l);
149
+ return [{
150
+ ...o ? {} : { path: a },
151
+ element: P(l, e.layout),
152
+ children: c.length > 0 ? c : void 0
153
+ }];
117
154
  }
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;
155
+ if (e.page && !o)
156
+ return [{
157
+ path: a,
158
+ element: f(e, l).element,
159
+ children: [...u, ...s].length > 0 ? [...u, ...s] : void 0
160
+ }];
161
+ if (e.page || e.notFound || !o) {
162
+ const c = [
163
+ ...e.page ? [f(e, l, !0)] : [],
164
+ ...u,
165
+ ...s
166
+ ];
167
+ return [{
168
+ ...o ? {} : { path: a },
169
+ children: c.length > 0 ? c : void 0
170
+ }];
132
171
  }
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;
172
+ return u;
138
173
  }
139
- function O(e, r, n) {
174
+ function J(e, t, n, r) {
140
175
  return () => {
141
- const t = S(e, r, n);
142
- return w(t, !0);
176
+ const a = j(e, t, n, r);
177
+ return b(a, !0);
143
178
  };
144
179
  }
180
+ function Q() {
181
+ const e = k(), t = D();
182
+ return typeof t == "string" && e["*"] !== void 0 ? { ...e, [t]: e["*"] } : e;
183
+ }
145
184
  export {
146
- S as buildRouteTree,
147
- j as clearMiddlewares,
148
- x as configureRouter,
149
- 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
185
+ N as CatchAllParamProvider,
186
+ j as buildRouteTree,
187
+ I as clearMiddlewares,
188
+ B as configureRouter,
189
+ w as createLazyElement,
190
+ J as createRouteGenerator,
191
+ z as extractRoutePath,
192
+ p as normalizeSegment,
193
+ H as registerMiddleware,
194
+ b as treeToRoutes,
195
+ Q as useParams
156
196
  };
@@ -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,128 @@
1
- import "node:fs";
2
- import "node:path";
3
- import { a as i } from "./index.plugin--ecyiAem.js";
1
+ import m from "node:fs";
2
+ import i from "node:path";
3
+ import { n as g } from "./path-BodMUTCM.js";
4
+ function h(o) {
5
+ const e = o.split(i.sep).filter(Boolean).map(g).filter(Boolean);
6
+ return e.length > 0 ? `/${e.join("/")}` : "/";
7
+ }
8
+ function x(o, e) {
9
+ const s = /* @__PURE__ */ new Set();
10
+ function r(n) {
11
+ for (const u of m.readdirSync(n, { withFileTypes: !0 })) {
12
+ const a = i.join(n, u.name);
13
+ u.isDirectory() ? r(a) : u.isFile() && u.name === e && s.add(h(i.relative(o, n)));
14
+ }
15
+ }
16
+ return m.existsSync(o) && r(o), [...s].sort();
17
+ }
18
+ function v(o) {
19
+ const e = o.match(/^\/(?:\[\.\.\.|:)([^/\]]+)\]?$/);
20
+ return e == null ? void 0 : e[1];
21
+ }
22
+ function P(o) {
23
+ const e = o.map((r) => ` | "${r}"`).join(`
24
+ `), s = o.map((r) => {
25
+ const n = r.split("/").map((a) => v(`/${a}`)).filter((a) => !!a), u = n.length > 0 ? n.map((a) => ` ${a}: string;`).join(`
26
+ `) : " [key: string]: never;";
27
+ return ` "${r}": {
28
+ ${u}
29
+ };`;
30
+ }).join(`
31
+ `);
32
+ return `declare module 'virtual:next-vite-router' {
33
+ import type { ReactElement } from 'react';
34
+ import type { RouteObject } from 'react-router-dom';
35
+
36
+ export type RoutePath =
37
+ ${e || ' | "/"'};
38
+
39
+ export type RouteParams<P extends RoutePath> = P extends keyof RoutePathParams
40
+ ? RoutePathParams[P]
41
+ : never;
42
+
43
+ export interface RoutePathParams {
44
+ ${s}
45
+ }
46
+
47
+ export const generateRoutes: () => RouteObject[];
48
+ export function Router(): ReactElement | null;
49
+ export function useParams<T extends Record<string, string | undefined> = Record<string, string | undefined>>(): T;
50
+ export { Link, useRoutes } from 'react-router-dom';
51
+ }
52
+ `;
53
+ }
54
+ function k(o = {}) {
55
+ const {
56
+ pagesDir: e = "src/app",
57
+ pageFile: s = "page.tsx",
58
+ layoutFile: r = "layout.tsx",
59
+ notFoundFile: n = "not-found.tsx"
60
+ } = o, u = { pagesDir: e, pageFile: s, layoutFile: r, notFoundFile: n }, a = "virtual:next-vite-router", c = "\0" + a;
61
+ let l = "", p = "";
62
+ function d() {
63
+ const t = P(x(l, s));
64
+ m.writeFileSync(p, t, "utf-8");
65
+ }
66
+ return {
67
+ name: "next-vite-router",
68
+ configResolved(t) {
69
+ l = i.resolve(t.root, e), p = i.join(i.dirname(l), "virtual-next-vite-router.d.ts"), d();
70
+ },
71
+ configureServer(t) {
72
+ const f = (y) => {
73
+ if (i.relative(l, y).startsWith("..") || ![s, r, n].includes(i.basename(y)))
74
+ return;
75
+ d();
76
+ const R = t.moduleGraph.getModuleById(c);
77
+ R && (t.moduleGraph.invalidateModule(R), t.ws.send({ type: "full-reload", path: "*" }));
78
+ };
79
+ t.watcher.on("add", f), t.watcher.on("unlink", f);
80
+ },
81
+ resolveId(t) {
82
+ if (t === a)
83
+ return c;
84
+ },
85
+ load(t) {
86
+ if (t === c)
87
+ return `
88
+ import { buildRouteTree, treeToRoutes, normalizeSegment, createLazyElement, useParams } from 'next-vite-router';
89
+ import { Link, useRoutes } from 'react-router-dom';
90
+
91
+ const pagesGlob = import.meta.glob('/${e}/**/${s}');
92
+ const layoutsGlob = import.meta.glob('/${e}/**/${r}', { eager: true });
93
+ const notFoundsGlob = import.meta.glob('/${e}/**/${n}');
94
+
95
+ const transformPath = (path) => path.replace('/${e}/', '');
96
+
97
+ const pages = Object.fromEntries(
98
+ Object.entries(pagesGlob).map(([key, value]) => [transformPath(key), value])
99
+ );
100
+ const layouts = Object.fromEntries(
101
+ Object.entries(layoutsGlob).map(([key, value]) => [transformPath(key), value])
102
+ );
103
+ const notFounds = Object.fromEntries(
104
+ Object.entries(notFoundsGlob).map(([key, value]) => [transformPath(key), value])
105
+ );
106
+
107
+ export function generateRoutes() {
108
+ const tree = buildRouteTree(pages, layouts, notFounds, ${JSON.stringify(u)});
109
+ const routes = treeToRoutes(tree, true);
110
+ return routes;
111
+ }
112
+
113
+ export function Router() {
114
+ routes ??= generateRoutes();
115
+ return useRoutes(routes);
116
+ }
117
+
118
+ let routes;
119
+
120
+ export { Link, useRoutes };
121
+ export { useParams };
122
+ `;
123
+ }
124
+ };
125
+ }
4
126
  export {
5
- i as nextViteRouter
127
+ k as nextViteRouter
6
128
  };
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "next-vite-router",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
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
- };