next-vite-router 1.2.4 → 1.3.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
@@ -4,7 +4,7 @@ File-based routing for React + Vite.
4
4
 
5
5
  Define routes with folders and let the package generate the
6
6
  [React Router](https://reactrouter.com/) `RouteObject[]`. It does not replace
7
- React Router, navigation, history, loaders, or application state.
7
+ React Router, navigation, history, or application state.
8
8
 
9
9
  ## Start here
10
10
 
@@ -31,6 +31,10 @@ export default defineConfig({
31
31
  });
32
32
  ```
33
33
 
34
+ The plugin ships both ESM and CJS, so this works when `package.json` has no
35
+ `"type": "module"` (Vite 5 loads `vite.config.ts` with `require()` in that
36
+ case). You can also rename the file to `vite.config.mts` to force ESM.
37
+
34
38
  Create your route tree:
35
39
 
36
40
  ```text
@@ -88,10 +92,12 @@ npm run dev
88
92
 
89
93
  ## Use React Router directly
90
94
 
91
- `Router` is only a convenience wrapper around `useRoutes(routes)`. Routes are
92
- generated lazily on the first render, so calls to `configureRouter` and
93
- `registerMiddleware` made during application setup are applied before route
94
- elements are created. You can keep using React Router APIs:
95
+ `Router` is only a convenience wrapper around `useRoutes(routes)`. The
96
+ lazy-loading behavior and `loadingFallback` configured through `configureRouter`
97
+ are read at render time, so they take effect even when `generateRoutes()` runs
98
+ at module scope before application setup finishes. Register middleware with
99
+ `registerMiddleware` *before* calling `generateRoutes()`, because route elements
100
+ are wrapped while the tree is built. You can keep using React Router APIs:
95
101
 
96
102
  ```tsx
97
103
  import {
@@ -115,10 +121,18 @@ export default function App() {
115
121
  }
116
122
  ```
117
123
 
118
- Use `useNavigate`, loaders, actions, and other React Router APIs from
119
- `react-router-dom`. The package `useParams` hook is also available from the
120
- virtual module and maps catch-all names such as `slug`. The package does not
121
- create competing navigation or data-loading APIs.
124
+ Keep using React Router's navigation and hooks from `react-router-dom`
125
+ (`useNavigate`, `useLocation`, `Link`, `Outlet`). For parameters, import
126
+ `useParams` from the virtual module instead of `react-router-dom`: dynamic
127
+ `[id]` segments work with either hook, but named catch-all segments
128
+ (`[...slug]`, `[[...slug]]`) are only surfaced as `slug` through the package
129
+ `useParams`. React Router's own `useParams` returns them under `params["*"]`.
130
+
131
+ The generated `RouteObject[]` carries page, layout, and not-found elements only.
132
+ There is no file convention that produces React Router route `loader` or
133
+ `action` functions, and those data APIs run only under a data router
134
+ (`createBrowserRouter` + `RouterProvider`), not under `BrowserRouter` with
135
+ `useRoutes`. Fetch data inside your page components instead.
122
136
 
123
137
  ## File conventions
124
138
 
@@ -210,6 +224,9 @@ configureRouter({
210
224
  });
211
225
  ```
212
226
 
227
+ The fallback is resolved when a route suspends during render, so the order of
228
+ `configureRouter` relative to `generateRoutes()` does not matter.
229
+
213
230
  `enableLazyLoading: false` is available for compatibility and testing. It does
214
231
  not turn Vite's asynchronous imports into synchronous imports.
215
232
 
@@ -234,7 +251,7 @@ replace React Router.
234
251
  For an interactive setup:
235
252
 
236
253
  ```bash
237
- npx next-vite-router init
254
+ npx next-vite-router create
238
255
  ```
239
256
 
240
257
  The CLI creates a Vite React project, installs the package and
package/dist/cli.cjs CHANGED
@@ -3,6 +3,8 @@
3
3
  const commander = require("commander");
4
4
  const node_child_process = require("node:child_process");
5
5
  const child_process = require("child_process");
6
+ const fs$1 = require("node:fs");
7
+ const path$1 = require("node:path");
6
8
  const prompts = require("prompts");
7
9
  const fs = require("fs");
8
10
  const path = require("path");
@@ -30,13 +32,13 @@ function detectPackageManager() {
30
32
  if (fromUA) candidates.push(fromUA);
31
33
  if (preferred && !candidates.includes(preferred)) candidates.push(preferred);
32
34
  ["pnpm", "bun", "yarn", "npm"].forEach((p) => {
33
- const pm2 = p;
34
- if (!candidates.includes(pm2)) candidates.push(pm2);
35
+ const pm = p;
36
+ if (!candidates.includes(pm)) candidates.push(pm);
35
37
  });
36
38
  let chosen = null;
37
- for (const pm2 of candidates) {
38
- if (isAvailable(pm2)) {
39
- chosen = pm2;
39
+ for (const pm of candidates) {
40
+ if (isAvailable(pm)) {
41
+ chosen = pm;
40
42
  break;
41
43
  }
42
44
  }
@@ -87,7 +89,7 @@ if (typeof process !== "undefined") {
87
89
  const $ = {
88
90
  enabled: !NODE_DISABLE_COLORS && NO_COLOR == null && TERM !== "dumb" && (FORCE_COLOR != null && FORCE_COLOR !== "0" || isTTY)
89
91
  };
90
- function init$1(x, y) {
92
+ function init(x, y) {
91
93
  let rgx = new RegExp(`\\x1b\\[${y}m`, "g");
92
94
  let open = `\x1B[${x}m`, close = `\x1B[${y}m`;
93
95
  return function(txt) {
@@ -95,10 +97,10 @@ function init$1(x, y) {
95
97
  return open + (!!~("" + txt).indexOf(close) ? txt.replace(rgx, close + open) : txt) + close;
96
98
  };
97
99
  }
98
- const red = init$1(31, 39);
99
- const green = init$1(32, 39);
100
- const yellow = init$1(33, 39);
101
- const cyan = init$1(36, 39);
100
+ const red = init(31, 39);
101
+ const green = init(32, 39);
102
+ const yellow = init(33, 39);
103
+ const cyan = init(36, 39);
102
104
  const highlighter = {
103
105
  error: red,
104
106
  warn: yellow,
@@ -164,7 +166,6 @@ function updateAppTsConfig(projectDir, src) {
164
166
  const aliasPath = src ? "./src/*" : "./*";
165
167
  tsConfigApp.compilerOptions = {
166
168
  ...tsConfigApp.compilerOptions,
167
- baseUrl: ".",
168
169
  paths: {
169
170
  ...tsConfigApp.compilerOptions?.paths,
170
171
  "@/*": [aliasPath]
@@ -194,12 +195,24 @@ function updateTsConfigJson(projectDir, src) {
194
195
  if (!tsConfig.compilerOptions) {
195
196
  tsConfig.compilerOptions = {};
196
197
  }
197
- tsConfig.compilerOptions.baseUrl = ".";
198
198
  tsConfig.compilerOptions.paths = {
199
199
  "@/*": [src ? "./src/*" : "./*"]
200
200
  };
201
201
  fs.writeFileSync(tsConfigPath, JSON.stringify(tsConfig, null, 2), "utf-8");
202
202
  }
203
+ function updateIndexHtml(projectDir, src) {
204
+ if (src) return;
205
+ const indexPath = path.join(projectDir, "index.html");
206
+ if (!fs.existsSync(indexPath)) return;
207
+ const content = fs.readFileSync(indexPath, "utf-8");
208
+ const updated = content.replace(
209
+ /(src\s*=\s*["'])\/src\/main\.tsx(["'])/,
210
+ "$1/main.tsx$2"
211
+ );
212
+ if (updated !== content) {
213
+ fs.writeFileSync(indexPath, updated, "utf-8");
214
+ }
215
+ }
203
216
  function createVirtualDTS(projectDir, useSrc) {
204
217
  const targetDir = useSrc ? path.join(projectDir, "src") : projectDir;
205
218
  if (useSrc && !fs.existsSync(targetDir)) {
@@ -374,7 +387,6 @@ function addTailwindToViteConfig(projectDir) {
374
387
  }
375
388
  writeViteConfig(projectDir, updatedContent);
376
389
  }
377
- const pm$1 = detectPackageManager();
378
390
  function startNextViteRouterConfig({
379
391
  projectDir = process.cwd(),
380
392
  src,
@@ -382,16 +394,31 @@ function startNextViteRouterConfig({
382
394
  }) {
383
395
  startViteConfig(projectDir, src);
384
396
  const srcPath = path.join(projectDir, "src");
385
- if (fs.existsSync(srcPath)) {
397
+ if (src) {
398
+ for (const entry of [
399
+ "App.tsx",
400
+ "App.css",
401
+ "index.css",
402
+ "main.tsx",
403
+ "assets"
404
+ ]) {
405
+ const entryPath = path.join(srcPath, entry);
406
+ if (fs.existsSync(entryPath)) {
407
+ fs.rmSync(entryPath, { recursive: true, force: true });
408
+ }
409
+ }
410
+ } else if (fs.existsSync(srcPath)) {
386
411
  fs.rmSync(srcPath, { recursive: true, force: true });
387
412
  }
388
413
  updateTsConfigJson(projectDir, src);
389
414
  updateAppTsConfig(projectDir, src);
390
415
  createVirtualDTS(projectDir, src);
391
416
  createMainTSX(projectDir, src);
417
+ updateIndexHtml(projectDir, src);
392
418
  if (tailwindcss) {
393
419
  logger.info("Setting up Tailwind CSS...");
394
- const addDevDepsCmd = pm$1.addDevDepsCommand([
420
+ const pm = detectPackageManager();
421
+ const addDevDepsCmd = pm.addDevDepsCommand([
395
422
  "tailwindcss",
396
423
  "@tailwindcss/vite"
397
424
  ]);
@@ -434,7 +461,7 @@ ${resolveConfig}
434
461
  }
435
462
  const pluginsRegex = /plugins\s*:\s*\[(.*?)\]/s;
436
463
  const match = viteConfigContent.match(pluginsRegex);
437
- const pluginString = "nextViteRouter()";
464
+ const pluginString = src ? "nextViteRouter()" : 'nextViteRouter({ pagesDir: "app" })';
438
465
  if (match) {
439
466
  const existing = match[1].trim();
440
467
  const newPlugins = existing ? `${existing}, ${pluginString}` : pluginString;
@@ -451,9 +478,9 @@ ${resolveConfig}
451
478
  }
452
479
  writeViteConfig(projectDir, viteConfigContent);
453
480
  }
454
- const pm = detectPackageManager();
455
- const init = new commander.Command().name("init").description(
456
- "Initialize a new Vite project with next-vite-router configuration"
481
+ const PROJECT_NAME_RE = /^[a-z0-9][a-z0-9._-]*$/i;
482
+ const create = new commander.Command().name("create").description(
483
+ "Create a new Vite project with next-vite-router configuration"
457
484
  ).action(async () => {
458
485
  const projectNameResponse = await prompts(
459
486
  {
@@ -470,6 +497,16 @@ const init = new commander.Command().name("init").description(
470
497
  }
471
498
  );
472
499
  const projectName = projectNameResponse.projectName;
500
+ if (!PROJECT_NAME_RE.test(projectName)) {
501
+ logger.error(
502
+ "Invalid project name (letters, digits, '.', '_' and '-' only)."
503
+ );
504
+ process.exit(1);
505
+ }
506
+ if (fs$1.existsSync(path$1.resolve(projectName))) {
507
+ logger.error(`Directory "${projectName}" already exists.`);
508
+ process.exit(1);
509
+ }
473
510
  const variantResponse = await prompts(
474
511
  {
475
512
  type: "select",
@@ -522,12 +559,10 @@ const init = new commander.Command().name("init").description(
522
559
  }
523
560
  );
524
561
  const useTailwind = tailwindResponse.useTailwind;
562
+ const pm = detectPackageManager();
525
563
  const createCommand = pm.createProjectCommand(projectName, variant);
526
564
  logger.info(`Creating project "${projectName}" using ${pm.name}...`);
527
- child_process.execSync(createCommand, {
528
- stdio: ["pipe", "inherit", "inherit"],
529
- input: "n\n"
530
- });
565
+ child_process.execSync(createCommand, { stdio: "inherit" });
531
566
  logger.info("Installing dependencies...");
532
567
  child_process.execSync(pm.installCommand, { stdio: "inherit", cwd: projectName });
533
568
  logger.info(
@@ -548,13 +583,18 @@ const init = new commander.Command().name("init").description(
548
583
  Project "${projectName}" is ready!
549
584
  `);
550
585
  });
551
- const version = "1.2.4";
586
+ const version = "1.3.1";
552
587
  const pkg = {
553
588
  version
554
589
  };
555
590
  async function main() {
556
591
  const program = new commander.Command();
557
- program.name("create-next-vite-router").version(pkg.version).addCommand(init);
558
- program.parse(process.argv);
592
+ program.name("next-vite-router").version(pkg.version).addCommand(create);
593
+ try {
594
+ await program.parseAsync(process.argv);
595
+ } catch (e) {
596
+ logger.error(e instanceof Error ? e.message : String(e));
597
+ process.exit(1);
598
+ }
559
599
  }
560
600
  main();
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("./tree-hc1dSMdb.cjs"),l=require("react"),S=require("react-router-dom"),C={enableLazyLoading:!0};let f={...C};function v(e){f={...f,...e}}function F(){f={...C}}function T(){return f}let g=[];function x(e,t){g.push({pattern:e,middleware:t})}function A(){g=[]}function y(e,t){let r=t;for(const n of g)n.pattern.lastIndex=0,n.pattern.test(e)&&(r=n.middleware(r)),n.pattern.lastIndex=0;return r}const M=()=>l.createElement("div"),h=new Map;function N(){const e=T();if(e.enableLazyLoading===!1)return null;const t=e.loadingFallback||M;return l.createElement(t)}function $(){h.clear()}function p(e,t,r){let n=h.get(t);n||(n=l.lazy(e),h.set(t,n));const a=l.createElement(l.Suspense,{fallback:l.createElement(N)},l.createElement(n));return r?y(r,a):a}const P=l.createContext(void 0);function w({name:e,children:t}){return l.createElement(P.Provider,{value:e},t)}function k(){return l.useContext(P)}function E(e,t){return y(e,l.createElement(t))}function D(e){const t=c.parseRouteSegments(e)[0];return(t==null?void 0:t.kind)==="catchAll"&&t.optional}function d(e){const t=c.parseRouteSegments(e)[0];return t?c.routeSegmentToPath(t):""}function s(e,t){return m(e,t,!0)}function m(e,t,r=!1){const n=e.page;if(!n)throw new Error(`Route node "${e.fullPath}" has no page loader`);const a=p(async()=>({default:(await n()).default}),`page:${e.fullPath||t}`,t),o=c.parseRouteSegments(e.segment)[0],u=(o==null?void 0:o.kind)==="catchAll"?l.createElement(w,{name:o.name,children:a}):a;return r?{index:!0,element:u}:{path:d(e.segment),element:u}}function q(e){const t=e.notFound;if(!t)throw new Error(`Route node "${e.fullPath}" has no not-found loader`);const r=e.fullPath?`/${e.fullPath}`:"/";return{path:"*",element:p(async()=>({default:(await t()).default}),`notfound:${e.fullPath}`,r)}}function L(e){return e.notFound?[q(e)]:[]}function I(e,t,r,n){return{...t?{path:t}:{},element:E(e,r),children:n.length>0?n:void 0}}function b(e,t){const r=[];return[...e.children.entries()].sort(([n],[a])=>n.localeCompare(a)).forEach(([n,a])=>{const o=d(n),u=o?t?`${t}/${o}`:`/${o}`:t;r.push(...R(a,!1,u))}),r}function z(e,t){const r=[];return e.page&&r.push(s(e,t||"/")),r.push(...b(e,t)),r.push(...L(e)),r}function G(e,t,r,n,a,o){if(D(e.segment)){const u=[...a,...o];return[s(e,t),{path:n,element:m(e,r).element,children:u.length>0?u:void 0}]}return a.length>0||o.length>0?[{path:n,children:[s(e,r),...a,...o]}]:[{path:n,element:m(e,r).element}]}function O(e,t,r,n){const a=[...e.page?[s(e,t)]:[],...r,...n];return[{children:a.length>0?a:void 0}]}function j(e){const t=z(e,"");return e.layout?[{path:"/",element:E("/",e.layout),children:t}]:e.page?t.length>1?[{path:"/",children:t}]:[{path:"/",element:m(e,"/",!1).element}]:e.notFound?[{path:"/",children:t}]:t}function R(e,t=!1,r=""){if(t)return j(e);const n=d(e.segment),a=!n,o=a?r:r+"/"+n,u=b(e,o),i=L(e);return e.layout?[I(o,a?"":n,e.layout,z(e,o))]:e.page&&!a?G(e,r,o,n,u,i):e.page||e.notFound||!a?a?O(e,o,u,i):[{path:n,children:[...e.page?[s(e,o)]:[],...u,...i]}]:u}function B(e,t,r,n){return()=>{const a=c.buildRouteTree(e,t,r,n);return R(a,!0)}}function H(){const e=S.useParams(),t=k();return typeof t=="string"&&e["*"]!==void 0?{...e,[t]:e["*"]}:e}exports.assertNoDuplicateRoutes=c.assertNoDuplicateRoutes;exports.assertNoRouteConflicts=c.assertNoRouteConflicts;exports.buildRouteTree=c.buildRouteTree;exports.extractRoutePath=c.extractRoutePath;exports.normalizeSegment=c.normalizeSegment;exports.parseRouteSegments=c.parseRouteSegments;exports.routeSegmentToPath=c.routeSegmentToPath;exports.CatchAllParamProvider=w;exports.clearLazyCache=$;exports.clearMiddlewares=A;exports.configureRouter=v;exports.createLazyElement=p;exports.createRouteGenerator=B;exports.registerMiddleware=x;exports.resetRouterConfig=F;exports.treeToRoutes=R;exports.useParams=H;
@@ -0,0 +1,144 @@
1
+ import { ComponentType } from 'react';
2
+ import { default as default_2 } from 'react';
3
+ import { ReactElement } from 'react';
4
+ import { RouteObject } from 'react-router-dom';
5
+
6
+ export declare function assertNoDuplicateRoutes(filePaths: string[], fileOptions?: RouteFileOptions): void;
7
+
8
+ export declare function assertNoRouteConflicts(pageFiles: string[], notFoundFiles: string[], fileOptions?: RouteFileOptions): void;
9
+
10
+ export declare function buildRouteTree(pages: Record<string, () => Promise<any>>, layouts: Record<string, any>, notFounds: Record<string, () => Promise<any>>, fileOptions?: RouteFileOptions): RouteNode;
11
+
12
+ export declare function CatchAllParamProvider({ name, children, }: {
13
+ name: string;
14
+ children: default_2.ReactElement;
15
+ }): default_2.ReactElement;
16
+
17
+ /**
18
+ * Clear the module-level lazy component cache.
19
+ *
20
+ * `lazyComponentCache` is a package-wide singleton keyed by route path, so a
21
+ * `React.lazy` component created in one context (for example a previous test)
22
+ * is reused whenever another context requests the same key with a different
23
+ * loader. Exposing an explicit reset keeps that state from leaking, mirroring
24
+ * `clearMiddlewares` for the middleware registry.
25
+ */
26
+ export declare function clearLazyCache(): void;
27
+
28
+ export declare function clearMiddlewares(): void;
29
+
30
+ export declare function configureRouter(config: RouterConfig): void;
31
+
32
+ /**
33
+ * Create a lazy element wrapped with Suspense and apply middlewares
34
+ * @param loader - Function that loads the component
35
+ * @param cacheKey - Unique key for the cache
36
+ * @param routePath - Route path for middlewares
37
+ */
38
+ export declare function createLazyElement(loader: () => Promise<any>, cacheKey: string, routePath?: string): default_2.ReactElement;
39
+
40
+ /**
41
+ * Create a custom route generator function using your own files
42
+ *
43
+ * ⚠️ NOTE: This function is intended for advanced use only.
44
+ * For most cases, use the Vite plugin which automatically handles the paths.
45
+ *
46
+ * @example
47
+ * // In your configuration file
48
+ * const pages = import.meta.glob("../../pages/**\\/page.tsx");
49
+ * const layouts = import.meta.glob("../../pages/**\\/layout.tsx", { eager: true });
50
+ * const notFounds = import.meta.glob("../../pages/**\\/not-found.tsx");
51
+ *
52
+ * export const generateCustomRoutes = createRouteGenerator(pages, layouts, notFounds);
53
+ *
54
+ * // Then use it
55
+ * const routes = generateCustomRoutes();
56
+ */
57
+ export declare function createRouteGenerator(pages: Record<string, () => Promise<any>>, layouts: Record<string, any>, notFounds: Record<string, () => Promise<any>>, fileOptions?: RouteFileOptions): () => RouteObject[];
58
+
59
+ /**
60
+ * Extract the route path from a file path
61
+ * @example "../../app/blog/[id]/page.tsx" -> "blog/[id]"
62
+ * @example "../../app/page.tsx" -> ""
63
+ *
64
+ * `pagesDir` may be relative or absolute. The plugin resolves it relative to
65
+ * the Vite project root before passing it here.
66
+ * The prefix is stripped lazily so it matches the FIRST directory boundary
67
+ * that equals `pagesDir` (or the leading `app`/`pages` root marker when no
68
+ * `pagesDir` is supplied). A greedy match would instead consume up to the
69
+ * LAST such folder, which mangles routes that legitimately contain a nested
70
+ * folder named like the pages directory (e.g. `app/docs/app/page.tsx`).
71
+ */
72
+ export declare function extractRoutePath(filePath: string, { pagesDir, pageFile, layoutFile, notFoundFile }?: RouteFileOptions): string;
73
+
74
+ /**
75
+ * Normalize a Next.js segment to a React Router segment
76
+ * @example (admin) -> ""
77
+ * @example [id] -> :id
78
+ * @example [...slug] -> *
79
+ */
80
+ export declare function normalizeSegment(segment: string): string;
81
+
82
+ export declare function parseRouteSegments(routePath: string): RouteSegment[];
83
+
84
+ export declare function registerMiddleware(pattern: RegExp, middleware: RouteMiddleware): void;
85
+
86
+ /**
87
+ * Restore the module-level router configuration to its defaults.
88
+ *
89
+ * `configureRouter` merges, so it can never drop keys back to their initial
90
+ * state. A dedicated reset lets isolated contexts (for example tests) start
91
+ * from a known configuration instead of inheriting whatever was set globally
92
+ * before, matching the explicit reset offered by `clearMiddlewares`.
93
+ */
94
+ export declare function resetRouterConfig(): void;
95
+
96
+ export declare type RouteFileOptions = {
97
+ pagesDir?: string;
98
+ pageFile?: string;
99
+ layoutFile?: string;
100
+ notFoundFile?: string;
101
+ };
102
+
103
+ export declare type RouteMiddleware = (element: ReactElement) => ReactElement;
104
+
105
+ export declare type RouteModuleLoader = () => Promise<{
106
+ default: ComponentType;
107
+ }>;
108
+
109
+ export declare type RouteNode = {
110
+ segment: string;
111
+ fullPath: string;
112
+ page?: RouteModuleLoader;
113
+ layout?: ComponentType;
114
+ notFound?: RouteModuleLoader;
115
+ children: Map<string, RouteNode>;
116
+ };
117
+
118
+ export declare type RouterConfig = {
119
+ loadingFallback?: React.ComponentType;
120
+ enableLazyLoading?: boolean;
121
+ };
122
+
123
+ export declare type RouteSegment = {
124
+ kind: "group";
125
+ name: string;
126
+ } | {
127
+ kind: "static";
128
+ name: string;
129
+ } | {
130
+ kind: "dynamic";
131
+ name: string;
132
+ } | {
133
+ kind: "catchAll";
134
+ name: string;
135
+ optional: boolean;
136
+ };
137
+
138
+ export declare function routeSegmentToPath(segment: RouteSegment): string;
139
+
140
+ export declare function treeToRoutes(node: RouteNode, isRoot?: boolean, currentPath?: string): RouteObject[];
141
+
142
+ export declare function useParams<T extends Record<string, string | undefined> = Record<string, string | undefined>>(): T;
143
+
144
+ export { }
package/dist/index.d.ts CHANGED
@@ -14,6 +14,17 @@ export declare function CatchAllParamProvider({ name, children, }: {
14
14
  children: default_2.ReactElement;
15
15
  }): default_2.ReactElement;
16
16
 
17
+ /**
18
+ * Clear the module-level lazy component cache.
19
+ *
20
+ * `lazyComponentCache` is a package-wide singleton keyed by route path, so a
21
+ * `React.lazy` component created in one context (for example a previous test)
22
+ * is reused whenever another context requests the same key with a different
23
+ * loader. Exposing an explicit reset keeps that state from leaking, mirroring
24
+ * `clearMiddlewares` for the middleware registry.
25
+ */
26
+ export declare function clearLazyCache(): void;
27
+
17
28
  export declare function clearMiddlewares(): void;
18
29
 
19
30
  export declare function configureRouter(config: RouterConfig): void;
@@ -52,6 +63,11 @@ export declare function createRouteGenerator(pages: Record<string, () => Promise
52
63
  *
53
64
  * `pagesDir` may be relative or absolute. The plugin resolves it relative to
54
65
  * the Vite project root before passing it here.
66
+ * The prefix is stripped lazily so it matches the FIRST directory boundary
67
+ * that equals `pagesDir` (or the leading `app`/`pages` root marker when no
68
+ * `pagesDir` is supplied). A greedy match would instead consume up to the
69
+ * LAST such folder, which mangles routes that legitimately contain a nested
70
+ * folder named like the pages directory (e.g. `app/docs/app/page.tsx`).
55
71
  */
56
72
  export declare function extractRoutePath(filePath: string, { pagesDir, pageFile, layoutFile, notFoundFile }?: RouteFileOptions): string;
57
73
 
@@ -67,6 +83,16 @@ export declare function parseRouteSegments(routePath: string): RouteSegment[];
67
83
 
68
84
  export declare function registerMiddleware(pattern: RegExp, middleware: RouteMiddleware): void;
69
85
 
86
+ /**
87
+ * Restore the module-level router configuration to its defaults.
88
+ *
89
+ * `configureRouter` merges, so it can never drop keys back to their initial
90
+ * state. A dedicated reset lets isolated contexts (for example tests) start
91
+ * from a known configuration instead of inheriting whatever was set globally
92
+ * before, matching the explicit reset offered by `clearMiddlewares`.
93
+ */
94
+ export declare function resetRouterConfig(): void;
95
+
70
96
  export declare type RouteFileOptions = {
71
97
  pagesDir?: string;
72
98
  pageFile?: string;
package/dist/index.js CHANGED
@@ -1,157 +1,160 @@
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";
1
+ import { p as h, r as F, b } from "./tree-DfnssFxU.js";
2
+ import { a as et, c as nt, e as rt, n as at } from "./tree-DfnssFxU.js";
3
+ import u, { lazy as v, Suspense as z, createContext as $, useContext as A } from "react";
4
4
  import { useParams as k } from "react-router-dom";
5
- const N = {
5
+ const d = {
6
6
  enableLazyLoading: !0
7
7
  };
8
- let f = { ...N };
8
+ let i = { ...d };
9
9
  function U(t) {
10
- f = { ...f, ...t };
11
- }
12
- function M() {
13
- return f;
14
- }
15
- let h = [];
16
- function V(t, e) {
17
- h.push({ pattern: t, middleware: e });
18
- }
19
- function W() {
20
- h = [];
21
- }
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
- return n;
27
- }
28
- const T = () => u.createElement("div"), C = /* @__PURE__ */ new Map();
29
- function y(t, e, n) {
30
- const r = M();
31
- if (r.enableLazyLoading === !1) {
32
- const c = u.lazy(t), d = u.createElement(
33
- R,
34
- { fallback: null },
35
- // No loading fallback when disabled
36
- u.createElement(c)
37
- );
38
- return n ? m(n, d) : d;
39
- }
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)
10
+ i = { ...i, ...t };
11
+ }
12
+ function V() {
13
+ i = { ...d };
14
+ }
15
+ function N() {
16
+ return i;
17
+ }
18
+ let p = [];
19
+ function W(t, e) {
20
+ p.push({ pattern: t, middleware: e });
21
+ }
22
+ function X() {
23
+ p = [];
24
+ }
25
+ function R(t, e) {
26
+ let r = e;
27
+ for (const n of p)
28
+ n.pattern.lastIndex = 0, n.pattern.test(t) && (r = n.middleware(r)), n.pattern.lastIndex = 0;
29
+ return r;
30
+ }
31
+ const M = () => u.createElement("div"), m = /* @__PURE__ */ new Map();
32
+ function T() {
33
+ const t = N();
34
+ if (t.enableLazyLoading === !1)
35
+ return null;
36
+ const e = t.loadingFallback || M;
37
+ return u.createElement(e);
38
+ }
39
+ function Y() {
40
+ m.clear();
41
+ }
42
+ function C(t, e, r) {
43
+ let n = m.get(e);
44
+ n || (n = v(t), m.set(e, n));
45
+ const a = u.createElement(
46
+ z,
47
+ { fallback: u.createElement(T) },
48
+ u.createElement(n)
46
49
  );
47
- return n ? m(n, l) : l;
50
+ return r ? R(r, a) : a;
48
51
  }
49
- const E = $(void 0);
50
- function D({
52
+ const y = $(void 0);
53
+ function I({
51
54
  name: t,
52
55
  children: e
53
56
  }) {
54
57
  return u.createElement(
55
- E.Provider,
58
+ y.Provider,
56
59
  { value: t },
57
60
  e
58
61
  );
59
62
  }
60
- function I() {
61
- return A(E);
63
+ function S() {
64
+ return A(y);
62
65
  }
63
- function w(t, e) {
64
- return m(t, u.createElement(e));
66
+ function P(t, e) {
67
+ return R(t, u.createElement(e));
65
68
  }
66
- function S(t) {
67
- const e = p(t)[0];
69
+ function D(t) {
70
+ const e = h(t)[0];
68
71
  return (e == null ? void 0 : e.kind) === "catchAll" && e.optional;
69
72
  }
70
73
  function g(t) {
71
- const e = p(t)[0];
72
- return e ? v(e) : "";
74
+ const e = h(t)[0];
75
+ return e ? F(e) : "";
73
76
  }
74
- function s(t, e) {
75
- return i(t, e, !0);
77
+ function c(t, e) {
78
+ return f(t, e, !0);
76
79
  }
77
- function i(t, e, n = !1) {
78
- const r = t.page;
79
- if (!r)
80
+ function f(t, e, r = !1) {
81
+ const n = t.page;
82
+ if (!n)
80
83
  throw new Error(`Route node "${t.fullPath}" has no page loader`);
81
- const a = y(
82
- async () => ({ default: (await r()).default }),
84
+ const a = C(
85
+ async () => ({ default: (await n()).default }),
83
86
  `page:${t.fullPath || e}`,
84
87
  e
85
- ), o = p(t.segment)[0], l = (o == null ? void 0 : o.kind) === "catchAll" ? u.createElement(D, {
88
+ ), o = h(t.segment)[0], l = (o == null ? void 0 : o.kind) === "catchAll" ? u.createElement(I, {
86
89
  name: o.name,
87
90
  children: a
88
91
  }) : a;
89
- return n ? { index: !0, element: l } : { path: g(t.segment), element: l };
92
+ return r ? { index: !0, element: l } : { path: g(t.segment), element: l };
90
93
  }
91
94
  function G(t) {
92
95
  const e = t.notFound;
93
96
  if (!e)
94
97
  throw new Error(`Route node "${t.fullPath}" has no not-found loader`);
95
- const n = t.fullPath ? `/${t.fullPath}` : "/";
98
+ const r = t.fullPath ? `/${t.fullPath}` : "/";
96
99
  return {
97
100
  path: "*",
98
- element: y(
101
+ element: C(
99
102
  async () => ({ default: (await e()).default }),
100
103
  `notfound:${t.fullPath}`,
101
- n
104
+ r
102
105
  )
103
106
  };
104
107
  }
105
- function P(t) {
108
+ function E(t) {
106
109
  return t.notFound ? [G(t)] : [];
107
110
  }
108
- function O(t, e, n, r) {
111
+ function O(t, e, r, n) {
109
112
  return {
110
113
  ...e ? { path: e } : {},
111
- element: w(t, n),
112
- children: r.length > 0 ? r : void 0
114
+ element: P(t, r),
115
+ children: n.length > 0 ? n : void 0
113
116
  };
114
117
  }
115
- function x(t, e) {
116
- const n = [];
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));
120
- }), n;
118
+ function w(t, e) {
119
+ const r = [];
120
+ return [...t.children.entries()].sort(([n], [a]) => n.localeCompare(a)).forEach(([n, a]) => {
121
+ const o = g(n), l = o ? e ? `${e}/${o}` : `/${o}` : e;
122
+ r.push(...x(a, !1, l));
123
+ }), r;
121
124
  }
122
125
  function L(t, e) {
123
- const n = [];
124
- return t.page && n.push(s(t, e || "/")), n.push(...x(t, e)), n.push(...P(t)), n;
126
+ const r = [];
127
+ return t.page && r.push(c(t, e || "/")), r.push(...w(t, e)), r.push(...E(t)), r;
125
128
  }
126
- function j(t, e, n, r, a, o) {
127
- if (S(t.segment)) {
129
+ function j(t, e, r, n, a, o) {
130
+ if (D(t.segment)) {
128
131
  const l = [...a, ...o];
129
132
  return [
130
- s(t, e),
133
+ c(t, e),
131
134
  {
132
- path: r,
133
- element: i(t, n).element,
135
+ path: n,
136
+ element: f(t, r).element,
134
137
  children: l.length > 0 ? l : void 0
135
138
  }
136
139
  ];
137
140
  }
138
141
  return a.length > 0 || o.length > 0 ? [{
139
- path: r,
142
+ path: n,
140
143
  children: [
141
- s(t, n),
144
+ c(t, r),
142
145
  ...a,
143
146
  ...o
144
147
  ]
145
148
  }] : [{
146
- path: r,
147
- element: i(t, n).element
149
+ path: n,
150
+ element: f(t, r).element
148
151
  }];
149
152
  }
150
- function q(t, e, n, r) {
153
+ function q(t, e, r, n) {
151
154
  const a = [
152
- ...t.page ? [s(t, e)] : [],
153
- ...n,
154
- ...r
155
+ ...t.page ? [c(t, e)] : [],
156
+ ...r,
157
+ ...n
155
158
  ];
156
159
  return [{
157
160
  children: a.length > 0 ? a : void 0
@@ -161,62 +164,64 @@ function B(t) {
161
164
  const e = L(t, "");
162
165
  return t.layout ? [{
163
166
  path: "/",
164
- element: w("/", t.layout),
167
+ element: P("/", t.layout),
165
168
  children: e
166
169
  }] : t.page ? e.length > 1 ? [{ path: "/", children: e }] : [{
167
170
  path: "/",
168
- element: i(t, "/", !1).element
171
+ element: f(t, "/", !1).element
169
172
  }] : t.notFound ? [{ path: "/", children: e }] : e;
170
173
  }
171
- function b(t, e = !1, n = "") {
174
+ function x(t, e = !1, r = "") {
172
175
  if (e)
173
176
  return B(t);
174
- const r = g(t.segment), a = !r, o = a ? n : n + "/" + r, l = x(t, o), c = P(t);
177
+ const n = g(t.segment), a = !n, o = a ? r : r + "/" + n, l = w(t, o), s = E(t);
175
178
  return t.layout ? [O(
176
179
  o,
177
- a ? "" : r,
180
+ a ? "" : n,
178
181
  t.layout,
179
182
  L(t, o)
180
183
  )] : t.page && !a ? j(
181
184
  t,
182
- n,
183
- o,
184
185
  r,
186
+ o,
187
+ n,
185
188
  l,
186
- c
187
- ) : t.page || t.notFound || !a ? a ? q(t, o, l, c) : [{
188
- path: r,
189
+ s
190
+ ) : t.page || t.notFound || !a ? a ? q(t, o, l, s) : [{
191
+ path: n,
189
192
  children: [
190
- ...t.page ? [s(t, o)] : [],
193
+ ...t.page ? [c(t, o)] : [],
191
194
  ...l,
192
- ...c
195
+ ...s
193
196
  ]
194
197
  }] : l;
195
198
  }
196
- function X(t, e, n, r) {
199
+ function Z(t, e, r, n) {
197
200
  return () => {
198
- const a = z(t, e, n, r);
199
- return b(a, !0);
201
+ const a = b(t, e, r, n);
202
+ return x(a, !0);
200
203
  };
201
204
  }
202
- function Y() {
203
- const t = k(), e = I();
205
+ function _() {
206
+ const t = k(), e = S();
204
207
  return typeof e == "string" && t["*"] !== void 0 ? { ...t, [e]: t["*"] } : t;
205
208
  }
206
209
  export {
207
- D as CatchAllParamProvider,
208
- K as assertNoDuplicateRoutes,
209
- tt as assertNoRouteConflicts,
210
- z as buildRouteTree,
211
- W as clearMiddlewares,
210
+ I as CatchAllParamProvider,
211
+ et as assertNoDuplicateRoutes,
212
+ nt as assertNoRouteConflicts,
213
+ b as buildRouteTree,
214
+ Y as clearLazyCache,
215
+ X as clearMiddlewares,
212
216
  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
217
+ C as createLazyElement,
218
+ Z as createRouteGenerator,
219
+ rt as extractRoutePath,
220
+ at as normalizeSegment,
221
+ h as parseRouteSegments,
222
+ W as registerMiddleware,
223
+ V as resetRouterConfig,
224
+ F as routeSegmentToPath,
225
+ x as treeToRoutes,
226
+ _ as useParams
222
227
  };
@@ -0,0 +1,63 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const b=require("node:fs"),f=require("node:path"),x=require("./tree-hc1dSMdb.cjs");function S(r){const a=r.map(x.routeSegmentToPath).filter(Boolean);return a.length>0?`/${a.join("/")}`:"/"}function T(r){const a=[["pagesDir",r.pagesDir],["pageFile",r.pageFile],["layoutFile",r.layoutFile],["notFoundFile",r.notFoundFile]];for(const[o,e]of a)if(!e.trim())throw new Error(`nextViteRouter option "${o}" must not be empty.`);const s=a.slice(1),i=s.find(([,o],e)=>s.slice(e+1).some(([,n])=>n===o));if(i)throw new Error(`nextViteRouter file options must be distinct; "${i[0]}" is duplicated.`)}function k(r){return r.replace(/\\/g,"/").replace(/^\.\//,"").replace(/^\/|\/$/g,"")}function G(r,a,s){const i=a.pageFile??"page.tsx",o=new Map;for(const e of R(r,i,s)){const n=x.extractRoutePath(e,a),l=x.parseRouteSegments(n),g=l.filter(u=>u.kind==="dynamic"||u.kind==="catchAll").map(u=>({name:u.name,optional:u.kind==="catchAll"&&u.optional})),p=S(l);o.set(p,{path:p,params:g});const m=l[l.length-1];if((m==null?void 0:m.kind)==="catchAll"&&m.optional){const u=S(l.slice(0,-1));o.has(u)||o.set(u,{path:u,params:[]})}}return[...o.values()].sort((e,n)=>e.path.localeCompare(n.path))}function R(r,a,s){const i=[];function o(e){for(const n of b.readdirSync(e,{withFileTypes:!0})){const l=f.join(e,n.name);if(n.isDirectory())o(l);else if(n.isFile()&&n.name===a){const g=f.relative(r,l).replace(/\\/g,"/");i.push(`/${s}/${g}`)}}}return b.existsSync(r)&&o(r),i}function O(r){const a=r.map(({path:i})=>` | ${JSON.stringify(i)}`).join(`
2
+ `),s=r.map(({path:i,params:o})=>{const e=o.length>0?[...o.map(({name:n,optional:l})=>` ${n}${l?"?":""}: string;`)].join(`
3
+ `):" [key: string]: never;";return` ${JSON.stringify(i)}: {
4
+ ${e}
5
+ };`}).join(`
6
+ `);return`declare module 'virtual:next-vite-router' {
7
+ import type { ReactElement } from 'react';
8
+ import type { RouteObject } from 'react-router-dom';
9
+
10
+ export type RoutePath =
11
+ ${a||' | "/"'};
12
+
13
+ export type RouteParams<P extends RoutePath> = P extends keyof RoutePathParams
14
+ ? RoutePathParams[P]
15
+ : never;
16
+
17
+ export interface RoutePathParams {
18
+ ${s}
19
+ }
20
+
21
+ export const generateRoutes: () => RouteObject[];
22
+ export function Router(): ReactElement | null;
23
+ export function useParams<T extends Record<string, string | undefined> = Record<string, string | undefined>>(): T;
24
+ export { Link, useRoutes } from 'react-router-dom';
25
+ }
26
+ `}function j(r={}){const{pagesDir:a="src/app",pageFile:s="page.tsx",layoutFile:i="layout.tsx",notFoundFile:o="not-found.tsx"}=r;T({pagesDir:a,pageFile:s,layoutFile:i,notFoundFile:o});const e=k(a),n={pagesDir:e,pageFile:s,layoutFile:i,notFoundFile:o},l="virtual:next-vite-router",g="\0"+l;let p="",m="",u,P,h;function v(){const t=O(G(p,n,e));try{b.mkdirSync(f.dirname(m),{recursive:!0}),b.writeFileSync(m,t,"utf-8")}catch(d){const c=`next-vite-router could not write route types to ${m}: ${d instanceof Error?d.message:String(d)}`;u?u.warn(c):console.warn(c)}}return{name:"next-vite-router",configResolved(t){p=f.resolve(t.root,a),m=f.join(f.dirname(p),"virtual-next-vite-router.d.ts"),u=t.logger,x.assertNoRouteConflicts(R(p,s,e),R(p,o,e),n),v()},configureServer(t){if(P===t)return;h==null||h(),P=t;const d=$=>{if(f.relative(p,$).startsWith("..")||![s,i,o].includes(f.basename($)))return;try{x.assertNoRouteConflicts(R(p,s,e),R(p,o,e),n)}catch(F){t.config.logger.error(F instanceof Error?F.message:String(F));return}v();const w=t.moduleGraph.getModuleById(g);w&&(t.moduleGraph.invalidateModule(w),c&&clearTimeout(c),c=setTimeout(()=>{c=void 0,t.ws.send({type:"full-reload",path:"*"})},0))};let c;const y=()=>{c&&(clearTimeout(c),c=void 0),t.watcher.off("add",d),t.watcher.off("unlink",d),t.watcher.off("close",y),P===t&&(P=void 0,h=void 0)};t.watcher.on("add",d),t.watcher.on("unlink",d),t.watcher.once("close",y),h=y},resolveId(t){if(t===l)return g},load(t){if(t===g){const d=`/${e}/**/${s}`,c=`/${e}/**/${i}`,y=`/${e}/**/${o}`;return`
27
+ import { buildRouteTree, treeToRoutes, normalizeSegment, createLazyElement, useParams } from 'next-vite-router';
28
+ import { Link, useRoutes } from 'react-router-dom';
29
+
30
+ const pagesGlob = import.meta.glob(${JSON.stringify(d)});
31
+ const layoutsGlob = import.meta.glob(${JSON.stringify(c)}, { eager: true });
32
+ const notFoundsGlob = import.meta.glob(${JSON.stringify(y)});
33
+
34
+ // Keep the raw root-relative glob keys. extractRoutePath (via buildRouteTree)
35
+ // is the single place that strips the pagesDir prefix; stripping it here as
36
+ // well would double-remove it and let the prefix regex match a same-named
37
+ // nested folder (e.g. pagesDir "app" turning "app/docs/app" into "/").
38
+ const pages = pagesGlob;
39
+ const layouts = layoutsGlob;
40
+ const notFounds = notFoundsGlob;
41
+
42
+ export function generateRoutes() {
43
+ const tree = buildRouteTree(pages, layouts, notFounds, ${JSON.stringify(n)});
44
+ const routes = treeToRoutes(tree, true);
45
+ return routes;
46
+ }
47
+
48
+ export function Router() {
49
+ routes ??= generateRoutes();
50
+ return useRoutes(routes);
51
+ }
52
+
53
+ let routes;
54
+
55
+ if (import.meta.hot) {
56
+ import.meta.hot.accept(() => {
57
+ routes = undefined;
58
+ });
59
+ }
60
+
61
+ export { Link, useRoutes };
62
+ export { useParams };
63
+ `}}}}exports.nextViteRouter=j;
@@ -0,0 +1,12 @@
1
+ import { Plugin as Plugin_2 } from 'vite';
2
+
3
+ export declare function nextViteRouter(options?: NextViteRouterPluginOptions): Plugin_2;
4
+
5
+ export declare type NextViteRouterPluginOptions = {
6
+ pagesDir?: string;
7
+ pageFile?: string;
8
+ layoutFile?: string;
9
+ notFoundFile?: string;
10
+ };
11
+
12
+ export { }
package/dist/plugin.js CHANGED
@@ -1,77 +1,73 @@
1
1
  import P from "node:fs";
2
- import l from "node:path";
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);
2
+ import f from "node:path";
3
+ import { c as w, e as T, p as k, r as G } from "./tree-DfnssFxU.js";
4
+ function S(r) {
5
+ const a = r.map(G).filter(Boolean);
6
6
  return a.length > 0 ? `/${a.join("/")}` : "/";
7
7
  }
8
- function w(n) {
8
+ function O(r) {
9
9
  const a = [
10
- ["pagesDir", n.pagesDir],
11
- ["pageFile", n.pageFile],
12
- ["layoutFile", n.layoutFile],
13
- ["notFoundFile", n.notFoundFile]
10
+ ["pagesDir", r.pagesDir],
11
+ ["pageFile", r.pageFile],
12
+ ["layoutFile", r.layoutFile],
13
+ ["notFoundFile", r.notFoundFile]
14
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)
15
+ for (const [o, e] of a)
16
+ if (!e.trim())
17
+ throw new Error(`nextViteRouter option "${o}" must not be empty.`);
18
+ const s = a.slice(1), i = s.find(
19
+ ([, o], e) => s.slice(e + 1).some(([, n]) => n === o)
20
20
  );
21
21
  if (i)
22
22
  throw new Error(
23
23
  `nextViteRouter file options must be distinct; "${i[0]}" is duplicated.`
24
24
  );
25
25
  }
26
- function O(n) {
27
- return n.replace(/\\/g, "/").replace(/^\/|\/$/g, "");
26
+ function j(r) {
27
+ return r.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/|\/$/g, "");
28
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: [] });
50
- }
51
- }
29
+ function D(r, a, s) {
30
+ const i = a.pageFile ?? "page.tsx", o = /* @__PURE__ */ new Map();
31
+ for (const e of R(r, i, s)) {
32
+ const n = T(e, a), l = k(n), g = l.filter(
33
+ (u) => u.kind === "dynamic" || u.kind === "catchAll"
34
+ ).map((u) => ({
35
+ name: u.name,
36
+ optional: u.kind === "catchAll" && u.optional
37
+ })), p = S(l);
38
+ o.set(p, { path: p, params: g });
39
+ const m = l[l.length - 1];
40
+ if ((m == null ? void 0 : m.kind) === "catchAll" && m.optional) {
41
+ const u = S(l.slice(0, -1));
42
+ o.has(u) || o.set(u, { path: u, params: [] });
52
43
  }
53
44
  }
54
- return P.existsSync(n) && i(n), [...r.values()].sort((e, o) => e.path.localeCompare(o.path));
45
+ return [...o.values()].sort((e, n) => e.path.localeCompare(n.path));
55
46
  }
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));
47
+ function R(r, a, s) {
48
+ const i = [];
49
+ function o(e) {
50
+ for (const n of P.readdirSync(e, { withFileTypes: !0 })) {
51
+ const l = f.join(e, n.name);
52
+ if (n.isDirectory())
53
+ o(l);
54
+ else if (n.isFile() && n.name === a) {
55
+ const g = f.relative(r, l).replace(/\\/g, "/");
56
+ i.push(`/${s}/${g}`);
57
+ }
62
58
  }
63
59
  }
64
- return P.existsSync(n) && i(n), r;
60
+ return P.existsSync(r) && o(r), i;
65
61
  }
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;`)
62
+ function N(r) {
63
+ const a = r.map(({ path: i }) => ` | ${JSON.stringify(i)}`).join(`
64
+ `), s = r.map(({ path: i, params: o }) => {
65
+ const e = o.length > 0 ? [
66
+ ...o.map(({ name: n, optional: l }) => ` ${n}${l ? "?" : ""}: string;`)
71
67
  ].join(`
72
68
  `) : " [key: string]: never;";
73
69
  return ` ${JSON.stringify(i)}: {
74
- ${o}
70
+ ${e}
75
71
  };`;
76
72
  }).join(`
77
73
  `);
@@ -87,7 +83,7 @@ ${a || ' | "/"'};
87
83
  : never;
88
84
 
89
85
  export interface RoutePathParams {
90
- ${r}
86
+ ${s}
91
87
  }
92
88
 
93
89
  export const generateRoutes: () => RouteObject[];
@@ -97,89 +93,90 @@ ${r}
97
93
  }
98
94
  `;
99
95
  }
100
- function M(n = {}) {
96
+ function L(r = {}) {
101
97
  const {
102
98
  pagesDir: a = "src/app",
103
- pageFile: r = "page.tsx",
99
+ pageFile: s = "page.tsx",
104
100
  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");
101
+ notFoundFile: o = "not-found.tsx"
102
+ } = r;
103
+ O({ pagesDir: a, pageFile: s, layoutFile: i, notFoundFile: o });
104
+ const e = j(a), n = { pagesDir: e, pageFile: s, layoutFile: i, notFoundFile: o }, l = "virtual:next-vite-router", g = "\0" + l;
105
+ let p = "", m = "", u, x, h;
106
+ function $() {
107
+ const t = N(D(p, n, e));
108
+ try {
109
+ P.mkdirSync(f.dirname(m), { recursive: !0 }), P.writeFileSync(m, t, "utf-8");
110
+ } catch (d) {
111
+ const c = `next-vite-router could not write route types to ${m}: ${d instanceof Error ? d.message : String(d)}`;
112
+ u ? u.warn(c) : console.warn(c);
113
+ }
113
114
  }
114
115
  return {
115
116
  name: "next-vite-router",
116
117
  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();
118
+ p = f.resolve(t.root, a), m = f.join(f.dirname(p), "virtual-next-vite-router.d.ts"), u = t.logger, w(
119
+ R(p, s, e),
120
+ R(p, o, e),
121
+ n
122
+ ), $();
122
123
  },
123
124
  configureServer(t) {
124
- if (m === t)
125
+ if (x === t)
125
126
  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)))
127
+ h == null || h(), x = t;
128
+ const d = (b) => {
129
+ if (f.relative(p, b).startsWith("..") || ![s, i, o].includes(f.basename(b)))
129
130
  return;
130
131
  try {
131
- $(
132
- b(s, r),
133
- b(s, e),
134
- u
132
+ w(
133
+ R(p, s, e),
134
+ R(p, o, e),
135
+ n
135
136
  );
136
- } catch (v) {
137
+ } catch (F) {
137
138
  t.config.logger.error(
138
- v instanceof Error ? v.message : String(v)
139
+ F instanceof Error ? F.message : String(F)
139
140
  );
140
141
  return;
141
142
  }
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: "*" });
143
+ $();
144
+ const v = t.moduleGraph.getModuleById(g);
145
+ v && (t.moduleGraph.invalidateModule(v), c && clearTimeout(c), c = setTimeout(() => {
146
+ c = void 0, t.ws.send({ type: "full-reload", path: "*" });
146
147
  }, 0));
147
148
  };
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);
149
+ let c;
150
+ const y = () => {
151
+ c && (clearTimeout(c), c = void 0), t.watcher.off("add", d), t.watcher.off("unlink", d), t.watcher.off("close", y), x === t && (x = void 0, h = void 0);
151
152
  };
152
- t.watcher.on("add", h), t.watcher.on("unlink", h), t.watcher.once("close", g), f = g;
153
+ t.watcher.on("add", d), t.watcher.on("unlink", d), t.watcher.once("close", y), h = y;
153
154
  },
154
155
  resolveId(t) {
155
- if (t === y)
156
- return d;
156
+ if (t === l)
157
+ return g;
157
158
  },
158
159
  load(t) {
159
- if (t === d) {
160
- const h = `/${o}/**/${r}`, p = `/${o}/**/${i}`, g = `/${o}/**/${e}`, x = `/${o}/`;
160
+ if (t === g) {
161
+ const d = `/${e}/**/${s}`, c = `/${e}/**/${i}`, y = `/${e}/**/${o}`;
161
162
  return `
162
163
  import { buildRouteTree, treeToRoutes, normalizeSegment, createLazyElement, useParams } from 'next-vite-router';
163
164
  import { Link, useRoutes } from 'react-router-dom';
164
165
 
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)});
168
-
169
- const transformPath = (path) => path.replace(${JSON.stringify(x)}, '');
166
+ const pagesGlob = import.meta.glob(${JSON.stringify(d)});
167
+ const layoutsGlob = import.meta.glob(${JSON.stringify(c)}, { eager: true });
168
+ const notFoundsGlob = import.meta.glob(${JSON.stringify(y)});
170
169
 
171
- const pages = Object.fromEntries(
172
- Object.entries(pagesGlob).map(([key, value]) => [transformPath(key), value])
173
- );
174
- const layouts = Object.fromEntries(
175
- Object.entries(layoutsGlob).map(([key, value]) => [transformPath(key), value])
176
- );
177
- const notFounds = Object.fromEntries(
178
- Object.entries(notFoundsGlob).map(([key, value]) => [transformPath(key), value])
179
- );
170
+ // Keep the raw root-relative glob keys. extractRoutePath (via buildRouteTree)
171
+ // is the single place that strips the pagesDir prefix; stripping it here as
172
+ // well would double-remove it and let the prefix regex match a same-named
173
+ // nested folder (e.g. pagesDir "app" turning "app/docs/app" into "/").
174
+ const pages = pagesGlob;
175
+ const layouts = layoutsGlob;
176
+ const notFounds = notFoundsGlob;
180
177
 
181
178
  export function generateRoutes() {
182
- const tree = buildRouteTree(pages, layouts, notFounds, ${JSON.stringify(u)});
179
+ const tree = buildRouteTree(pages, layouts, notFounds, ${JSON.stringify(n)});
183
180
  const routes = treeToRoutes(tree, true);
184
181
  return routes;
185
182
  }
@@ -205,5 +202,5 @@ export { useParams };
205
202
  };
206
203
  }
207
204
  export {
208
- M as nextViteRouter
205
+ L as nextViteRouter
209
206
  };
@@ -27,9 +27,9 @@ function m(t) {
27
27
  return o;
28
28
  }
29
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("|");
30
+ const n = t.replace(/\\/g, "/"), e = s == null ? void 0 : s.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/|\/$/g, ""), r = e == null ? void 0 : e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), i = [o, c, a].map((l) => l.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
31
31
  return n.replace(
32
- e ? new RegExp(`^.*(?:^|/)${r}/`) : /^.*(?:^|\/)(?:app|pages)\//,
32
+ e ? new RegExp(`^.*?(?:^|/)${r}/`) : /^.*?(?:^|\/)(?:app|pages)\//,
33
33
  ""
34
34
  ).replace(/\\/g, "/").replace(new RegExp(`/?(?:${i})$`), "");
35
35
  }
@@ -0,0 +1,10 @@
1
+ "use strict";function w(t){return/^\([^)]+\)$/.test(t)?"":/^\[\[\.\.\.(.+)\]\]$/.test(t)||/^\[\.\.\.(.+)\]$/.test(t)?"*":/^\[([^\]]+)\]$/.test(t)?t.replace(/^\[([^\]]+)\]$/,":$1"):t}const R=/^[A-Za-z_][A-Za-z0-9_]*$/;function $(t){return t.kind==="group"?"":t.kind==="dynamic"?`:${t.name}`:t.kind==="catchAll"?"*":t.name}function d(t){const s=t?t.split("/").filter(Boolean):[],o=[],c=new Set;for(const a of s){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;if(n)o.push({kind:"group",name:n[1]});else if(l){if(!R.test(l.name))throw new Error(`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.`);if(c.has(l.name))throw new Error(`Duplicate route parameter "${l.name}" in route "${t||"/"}".`);c.add(l.name),o.push(l)}else o.push({kind:"static",name:a})}return o}function u(t,{pagesDir:s,pageFile:o="page.tsx",layoutFile:c="layout.tsx",notFoundFile:a="not-found.tsx"}={}){const n=t.replace(/\\/g,"/"),e=s==null?void 0:s.replace(/\\/g,"/").replace(/^\.\//,"").replace(/^\/|\/$/g,""),r=e==null?void 0:e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),i=[o,c,a].map(l=>l.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")).join("|");return n.replace(e?new RegExp(`^.*?(?:^|/)${r}/`):/^.*?(?:^|\/)(?:app|pages)\//,"").replace(/\\/g,"/").replace(new RegExp(`/?(?:${i})$`),"")}function g(t){return t.kind==="group"?"":t.kind==="catchAll"?"*":t.kind==="dynamic"?":param":$(t)}function P(t,s){const o=u(t,s),c=d(o),a=c.map(g).filter(Boolean),n=[a.length>0?`/${a.join("/")}`:"/"],e=c[c.length-1];if((e==null?void 0:e.kind)==="catchAll"&&e.optional){const r=a.slice(0,-1);n.push(r.length>0?`/${r.join("/")}`:"/")}return n}function j(t){return/(^|\/)(:param|\*)($|\/)/.test(t)?"pattern":"literal"}function E(t,s,o){const c=t.flatMap(n=>{const e=u(n,o),r=d(e),i=r[r.length-1];if((i==null?void 0:i.kind)!=="catchAll")return[];const l=r.map(g).filter(Boolean),h=l.length>0?`/${l.join("/")}`:"/",m=l.slice(0,-1),A=m.length>0?`/${m.join("/")}`:"/";return[{filePath:n,paths:new Set([h,A])}]}),a=new Map;for(const n of s)for(const e of P(n,o))for(const r of c)if(r.paths.has(e)){const i=`${e}\0${r.filePath}\0${n}`;a.set(i,{routePath:e,pageFile:r.filePath,notFoundFile:n})}if(a.size>0)throw new Error([...a.values()].sort((n,e)=>n.routePath.localeCompare(e.routePath)).map(({routePath:n,pageFile:e,notFoundFile:r})=>`Conflicting fallback routes "${n}" detected:
2
+ catch-all page: ${e}
3
+ not-found file: ${r}
4
+ A not-found route and a catch-all page cannot coexist at the same route level.`).join(`
5
+
6
+ `))}function p(t,s){const o=new Map;for(const a of t)for(const n of P(a,s)){const e=o.get(n)??[];e.push({kind:j(n),filePath:a}),o.set(n,e)}const c=[...o.entries()].filter(([,a])=>a.length>1).sort(([a],[n])=>a.localeCompare(n));if(c.length>0)throw new Error(c.map(([a,n])=>`${n[0].kind==="pattern"?"Ambiguous route pattern":"Duplicate route"} "${a}" detected:
7
+ ${n.sort((e,r)=>e.filePath.localeCompare(r.filePath)).map(e=>` ${e.filePath}`).join(`
8
+ `)}`).join(`
9
+
10
+ `))}function k(t,s,o){p(t,o),p(s,o),E(t,s,o)}function f(t,s,o,c,a){const n=u(s,a),e=n?n.split("/"):[];let r=t,i="";e.forEach((l,h)=>{i=i?`${i}/${l}`:l,r.children.has(l)||r.children.set(l,{segment:l,fullPath:i,children:new Map}),r=r.children.get(l),h===e.length-1&&(r[c]=o)}),e.length===0&&(t[c]=o)}function b(t,s,o,c){k(Object.keys(t),Object.keys(o),c);const a={segment:"",fullPath:"",children:new Map};return Object.entries(s).forEach(([n,e])=>{f(a,n,e.default,"layout",c)}),Object.entries(t).forEach(([n,e])=>{f(a,n,e,"page",c)}),Object.entries(o).forEach(([n,e])=>{f(a,n,e,"notFound",c)}),a}exports.assertNoDuplicateRoutes=p;exports.assertNoRouteConflicts=k;exports.buildRouteTree=b;exports.extractRoutePath=u;exports.normalizeSegment=w;exports.parseRouteSegments=d;exports.routeSegmentToPath=$;
package/package.json CHANGED
@@ -1,22 +1,34 @@
1
1
  {
2
2
  "name": "next-vite-router",
3
- "version": "1.2.4",
3
+ "version": "1.3.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
8
  "sideEffects": false,
9
9
  "bin": {
10
- "create-next-vite-router": "./dist/cli.cjs"
10
+ "next-vite-router": "./dist/cli.cjs"
11
11
  },
12
12
  "exports": {
13
13
  ".": {
14
- "import": "./dist/index.js",
15
- "types": "./dist/index.d.ts"
14
+ "import": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ },
18
+ "require": {
19
+ "types": "./dist/index.d.cts",
20
+ "default": "./dist/index.cjs"
21
+ }
16
22
  },
17
23
  "./plugin": {
18
- "import": "./dist/plugin.js",
19
- "types": "./dist/plugin.d.ts"
24
+ "import": {
25
+ "types": "./dist/plugin.d.ts",
26
+ "default": "./dist/plugin.js"
27
+ },
28
+ "require": {
29
+ "types": "./dist/plugin.d.cts",
30
+ "default": "./dist/plugin.cjs"
31
+ }
20
32
  }
21
33
  },
22
34
  "files": [
@@ -36,7 +48,7 @@
36
48
  "author": "Rlzee",
37
49
  "repository": {
38
50
  "type": "git",
39
- "url": "https://github.com/Rlzee/next-vite-router"
51
+ "url": "git+https://github.com/Rlzee/next-vite-router.git"
40
52
  },
41
53
  "homepage": "https://github.com/Rlzee/next-vite-router#readme",
42
54
  "license": "MIT",
@@ -62,7 +74,8 @@
62
74
  },
63
75
  "scripts": {
64
76
  "dev": "vite build --watch",
65
- "build": "vite build && vite build -c vite.config.cli.ts",
77
+ "build": "vite build && vite build -c vite.config.cli.ts && node -e \"import fs from 'node:fs'; for (const f of ['index','plugin']) fs.copyFileSync('dist/'+f+'.d.ts','dist/'+f+'.d.cts')\"",
78
+ "test": "pnpm run test:params && pnpm run test:generator && pnpm run test:routes && pnpm run test:local && pnpm run test:compatibility",
66
79
  "test:local": "node test/test-package.js",
67
80
  "test:routes": "node test/test-route-types.js",
68
81
  "test:generator": "node --test test/routes.test.js",