create-tsrouter-app 0.0.4 → 0.1.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.
Files changed (31) hide show
  1. package/CONTRIBUTING.md +8 -6
  2. package/README.md +5 -1
  3. package/dist/index.js +90 -44
  4. package/package.json +1 -1
  5. package/src/index.ts +116 -43
  6. package/{project-template → templates/base}/README.md.ejs +36 -4
  7. package/{project-template → templates/base}/package.json +1 -1
  8. package/{project-template → templates/base}/src/App.tsx.ejs +15 -5
  9. package/templates/base/vite.config.js.ejs +15 -0
  10. package/templates/file-router/package.fr.json +5 -0
  11. package/templates/file-router/src/main.tsx.ejs +35 -0
  12. package/templates/file-router/src/routes/__root.tsx +11 -0
  13. package/project-template/vite.config.js.ejs +0 -14
  14. /package/{project-template → templates/base}/.vscode/settings.json +0 -0
  15. /package/{project-template → templates/base}/gitignore +0 -0
  16. /package/{project-template → templates/base}/index.html.ejs +0 -0
  17. /package/{project-template → templates/base}/package.ts.json +0 -0
  18. /package/{project-template → templates/base}/package.tw.json +0 -0
  19. /package/{project-template → templates/base}/public/favicon.ico +0 -0
  20. /package/{project-template → templates/base}/public/logo192.png +0 -0
  21. /package/{project-template → templates/base}/public/logo512.png +0 -0
  22. /package/{project-template → templates/base}/public/manifest.json +0 -0
  23. /package/{project-template → templates/base}/public/robots.txt +0 -0
  24. /package/{project-template → templates/base}/src/App.css +0 -0
  25. /package/{project-template → templates/base}/src/App.test.tsx.ejs +0 -0
  26. /package/{project-template → templates/base}/src/logo.svg +0 -0
  27. /package/{project-template → templates/base}/src/reportWebVitals.ts.ejs +0 -0
  28. /package/{project-template → templates/base}/src/styles.css.ejs +0 -0
  29. /package/{project-template → templates/base}/tsconfig.dev.json +0 -0
  30. /package/{project-template → templates/base}/tsconfig.json +0 -0
  31. /package/{project-template → templates/code-router}/src/main.tsx.ejs +0 -0
package/CONTRIBUTING.md CHANGED
@@ -24,9 +24,11 @@
24
24
 
25
25
  These must all product running applications that can be built (`pnpm build`) and tested (`pnpm test`).
