src/installers/react-router-mpa.ts 4.7 K raw
1
import path from "node:path";
2
import fs from "fs-extra";
3
import type { ProjectOptions } from "@/types";
4
import yoctoSpinner from "yocto-spinner";
5
import pc from "picocolors";
6
import { consola } from "consola";
7
import { addPackageDependency } from "@/utils/add-package-dependency";
8
import { EXTRAS_DIR } from "@/utils";
9
import { nameGenerator } from "@/utils/name-generator";
10
11
export const reactRouterMpaInstaller = async (
12
	options: Required<ProjectOptions>,
13
): Promise<boolean> => {
14
	const spinner = yoctoSpinner({
15
		text: "Setting up React Router MPA...",
16
	}).start();
17
18
	try {
19
		const { projectName, rpc, shadcn, tailwind, tanstackQuery } = options;
20
21
		const projectPath = path.resolve(process.cwd(), projectName);
22
23
		spinner.text = "Installing React Router MPA dependencies...";
24
		await addPackageDependency({
25
			dependencies: [
26
				"react-router",
27
				"@react-router/dev",
28
				"@react-router/node",
29
				"@react-router/serve",
30
				"vite-tsconfig-paths",
31
				"isbot",
32
			],
33
			target: "client",
34
			projectName,
35
		});
36
37
		// Copy root.tsx
38
		const rootTsxSrc = path.join(
39
			EXTRAS_DIR,
40
			"client",
41
			"src",
42
			"root.tsx",
43
			"root.tsx",
44
		);
45
		const rootTsxTarget = path.join(projectPath, "client", "src", "root.tsx");
46
		fs.copySync(rootTsxSrc, rootTsxTarget);
47
48
		// Copy routes.ts
49
		const routesTsSrc = path.join(
50
			EXTRAS_DIR,
51
			"client",
52
			"src",
53
			"routes.ts",
54
			"routes.ts",
55
		);
56
		const routesTsTarget = path.join(projectPath, "client", "src", "routes.ts");
57
		fs.copySync(routesTsSrc, routesTsTarget);
58
59
		// Create routes directory and copy home.tsx
60
		const routesDir = path.join(projectPath, "client", "src", "routes");
61
		fs.ensureDirSync(routesDir);
62
63
		const homeTsxSrc = path.join(
64
			EXTRAS_DIR,
65
			"client",
66
			"src",
67
			"routes",
68
			"home.tsx",
69
			"home.tsx",
70
		);
71
		const homeTsxTarget = path.join(routesDir, "home.tsx");
72
		fs.copySync(homeTsxSrc, homeTsxTarget);
73
74
		// Copy ClientOnly component
75
		const clientOnlySrc = path.join(
76
			EXTRAS_DIR,
77
			"client",
78
			"src",
79
			"components",
80
			"ClientOnly.tsx",
81
			"ClientOnly.tsx",
82
		);
83
		const clientOnlyTarget = path.join(
84
			projectPath,
85
			"client",
86
			"src",
87
			"components",
88
			"ClientOnly.tsx",
89
		);
90
		fs.copySync(clientOnlySrc, clientOnlyTarget);
91
92
		// Update Home component with ClientOnly wrapper
93
		const homeTsxTemplate = nameGenerator("Home.tsx", {
94
			reactroutermpa: true,
95
			rpc,
96
			shadcn,
97
			tailwind,
98
			tanstackQuery,
99
		});
100
101
		const homeComponentSrc = path.join(
102
			EXTRAS_DIR,
103
			"client",
104
			"src",
105
			"components",
106
			"Home.tsx",
107
			homeTsxTemplate,
108
		);
109
		const homeComponentTarget = path.join(
110
			projectPath,
111
			"client",
112
			"src",
113
			"components",
114
			"Home.tsx",
115
		);
116
		fs.copySync(homeComponentSrc, homeComponentTarget);
117
118
		// Remove unused files (App.tsx and main.tsx are not used in MPA setup)
119
		const appTsxPath = path.join(projectPath, "client", "src", "App.tsx");
120
		const mainTsxPath = path.join(projectPath, "client", "src", "main.tsx");
121
122
		if (fs.existsSync(appTsxPath)) {
123
			fs.removeSync(appTsxPath);
124
		}
125
126
		if (fs.existsSync(mainTsxPath)) {
127
			fs.removeSync(mainTsxPath);
128
		}
129
130
		// Copy react-router.config.ts
131
		const reactRouterConfigSrc = path.join(
132
			EXTRAS_DIR,
133
			"client",
134
			"react-router.config.ts",
135
			"react-router.config.ts",
136
		);
137
		const reactRouterConfigTarget = path.join(
138
			projectPath,
139
			"client",
140
			"react-router.config.ts",
141
		);
142
		fs.copySync(reactRouterConfigSrc, reactRouterConfigTarget);
143
144
		// Copy tsconfig.app.json
145
		const tsconfigAppSrc = path.join(
146
			EXTRAS_DIR,
147
			"client",
148
			"tsconfig.app.json",
149
			"tsconfig.app.json",
150
		);
151
		const tsconfigAppTarget = path.join(
152
			projectPath,
153
			"client",
154
			"tsconfig.app.json",
155
		);
156
		fs.copySync(tsconfigAppSrc, tsconfigAppTarget);
157
158
		// Update vite.config.ts
159
		const viteConfigTemplate = nameGenerator("vite.config.ts", {
160
			reactroutermpa: true,
161
			shadcn,
162
			tailwind,
163
		});
164
165
		const viteConfigSrc = path.join(
166
			EXTRAS_DIR,
167
			"client",
168
			"vite.config.ts",
169
			viteConfigTemplate,
170
		);
171
		const viteConfigTarget = path.join(projectPath, "client", "vite.config.ts");
172
		fs.copySync(viteConfigSrc, viteConfigTarget);
173
174
		// Update package.json scripts
175
		const packageJsonPath = path.join(projectPath, "client", "package.json");
176
		const packageJson = fs.readJsonSync(packageJsonPath);
177
178
		packageJson.scripts = {
179
			...packageJson.scripts,
180
			dev: "react-router dev",
181
			build: "react-router typegen && tsc -b && react-router build",
182
			typecheck: "react-router typegen && tsc",
183
		};
184
185
		fs.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
186
187
		spinner.success("React Router MPA setup completed");
188
		return true;
189
	} catch (err: unknown) {
190
		spinner.error("Failed to set up React Router MPA");
191
		if (err instanceof Error) {
192
			consola.error(pc.red("Error:"), err.message);
193
		} else {
194
			consola.error(pc.red("Error: Unknown error"));
195
		}
196
		return false;
197
	}
198
};