Skip to content

Commit 85b0062

Browse files
committed
feat: Adding a file routing template
1 parent 3c77b38 commit 85b0062

30 files changed

+249
-74
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ app-js
44
app-ts
55
app-js-tw
66
app-ts-tw
7+
app-fr
8+
app-fr-tw
79
.DS_Store

CONTRIBUTING.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424

2525
These must all product running applications that can be built (`pnpm build`) and tested (`pnpm test`).
2626

27-
| Command | Description |
28-
| ------------------------------------------------------- | ------------------------------------------ |
29-
| `pnpm start app-js` | Creates a JavaScript app |
30-
| `pnpm start app-ts --template typescript` | Creates a TypeScript app |
31-
| `pnpm start app-js-tw --tailwind` | Creates a JavaScript app with Tailwind CSS |
32-
| `pnpm start app-ts-tw --template typescript --tailwind` | Creates a TypeScript app with Tailwind CSS |
27+
| Command | Description |
28+
| -------------------------------------------------------- | ------------------------------------------------------------------ |
29+
| `pnpm start app-js` | Creates a JavaScript app |
30+
| `pnpm start app-ts --template typescript` | Creates a TypeScript app |
31+
| `pnpm start app-js-tw --tailwind` | Creates a JavaScript app with Tailwind CSS |
32+
| `pnpm start app-ts-tw --template typescript --tailwind` | Creates a TypeScript app with Tailwind CSS |
33+
| `pnpm start app-fr --template file-router` | Creates a TypeScript app with File Based Routing |
34+
| `pnpm start app-fr-tw --template file-router --tailwind` | Creates a TypeScript app with File Based Routing with Tailwind CSS |

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This CLI applications builds Tanstack Start applications that are the functional equivalent of [Create React App](https://create-react-app.dev/).
44

5-
To help accelerate the migration away from create-react-app we created the create-tsrouter-app CLI which is a plug-n-play replacement for CRA.
5+
To help accelerate the migration away from `create-react-app` we created the `create-tsrouter-app` CLI which is a plug-n-play replacement for CRA.
66

77
Instead of:
88

@@ -38,6 +38,10 @@ You can also specify your preferred package manager with `--package-manager` suc
3838

3939
Extensive documentation on using the TanStack Router, migrating to a File Base Routing approach, as well as integrating [@tanstack/react-query](https://tanstack.com/query/latest) and [@tanstack/store](https://tanstack.com/store/latest) be found in the generated `README.md` for your project.
4040

41+
## File Based Routing
42+
43+
By default `create-tsrouter-app` will create a Code Based Routing application. If you want to use File Based Routing then you can specify `--template file-router`. The location of the home page will be `app/routes/index.tsx`.
44+
4145
# Contributing
4246

4347
Check out the [Contributing](CONTRIBUTING.md) guide.

project-template/vite.config.js.ejs

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/index.ts

Lines changed: 116 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,27 @@ import type { PackageManager } from './utils/getPackageManager.js'
1717

1818
const program = new Command()
1919

20+
const CODE_ROUTER = 'code-router'
21+
const FILE_ROUTER = 'file-router'
22+
2023
interface Options {
2124
typescript: boolean
2225
tailwind: boolean
2326
packageManager: PackageManager
27+
mode: typeof CODE_ROUTER | typeof FILE_ROUTER
28+
}
29+
30+
function sortObject(obj: Record<string, string>): Record<string, string> {
31+
return Object.keys(obj)
32+
.sort()
33+
.reduce<Record<string, string>>((acc, key) => {
34+
acc[key] = obj[key]
35+
return acc
36+
}, {})
2437
}
2538

26-
const createCopyFile = (templateDir: string, targetDir: string) =>
27-
async function copyFiles(files: Array<string>) {
39+
function createCopyFile(targetDir: string) {
40+
return async function copyFiles(templateDir: string, files: Array<string>) {
2841
for (const file of files) {
2942
const targetFileName = file.replace('.tw', '')
3043
await copyFile(
@@ -33,33 +46,41 @@ const createCopyFile = (templateDir: string, targetDir: string) =>
3346
)
3447
}
3548
}
49+
}
3650

37-
const createTemplateFile = (
51+
function createTemplateFile(
3852
projectName: string,
3953
options: Required<Options>,
40-
templateDir: string,
4154
targetDir: string,
42-
) =>
43-
async function templateFile(file: string, targetFileName?: string) {
55+
) {
56+
return async function templateFile(
57+
templateDir: string,
58+
file: string,
59+
targetFileName?: string,
60+
) {
4461
const templateValues = {
4562
packageManager: options.packageManager,
4663
projectName: projectName,
4764
typescript: options.typescript,
4865
tailwind: options.tailwind,
4966
js: options.typescript ? 'ts' : 'js',
5067
jsx: options.typescript ? 'tsx' : 'jsx',
68+
fileRouter: options.mode === FILE_ROUTER,
69+
codeRouter: options.mode === CODE_ROUTER,
5170
}
5271

5372
const template = await readFile(resolve(templateDir, file), 'utf-8')
5473
const content = render(template, templateValues)
5574
const target = targetFileName ?? file.replace('.ejs', '')
5675
await writeFile(resolve(targetDir, target), content)
5776
}
77+
}
5878

5979
async function createPackageJSON(
6080
projectName: string,
6181
options: Required<Options>,
6282
templateDir: string,
83+
routerDir: string,
6384
targetDir: string,
6485
) {
6586
let packageJSON = JSON.parse(
@@ -90,25 +111,41 @@ async function createPackageJSON(
90111
},
91112
}
92113
}
114+
if (options.mode === FILE_ROUTER) {
115+
const frPackageJSON = JSON.parse(
116+
await readFile(resolve(routerDir, 'package.fr.json'), 'utf8'),
117+
)
118+
packageJSON = {
119+
...packageJSON,
120+
dependencies: {
121+
...packageJSON.dependencies,
122+
...frPackageJSON.dependencies,
123+
},
124+
}
125+
}
126+
packageJSON.dependencies = sortObject(
127+
packageJSON.dependencies as Record<string, string>,
128+
)
129+
packageJSON.devDependencies = sortObject(
130+
packageJSON.devDependencies as Record<string, string>,
131+
)
93132
await writeFile(
94133
resolve(targetDir, 'package.json'),
95134
JSON.stringify(packageJSON, null, 2),
96135
)
97136
}
98137

99138
async function createApp(projectName: string, options: Required<Options>) {
100-
const templateDir = fileURLToPath(
101-
new URL('../project-template', import.meta.url),
139+
const templateDirBase = fileURLToPath(
140+
new URL('../templates/base', import.meta.url),
141+
)
142+
const templateDirRouter = fileURLToPath(
143+
new URL(`../templates/${options.mode}`, import.meta.url),
102144
)
103145
const targetDir = resolve(process.cwd(), projectName)
104146

105-
const copyFiles = createCopyFile(templateDir, targetDir)
106-
const templateFile = createTemplateFile(
107-
projectName,
108-
options,
109-
templateDir,
110-
targetDir,
111-
)
147+
const copyFiles = createCopyFile(targetDir)
148+
const templateFile = createTemplateFile(projectName, options, targetDir)
112149

113150
intro(`Creating a new TanStack app in ${targetDir}...`)
114151

@@ -118,13 +155,13 @@ async function createApp(projectName: string, options: Required<Options>) {
118155
// Setup the .vscode directory
119156
await mkdir(resolve(targetDir, '.vscode'), { recursive: true })
120157
await copyFile(
121-
resolve(templateDir, '.vscode/settings.json'),
158+
resolve(templateDirBase, '.vscode/settings.json'),
122159
resolve(targetDir, '.vscode/settings.json'),
123160
)
124161

125162
// Fill the public directory
126163
await mkdir(resolve(targetDir, 'public'), { recursive: true })
127-
copyFiles([
164+
copyFiles(templateDirBase, [
128165
'./public/robots.txt',
129166
'./public/favicon.ico',
130167
'./public/manifest.json',
@@ -134,55 +171,85 @@ async function createApp(projectName: string, options: Required<Options>) {
134171

135172
// Make the src directory
136173
await mkdir(resolve(targetDir, 'src'), { recursive: true })
174+
if (options.mode === FILE_ROUTER) {
175+
await mkdir(resolve(targetDir, 'src/routes'), { recursive: true })
176+
}
137177

138178
// Copy in Vite and Tailwind config and CSS
139179
if (!options.tailwind) {
140-
await copyFiles(['./src/App.css'])
180+
await copyFiles(templateDirBase, ['./src/App.css'])
141181
}
142-
await templateFile('./vite.config.js.ejs')
143-
await templateFile('./src/styles.css.ejs')
182+
await templateFile(templateDirBase, './vite.config.js.ejs')
183+
await templateFile(templateDirBase, './src/styles.css.ejs')
144184

145-
copyFiles(['./src/logo.svg'])
185+
copyFiles(templateDirBase, ['./src/logo.svg'])
146186

147187
// Setup the app component. There are four variations, typescript/javascript and tailwind/non-tailwind.
148-
await templateFile(
149-
'./src/App.tsx.ejs',
150-
options.typescript ? undefined : './src/App.jsx',
151-
)
152-
await templateFile(
153-
'./src/App.test.tsx.ejs',
154-
options.typescript ? undefined : './src/App.test.jsx',
155-
)
188+
if (options.mode === FILE_ROUTER) {
189+
copyFiles(templateDirRouter, ['./src/routes/__root.tsx'])
190+
await templateFile(
191+
templateDirBase,
192+
'./src/App.tsx.ejs',
193+
'./src/routes/index.tsx',
194+
)
195+
} else {
196+
await templateFile(
197+
templateDirBase,
198+
'./src/App.tsx.ejs',
199+
options.typescript ? undefined : './src/App.jsx',
200+
)
201+
await templateFile(
202+
templateDirBase,
203+
'./src/App.test.tsx.ejs',
204+
options.typescript ? undefined : './src/App.test.jsx',
205+
)
206+
}
207+
208+
// Create the main entry point
209+
if (options.typescript) {
210+
await templateFile(templateDirRouter, './src/main.tsx.ejs')
211+
} else {
212+
await templateFile(
213+
templateDirRouter,
214+
'./src/main.tsx.ejs',
215+
'./src/main.jsx',
216+
)
217+
}
156218

157219
// Setup the main, reportWebVitals and index.html files
158220
if (options.typescript) {
159-
await templateFile('./src/main.tsx.ejs')
160-
await templateFile('./src/reportWebVitals.ts.ejs')
221+
await templateFile(templateDirBase, './src/reportWebVitals.ts.ejs')
161222
} else {
162-
await templateFile('./src/main.tsx.ejs', './src/main.jsx')
163223
await templateFile(
224+
templateDirBase,
164225
'./src/reportWebVitals.ts.ejs',
165226
'./src/reportWebVitals.js',
166227
)
167228
}
168-
await templateFile('./index.html.ejs')
229+
await templateFile(templateDirBase, './index.html.ejs')
169230

170231
// Setup tsconfig
171232
if (options.typescript) {
172-
await copyFiles(['./tsconfig.json', './tsconfig.dev.json'])
233+
await copyFiles(templateDirBase, ['./tsconfig.json', './tsconfig.dev.json'])
173234
}
174235

175236
// Setup the package.json file, optionally with typescript and tailwind
176-
await createPackageJSON(projectName, options, templateDir, targetDir)
237+
await createPackageJSON(
238+
projectName,
239+
options,
240+
templateDirBase,
241+
templateDirRouter,
242+
targetDir,
243+
)
177244

178245
// Add .gitignore
179246
await copyFile(
180-
resolve(templateDir, 'gitignore'),
247+
resolve(templateDirBase, 'gitignore'),
181248
resolve(targetDir, '.gitignore'),
182249
)
183250

184251
// Create the README.md
185-
await templateFile('README.md.ejs')
252+
await templateFile(templateDirBase, 'README.md.ejs')
186253

187254
// Install dependencies
188255
const s = spinner()
@@ -197,13 +264,17 @@ program
197264
.name('create-tsrouter-app')
198265
.description('CLI to create a new TanStack application')
199266
.argument('<project-name>', 'name of the project')
200-
.option<'typescript' | 'javascript'>(
267+
.option<'typescript' | 'javascript' | 'file-router'>(
201268
'--template <type>',
202-
'project template (typescript/javascript)',
269+
'project template (typescript, javascript, file-router)',
203270
(value) => {
204-
if (value !== 'typescript' && value !== 'javascript') {
271+
if (
272+
value !== 'typescript' &&
273+
value !== 'javascript' &&
274+
value !== 'file-router'
275+
) {
205276
throw new InvalidArgumentError(
206-
`Invalid template: ${value}. Only the following are allowed: typescript, javascript`,
277+
`Invalid template: ${value}. Only the following are allowed: typescript, javascript, file-router`,
207278
)
208279
}
209280
return value
@@ -230,17 +301,19 @@ program
230301
(
231302
projectName: string,
232303
options: {
233-
template: string
304+
template: 'typescript' | 'javascript' | 'file-router'
234305
tailwind: boolean
235306
packageManager: PackageManager
236307
},
237308
) => {
238-
const typescript = options.template === 'typescript'
309+
const typescript =
310+
options.template === 'typescript' || options.template === 'file-router'
239311

240312
createApp(projectName, {
241313
typescript,
242314
tailwind: options.tailwind,
243315
packageManager: options.packageManager,
316+
mode: options.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER,
244317
})
245318
},
246319
)

0 commit comments

Comments
 (0)