26
26
 
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 |
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This CLI applications builds Tanstack Start applications that are the functional equivalent of [Create React App](https://create-react-app.dev/).
4
4
 
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.
6
6
 
7
7
  Instead of:
8
8
 
@@ -38,6 +38,10 @@ You can also specify your preferred package manager with `--package-manager` suc
38
38
 
39
39
  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.
40
40
 
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
+
41
45
  # Contributing
42
46
 
43
47
  Check out the [Contributing](CONTRIBUTING.md) guide.
package/dist/index.js CHANGED
@@ -8,27 +8,43 @@ import { execa } from 'execa';
8
8
  import { render } from 'ejs';
9
9
  import { SUPPORTED_PACKAGE_MANAGERS, getPackageManager, } from './utils/getPackageManager.js';
10
10
  const program = new Command();
11
- const createCopyFile = (templateDir, targetDir) => async function copyFiles(files) {
12
- for (const file of files) {
13
- const targetFileName = file.replace('.tw', '');
14
- await copyFile(resolve(templateDir, file), resolve(targetDir, targetFileName));
15
- }
16
- };
17
- const createTemplateFile = (projectName, options, templateDir, targetDir) => async function templateFile(file, targetFileName) {
18
- const templateValues = {
19
- packageManager: options.packageManager,
20
- projectName: projectName,
21
- typescript: options.typescript,
22
- tailwind: options.tailwind,
23
- js: options.typescript ? 'ts' : 'js',
24
- jsx: options.typescript ? 'tsx' : 'jsx',
11
+ const CODE_ROUTER = 'code-router';
12
+ const FILE_ROUTER = 'file-router';
13
+ function sortObject(obj) {
14
+ return Object.keys(obj)
15
+ .sort()
16
+ .reduce((acc, key) => {
17
+ acc[key] = obj[key];
18
+ return acc;
19
+ }, {});
20
+ }
21
+ function createCopyFile(targetDir) {
22
+ return async function copyFiles(templateDir, files) {
23
+ for (const file of files) {
24
+ const targetFileName = file.replace('.tw', '');
25
+ await copyFile(resolve(templateDir, file), resolve(targetDir, targetFileName));
26
+ }
27
+ };
28
+ }
29
+ function createTemplateFile(projectName, options, targetDir) {
30
+ return async function templateFile(templateDir, file, targetFileName) {
31
+ const templateValues = {
32
+ packageManager: options.packageManager,
33
+ projectName: projectName,
34
+ typescript: options.typescript,
35
+ tailwind: options.tailwind,
36
+ js: options.typescript ? 'ts' : 'js',
37
+ jsx: options.typescript ? 'tsx' : 'jsx',
38
+ fileRouter: options.mode === FILE_ROUTER,
39
+ codeRouter: options.mode === CODE_ROUTER,
40
+ };
41
+ const template = await readFile(resolve(templateDir, file), 'utf-8');
42
+ const content = render(template, templateValues);
43
+ const target = targetFileName ?? file.replace('.ejs', '');
44
+ await writeFile(resolve(targetDir, target), content);
25
45
  };
26
- const template = await readFile(resolve(templateDir, file), 'utf-8');
27
- const content = render(template, templateValues);
28
- const target = targetFileName ?? file.replace('.ejs', '');
29
- await writeFile(resolve(targetDir, target), content);
30
- };
31
- async function createPackageJSON(projectName, options, templateDir, targetDir) {
46
+ }
47
+ async function createPackageJSON(projectName, options, templateDir, routerDir, targetDir) {
32
48
  let packageJSON = JSON.parse(await readFile(resolve(templateDir, 'package.json'), 'utf8'));
33
49
  packageJSON.name = projectName;
34
50
  if (options.typescript) {
@@ -51,22 +67,35 @@ async function createPackageJSON(projectName, options, templateDir, targetDir) {
51
67
  },
52
68
  };
53
69
  }
70
+ if (options.mode === FILE_ROUTER) {
71
+ const frPackageJSON = JSON.parse(await readFile(resolve(routerDir, 'package.fr.json'), 'utf8'));
72
+ packageJSON = {
73
+ ...packageJSON,
74
+ dependencies: {
75
+ ...packageJSON.dependencies,
76
+ ...frPackageJSON.dependencies,
77
+ },
78
+ };
79
+ }
80
+ packageJSON.dependencies = sortObject(packageJSON.dependencies);
81
+ packageJSON.devDependencies = sortObject(packageJSON.devDependencies);
54
82
  await writeFile(resolve(targetDir, 'package.json'), JSON.stringify(packageJSON, null, 2));
55
83
  }
56
84
  async function createApp(projectName, options) {
57
- const templateDir = fileURLToPath(new URL('../project-template', import.meta.url));
85
+ const templateDirBase = fileURLToPath(new URL('../templates/base', import.meta.url));
86
+ const templateDirRouter = fileURLToPath(new URL(`../templates/${options.mode}`, import.meta.url));
58
87
  const targetDir = resolve(process.cwd(), projectName);
59
- const copyFiles = createCopyFile(templateDir, targetDir);
60
- const templateFile = createTemplateFile(projectName, options, templateDir, targetDir);
88
+ const copyFiles = createCopyFile(targetDir);
89
+ const templateFile = createTemplateFile(projectName, options, targetDir);
61
90
  intro(`Creating a new TanStack app in ${targetDir}...`);
62
91
  // Make the root directory
63
92
  await mkdir(targetDir, { recursive: true });
64
93
  // Setup the .vscode directory
65
94
  await mkdir(resolve(targetDir, '.vscode'), { recursive: true });
66
- await copyFile(resolve(templateDir, '.vscode/settings.json'), resolve(targetDir, '.vscode/settings.json'));
95
+ await copyFile(resolve(templateDirBase, '.vscode/settings.json'), resolve(targetDir, '.vscode/settings.json'));
67
96
  // Fill the public directory
68
97
  await mkdir(resolve(targetDir, 'public'), { recursive: true });
69
- copyFiles([
98
+ copyFiles(templateDirBase, [
70
99
  './public/robots.txt',
71
100
  './public/favicon.ico',
72
101
  './public/manifest.json',
@@ -75,36 +104,50 @@ async function createApp(projectName, options) {
75
104
  ]);
76
105
  // Make the src directory
77
106
  await mkdir(resolve(targetDir, 'src'), { recursive: true });
107
+ if (options.mode === FILE_ROUTER) {
108
+ await mkdir(resolve(targetDir, 'src/routes'), { recursive: true });
109
+ }
78
110
  // Copy in Vite and Tailwind config and CSS
79
111
  if (!options.tailwind) {
80
- await copyFiles(['./src/App.css']);
112
+ await copyFiles(templateDirBase, ['./src/App.css']);
81
113
  }
82
- await templateFile('./vite.config.js.ejs');
83
- await templateFile('./src/styles.css.ejs');
84
- copyFiles(['./src/logo.svg']);
114
+ await templateFile(templateDirBase, './vite.config.js.ejs');
115
+ await templateFile(templateDirBase, './src/styles.css.ejs');
116
+ copyFiles(templateDirBase, ['./src/logo.svg']);
85
117
  // Setup the app component. There are four variations, typescript/javascript and tailwind/non-tailwind.
86
- await templateFile('./src/App.tsx.ejs', options.typescript ? undefined : './src/App.jsx');
87
- await templateFile('./src/App.test.tsx.ejs', options.typescript ? undefined : './src/App.test.jsx');
118
+ if (options.mode === FILE_ROUTER) {
119
+ copyFiles(templateDirRouter, ['./src/routes/__root.tsx']);
120
+ await templateFile(templateDirBase, './src/App.tsx.ejs', './src/routes/index.tsx');
121
+ }
122
+ else {
123
+ await templateFile(templateDirBase, './src/App.tsx.ejs', options.typescript ? undefined : './src/App.jsx');
124
+ await templateFile(templateDirBase, './src/App.test.tsx.ejs', options.typescript ? undefined : './src/App.test.jsx');
125
+ }
126
+ // Create the main entry point
127
+ if (options.typescript) {
128
+ await templateFile(templateDirRouter, './src/main.tsx.ejs');
129
+ }
130
+ else {
131
+ await templateFile(templateDirRouter, './src/main.tsx.ejs', './src/main.jsx');
132
+ }
88
133
  // Setup the main, reportWebVitals and index.html files
89
134
  if (options.typescript) {
90
- await templateFile('./src/main.tsx.ejs');
91
- await templateFile('./src/reportWebVitals.ts.ejs');
135
+ await templateFile(templateDirBase, './src/reportWebVitals.ts.ejs');
92
136
  }
93
137
  else {
94
- await templateFile('./src/main.tsx.ejs', './src/main.jsx');
95
- await templateFile('./src/reportWebVitals.ts.ejs', './src/reportWebVitals.js');
138
+ await templateFile(templateDirBase, './src/reportWebVitals.ts.ejs', './src/reportWebVitals.js');
96
139
  }
97
- await templateFile('./index.html.ejs');
140
+ await templateFile(templateDirBase, './index.html.ejs');
98
141
  // Setup tsconfig
99
142
  if (options.typescript) {
100
- await copyFiles(['./tsconfig.json', './tsconfig.dev.json']);
143
+ await copyFiles(templateDirBase, ['./tsconfig.json', './tsconfig.dev.json']);
101
144
  }
102
145
  // Setup the package.json file, optionally with typescript and tailwind
103
- await createPackageJSON(projectName, options, templateDir, targetDir);
146
+ await createPackageJSON(projectName, options, templateDirBase, templateDirRouter, targetDir);
104
147
  // Add .gitignore
105
- await copyFile(resolve(templateDir, 'gitignore'), resolve(targetDir, '.gitignore'));
148
+ await copyFile(resolve(templateDirBase, 'gitignore'), resolve(targetDir, '.gitignore'));
106
149
  // Create the README.md
107
- await templateFile('README.md.ejs');
150
+ await templateFile(templateDirBase, 'README.md.ejs');
108
151
  // Install dependencies
109
152
  const s = spinner();
110
153
  s.start(`Installing dependencies via ${options.packageManager}...`);
@@ -116,9 +159,11 @@ program
116
159
  .name('create-tsrouter-app')
117
160
  .description('CLI to create a new TanStack application')
118
161
  .argument('<project-name>', 'name of the project')
119
- .option('--template <type>', 'project template (typescript/javascript)', (value) => {
120
- if (value !== 'typescript' && value !== 'javascript') {
121
- throw new InvalidArgumentError(`Invalid template: ${value}. Only the following are allowed: typescript, javascript`);
162
+ .option('--template <type>', 'project template (typescript, javascript, file-router)', (value) => {
163
+ if (value !== 'typescript' &&
164
+ value !== 'javascript' &&
165
+ value !== 'file-router') {
166
+ throw new InvalidArgumentError(`Invalid template: ${value}. Only the following are allowed: typescript, javascript, file-router`);
122
167
  }
123
168
  return value;
124
169
  }, 'javascript')
@@ -130,11 +175,12 @@ program
130
175
  }, getPackageManager())
131
176
  .option('--tailwind', 'add Tailwind CSS', false)
132
177
  .action((projectName, options) => {
133
- const typescript = options.template === 'typescript';
178
+ const typescript = options.template === 'typescript' || options.template === 'file-router';
134
179
  createApp(projectName, {
135
180
  typescript,
136
181
  tailwind: options.tailwind,
137
182
  packageManager: options.packageManager,
183
+ mode: options.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER,
138
184
  });
139
185
  });
140
186
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-tsrouter-app",
3
- "version": "0.0.4",
3
+ "version": "0.1.0",
4
4
  "description": "Tanstack Application Builder",
5
5
  "bin": "./dist/index.js",
6
6
  "type": "module",
package/src/index.ts CHANGED
@@ -17,14 +17,27 @@ import type { PackageManager } from './utils/getPackageManager.js'
17
17
 
18
18
  const program = new Command()
19
19
 
20
+ const CODE_ROUTER = 'code-router'
21
+ const FILE_ROUTER = 'file-router'
22
+
20
23
  interface Options {
21
24
  typescript: boolean
22
25
  tailwind: boolean
23
26
  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
+ }, {})
24
37
  }
25
38
 
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>) {
28
41
  for (const file of files) {
29
42
  const targetFileName = file.replace('.tw', '')
30
43
  await copyFile(
@@ -33,14 +46,18 @@ const createCopyFile = (templateDir: string, targetDir: string) =>
33
46
  )
34
47
  }
35
48
  }
49
+ }
36
50
 
37
- const createTemplateFile = (
51
+ function createTemplateFile(
38
52
  projectName: string,
39
53
  options: Required<Options>,
40
- templateDir: string,
41
54
  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
+ ) {
44
61
  const templateValues = {
45
62
  packageManager: options.packageManager,
46
63
  projectName: projectName,
@@ -48,6 +65,8 @@ const createTemplateFile = (
48
65
  tailwind: options.tailwind,
49
66
  js: options.typescript ? 'ts' : 'js',
50
67
  jsx: options.typescript ? 'tsx' : 'jsx',
68
+ fileRouter: options.mode === FILE_ROUTER,
69
+ codeRouter: options.mode === CODE_ROUTER,
51
70
  }
52
71
 
53
72
  const template = await readFile(resolve(templateDir, file), 'utf-8')
@@ -55,11 +74,13 @@ const createTemplateFile = (
55
74
  const target = targetFileName ?? file.replace('.ejs', '')
56
75
  await writeFile(resolve(targetDir, target), content)
57
76
  }
77
+ }
58
78
 
59
79
  async function createPackageJSON(
60
80
  projectName: string,
61
81
  options: Required<Options>,
62
82
  templateDir: string,
83
+ routerDir: string,
63
84
  targetDir: string,
64
85
  ) {
65
86
  let packageJSON = JSON.parse(
@@ -90,6 +111,24 @@ async function createPackageJSON(
90
111
  },
91
112
  }
92
113
  }
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
+ )
93
132
  await writeFile(
94
133
  resolve(targetDir, 'package.json'),
95
134
  JSON.stringify(packageJSON, null, 2),
@@ -97,18 +136,16 @@ async function createPackageJSON(
97
136
  }
98
137
 
99
138
  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),
102
144
  )
103
145
  const targetDir = resolve(process.cwd(), projectName)
104
146
 
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)
112
149
 
113
150
  intro(`Creating a new TanStack app in ${targetDir}...`)
114
151
 
@@ -118,13 +155,13 @@ async function createApp(projectName: string, options: Required<Options>) {
118
155
  // Setup the .vscode directory
119
156
  await mkdir(resolve(targetDir, '.vscode'), { recursive: true })
120
157
  await copyFile(
121
- resolve(templateDir, '.vscode/settings.json'),
158
+ resolve(templateDirBase, '.vscode/settings.json'),
122
159
  resolve(targetDir, '.vscode/settings.json'),
123
160
  )
124
161
 
125
162
  // Fill the public directory
126
163
  await mkdir(resolve(targetDir, 'public'), { recursive: true })
127
- copyFiles([
164
+ copyFiles(templateDirBase, [
128
165
  './public/robots.txt',
129
166
  './public/favicon.ico',
130
167
  './public/manifest.json',
@@ -134,55 +171,85 @@ async function createApp(projectName: string, options: Required<Options>) {
134
171
 
135
172
  // Make the src directory
136
173
  await mkdir(resolve(targetDir, 'src'), { recursive: true })
174
+ if (options.mode === FILE_ROUTER) {
175
+ await mkdir(resolve(targetDir, 'src/routes'), { recursive: true })
176
+ }
137
177
 
138
178
  // Copy in Vite and Tailwind config and CSS
139
179
  if (!options.tailwind) {
140
- await copyFiles(['./src/App.css'])
180
+ await copyFiles(templateDirBase, ['./src/App.css'])
141
181
  }
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')
144
184
 
145
- copyFiles(['./src/logo.svg'])
185
+ copyFiles(templateDirBase, ['./src/logo.svg'])
146
186
 
147
187
  // 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
+ }
156
218
 
157
219
  // Setup the main, reportWebVitals and index.html files
158
220
  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')
161
222
  } else {
162
- await templateFile('./src/main.tsx.ejs', './src/main.jsx')
163
223
  await templateFile(
224
+ templateDirBase,
164
225
  './src/reportWebVitals.ts.ejs',
165
226
  './src/reportWebVitals.js',
166
227
  )
167
228
  }
168
- await templateFile('./index.html.ejs')
229
+ await templateFile(templateDirBase, './index.html.ejs')
169
230
 
170
231
  // Setup tsconfig
171
232
  if (options.typescript) {
172
- await copyFiles(['./tsconfig.json', './tsconfig.dev.json'])
233
+ await copyFiles(templateDirBase, ['./tsconfig.json', './tsconfig.dev.json'])
173
234
  }
174
235
 
175
236
  // 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
+ )
177
244
 
178
245
  // Add .gitignore
179
246
  await copyFile(
180
- resolve(templateDir, 'gitignore'),
247
+ resolve(templateDirBase, 'gitignore'),
181
248
  resolve(targetDir, '.gitignore'),
182
249
  )
183
250
 
184
251
  // Create the README.md
185
- await templateFile('README.md.ejs')
252
+ await templateFile(templateDirBase, 'README.md.ejs')
186
253
 
187
254
  // Install dependencies
188
255
  const s = spinner()
@@ -197,13 +264,17 @@ program
197
264
  .name('create-tsrouter-app')
198
265
  .description('CLI to create a new TanStack application')
199
266
  .argument('<project-name>', 'name of the project')
200
- .option<'typescript' | 'javascript'>(
267
+ .option<'typescript' | 'javascript' | 'file-router'>(
201
268
  '--template <type>',
202
- 'project template (typescript/javascript)',
269
+ 'project template (typescript, javascript, file-router)',
203
270
  (value) => {
204
- if (value !== 'typescript' && value !== 'javascript') {
271
+ if (
272
+ value !== 'typescript' &&
273
+ value !== 'javascript' &&
274
+ value !== 'file-router'
275
+ ) {
205
276
  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`,
207
278
  )
208
279
  }
209
280
  return value
@@ -230,17 +301,19 @@ program
230
301
  (
231
302
  projectName: string,
232
303
  options: {
233
- template: string
304
+ template: 'typescript' | 'javascript' | 'file-router'
234
305
  tailwind: boolean
235
306
  packageManager: PackageManager
236
307
  },
237
308
  ) => {
238
- const typescript = options.template === 'typescript'
309
+ const typescript =
310
+ options.template === 'typescript' || options.template === 'file-router'
239
311
 
240
312
  createApp(projectName, {
241
313
  typescript,
242
314
  tailwind: options.tailwind,
243
315
  packageManager: options.packageManager,
316
+ mode: options.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER,
244
317
  })
245
318
  },
246
319
  )
@@ -32,11 +32,14 @@ This project uses [Tailwind CSS](https://tailwindcss.com/) for styling.
32
32
  This project uses CSS for styling.
33
33
  <% } %>
34
34
  ## Routing
35
-
36
- This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a code based router. Which means that the routes are defined in code (in the `./src/main.<%= jsx %>` file). If you like you can also use a file based routing setup by following the [File Based Routing](https://tanstack.com/router/latest/docs/framework/react/guide/file-based-routing) guide.
35
+ <% if (fileRouter) { %>This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as fiels in `src/routes`.<% } else { %>This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a code based router. Which means that the routes are defined in code (in the `./src/main.<%= jsx %>` file). If you like you can also use a file based routing setup by following the [File Based Routing](https://tanstack.com/router/latest/docs/framework/react/guide/file-based-routing) guide.<% } %>
37
36
 
38
37
  ### Adding A Route
38
+ <% if (fileRouter) { %>
39
+ To add a new route to your application just add another a new file in the `./src/routes` directory.
39
40
 
41
+ TanStack will automatically generate the content of the route file for you.
42
+ <% } else { %>
40
43
  To add a new route to your application just add another `createRoute` call to the `./src/main.<%= jsx %>` file. The example below adds a new `/about`route to the root route.
41
44
 
42
45
  ```tsx
@@ -70,7 +73,7 @@ const aboutRoute = createRoute({
70
73
  That is how we have the `App` component set up with the home page.
71
74
 
72
75
  For more information on the options you have when you are creating code based routes check out the [Code Based Routing](https://tanstack.com/router/latest/docs/framework/react/guide/code-based-routing) documentation.
73
-
76
+ <% } %>
74
77
  Now that you have two routes you can use a `Link` component to navigate between them.
75
78
 
76
79
  ### Adding Links
@@ -93,6 +96,7 @@ More information on the `Link` component can be found in the [Link documentation
93
96
 
94
97
  ### Using A Layout
95
98
 
99
+ <% if (codeRouter) { %>
96
100
  Layouts can be used to wrap the contents of the routes in menus, headers, footers, etc.
97
101
 
98
102
  There is already a layout in the `src/main.<%= jsx %>` file:
@@ -111,6 +115,8 @@ const rootRoute = createRootRoute({
111
115
  You can use the React component specified in the `component` property of the `rootRoute` to wrap the contents of the routes. The `<Outlet />` component is used to render the current route within the body of the layout. For example you could add a header to the layout like so:
112
116
 
113
117
  ```tsx
118
+ import { Link } from "@tanstack/react-router";
119
+
114
120
  const rootRoute = createRootRoute({
115
121
  component: () => (
116
122
  <>
@@ -126,11 +132,37 @@ const rootRoute = createRootRoute({
126
132
  ),
127
133
  });
128
134
  ```
135
+ <% } else { %>In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you use the `<Outlet />` component.
136
+
137
+ Here is an example layout that includes a header:
129
138
 
139
+ ```tsx
140
+ import { createRootRoute, Outlet } from '@tanstack/react-router'
141
+ import { TanStackRouterDevtools } from '@tanstack/router-devtools'
142
+
143
+ import { Link } from "@tanstack/react-router";
144
+
145
+ export const Route = createRootRoute({
146
+ component: () => (
147
+ <>
148
+ <header>
149
+ <nav>
150
+ <Link to="/">Home</Link>
151
+ <Link to="/about">About</Link>
152
+ </nav>
153
+ </header>
154
+ <Outlet />
155
+ <TanStackRouterDevtools />
156
+ </>
157
+ ),
158
+ })
159
+ ```
160
+ <% } %>
130
161
  The `<TanStackRouterDevtools />` component is not required so you can remove it if you don't want it in your layout.
131
162
 
132
163
  More information on layouts can be found in the [Layouts documentation](hthttps://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).
133
164
 
165
+ <% if (codeRouter) { %>
134
166
  ### Migrating To File Base Routing
135
167
 
136
168
  First you need to add the Vite plugin for Tanstack Router:
@@ -301,7 +333,7 @@ reportWebVitals();
301
333
  Now you've got a file based routing setup in your project! Let's have some fun with it! Just create a file in `about.<%= jsx %>` in `src/routes` and it if the application is running TanStack will automatically add contents to the file and you'll have the start of your `/about` route ready to go with no additional work. You can see why folks find File Based Routing so easy to use.
302
334
 
303
335
  You can find out everything you need to know on how to use file based routing in the [File Based Routing](https://tanstack.com/router/latest/docs/framework/react/guide/file-based-routing) documentation.
304
-
336
+ <% } %>
305
337
  ## Data Fetching
306
338
 
307
339
  There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered.
@@ -6,7 +6,7 @@
6
6
  "start": "vite --port 3000",
7
7
  "build": "vite build && tsc --noEmit",
8
8
  "serve": "vite preview",
9
- "test": "vitest"
9
+ "test": "vitest run"
10
10
  },
11
11
  "dependencies": {
12
12
  "@tanstack/react-router": "^1.104.1",
@@ -1,7 +1,15 @@
1
- import logo from "./logo.svg";
2
- <% if (!tailwind) { %>
3
- import "./App.css";
1
+ <% if (fileRouter) { %>
2
+ import { createFileRoute } from "@tanstack/react-router";
3
+ import logo from "../logo.svg";<% if (!tailwind) { %>
4
+ import "../App.css";
4
5
  <% } %>
6
+ <% } else { %>import logo from "./logo.svg";<% if (!tailwind) { %>
7
+ import "./App.css";
8
+ <% } %><% } %><% if (fileRouter) { %>
9
+
10
+ export const Route = createFileRoute("/")({
11
+ component: App,
12
+ });<% } %>
5
13
 
6
14
  function App() {
7
15
  return (<% if (tailwind) { %>
@@ -13,7 +21,7 @@ function App() {
13
21
  alt="logo"
14
22
  />
15
23
  <p>
16
- Edit <code>src/App.<%= jsx %></code> and save to reload.
24
+ Edit <code><% if (fileRouter) { %>src/routes/index.tsx<% } else {%>src/App.<%= jsx %><% } %></code> and save to reload.
17
25
  </p>
18
26
  <a
19
27
  className="text-[#61dafb] hover:underline"
@@ -38,7 +46,7 @@ function App() {
38
46
  <header className="App-header">
39
47
  <img src={logo} className="App-logo" alt="logo" />
40
48
  <p>
41
- Edit <code>src/App.<%= jsx %></code> and save to reload.
49
+ Edit <code><% if (fileRouter) { %>src/routes/index.tsx<% } else {%>src/App.<%= jsx %><% } %></code> and save to reload.
42
50
  </p>
43
51
  <a
44
52
  className="App-link"
@@ -61,4 +69,6 @@ function App() {
61
69
  <% } %> );
62
70
  }
63
71
 
72
+ <% if (!fileRouter) { %>
64
73
  export default App;
74
+ <% } %>
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from "vite";
2
+ import viteReact from "@vitejs/plugin-react";<% if (tailwind) { %>
3
+ import tailwindcss from "@tailwindcss/vite";
4
+ <% } %><%if (fileRouter) { %>
5
+ import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
6
+ <% } %>
7
+
8
+ // https://vitejs.dev/config/
9
+ export default defineConfig({
10
+ plugins: [<% if(fileRouter) { %>TanStackRouterVite(), <% } %>viteReact()<% if (tailwind) { %>, tailwindcss()<% } %>],
11
+ test: {
12
+ globals: true,
13
+ environment: "jsdom",
14
+ },
15
+ });
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {
3
+ "@tanstack/router-plugin": "^1.105.0"
4
+ }
5
+ }
@@ -0,0 +1,35 @@
1
+ import { StrictMode } from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import { RouterProvider, createRouter } from "@tanstack/react-router";
4
+
5
+ // Import the generated route tree
6
+ import { routeTree } from "./routeTree.gen";
7
+
8
+ import "./styles.css";
9
+ import reportWebVitals from "./reportWebVitals";
10
+
11
+ // Create a new router instance
12
+ const router = createRouter({ routeTree });
13
+
14
+ // Register the router instance for type safety
15
+ declare module "@tanstack/react-router" {
16
+ interface Register {
17
+ router: typeof router;
18
+ }
19
+ }
20
+
21
+ // Render the app
22
+ const rootElement = document.getElementById("app")!;
23
+ if (!rootElement.innerHTML) {
24
+ const root = ReactDOM.createRoot(rootElement);
25
+ root.render(
26
+ <StrictMode>
27
+ <RouterProvider router={router} />
28
+ </StrictMode>
29
+ );
30
+ }
31
+
32
+ // If you want to start measuring performance in your app, pass a function
33
+ // to log results (for example: reportWebVitals(console.log))
34
+ // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
35
+ reportWebVitals();
@@ -0,0 +1,11 @@
1
+ import { createRootRoute, Outlet } from '@tanstack/react-router'
2
+ import { TanStackRouterDevtools } from '@tanstack/router-devtools'
3
+
4
+ export const Route = createRootRoute({
5
+ component: () => (
6
+ <>
7
+ <Outlet />
8
+ <TanStackRouterDevtools />
9
+ </>
10
+ ),
11
+ })
@@ -1,14 +0,0 @@
1
- import { defineConfig } from "vite";
2
- import viteReact from "@vitejs/plugin-react";
3
- <% if (tailwind) { %>
4
- import tailwindcss from "@tailwindcss/vite";
5
- <% } %>
6
-
7
- // https://vitejs.dev/config/
8
- export default defineConfig({
9
- plugins: [viteReact()<% if (tailwind) { %>, tailwindcss()<% } %>],
10
- test: {
11
- globals: true,
12
- environment: "jsdom",
13
- },
14
- });
File without changes