Skip to content

Commit cfd0342

Browse files
committed
feat: 支持基于文件系统的路由
1 parent 91064c7 commit cfd0342

File tree

8 files changed

+791
-0
lines changed

8 files changed

+791
-0
lines changed

Diff for: packages/unplugin-react-pages/src/const.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const pkgName = process.env.PKG_NAME;
2+
export const pkgVersion = process.env.PKG_VERSION;
3+
export const officialLink = 'https://github.com/FrontEndDev-org/vite-plugin-react-app';

Diff for: packages/unplugin-react-pages/src/core/FSRouter.ts

+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import { indentLine, jsxLikeStringify } from '../helpers';
2+
import type { FSNode, FSTree } from './FSTree';
3+
import nodePath from 'path';
4+
5+
class PageRoute {
6+
parent?: LayoutRoute;
7+
constructor(readonly fsNode: FSNode) {}
8+
9+
get id() {
10+
return this.fsNode.id;
11+
}
12+
13+
get path() {
14+
const paths = [this.fsNode.path];
15+
const find = (fsNode?: FSNode) => {
16+
if (!fsNode) return;
17+
18+
// 有分组 depth = 1 结束
19+
if (fsNode.group && fsNode.depth === 1) {
20+
paths.unshift(fsNode.path);
21+
return;
22+
}
23+
24+
// 直到包含路由的节点为止
25+
if (!fsNode.group && fsNode.layoutFileName) return;
26+
27+
paths.unshift(fsNode.path);
28+
find(fsNode.parent);
29+
};
30+
31+
find(this.fsNode.parent);
32+
return paths.length === 0 || (paths.length === 1 && paths.at(0) === '') ? '' : nodePath.join(...paths);
33+
}
34+
35+
render(indent = 0, tabSize = 4) {
36+
const indent_1 = indent * tabSize;
37+
const indent_2 = (indent + 1) * tabSize;
38+
const lines = [
39+
//
40+
indentLine('{', indent_1),
41+
];
42+
43+
// path
44+
lines.push(indentLine(`path: ${JSON.stringify(this.path)},`, indent_2));
45+
46+
// element
47+
const pageName = FSRouter.makePageComponentName(this.fsNode.id);
48+
const element = jsxLikeStringify({
49+
tag: 'Suspense',
50+
props: {},
51+
children: [{ tag: pageName }],
52+
});
53+
lines.push(indentLine(`element: ${element},`, indent_2));
54+
lines.push(indentLine('},', indent_1));
55+
56+
return lines;
57+
}
58+
}
59+
60+
class LayoutRoute {
61+
parent?: LayoutRoute;
62+
children: (LayoutRoute | PageRoute)[] = [];
63+
constructor(readonly fsNode: FSNode) {}
64+
65+
get id() {
66+
return this.fsNode.id;
67+
}
68+
69+
render(indent = 0, tabSize = 4) {
70+
const indent_1 = indent * tabSize;
71+
const indent_2 = (indent + 1) * tabSize;
72+
const lines = [
73+
//
74+
indentLine('{', indent_1),
75+
];
76+
77+
// element
78+
const layoutName = FSRouter.makeLayoutComponentName(this.fsNode.id);
79+
const element = jsxLikeStringify({
80+
tag: 'Suspense',
81+
props: {},
82+
children: [{ tag: layoutName }],
83+
});
84+
lines.push(indentLine(`element: ${element},`, indent_2));
85+
86+
if (this.children.length) {
87+
lines.push(indentLine('children: [', indent_2));
88+
lines.push(...this.children.flatMap((child) => child.render(indent + 2, tabSize)));
89+
lines.push(indentLine('],', indent_2));
90+
}
91+
92+
lines.push(indentLine('},', indent_1));
93+
return lines;
94+
}
95+
}
96+
97+
export class FSRouter {
98+
constructor(readonly fsTree: FSTree) {}
99+
100+
pageRoutes = new Map<number, PageRoute>();
101+
addPageNode(fsNode: FSNode) {
102+
this.pageRoutes.set(fsNode.id, new PageRoute(fsNode));
103+
}
104+
105+
layoutRoutes = new Map<number, LayoutRoute>();
106+
addLayoutNode(fsNode: FSNode) {
107+
this.layoutRoutes.set(fsNode.id, new LayoutRoute(fsNode));
108+
}
109+
110+
build() {
111+
this.pageRoutes.forEach((pageRoute) => {
112+
const find = (node: FSNode) => {
113+
if (node.layoutFileName) {
114+
const layoutRoute = this.layoutRoutes.get(node.id);
115+
116+
if (layoutRoute) {
117+
pageRoute.parent = layoutRoute;
118+
layoutRoute.children.push(pageRoute);
119+
return;
120+
}
121+
}
122+
123+
if (node.parent) find(node.parent);
124+
};
125+
126+
find(pageRoute.fsNode);
127+
});
128+
129+
this.layoutRoutes.forEach((layoutRoute) => {
130+
const find = (node: FSNode) => {
131+
const parent = node.parent;
132+
if (!parent) return;
133+
134+
// 独立分组布局根节点
135+
if (node.group && node.depth === 1) return;
136+
137+
if (parent.layoutFileName) {
138+
const parentRoute = this.layoutRoutes.get(parent.id);
139+
140+
if (parentRoute) {
141+
layoutRoute.parent = parentRoute;
142+
parentRoute.children.push(layoutRoute);
143+
return;
144+
}
145+
}
146+
147+
find(parent);
148+
};
149+
150+
find(layoutRoute.fsNode);
151+
});
152+
}
153+
154+
render(tabSize = 4) {
155+
const importPages = ['// pages'];
156+
const importLayouts = ['// layouts'];
157+
158+
const pageLines: string[] = [];
159+
this.pageRoutes.forEach((route) => {
160+
const importName = FSRouter.makePageComponentName(route.id);
161+
importPages.push(`const ${importName} = lazy(() => import("${route.fsNode.pageFile}"));`);
162+
163+
if (!route.parent) {
164+
pageLines.push(...route.render(0, tabSize));
165+
}
166+
});
167+
168+
const layoutLines: string[] = [];
169+
this.layoutRoutes.forEach((route) => {
170+
const importName = FSRouter.makeLayoutComponentName(route.id);
171+
importLayouts.push(`const ${importName} = lazy(() => import("${route.fsNode.layoutFile}"));`);
172+
173+
if (!route.parent) {
174+
layoutLines.push(...route.render(0, tabSize));
175+
}
176+
});
177+
178+
return [
179+
'/**',
180+
' * generated by pkg-name-for-test@pkg-version-for-test',
181+
' * @ref https://github.com/FrontEndDev-org/vite-plugin-react-app',
182+
' * @ref https://reactrouter.com/',
183+
' */',
184+
'',
185+
'import { Suspense, lazy, createElement } from "react";',
186+
'import { Outlet } from "react-router-dom";',
187+
'',
188+
...importPages,
189+
'',
190+
...importLayouts,
191+
'',
192+
'export const routes = [',
193+
...pageLines,
194+
...layoutLines,
195+
'];',
196+
'',
197+
].join('\n');
198+
}
199+
200+
// TODO
201+
update() {}
202+
203+
static makeLayoutComponentName(id: number) {
204+
return 'Layout_' + id;
205+
}
206+
207+
static makePageComponentName(id: number) {
208+
return 'Page_' + id;
209+
}
210+
}

0 commit comments

Comments
 (0)