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.
- package/CONTRIBUTING.md +8 -6
- package/README.md +5 -1
- package/dist/index.js +90 -44
- package/package.json +1 -1
- package/src/index.ts +116 -43
- package/{project-template → templates/base}/README.md.ejs +36 -4
- package/{project-template → templates/base}/package.json +1 -1
- package/{project-template → templates/base}/src/App.tsx.ejs +15 -5
- package/templates/base/vite.config.js.ejs +15 -0
- package/templates/file-router/package.fr.json +5 -0
- package/templates/file-router/src/main.tsx.ejs +35 -0
- package/templates/file-router/src/routes/__root.tsx +11 -0
- package/project-template/vite.config.js.ejs +0 -14
- /package/{project-template → templates/base}/.vscode/settings.json +0 -0
- /package/{project-template → templates/base}/gitignore +0 -0
- /package/{project-template → templates/base}/index.html.ejs +0 -0
- /package/{project-template → templates/base}/package.ts.json +0 -0
- /package/{project-template → templates/base}/package.tw.json +0 -0
- /package/{project-template → templates/base}/public/favicon.ico +0 -0
- /package/{project-template → templates/base}/public/logo192.png +0 -0
- /package/{project-template → templates/base}/public/logo512.png +0 -0
- /package/{project-template → templates/base}/public/manifest.json +0 -0
- /package/{project-template → templates/base}/public/robots.txt +0 -0
- /package/{project-template → templates/base}/src/App.css +0 -0
- /package/{project-template → templates/base}/src/App.test.tsx.ejs +0 -0
- /package/{project-template → templates/base}/src/logo.svg +0 -0
- /package/{project-template → templates/base}/src/reportWebVitals.ts.ejs +0 -0
- /package/{project-template → templates/base}/src/styles.css.ejs +0 -0
- /package/{project-template → templates/base}/tsconfig.dev.json +0 -0
- /package/{project-template → templates/base}/tsconfig.json +0 -0
- /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
|
|
28
|
-
|
|
|
29
|
-
| `pnpm start app-js`
|
|
30
|
-
| `pnpm start app-ts --template typescript`
|
|
31
|
-
| `pnpm start app-js-tw --tailwind`
|
|
32
|
-
| `pnpm start app-ts-tw --template typescript --tailwind`
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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
|
|
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(
|
|
60
|
-
const templateFile = createTemplateFile(projectName, options,
|
|
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(
|
|
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
|
-
|
|
87
|
-
|
|
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/
|
|
91
|
-
await templateFile('./src/reportWebVitals.ts.ejs');
|
|
135
|
+
await templateFile(templateDirBase, './src/reportWebVitals.ts.ejs');
|
|
92
136
|
}
|
|
93
137
|
else {
|
|
94
|
-
await templateFile('./src/
|
|
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,
|
|
146
|
+
await createPackageJSON(projectName, options, templateDirBase, templateDirRouter, targetDir);
|
|
104
147
|
// Add .gitignore
|
|
105
|
-
await copyFile(resolve(
|
|
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
|
|
120
|
-
if (value !== 'typescript' &&
|
|
121
|
-
|
|
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
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
|
-
|
|
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
|
-
|
|
51
|
+
function createTemplateFile(
|
|
38
52
|
projectName: string,
|
|
39
53
|
options: Required<Options>,
|
|
40
|
-
templateDir: string,
|
|
41
54
|
targetDir: string,
|
|
42
|
-
)
|
|
43
|
-
async function templateFile(
|
|
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
|
|
101
|
-
new 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(
|
|
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(
|
|
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
|
-
|
|
149
|
-
'./src/
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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/
|
|
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(
|
|
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(
|
|
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
|
|
269
|
+
'project template (typescript, javascript, file-router)',
|
|
203
270
|
(value) => {
|
|
204
|
-
if (
|
|
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:
|
|
304
|
+
template: 'typescript' | 'javascript' | 'file-router'
|
|
234
305
|
tailwind: boolean
|
|
235
306
|
packageManager: PackageManager
|
|
236
307
|
},
|
|
237
308
|
) => {
|
|
238
|
-
const 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.
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import "
|
|
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
|
|
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
|
|
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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|