react-client 1.0.1 → 1.0.5
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/LICENSE +21 -0
- package/README.md +126 -0
- package/dist/cli/commands/build.js +61 -0
- package/dist/cli/commands/build.ssr.js +69 -0
- package/dist/cli/commands/dev.js +29 -0
- package/dist/cli/commands/generate.js +42 -0
- package/dist/cli/commands/init.js +24 -0
- package/dist/cli/commands/preview.js +30 -0
- package/dist/cli/index.js +47 -0
- package/dist/cli/types.js +3 -0
- package/dist/index.js +20 -0
- package/dist/utils/string.js +6 -0
- package/package.json +91 -8
- package/index.js +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Venkatesh Sundaram
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/react-client)
|
|
2
|
+
[](https://npm-stat.com/charts.html?package=react-client)
|
|
3
|
+
[](https://github.com/venkateshsundaram/react-client/issues)
|
|
4
|
+
|
|
5
|
+
react-client is a lightweight CLI and runtime for building React apps with fast iteration. It is designed to be esbuild-based, Node-native, and modular like Vite/Next.js while remaining minimal.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Wiki](#wiki)
|
|
10
|
+
- [Available templates](#available-templates)
|
|
11
|
+
- [Features supported by the CLI](#features-supported-by-the-cli)
|
|
12
|
+
- [Template specifics](#template-specifics)
|
|
13
|
+
* [react-ssr-ts](#react-ssr-ts)
|
|
14
|
+
* [react-tailwind-ts](#react-tailwind-ts)
|
|
15
|
+
- [How the CLI wires features](#how-the-cli-wires-features)
|
|
16
|
+
- [Local testing checklist](#local-testing-checklist)
|
|
17
|
+
- [Troubleshooting](#troubleshooting)
|
|
18
|
+
- [Extending & Contributing](#extending-contributing)
|
|
19
|
+
- [Contributing](#contributing)
|
|
20
|
+
- [Publishing](#publishing)
|
|
21
|
+
- [Feedbacks and Issues](#feedbacks-and-issues)
|
|
22
|
+
- [License](#license)
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
The React-client package lives in npm.
|
|
27
|
+
|
|
28
|
+
To install the latest stable version, run the following command:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g react-client
|
|
32
|
+
react-client init myapp --template react-ts
|
|
33
|
+
cd myapp
|
|
34
|
+
npm run dev
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Wiki
|
|
38
|
+
|
|
39
|
+
[](https://deepwiki.com/venkateshsundaram/react-client)
|
|
40
|
+
|
|
41
|
+
## Available templates
|
|
42
|
+
|
|
43
|
+
- react — JavaScript SPA
|
|
44
|
+
- react-ts — TypeScript SPA
|
|
45
|
+
- react-ssr — JavaScript SSR app
|
|
46
|
+
- react-ssr-ts — TypeScript SSR app
|
|
47
|
+
- react-tailwind — JavaScript + Tailwind
|
|
48
|
+
- react-tailwind-ts — TypeScript + Tailwind
|
|
49
|
+
|
|
50
|
+
## Features supported by the CLI
|
|
51
|
+
|
|
52
|
+
- React Fast Refresh: HMR that preserves component state
|
|
53
|
+
- PostCSS/Tailwind auto-injection and setup for Tailwind templates
|
|
54
|
+
- CSS Modules support with hashing and AST-based import rewriting
|
|
55
|
+
- Asset hashing and manifest generation for SSR runtime lookups
|
|
56
|
+
- Plugin hook system (configResolved, transform, buildEnd)
|
|
57
|
+
- Generators: component/route/test scaffolding
|
|
58
|
+
- SSR runtime with server that consults manifest.json
|
|
59
|
+
|
|
60
|
+
## Template specifics
|
|
61
|
+
|
|
62
|
+
### react-ssr-ts
|
|
63
|
+
|
|
64
|
+
- `src/entry-client.tsx` — client hydration entry
|
|
65
|
+
- `src/entry-server.tsx` — server rendering entry (exports `render(url)`)
|
|
66
|
+
- `src/pages/` — route components
|
|
67
|
+
- `index.html` — template used by build system
|
|
68
|
+
- `package.json` lists devDependencies required for local testing (esbuild, react-refresh)
|
|
69
|
+
|
|
70
|
+
### react-tailwind-ts
|
|
71
|
+
|
|
72
|
+
- `postcss.config.cjs` and `tailwind.config.cjs` included
|
|
73
|
+
- `src/index.css` with Tailwind directives
|
|
74
|
+
- `package.json` includes tailwind/postcss devDependencies (install locally in the app)
|
|
75
|
+
|
|
76
|
+
## How the CLI wires features
|
|
77
|
+
|
|
78
|
+
1. **HMR & React Refresh**: dev plugin injects `import '/@react-refresh-shim'` into files that import React. During build, the CLI attempts to `require.resolve('react-refresh/runtime')` and copy it into the client vendor folder to be included in bundles.
|
|
79
|
+
2. **CSS Modules**: build step uses `postcss-modules` to produce JSON class maps and then uses `es-module-lexer` + `magic-string` to rewrite `.module.css` import specifiers to the hashed filenames emitted by the bundler.
|
|
80
|
+
3. **Manifest**: after building, the CLI hashes outputs and writes `client/manifest.json` mapping original -> hashed filenames. The SSR server uses this manifest to serve correct hashed assets and update HTML script tags dynamically.
|
|
81
|
+
4. **PostCSS/Tailwind**: if `postcss.config.cjs` or `tailwind.config.cjs` exists in the project, the build process runs `npx postcss` to process CSS and includes the output in the final client bundle.
|
|
82
|
+
|
|
83
|
+
## Local testing checklist
|
|
84
|
+
|
|
85
|
+
1. Node 20+ installed.
|
|
86
|
+
2. Scaffold a new app using the CLI `init` command pointing to these templates.
|
|
87
|
+
3. `npm install` in the new app to get dependencies.
|
|
88
|
+
4. Run `node /path/to/react-client/dist/cli/index.js dev` for HMR-enabled dev server, or `build:ssr` to produce SSR build and run `node dist/server/server.js` to preview.
|
|
89
|
+
|
|
90
|
+
## Troubleshooting
|
|
91
|
+
|
|
92
|
+
- If `react-refresh/runtime` isn't found during build, install in the environment running the CLI:
|
|
93
|
+
```bash
|
|
94
|
+
npm install --save-dev react-refresh
|
|
95
|
+
```
|
|
96
|
+
- For PostCSS/Tailwind errors, ensure tailwind & postcss installed in the target app:
|
|
97
|
+
```bash
|
|
98
|
+
npm install --save-dev tailwindcss postcss autoprefixer
|
|
99
|
+
```
|
|
100
|
+
- For AST rewriting, ensure `es-module-lexer` and `magic-string` are installed where the CLI runs:
|
|
101
|
+
```bash
|
|
102
|
+
npm install --save-dev es-module-lexer magic-string
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Extending & Contributing
|
|
106
|
+
|
|
107
|
+
- Add plugins under `src/plugins` with `configResolved` and `transform` hooks.
|
|
108
|
+
- Tests: add Jest configurations inside templates and include `test` npm scripts.
|
|
109
|
+
|
|
110
|
+
## Contributing
|
|
111
|
+
|
|
112
|
+
Development of react-client happens in the open on GitHub, and we are grateful to the community for contributing bugfixes and improvements. Read below to learn how you can take part in improving react-client.
|
|
113
|
+
|
|
114
|
+
- [Contributing Guide](./CONTRIBUTING.md)
|
|
115
|
+
|
|
116
|
+
## Publishing
|
|
117
|
+
|
|
118
|
+
Before pushing your changes to Github, make sure that `version` in `package.json` is changed to newest version. Then run `npm install` for synchronize it to `package-lock.json` and `pnpm install` for synchronize it to `pnpm-lock.yaml`
|
|
119
|
+
|
|
120
|
+
## Feedbacks and Issues
|
|
121
|
+
|
|
122
|
+
Feel free to open issues if you found any feedback or issues on `react-client`. And feel free if you want to contribute too! 😄
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
React-client is [MIT licensed](./LICENSE).
|
|
@@ -0,0 +1,61 @@
|
|
|
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 = build;
|
|
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 child_process_1 = require("child_process");
|
|
11
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
12
|
+
function hash(buf) {
|
|
13
|
+
return crypto_1.default.createHash('sha256').update(buf).digest('hex').slice(0, 8);
|
|
14
|
+
}
|
|
15
|
+
async function build() {
|
|
16
|
+
const root = process.cwd();
|
|
17
|
+
const entry = path_1.default.join(root, 'src', 'main.tsx');
|
|
18
|
+
const out = path_1.default.join(root, 'dist');
|
|
19
|
+
await fs_extra_1.default.remove(out);
|
|
20
|
+
const result = await esbuild_1.default.build({
|
|
21
|
+
entryPoints: [entry],
|
|
22
|
+
bundle: true,
|
|
23
|
+
outdir: path_1.default.join(out, 'client'),
|
|
24
|
+
metafile: true,
|
|
25
|
+
minify: true,
|
|
26
|
+
loader: {
|
|
27
|
+
'.ts': 'ts',
|
|
28
|
+
'.tsx': 'tsx',
|
|
29
|
+
'.js': 'jsx',
|
|
30
|
+
'.jsx': 'jsx',
|
|
31
|
+
'.png': 'file',
|
|
32
|
+
'.jpg': 'file',
|
|
33
|
+
'.svg': 'file',
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
const postcssConfig = path_1.default.join(root, 'postcss.config.cjs');
|
|
37
|
+
if (fs_extra_1.default.existsSync(postcssConfig)) {
|
|
38
|
+
try {
|
|
39
|
+
(0, child_process_1.execSync)('npx postcss ' + path_1.default.join(out, 'client', '*.css') + ' -d ' + path_1.default.join(out, 'client'));
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
console.warn('PostCSS failed', e && e.message);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const manifest = {};
|
|
46
|
+
for (const outPath in result.metafile.outputs) {
|
|
47
|
+
const abs = path_1.default.join(root, outPath);
|
|
48
|
+
if (!fs_extra_1.default.existsSync(abs))
|
|
49
|
+
continue;
|
|
50
|
+
const data = await fs_extra_1.default.readFile(abs);
|
|
51
|
+
const h = hash(data);
|
|
52
|
+
const rel = outPath.replace(/^client\//, '');
|
|
53
|
+
const ext = path_1.default.extname(rel);
|
|
54
|
+
const hashed = rel.replace(ext, '.' + h + ext);
|
|
55
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(out, 'client', path_1.default.dirname(hashed)));
|
|
56
|
+
await fs_extra_1.default.move(abs, path_1.default.join(out, 'client', hashed));
|
|
57
|
+
manifest[rel] = hashed;
|
|
58
|
+
}
|
|
59
|
+
await fs_extra_1.default.writeFile(path_1.default.join(out, 'client', 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
|
|
60
|
+
console.log('Built client to', path_1.default.join(out, 'client'));
|
|
61
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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 crypto_1 = __importDefault(require("crypto"));
|
|
11
|
+
function hash(buf) {
|
|
12
|
+
return crypto_1.default.createHash('sha256').update(buf).digest('hex').slice(0, 8);
|
|
13
|
+
}
|
|
14
|
+
async function buildSsr() {
|
|
15
|
+
const root = process.cwd();
|
|
16
|
+
const clientEntry = path_1.default.join(root, 'src', 'entry-client.tsx');
|
|
17
|
+
const serverEntry = path_1.default.join(root, 'src', 'entry-server.tsx');
|
|
18
|
+
if (!fs_extra_1.default.existsSync(clientEntry) || !fs_extra_1.default.existsSync(serverEntry)) {
|
|
19
|
+
console.error('SSR entries missing');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const out = path_1.default.join(root, 'dist');
|
|
23
|
+
await fs_extra_1.default.remove(out);
|
|
24
|
+
const clientResult = await esbuild_1.default.build({
|
|
25
|
+
entryPoints: [clientEntry],
|
|
26
|
+
bundle: true,
|
|
27
|
+
outdir: path_1.default.join(out, 'client'),
|
|
28
|
+
metafile: true,
|
|
29
|
+
minify: true,
|
|
30
|
+
loader: {
|
|
31
|
+
'.ts': 'ts',
|
|
32
|
+
'.tsx': 'tsx',
|
|
33
|
+
'.js': 'jsx',
|
|
34
|
+
'.jsx': 'jsx',
|
|
35
|
+
'.png': 'file',
|
|
36
|
+
'.jpg': 'file',
|
|
37
|
+
'.svg': 'file',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const _serverResult = await esbuild_1.default.build({
|
|
41
|
+
entryPoints: [serverEntry],
|
|
42
|
+
bundle: true,
|
|
43
|
+
platform: 'node',
|
|
44
|
+
format: 'cjs',
|
|
45
|
+
outfile: path_1.default.join(out, 'server', 'entry-server.js'),
|
|
46
|
+
external: ['react', 'react-dom'],
|
|
47
|
+
});
|
|
48
|
+
const manifest = {};
|
|
49
|
+
for (const outPath in clientResult.metafile.outputs) {
|
|
50
|
+
if (!outPath.startsWith('client/'))
|
|
51
|
+
continue;
|
|
52
|
+
const abs = path_1.default.join(out, outPath);
|
|
53
|
+
if (!fs_extra_1.default.existsSync(abs))
|
|
54
|
+
continue;
|
|
55
|
+
const data = await fs_extra_1.default.readFile(abs);
|
|
56
|
+
const h = hash(data);
|
|
57
|
+
const rel = outPath.replace(/^client\//, '');
|
|
58
|
+
const ext = path_1.default.extname(rel);
|
|
59
|
+
const hashed = rel.replace(ext, '.' + h + ext);
|
|
60
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(out, 'client', path_1.default.dirname(hashed)));
|
|
61
|
+
await fs_extra_1.default.move(abs, path_1.default.join(out, 'client', hashed));
|
|
62
|
+
manifest[rel] = hashed;
|
|
63
|
+
}
|
|
64
|
+
await fs_extra_1.default.writeFile(path_1.default.join(out, 'client', 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
|
|
65
|
+
const runtime = `const http = require('http');const fs = require('fs');const path=require('path');const { render } = require('./entry-server.js');const clientDir = path.join(__dirname,'..','client');const manifest = JSON.parse(fs.readFileSync(path.join(clientDir,'manifest.json'),'utf8'));const PORT = process.env.PORT||3000;const server = http.createServer(async (req,res)=>{ try{ if(req.url && req.url.startsWith('/client/')){ const rel = req.url.replace('/client/',''); const mapped = manifest[rel] || rel; const p = path.join(clientDir, mapped); if(fs.existsSync(p)) { res.writeHead(200); res.end(fs.readFileSync(p)); return; } } const htmlPath = path.join(clientDir,'index.html'); let html = '<!doctype html><html><head></head><body><div id="root"></div><script type="module" src="/client/bundle.js"></script></body></html>'; if(fs.existsSync(htmlPath)) html = fs.readFileSync(htmlPath,'utf8'); const content = await render(req.url||'/'); const outHtml = html.replace('<div id="root"></div>','<div id="root">'+content+'</div>'); res.writeHead(200,{'Content-Type':'text/html'}); res.end(outHtml);}catch(e){ console.error(e); res.writeHead(500); res.end('SSR error');}});server.listen(PORT,()=>console.log('SSR server running on port '+PORT));`;
|
|
66
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(out, 'server'));
|
|
67
|
+
await fs_extra_1.default.writeFile(path_1.default.join(out, 'server', 'server.js'), runtime, 'utf8');
|
|
68
|
+
console.log('SSR build complete at', out);
|
|
69
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
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 = dev;
|
|
7
|
+
const esbuild_1 = __importDefault(require("esbuild"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
|
+
async function dev() {
|
|
11
|
+
const root = process.cwd();
|
|
12
|
+
const entry = path_1.default.join(root, 'src', 'main.tsx');
|
|
13
|
+
if (!fs_extra_1.default.existsSync(entry)) {
|
|
14
|
+
console.error('Entry not found: src/main.tsx');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const outdir = path_1.default.join(root, '.react-client', 'dev');
|
|
18
|
+
await fs_extra_1.default.ensureDir(outdir);
|
|
19
|
+
const ctx = await esbuild_1.default.context({
|
|
20
|
+
entryPoints: [entry],
|
|
21
|
+
bundle: true,
|
|
22
|
+
sourcemap: true,
|
|
23
|
+
outdir,
|
|
24
|
+
define: { 'process.env.NODE_ENV': '"development"' },
|
|
25
|
+
loader: { '.ts': 'ts', '.tsx': 'tsx', '.js': 'jsx', '.jsx': 'jsx' },
|
|
26
|
+
});
|
|
27
|
+
await ctx.watch();
|
|
28
|
+
await ctx.serve({ servedir: outdir, port: 5173 });
|
|
29
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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 = init;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
async function init(name, opts = {}) {
|
|
10
|
+
const root = path_1.default.resolve(process.cwd(), name);
|
|
11
|
+
const template = opts.template || 'react-ts';
|
|
12
|
+
await fs_extra_1.default.ensureDir(root);
|
|
13
|
+
const tpl = path_1.default.join(__dirname, '../../templates', template);
|
|
14
|
+
if (!fs_extra_1.default.existsSync(tpl)) {
|
|
15
|
+
console.error('Template not found:', template);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
await fs_extra_1.default.copy(tpl, root);
|
|
19
|
+
if (opts.withConfig) {
|
|
20
|
+
const cfg = "import { defineConfig } from 'react-client';\nexport default defineConfig({});\n";
|
|
21
|
+
await fs_extra_1.default.writeFile(path_1.default.join(root, 'react-client.config.ts'), cfg, 'utf8');
|
|
22
|
+
}
|
|
23
|
+
console.log('Project created at', root);
|
|
24
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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 = preview;
|
|
7
|
+
const http_1 = __importDefault(require("http"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
async function preview() {
|
|
11
|
+
const out = path_1.default.join(process.cwd(), 'dist');
|
|
12
|
+
if (!fs_1.default.existsSync(out)) {
|
|
13
|
+
console.error('dist not found, run build');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const server = http_1.default.createServer((req, res) => {
|
|
17
|
+
let url = req.url?.split('?')[0] || '/';
|
|
18
|
+
if (url === '/')
|
|
19
|
+
url = '/index.html';
|
|
20
|
+
const f = path_1.default.join(out, url);
|
|
21
|
+
if (fs_1.default.existsSync(f)) {
|
|
22
|
+
res.writeHead(200);
|
|
23
|
+
res.end(fs_1.default.readFileSync(f));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
res.writeHead(404);
|
|
27
|
+
res.end('Not found');
|
|
28
|
+
});
|
|
29
|
+
server.listen(5000, () => console.log('Preview running at http://localhost:5000'));
|
|
30
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const init_1 = __importDefault(require("./commands/init"));
|
|
9
|
+
const generate_1 = __importDefault(require("./commands/generate"));
|
|
10
|
+
const dev_1 = __importDefault(require("./commands/dev"));
|
|
11
|
+
const build_1 = __importDefault(require("./commands/build"));
|
|
12
|
+
const build_ssr_1 = __importDefault(require("./commands/build.ssr"));
|
|
13
|
+
const preview_1 = __importDefault(require("./commands/preview"));
|
|
14
|
+
const program = new commander_1.Command();
|
|
15
|
+
program.name('react-client').version('1.0.0').description('react-client CLI');
|
|
16
|
+
program
|
|
17
|
+
.command('init <name>')
|
|
18
|
+
.option('-t,--template <template>', 'template', 'react-ts')
|
|
19
|
+
.option('--with-config', 'create config')
|
|
20
|
+
.action((name, opts) => (0, init_1.default)(name, opts));
|
|
21
|
+
program
|
|
22
|
+
.command('generate <kind> <name>')
|
|
23
|
+
.option('-p,--path <path>', 'path')
|
|
24
|
+
.option('--no-ts', 'generate JS')
|
|
25
|
+
.option('-f,--force', 'force')
|
|
26
|
+
.action((k, n, o) => (0, generate_1.default)(k, n, o));
|
|
27
|
+
program
|
|
28
|
+
.command('dev')
|
|
29
|
+
.description('start dev server')
|
|
30
|
+
.action(() => (0, dev_1.default)());
|
|
31
|
+
program
|
|
32
|
+
.command('build')
|
|
33
|
+
.description('build app')
|
|
34
|
+
.action(() => (0, build_1.default)());
|
|
35
|
+
program
|
|
36
|
+
.command('build:ssr')
|
|
37
|
+
.description('build ssr')
|
|
38
|
+
.action(() => (0, build_ssr_1.default)());
|
|
39
|
+
program
|
|
40
|
+
.command('preview')
|
|
41
|
+
.description('preview build')
|
|
42
|
+
.action(() => (0, preview_1.default)());
|
|
43
|
+
// Only parse argv when executed directly as a CLI, not when imported by tests or other code.
|
|
44
|
+
if (require.main === module) {
|
|
45
|
+
program.parse(process.argv);
|
|
46
|
+
}
|
|
47
|
+
exports.default = program;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.defineConfig = void 0;
|
|
18
|
+
const defineConfig = (c) => c;
|
|
19
|
+
exports.defineConfig = defineConfig;
|
|
20
|
+
__exportStar(require("./cli/index"), exports);
|
package/package.json
CHANGED
|
@@ -1,17 +1,100 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-client",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "react
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"
|
|
7
|
-
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "react-client is a lightweight CLI and runtime for building React apps with fast iteration. It is designed to be esbuild-based, Node-native, and modular like Vite/Next.js with SSR, HMR, Tailwind, CSS Modules, and generators.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"bin": {
|
|
12
|
+
"react-client": "dist/cli/index.js"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/venkateshsundaram/react-client.git"
|
|
8
17
|
},
|
|
9
18
|
"keywords": [
|
|
10
19
|
"react",
|
|
20
|
+
"javascript",
|
|
21
|
+
"typescript",
|
|
11
22
|
"cli",
|
|
12
23
|
"react-cli",
|
|
13
|
-
"react-client"
|
|
24
|
+
"react-client",
|
|
25
|
+
"ssr",
|
|
26
|
+
"esbuild",
|
|
27
|
+
"tailwind",
|
|
28
|
+
"css-modules",
|
|
29
|
+
"hmr",
|
|
30
|
+
"framework",
|
|
31
|
+
"tooling"
|
|
14
32
|
],
|
|
15
|
-
"author": "
|
|
16
|
-
"license": "MIT"
|
|
33
|
+
"author": "venkateshsundaram",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"workspaces": [
|
|
36
|
+
"templates/*"
|
|
37
|
+
],
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/venkateshsundaram/react-client/issues"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"clean": "rm -rf dist",
|
|
43
|
+
"compile": "tsc -p tsconfig.build.json",
|
|
44
|
+
"build": "npm run clean && npm run compile",
|
|
45
|
+
"build:cli": "node dist/cli/index.js build",
|
|
46
|
+
"build:ssr": "node dist/cli/index.js build:ssr",
|
|
47
|
+
"dev": "node dist/cli/index.js dev",
|
|
48
|
+
"prepare": "npm run compile",
|
|
49
|
+
"lint": "eslint . --ext .ts",
|
|
50
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
51
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
52
|
+
"release": "standard-version",
|
|
53
|
+
"postrelease": "git push --follow-tags",
|
|
54
|
+
"prepublishOnly": "npm run compile",
|
|
55
|
+
"test": "jest",
|
|
56
|
+
"typecheck": "tsc --noEmit"
|
|
57
|
+
},
|
|
58
|
+
"husky": {
|
|
59
|
+
"hooks": {
|
|
60
|
+
"pre-commit": "lint-staged"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"lint-staged": {
|
|
64
|
+
"*.ts": [
|
|
65
|
+
"eslint --fix",
|
|
66
|
+
"prettier --write"
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
"commitlint": {
|
|
70
|
+
"extends": [
|
|
71
|
+
"@commitlint/config-conventional"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@commitlint/cli": "^19.8.0",
|
|
76
|
+
"@commitlint/config-conventional": "^19.8.0",
|
|
77
|
+
"@types/commander": "^2.12.0",
|
|
78
|
+
"@types/fs-extra": "^9.0.13",
|
|
79
|
+
"@types/jest": "^29.0.0",
|
|
80
|
+
"@types/node": "^18.0.0",
|
|
81
|
+
"@typescript-eslint/eslint-plugin": "^8.46.3",
|
|
82
|
+
"@typescript-eslint/parser": "^8.46.3",
|
|
83
|
+
"esbuild": "^0.25.12",
|
|
84
|
+
"eslint": "^8.0.0",
|
|
85
|
+
"eslint-config-prettier": "^8.0.0",
|
|
86
|
+
"eslint-plugin-prettier": "^4.0.0",
|
|
87
|
+
"eslint-plugin-react": "^7.32.2",
|
|
88
|
+
"fs-extra": "^11.1.1",
|
|
89
|
+
"husky": "^9.1.7",
|
|
90
|
+
"jest": "^29.0.0",
|
|
91
|
+
"lint-staged": "^15.4.3",
|
|
92
|
+
"prettier": "^2.8.8",
|
|
93
|
+
"standard-version": "^9.5.0",
|
|
94
|
+
"ts-jest": "^29.0.0",
|
|
95
|
+
"typescript": "^5.3.0"
|
|
96
|
+
},
|
|
97
|
+
"dependencies": {
|
|
98
|
+
"commander": "^14.0.2"
|
|
99
|
+
}
|
|
17
100
|
}
|
package/index.js
DELETED