react-client 1.0.8 β 1.0.10
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/README.md +0 -22
- package/dist/cli/commands/dev.js +35 -7
- package/dist/cli/index.js +1 -27
- package/dist/utils/loadConfig.js +31 -33
- package/package.json +23 -17
- package/templates/react/index.html +13 -1
- package/templates/react/public/favicon.ico +0 -0
- package/templates/react/src/App.jsx +1 -1
- package/templates/react/src/main.jsx +6 -1
- package/templates/react-tailwind/index.html +13 -1
- package/templates/react-tailwind/public/favicon.ico +0 -0
- package/templates/react-tailwind/src/App.jsx +3 -1
- package/templates/react-tailwind/src/index.css +1 -0
- package/templates/react-tailwind/src/main.jsx +9 -1
- package/templates/react-tailwind-ts/index.html +13 -1
- package/templates/react-tailwind-ts/public/favicon.ico +0 -0
- package/templates/react-tailwind-ts/src/App.tsx +3 -1
- package/templates/react-tailwind-ts/src/main.tsx +1 -0
- package/templates/react-ts/index.html +13 -1
- package/templates/react-ts/public/favicon.ico +0 -0
- package/templates/react-ts/src/main.tsx +6 -1
- package/templates/{react-ssr-ts β react-ts}/tsconfig.json +1 -2
- package/dist/cli/commands/build.ssr.js +0 -46
- package/dist/cli/commands/generate.js +0 -42
- package/templates/react-ssr/index.html +0 -1
- package/templates/react-ssr/package.json +0 -14
- package/templates/react-ssr/src/entry-client.jsx +0 -1
- package/templates/react-ssr/src/entry-server.jsx +0 -1
- package/templates/react-ssr/src/pages/index.jsx +0 -1
- package/templates/react-ssr-ts/index.html +0 -1
- package/templates/react-ssr-ts/package.json +0 -21
- package/templates/react-ssr-ts/src/entry-client.js +0 -5
- package/templates/react-ssr-ts/src/entry-client.tsx +0 -6
- package/templates/react-ssr-ts/src/entry-server.js +0 -4
- package/templates/react-ssr-ts/src/entry-server.tsx +0 -5
- package/templates/react-ssr-ts/src/pages/index.js +0 -2
- package/templates/react-ssr-ts/src/pages/index.tsx +0 -3
- package/templates/react-tailwind-ts/src/App.js +0 -2
- package/templates/react-tailwind-ts/src/main.js +0 -7
package/README.md
CHANGED
|
@@ -76,8 +76,6 @@ export default defineConfig({
|
|
|
76
76
|
|-----------|-------------|
|
|
77
77
|
| `react` | JavaScript SPA |
|
|
78
78
|
| `react-ts` | TypeScript SPA |
|
|
79
|
-
| `react-ssr` | JavaScript SSR |
|
|
80
|
-
| `react-ssr-ts` | TypeScript SSR |
|
|
81
79
|
| `react-tailwind` | JS + Tailwind |
|
|
82
80
|
| `react-tailwind-ts` | TS + Tailwind |
|
|
83
81
|
|
|
@@ -94,8 +92,6 @@ Each template is pre-configured for esbuild, HMR, and fast bootstrapping.
|
|
|
94
92
|
- π¬ **Auto Port Detection** β Prompts when default port 5173 is occupied
|
|
95
93
|
- π§ **Smart Config Loader** β Detects project root, compiles `.ts` configs dynamically
|
|
96
94
|
- π¨ **PrismJS Highlighting** β For pretty overlay code frames
|
|
97
|
-
- π§± **SSR Runtime Support** β For server-side templates (`react-ssr*`)
|
|
98
|
-
- π§© **Generators** β Create components, routes, and tests instantly
|
|
99
95
|
- π **Plugin Hook System** β Extendable with `configResolved`, `transform`, `buildEnd`
|
|
100
96
|
|
|
101
97
|
---
|
|
@@ -200,21 +196,3 @@ Found an issue or have a feature request?
|
|
|
200
196
|
## πͺͺ License
|
|
201
197
|
|
|
202
198
|
**MIT Licensed** Β© [Venkatesh Sundaram](https://github.com/venkateshsundaram)
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
## πΊοΈ Architecture Overview (Bonus)
|
|
207
|
-
|
|
208
|
-
```text
|
|
209
|
-
βββββββββββββββββββββββββββββ
|
|
210
|
-
β react-client (CLI) β
|
|
211
|
-
β βββ esbuild (watch/bundle)
|
|
212
|
-
β βββ connect (dev server)
|
|
213
|
-
β βββ websocket (HMR)
|
|
214
|
-
β βββ prismjs overlay
|
|
215
|
-
β βββ chokidar (file watch)
|
|
216
|
-
βββββββββββββββββββββββββββββ
|
|
217
|
-
β
|
|
218
|
-
βΌ
|
|
219
|
-
Browser β Live HMR + Overlay
|
|
220
|
-
```
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -32,6 +32,7 @@ async function dev() {
|
|
|
32
32
|
process.exit(1);
|
|
33
33
|
}
|
|
34
34
|
await fs_extra_1.default.ensureDir(outDir);
|
|
35
|
+
// π§ Detect open port
|
|
35
36
|
const availablePort = await (0, detect_port_1.default)(defaultPort);
|
|
36
37
|
const port = availablePort;
|
|
37
38
|
if (availablePort !== defaultPort) {
|
|
@@ -79,17 +80,35 @@ async function dev() {
|
|
|
79
80
|
outdir: outDir,
|
|
80
81
|
define: { 'process.env.NODE_ENV': '"development"' },
|
|
81
82
|
loader: { '.ts': 'ts', '.tsx': 'tsx', '.js': 'jsx', '.jsx': 'jsx' },
|
|
83
|
+
entryNames: '[name]', // β
output main.js (not src/main.js)
|
|
84
|
+
assetNames: 'assets/[name]',
|
|
82
85
|
});
|
|
83
86
|
await ctx.watch();
|
|
87
|
+
console.log(chalk_1.default.gray('π¦ Watching and building dev bundle...'));
|
|
88
|
+
console.log(chalk_1.default.gray(' Output dir:'), chalk_1.default.blue(outDir));
|
|
89
|
+
console.log(chalk_1.default.gray(' Entry file:'), chalk_1.default.yellow(entry));
|
|
84
90
|
// π connect server
|
|
85
91
|
const app = (0, connect_1.default)();
|
|
86
|
-
//
|
|
92
|
+
// π‘ Security headers
|
|
93
|
+
app.use((_req, res, next) => {
|
|
94
|
+
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
|
|
95
|
+
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
|
|
96
|
+
next();
|
|
97
|
+
});
|
|
98
|
+
// 1οΈβ£ Serve react-refresh runtime with safe browser shim
|
|
87
99
|
app.use('/@react-refresh', async (_req, res) => {
|
|
88
100
|
const runtime = await fs_extra_1.default.readFile(reactRefreshRuntime, 'utf8');
|
|
101
|
+
const shim = `
|
|
102
|
+
// React Refresh browser shims
|
|
103
|
+
window.process = window.process || { env: { NODE_ENV: 'development' } };
|
|
104
|
+
window.module = { exports: {} };
|
|
105
|
+
window.global = window;
|
|
106
|
+
window.require = () => window.module.exports;
|
|
107
|
+
`;
|
|
89
108
|
res.setHeader('Content-Type', 'application/javascript');
|
|
90
|
-
res.end(runtime);
|
|
109
|
+
res.end(shim + '\n' + runtime);
|
|
91
110
|
});
|
|
92
|
-
// 2οΈβ£ Serve PrismJS for highlighting
|
|
111
|
+
// 2οΈβ£ Serve PrismJS for code highlighting (overlay)
|
|
93
112
|
app.use('/@prismjs', async (_req, res) => {
|
|
94
113
|
const prismPath = require.resolve('prismjs/prism.js');
|
|
95
114
|
const css = await fs_extra_1.default.readFile(require.resolve('prismjs/themes/prism-tomorrow.css'), 'utf8');
|
|
@@ -104,7 +123,7 @@ async function dev() {
|
|
|
104
123
|
})();
|
|
105
124
|
`);
|
|
106
125
|
});
|
|
107
|
-
// 3οΈβ£
|
|
126
|
+
// 3οΈβ£ Source map resolver (for overlay stack trace)
|
|
108
127
|
app.use('/@source-map', async (req, res) => {
|
|
109
128
|
const url = new URL(req.url ?? '', `http://localhost:${port}`);
|
|
110
129
|
const file = url.searchParams.get('file');
|
|
@@ -152,7 +171,7 @@ async function dev() {
|
|
|
152
171
|
res.end(JSON.stringify({ error: msg }));
|
|
153
172
|
}
|
|
154
173
|
});
|
|
155
|
-
// 4οΈβ£
|
|
174
|
+
// 4οΈβ£ Serve index.html with injected refresh + HMR
|
|
156
175
|
app.use(async (req, res, next) => {
|
|
157
176
|
if (req.url === '/' || req.url === '/index.html') {
|
|
158
177
|
if (!fs_extra_1.default.existsSync(indexHtml)) {
|
|
@@ -188,8 +207,17 @@ async function dev() {
|
|
|
188
207
|
res.setHeader('Content-Type', 'text/html');
|
|
189
208
|
res.end(html);
|
|
190
209
|
}
|
|
191
|
-
else
|
|
192
|
-
|
|
210
|
+
else {
|
|
211
|
+
// β
Serve compiled output files
|
|
212
|
+
const filePath = path_1.default.join(outDir, req.url || '');
|
|
213
|
+
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
214
|
+
const content = await fs_extra_1.default.readFile(filePath);
|
|
215
|
+
res.setHeader('Content-Type', 'application/javascript');
|
|
216
|
+
res.end(content);
|
|
217
|
+
}
|
|
218
|
+
else
|
|
219
|
+
next();
|
|
220
|
+
}
|
|
193
221
|
});
|
|
194
222
|
const server = http_1.default.createServer(app);
|
|
195
223
|
const wss = new ws_1.WebSocketServer({ server });
|
package/dist/cli/index.js
CHANGED
|
@@ -9,10 +9,8 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
11
|
const init_1 = __importDefault(require("./commands/init"));
|
|
12
|
-
const generate_1 = __importDefault(require("./commands/generate"));
|
|
13
12
|
const dev_1 = __importDefault(require("./commands/dev"));
|
|
14
13
|
const build_1 = __importDefault(require("./commands/build"));
|
|
15
|
-
const build_ssr_1 = __importDefault(require("./commands/build.ssr"));
|
|
16
14
|
const preview_1 = __importDefault(require("./commands/preview"));
|
|
17
15
|
// Load package.json version dynamically
|
|
18
16
|
const pkgPath = path_1.default.resolve(__dirname, '../../package.json');
|
|
@@ -23,7 +21,7 @@ const pkg = fs_extra_1.default.existsSync(pkgPath)
|
|
|
23
21
|
function showBanner(cmd) {
|
|
24
22
|
const title = chalk_1.default.bold.cyan('β‘ React Client');
|
|
25
23
|
const version = chalk_1.default.gray(`v${pkg.version}`);
|
|
26
|
-
const tagline = chalk_1.default.dim('Fast esbuild-based React CLI with HMR
|
|
24
|
+
const tagline = chalk_1.default.dim('Fast esbuild-based React CLI with HMR & Overlay');
|
|
27
25
|
const line = chalk_1.default.gray('ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ');
|
|
28
26
|
console.log(`\n${title} ${version}`);
|
|
29
27
|
console.log(tagline);
|
|
@@ -32,18 +30,12 @@ function showBanner(cmd) {
|
|
|
32
30
|
case 'init':
|
|
33
31
|
console.log(chalk_1.default.cyan('π¦ Initializing new React project...\n'));
|
|
34
32
|
break;
|
|
35
|
-
case 'generate':
|
|
36
|
-
console.log(chalk_1.default.white('β¨ Generating boilerplate files...\n'));
|
|
37
|
-
break;
|
|
38
33
|
case 'dev':
|
|
39
34
|
console.log(chalk_1.default.green('π Starting development server...\n'));
|
|
40
35
|
break;
|
|
41
36
|
case 'build':
|
|
42
37
|
console.log(chalk_1.default.yellow('ποΈ Building for production...\n'));
|
|
43
38
|
break;
|
|
44
|
-
case 'build:ssr':
|
|
45
|
-
console.log(chalk_1.default.magenta('π§± Building for server-side rendering (SSR)...\n'));
|
|
46
|
-
break;
|
|
47
39
|
case 'preview':
|
|
48
40
|
console.log(chalk_1.default.blue('π Starting production preview server...\n'));
|
|
49
41
|
break;
|
|
@@ -69,17 +61,6 @@ program
|
|
|
69
61
|
showBanner('init');
|
|
70
62
|
(0, init_1.default)(name, opts);
|
|
71
63
|
});
|
|
72
|
-
program
|
|
73
|
-
.command('generate <kind> <name>')
|
|
74
|
-
.alias('g')
|
|
75
|
-
.option('-p, --path <path>', 'output path')
|
|
76
|
-
.option('--no-ts', 'generate JS instead of TS')
|
|
77
|
-
.option('-f, --force', 'overwrite if exists')
|
|
78
|
-
.description('generate components, pages, or stores')
|
|
79
|
-
.action((kind, name, opts) => {
|
|
80
|
-
showBanner('generate');
|
|
81
|
-
(0, generate_1.default)(kind, name, opts);
|
|
82
|
-
});
|
|
83
64
|
program
|
|
84
65
|
.command('dev')
|
|
85
66
|
.description('start dev server (with React Fast Refresh)')
|
|
@@ -94,13 +75,6 @@ program
|
|
|
94
75
|
showBanner('build');
|
|
95
76
|
(0, build_1.default)();
|
|
96
77
|
});
|
|
97
|
-
program
|
|
98
|
-
.command('build:ssr')
|
|
99
|
-
.description('build for server-side rendering (SSR)')
|
|
100
|
-
.action(() => {
|
|
101
|
-
showBanner('build:ssr');
|
|
102
|
-
(0, build_ssr_1.default)();
|
|
103
|
-
});
|
|
104
78
|
program
|
|
105
79
|
.command('preview')
|
|
106
80
|
.description('preview production build')
|
package/dist/utils/loadConfig.js
CHANGED
|
@@ -41,71 +41,69 @@ const path_1 = __importDefault(require("path"));
|
|
|
41
41
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
42
42
|
const url_1 = require("url");
|
|
43
43
|
const chalk_1 = __importDefault(require("chalk"));
|
|
44
|
+
const esbuild_1 = require("esbuild");
|
|
44
45
|
/**
|
|
45
|
-
* Dynamically
|
|
46
|
-
*
|
|
46
|
+
* Dynamically loads react-client.config.(ts|js|mjs)
|
|
47
|
+
* Compiles .ts and .js configs to .mjs temporarily for import.
|
|
47
48
|
*/
|
|
48
49
|
async function loadReactClientConfig(cwd) {
|
|
49
50
|
let projectRoot = cwd;
|
|
50
51
|
try {
|
|
51
|
-
//
|
|
52
|
+
// Detect if running inside react-client repo for local testing
|
|
52
53
|
const pkgPath = path_1.default.join(cwd, 'package.json');
|
|
53
|
-
if (fs_extra_1.default.
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (fs_extra_1.default.existsSync(myappPath)) {
|
|
59
|
-
console.log(chalk_1.default.gray('π§© Detected local CLI environment, using ./myapp as root.'));
|
|
60
|
-
projectRoot = myappPath;
|
|
61
|
-
}
|
|
54
|
+
if (await fs_extra_1.default.pathExists(pkgPath)) {
|
|
55
|
+
const pkg = JSON.parse(await fs_extra_1.default.readFile(pkgPath, 'utf8'));
|
|
56
|
+
if (pkg.name === 'react-client' && (await fs_extra_1.default.pathExists(path_1.default.join(cwd, 'myapp')))) {
|
|
57
|
+
console.log(chalk_1.default.gray('π§© Detected local CLI environment, using ./myapp as root.'));
|
|
58
|
+
projectRoot = path_1.default.join(cwd, 'myapp');
|
|
62
59
|
}
|
|
63
60
|
}
|
|
64
|
-
// π Possible config file names (prefer .js for Node compatibility)
|
|
65
61
|
const filenames = [
|
|
66
|
-
'react-client.config.js',
|
|
67
|
-
'react-client.config.mjs',
|
|
68
62
|
'react-client.config.ts',
|
|
63
|
+
'react-client.config.mjs',
|
|
64
|
+
'react-client.config.js',
|
|
69
65
|
];
|
|
70
66
|
let configFile = null;
|
|
71
67
|
for (const name of filenames) {
|
|
72
68
|
const file = path_1.default.join(projectRoot, name);
|
|
73
|
-
if (fs_extra_1.default.
|
|
69
|
+
if (await fs_extra_1.default.pathExists(file)) {
|
|
74
70
|
configFile = file;
|
|
75
71
|
break;
|
|
76
72
|
}
|
|
77
73
|
}
|
|
78
74
|
if (!configFile) {
|
|
79
|
-
console.log(chalk_1.default.gray('βΉοΈ No react-client.config
|
|
75
|
+
console.log(chalk_1.default.gray('βΉοΈ No react-client.config found, using defaults.'));
|
|
80
76
|
return {};
|
|
81
77
|
}
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
//
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
const outFile = path_1.default.join(projectRoot, '.react-client.temp.config.js');
|
|
88
|
-
await esbuild.build({
|
|
78
|
+
const ext = path_1.default.extname(configFile);
|
|
79
|
+
const tempFile = path_1.default.join(projectRoot, `.react-client.temp-${Date.now()}.mjs`);
|
|
80
|
+
// π§ Always compile .ts or .js β .mjs for safe ESM import
|
|
81
|
+
if (ext === '.ts' || ext === '.js') {
|
|
82
|
+
await (0, esbuild_1.build)({
|
|
89
83
|
entryPoints: [configFile],
|
|
90
|
-
outfile:
|
|
91
|
-
format: 'esm',
|
|
84
|
+
outfile: tempFile,
|
|
92
85
|
platform: 'node',
|
|
86
|
+
format: 'esm',
|
|
87
|
+
target: 'node18',
|
|
93
88
|
bundle: true,
|
|
94
89
|
write: true,
|
|
90
|
+
logLevel: 'silent',
|
|
95
91
|
});
|
|
96
|
-
const mod = await Promise.resolve(`${(0, url_1.pathToFileURL)(outFile).href}`).then(s => __importStar(require(s)));
|
|
97
|
-
await fs_extra_1.default.remove(outFile);
|
|
98
|
-
console.log(chalk_1.default.green(`π§© Loaded config from ${path_1.default.basename(configFile)}`));
|
|
99
|
-
return mod.default || mod;
|
|
100
92
|
}
|
|
101
|
-
|
|
93
|
+
else {
|
|
94
|
+
await fs_extra_1.default.copyFile(configFile, tempFile);
|
|
95
|
+
}
|
|
96
|
+
// Import via file:// URL
|
|
97
|
+
const fileUrl = (0, url_1.pathToFileURL)(tempFile).href;
|
|
102
98
|
const mod = await Promise.resolve(`${fileUrl}`).then(s => __importStar(require(s)));
|
|
99
|
+
await fs_extra_1.default.remove(tempFile);
|
|
100
|
+
const config = mod.default || mod;
|
|
103
101
|
console.log(chalk_1.default.green(`π§© Loaded config from ${path_1.default.basename(configFile)}`));
|
|
104
|
-
return
|
|
102
|
+
return config;
|
|
105
103
|
}
|
|
106
104
|
catch (err) {
|
|
107
105
|
const msg = err instanceof Error ? err.message : String(err);
|
|
108
|
-
console.error(chalk_1.default.red(`β
|
|
106
|
+
console.error(chalk_1.default.red(`β Could not load config (${path_1.default.join(cwd, 'react-client.config.js')}): ${msg}`));
|
|
109
107
|
return {};
|
|
110
108
|
}
|
|
111
109
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-client",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "react-client is a lightweight CLI and runtime for building React apps with fast iteration.
|
|
3
|
+
"version": "1.0.10",
|
|
4
|
+
"description": "react-client is a lightweight CLI and runtime for building React apps with fast iteration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Venkatesh Sundaram",
|
|
7
7
|
"repository": {
|
|
@@ -11,21 +11,22 @@
|
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/venkateshsundaram/react-client/issues"
|
|
13
13
|
},
|
|
14
|
-
"
|
|
15
|
-
"types": "dist/index.d.ts",
|
|
14
|
+
"homepage": "https://github.com/venkateshsundaram/react-client#readme",
|
|
16
15
|
"bin": {
|
|
17
16
|
"react-client": "dist/cli/index.js"
|
|
18
17
|
},
|
|
18
|
+
"main": "dist/index.js",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
19
20
|
"exports": {
|
|
20
21
|
".": {
|
|
21
|
-
"
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
22
23
|
"require": "./dist/index.js",
|
|
23
|
-
"
|
|
24
|
+
"import": "./dist/index.js"
|
|
24
25
|
},
|
|
25
26
|
"./config": {
|
|
26
|
-
"
|
|
27
|
+
"types": "./dist/config.d.ts",
|
|
27
28
|
"require": "./dist/config.js",
|
|
28
|
-
"
|
|
29
|
+
"import": "./dist/config.js"
|
|
29
30
|
}
|
|
30
31
|
},
|
|
31
32
|
"typesVersions": {
|
|
@@ -50,29 +51,30 @@
|
|
|
50
51
|
"react-cli",
|
|
51
52
|
"framework",
|
|
52
53
|
"hmr",
|
|
53
|
-
"ssr",
|
|
54
54
|
"esbuild",
|
|
55
55
|
"tooling",
|
|
56
56
|
"vite-alternative",
|
|
57
|
-
"fast-refresh"
|
|
57
|
+
"fast-refresh",
|
|
58
|
+
"react-refresh",
|
|
59
|
+
"tailwind",
|
|
60
|
+
"developer-experience"
|
|
58
61
|
],
|
|
59
62
|
"scripts": {
|
|
60
|
-
"clean": "rm -rf dist",
|
|
63
|
+
"clean": "rm -rf dist .turbo .react-client.temp.config*",
|
|
61
64
|
"compile": "tsc -p tsconfig.build.json",
|
|
62
65
|
"build": "npm run clean && npm run compile",
|
|
63
66
|
"build:cli": "node dist/cli/index.js build",
|
|
64
|
-
"build:ssr": "node dist/cli/index.js build:ssr",
|
|
65
67
|
"dev": "node dist/cli/index.js dev",
|
|
66
68
|
"prepare": "npm run compile",
|
|
67
|
-
"
|
|
68
|
-
"lint": "eslint . --ext .ts",
|
|
69
|
+
"lint": "eslint . --ext .ts --max-warnings 0",
|
|
69
70
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
70
71
|
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
71
72
|
"release": "standard-version",
|
|
72
73
|
"postrelease": "git push --follow-tags",
|
|
73
|
-
"prepublishOnly": "npm run
|
|
74
|
+
"prepublishOnly": "npm run lint && npm run format:check && npm run build && npm test",
|
|
74
75
|
"test": "jest",
|
|
75
|
-
"typecheck": "tsc --noEmit"
|
|
76
|
+
"typecheck": "tsc --noEmit",
|
|
77
|
+
"verify": "bash scripts/local-verify.sh"
|
|
76
78
|
},
|
|
77
79
|
"lint-staged": {
|
|
78
80
|
"*.ts": [
|
|
@@ -82,7 +84,8 @@
|
|
|
82
84
|
},
|
|
83
85
|
"husky": {
|
|
84
86
|
"hooks": {
|
|
85
|
-
"pre-commit": "lint-staged"
|
|
87
|
+
"pre-commit": "lint-staged",
|
|
88
|
+
"pre-push": "npm run lint && npm run typecheck"
|
|
86
89
|
}
|
|
87
90
|
},
|
|
88
91
|
"commitlint": {
|
|
@@ -130,5 +133,8 @@
|
|
|
130
133
|
"standard-version": "^9.5.0",
|
|
131
134
|
"ts-jest": "^29.0.0",
|
|
132
135
|
"typescript": "^5.3.0"
|
|
136
|
+
},
|
|
137
|
+
"engines": {
|
|
138
|
+
"node": ">=18.0.0"
|
|
133
139
|
}
|
|
134
140
|
}
|
|
@@ -1 +1,13 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>React Client App</title>
|
|
7
|
+
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id='root'></div>
|
|
11
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export default ()=> <div>Hello React</div>;
|
|
1
|
+
export default () => <div>Hello React</div>;
|
|
@@ -1 +1,6 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import App from './App';
|
|
4
|
+
|
|
5
|
+
const root = createRoot(document.getElementById('root'));
|
|
6
|
+
root.render(<App />);
|
|
@@ -1 +1,13 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>React Client App</title>
|
|
7
|
+
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id='root'></div>
|
|
11
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@tailwind base;@tailwind components;@tailwind utilities;
|
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
import '/@react-refresh-shim';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { createRoot } from 'react-dom/client';
|
|
5
|
+
import App from './App';
|
|
6
|
+
import './index.css';
|
|
7
|
+
|
|
8
|
+
const root = createRoot(document.getElementById('root'));
|
|
9
|
+
root.render(<App/>);
|
|
@@ -1 +1,13 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>React Client App</title>
|
|
7
|
+
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id='root'></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
Binary file
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
export default function App(){
|
|
2
|
+
export default function App() {
|
|
3
|
+
return <div className='p-8'><h1 className='text-2xl font-bold'>Tailwind TS</h1><p>Welcome to react-tailwind-ts template.</p></div>;
|
|
4
|
+
}
|
|
@@ -1 +1,13 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>React Client App</title>
|
|
7
|
+
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id='root'></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
Binary file
|
|
@@ -1 +1,6 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import App from './App';
|
|
4
|
+
|
|
5
|
+
const root = createRoot(document.getElementById('root')!);
|
|
6
|
+
root.render(<App/>);
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.default = buildSsr;
|
|
7
|
-
const esbuild_1 = __importDefault(require("esbuild"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
-
const loadConfig_1 = require("../../utils/loadConfig");
|
|
12
|
-
async function buildSsr() {
|
|
13
|
-
const root = process.cwd();
|
|
14
|
-
const config = await (0, loadConfig_1.loadReactClientConfig)(root);
|
|
15
|
-
const appRoot = path_1.default.resolve(root, config.root || '.');
|
|
16
|
-
const outDir = path_1.default.join(appRoot, config.build?.outDir || '.react-client/ssr');
|
|
17
|
-
console.log(chalk_1.default.cyan(`\nπ§± Building SSR bundle...`));
|
|
18
|
-
console.log(chalk_1.default.gray(`Root: ${appRoot}`));
|
|
19
|
-
console.log(chalk_1.default.gray(`Output: ${outDir}\n`));
|
|
20
|
-
const entry = path_1.default.join(appRoot, 'src', 'server.tsx');
|
|
21
|
-
if (!fs_extra_1.default.existsSync(entry)) {
|
|
22
|
-
console.error(chalk_1.default.red('β SSR entry not found: src/server.tsx'));
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
await fs_extra_1.default.ensureDir(outDir);
|
|
26
|
-
try {
|
|
27
|
-
await esbuild_1.default.build({
|
|
28
|
-
entryPoints: [entry],
|
|
29
|
-
bundle: true,
|
|
30
|
-
platform: 'node',
|
|
31
|
-
format: 'esm',
|
|
32
|
-
target: 'node18',
|
|
33
|
-
external: ['react', 'react-dom'],
|
|
34
|
-
outdir: outDir,
|
|
35
|
-
define: { 'process.env.NODE_ENV': '"production"' },
|
|
36
|
-
loader: { '.ts': 'ts', '.tsx': 'tsx' },
|
|
37
|
-
});
|
|
38
|
-
console.log(chalk_1.default.green(`β
SSR build completed successfully!`));
|
|
39
|
-
console.log(chalk_1.default.gray(`Output directory: ${outDir}`));
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
43
|
-
console.error('β SSR build failed:', msg);
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.default = generate;
|
|
7
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const string_1 = require("../../utils/string");
|
|
10
|
-
async function generate(kind, name, opts = {}) {
|
|
11
|
-
const root = process.cwd();
|
|
12
|
-
const useTS = opts.ts !== false && opts['no-ts'] !== true;
|
|
13
|
-
if (kind === 'component') {
|
|
14
|
-
const pascal = (0, string_1.pascalCase)(name);
|
|
15
|
-
const dir = path_1.default.join(root, opts.path || 'src/components');
|
|
16
|
-
await fs_extra_1.default.ensureDir(dir);
|
|
17
|
-
const ext = useTS ? 'tsx' : 'jsx';
|
|
18
|
-
const compPath = path_1.default.join(dir, `${pascal}.${ext}`);
|
|
19
|
-
const css = path_1.default.join(dir, `${pascal}.module.css`);
|
|
20
|
-
await fs_extra_1.default.writeFile(compPath, `import React from 'react';\nimport styles from './${pascal}.module.css';\nexport default function ${pascal}(){ return <div className={styles.root}>${pascal}</div>; }\n`);
|
|
21
|
-
await fs_extra_1.default.writeFile(css, `.root{display:block}`);
|
|
22
|
-
console.log('Created component', compPath);
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
if (kind === 'route') {
|
|
26
|
-
const parts = name.replace(/^\//, '').split('/').filter(Boolean);
|
|
27
|
-
const pages = path_1.default.join(root, 'src', 'pages', ...parts.slice(0, -1));
|
|
28
|
-
await fs_extra_1.default.ensureDir(pages);
|
|
29
|
-
const last = parts[parts.length - 1] || 'index';
|
|
30
|
-
const file = path_1.default.join(pages, last + '.' + (useTS ? 'tsx' : 'jsx'));
|
|
31
|
-
if (await fs_extra_1.default.pathExists(file)) {
|
|
32
|
-
console.error('Route exists');
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
const compName = (0, string_1.pascalCase)(parts.join('-') || 'IndexPage');
|
|
36
|
-
const content = `import React from 'react';\nexport default function ${compName}(){ return (<div style={{padding:20}}><h1>${compName}</h1></div>); }\n`;
|
|
37
|
-
await fs_extra_1.default.writeFile(file, content, 'utf8');
|
|
38
|
-
console.log('Created route', file);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
console.log('Unknown generator', kind);
|
|
42
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<!doctype html><html><head><meta charset='utf-8'></head><body><div id='root'></div><script type='module' src='/client/bundle.js'></script></body></html>
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "template-react-ssr",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"private": true,
|
|
5
|
-
"scripts": {
|
|
6
|
-
"dev": "react-client dev",
|
|
7
|
-
"build": "react-client build:ssr",
|
|
8
|
-
"preview": "react-client preview"
|
|
9
|
-
},
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"react": "^18.2.0",
|
|
12
|
-
"react-dom": "^18.2.0"
|
|
13
|
-
}
|
|
14
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import React from 'react';import { hydrateRoot } from 'react-dom/client';import Home from './pages/index';hydrateRoot(document.getElementById('root'), React.createElement(Home));
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import React from 'react';import { renderToString } from 'react-dom/server';import Home from './pages/index';exports.render = function(url){ const Page = Home; return Promise.resolve(renderToString(React.createElement(Page))); };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import React from 'react';export default function Home(){ return <div>Home SSR</div>; }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<!doctype html><html><head><meta charset='utf-8' /></head><body><div id='root'></div><script type='module' src='/client/bundle.js'></script></body></html>
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "my-react-ssr-ts-app",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"private": true,
|
|
5
|
-
"scripts": {
|
|
6
|
-
"dev": "react-client dev",
|
|
7
|
-
"build": "react-client build:ssr",
|
|
8
|
-
"preview": "react-client preview"
|
|
9
|
-
},
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"react": "^18.2.0",
|
|
12
|
-
"react-dom": "^18.2.0"
|
|
13
|
-
},
|
|
14
|
-
"devDependencies": {
|
|
15
|
-
"@types/react": "^18.0.0",
|
|
16
|
-
"@types/react-dom": "^18.0.0",
|
|
17
|
-
"esbuild": "^0.25.12",
|
|
18
|
-
"react-refresh": "^0.14.0",
|
|
19
|
-
"typescript": "^5.0.0"
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
export default function App() { return _jsxs("div", { className: 'p-8', children: [_jsx("h1", { className: 'text-2xl font-bold', children: "Tailwind TS" }), _jsx("p", { children: "Welcome to react-tailwind-ts template." })] }); }
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import '/@react-refresh-shim';
|
|
3
|
-
import { createRoot } from 'react-dom/client';
|
|
4
|
-
import App from './App';
|
|
5
|
-
import './index.css';
|
|
6
|
-
const root = createRoot(document.getElementById('root'));
|
|
7
|
-
root.render(_jsx(App, {}));
|