@troyio/vite-file-router 0.1.0 → 0.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 +12 -19
- package/dist/generator.d.ts +5 -1
- package/dist/index.js +62 -14
- package/dist/scanner.d.ts +1 -0
- package/package.json +14 -5
package/README.md
CHANGED
|
@@ -4,30 +4,17 @@ A Vite plugin that generates a route tree from a file-based page directory.
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
The package is published to GitHub Packages on every push to `main`, under
|
|
8
|
-
`@OWNER/vite-file-router`. Configure npm to use GitHub Packages for that scope
|
|
9
|
-
and authenticate with a GitHub token that has `read:packages` access:
|
|
10
|
-
|
|
11
|
-
```ini
|
|
12
|
-
@OWNER:registry=https://npm.pkg.github.com
|
|
13
|
-
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
Then install it with:
|
|
17
|
-
|
|
18
7
|
```sh
|
|
19
|
-
npm install @
|
|
8
|
+
npm install @troyio/vite-file-router
|
|
20
9
|
```
|
|
21
10
|
|
|
22
|
-
Each main-branch build is published with a unique prerelease version.
|
|
23
|
-
|
|
24
11
|
Vite is a peer dependency and must also be installed in your project.
|
|
25
12
|
|
|
26
13
|
## Configure
|
|
27
14
|
|
|
28
15
|
```ts
|
|
29
16
|
import { defineConfig } from "vite";
|
|
30
|
-
import { routes } from "@
|
|
17
|
+
import { routes } from "@troyio/vite-file-router";
|
|
31
18
|
|
|
32
19
|
export default defineConfig({
|
|
33
20
|
plugins: [routes()],
|
|
@@ -41,14 +28,20 @@ project root to use another directory:
|
|
|
41
28
|
plugins: [routes("src/routes")];
|
|
42
29
|
```
|
|
43
30
|
|
|
44
|
-
Each `page.tsx` becomes a route page, and each `layout.
|
|
45
|
-
layout for its directory. Directories become nested
|
|
31
|
+
Each `page.jsx` or `page.tsx` becomes a route page, and each `layout.jsx` or
|
|
32
|
+
`layout.tsx` becomes a lazy layout for its directory. Directories become nested
|
|
33
|
+
routes. Add a `loading.jsx` or `loading.tsx` to provide a Suspense fallback for
|
|
34
|
+
that directory's page and nested routes; a nested loading file provides a more
|
|
35
|
+
specific fallback. Each file must default-export a React component. If both
|
|
36
|
+
extensions exist for the same component, `.tsx` takes precedence. For example:
|
|
46
37
|
|
|
47
38
|
```text
|
|
48
39
|
src/pages/
|
|
49
|
-
layout.
|
|
50
|
-
|
|
40
|
+
layout.jsx
|
|
41
|
+
loading.jsx
|
|
42
|
+
page.jsx
|
|
51
43
|
users/
|
|
44
|
+
loading.tsx
|
|
52
45
|
page.tsx
|
|
53
46
|
[id]/
|
|
54
47
|
page.tsx
|
package/dist/generator.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
import type { Node } from "./scanner";
|
|
2
|
-
export
|
|
2
|
+
export type GeneratedRoutes = {
|
|
3
|
+
imports: string[];
|
|
4
|
+
routes: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function generate(node: Node, root?: boolean): GeneratedRoutes;
|
package/dist/index.js
CHANGED
|
@@ -17,8 +17,11 @@ function scan(directory) {
|
|
|
17
17
|
node.children.push(scan(file));
|
|
18
18
|
continue;
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
if (
|
|
20
|
+
const match = /^(page|layout|loading)\.(jsx|tsx)$/.exec(entry.name);
|
|
21
|
+
if (!match) continue;
|
|
22
|
+
const [, name, extension] = match;
|
|
23
|
+
const key = name;
|
|
24
|
+
if (!node[key] || extension === "tsx") node[key] = file;
|
|
22
25
|
}
|
|
23
26
|
return node;
|
|
24
27
|
}
|
|
@@ -29,32 +32,77 @@ function getRoutePath(directory) {
|
|
|
29
32
|
return segment.replace(/^\[(.+)\]$/, ":$1");
|
|
30
33
|
}
|
|
31
34
|
function generateImport(file) {
|
|
32
|
-
return `async ()=>({
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
return `async () => ({
|
|
36
|
+
Component: (await import(${JSON.stringify(normalizePath(file))})).default
|
|
37
|
+
})`;
|
|
35
38
|
}
|
|
36
|
-
function
|
|
39
|
+
function generateLazyComponent(file) {
|
|
40
|
+
return `lazy(() => import(${JSON.stringify(normalizePath(file))}))`;
|
|
41
|
+
}
|
|
42
|
+
function generateNode(node, root, context, inheritedBoundary) {
|
|
37
43
|
const route = {};
|
|
44
|
+
const hasBoundary = inheritedBoundary || Boolean(node.loading);
|
|
38
45
|
if (!root) route.path = JSON.stringify(getRoutePath(node.directory));
|
|
39
|
-
|
|
46
|
+
let loadingComponent;
|
|
47
|
+
if (node.loading) {
|
|
48
|
+
loadingComponent = `Loading${context.nextLoadingId++}`;
|
|
49
|
+
context.loadingImports.push(`import ${loadingComponent} from ${JSON.stringify(normalizePath(node.loading))};`);
|
|
50
|
+
context.hasBoundaries = true;
|
|
51
|
+
}
|
|
52
|
+
if (loadingComponent) {
|
|
53
|
+
const child = node.layout ? generateLazyComponent(node.layout) : "Outlet";
|
|
54
|
+
route.Component = `createBoundary(${loadingComponent}, ${child})`;
|
|
55
|
+
} else if (node.layout) {
|
|
56
|
+
if (hasBoundary) route.Component = generateLazyComponent(node.layout);
|
|
57
|
+
else route.lazy = generateImport(node.layout);
|
|
58
|
+
}
|
|
40
59
|
const children = [];
|
|
41
|
-
if (node.page)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
if (node.page) {
|
|
61
|
+
const page = hasBoundary ? {
|
|
62
|
+
index: "true",
|
|
63
|
+
Component: generateLazyComponent(node.page)
|
|
64
|
+
} : {
|
|
65
|
+
index: "true",
|
|
66
|
+
lazy: generateImport(node.page)
|
|
67
|
+
};
|
|
68
|
+
children.push(genObjectFromRaw(page));
|
|
69
|
+
}
|
|
70
|
+
for (const child of node.children) children.push(generateNode(child, false, context, hasBoundary));
|
|
46
71
|
if (children.length) route.children = genArrayFromRaw(children);
|
|
47
72
|
return genObjectFromRaw(route);
|
|
48
73
|
}
|
|
74
|
+
function generate(node, root = false) {
|
|
75
|
+
const context = {
|
|
76
|
+
loadingImports: [],
|
|
77
|
+
nextLoadingId: 0,
|
|
78
|
+
hasBoundaries: false
|
|
79
|
+
};
|
|
80
|
+
const routes = generateNode(node, root, context, false);
|
|
81
|
+
const imports = [...context.loadingImports];
|
|
82
|
+
if (context.hasBoundaries) imports.unshift(`import { createElement, lazy, Suspense } from "react";`, `import { Outlet } from "react-router";`, `function createBoundary(Fallback, Component) {
|
|
83
|
+
return function RouteBoundary() {
|
|
84
|
+
return createElement(
|
|
85
|
+
Suspense,
|
|
86
|
+
{ fallback: createElement(Fallback) },
|
|
87
|
+
createElement(Component),
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
}`);
|
|
91
|
+
return {
|
|
92
|
+
imports,
|
|
93
|
+
routes
|
|
94
|
+
};
|
|
95
|
+
}
|
|
49
96
|
// src/index.ts
|
|
50
97
|
var MODULE_ID = "virtual:routes";
|
|
51
98
|
var RESOLVED_ID = `\0${MODULE_ID}`;
|
|
52
99
|
function routes(directory = "src/pages") {
|
|
53
100
|
let root = "";
|
|
54
101
|
function execute() {
|
|
55
|
-
const
|
|
102
|
+
const generated = generate(scan(root), true);
|
|
56
103
|
return `
|
|
57
|
-
|
|
104
|
+
${generated.imports.join("\n")}
|
|
105
|
+
export const routes = ${genArrayFromRaw([generated.routes])};
|
|
58
106
|
`;
|
|
59
107
|
}
|
|
60
108
|
return {
|
package/dist/scanner.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@troyio/vite-file-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "File-system routing plugin for Vite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
|
+
"keywords": [
|
|
8
|
+
"vite",
|
|
9
|
+
"react",
|
|
10
|
+
"routing",
|
|
11
|
+
"react-router"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
7
14
|
"publishConfig": {
|
|
8
15
|
"access": "public"
|
|
9
16
|
},
|
|
@@ -21,7 +28,8 @@
|
|
|
21
28
|
},
|
|
22
29
|
"scripts": {
|
|
23
30
|
"build": "vite build && tsc -p tsconfig.build.json",
|
|
24
|
-
"lint": "eslint src vite.config.ts"
|
|
31
|
+
"lint": "eslint src vite.config.ts",
|
|
32
|
+
"prepublish": "pnpm build"
|
|
25
33
|
},
|
|
26
34
|
"dependencies": {
|
|
27
35
|
"knitwork": "^1.3.0",
|
|
@@ -29,15 +37,16 @@
|
|
|
29
37
|
"vite-file-router": "link:"
|
|
30
38
|
},
|
|
31
39
|
"peerDependencies": {
|
|
40
|
+
"react": ">=19.2.7",
|
|
32
41
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
33
42
|
},
|
|
34
43
|
"devDependencies": {
|
|
35
44
|
"@eslint/js": "^10.0.1",
|
|
36
45
|
"@types/node": "^24.19.2",
|
|
37
|
-
"eslint": "^10.
|
|
38
|
-
"globals": "^17.
|
|
46
|
+
"eslint": "^10.14.0",
|
|
47
|
+
"globals": "^17.14.0",
|
|
39
48
|
"typescript": "~6.0.3",
|
|
40
|
-
"typescript-eslint": "^8.72.
|
|
49
|
+
"typescript-eslint": "^8.72.1",
|
|
41
50
|
"vite": "^8.3.5"
|
|
42
51
|
}
|
|
43
52
|
}
|