react-client 1.0.19 → 1.0.21

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 CHANGED
@@ -59,7 +59,7 @@ import { defineConfig } from 'react-client/config';
59
59
 
60
60
  export default defineConfig({
61
61
  root: './src',
62
- server: { port: 5173 },
62
+ server: { port: 2202 },
63
63
  build: { outDir: '.react-client/build' }
64
64
  });
65
65
  ```
@@ -89,7 +89,7 @@ Each template is pre-configured for esbuild, HMR, and fast bootstrapping.
89
89
  - šŸ” **React Fast Refresh (HMR)** — State-preserving reloads
90
90
  - šŸ’„ **Overlay** — Syntax-highlighted stack frames, clickable file links (`vscode://file`)
91
91
  - šŸ” **Source Map Stack Mapping** — Maps runtime errors to original TS/JS source lines
92
- - šŸ’¬ **Auto Port Detection** — Prompts when default port 5173 is occupied
92
+ - šŸ’¬ **Auto Port Detection** — Prompts when default port 2202 is occupied
93
93
  - 🧠 **Smart Config Loader** — Detects project root, compiles `.ts` configs dynamically
94
94
  - šŸŽØ **PrismJS Highlighting** — For pretty overlay code frames
95
95
  - šŸ”Œ **Plugin Hook System** — Extendable with `configResolved`, `transform`, `buildEnd`
@@ -142,7 +142,7 @@ npm install react-refresh
142
142
  ### āš ļø Port already in use
143
143
  CLI will auto-detect and prompt:
144
144
  ```
145
- Port 5173 is occupied. Use 5174 instead? (Y/n)
145
+ Port 2202 is occupied. Use 5174 instead? (Y/n)
146
146
  ```
147
147
 
148
148
  ### āš ļø Permission denied
@@ -1,4 +1,14 @@
1
1
  "use strict";
2
+ /**
3
+ * šŸš€ react-client Dev Server (Final Version)
4
+ * Includes:
5
+ * - Favicon & public asset support
6
+ * - ETag + gzip/brotli caching
7
+ * - Persistent prebundle deps (.react-client/deps)
8
+ * - HMR + overlay
9
+ * - CSS hot reload
10
+ * - ESLint + Prettier clean
11
+ */
2
12
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
13
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
14
  };
@@ -15,50 +25,106 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
15
25
  const open_1 = __importDefault(require("open"));
16
26
  const child_process_1 = require("child_process");
17
27
  const chalk_1 = __importDefault(require("chalk"));
28
+ const zlib_1 = __importDefault(require("zlib"));
29
+ const crypto_1 = __importDefault(require("crypto"));
18
30
  const loadConfig_1 = require("../../utils/loadConfig");
19
31
  const broadcastManager_1 = require("../../server/broadcastManager");
32
+ // Node polyfill mapping
33
+ const NODE_POLYFILLS = {
34
+ buffer: 'buffer/',
35
+ process: 'process/browser',
36
+ path: 'path-browserify',
37
+ fs: 'browserify-fs',
38
+ os: 'os-browserify/browser',
39
+ stream: 'stream-browserify',
40
+ util: 'util/',
41
+ url: 'url/',
42
+ assert: 'assert/',
43
+ crypto: 'crypto-browserify',
44
+ events: 'events/',
45
+ constants: 'constants-browserify',
46
+ querystring: 'querystring-es3',
47
+ zlib: 'browserify-zlib',
48
+ };
49
+ // --- Helper utilities
50
+ const computeHash = (content) => crypto_1.default.createHash('sha1').update(content).digest('hex');
51
+ const getMimeType = (file) => {
52
+ const ext = path_1.default.extname(file).toLowerCase();
53
+ const mime = {
54
+ '.ico': 'image/x-icon',
55
+ '.png': 'image/png',
56
+ '.jpg': 'image/jpeg',
57
+ '.jpeg': 'image/jpeg',
58
+ '.gif': 'image/gif',
59
+ '.svg': 'image/svg+xml',
60
+ '.webp': 'image/webp',
61
+ '.json': 'application/json',
62
+ '.txt': 'text/plain',
63
+ '.js': 'application/javascript',
64
+ '.mjs': 'application/javascript',
65
+ '.css': 'text/css',
66
+ '.html': 'text/html',
67
+ '.woff': 'font/woff',
68
+ '.woff2': 'font/woff2',
69
+ '.ttf': 'font/ttf',
70
+ '.otf': 'font/otf',
71
+ '.mp4': 'video/mp4',
72
+ '.mp3': 'audio/mpeg',
73
+ };
74
+ return mime[ext] || 'application/octet-stream';
75
+ };
76
+ // āœ… Unused helpers are underscored to comply with eslint rules
77
+ const _gunzipAsync = (input) => new Promise((res, rej) => zlib_1.default.gunzip(input, (e, out) => (e ? rej(e) : res(out))));
78
+ const _brotliAsync = (input) => new Promise((res, rej) => zlib_1.default.brotliDecompress(input, (e, out) => (e ? rej(e) : res(out))));
20
79
  async function dev() {
21
80
  const root = process.cwd();
22
81
  const userConfig = await (0, loadConfig_1.loadReactClientConfig)(root);
23
82
  const appRoot = path_1.default.resolve(root, userConfig.root || '.');
24
83
  const defaultPort = userConfig.server?.port || 5173;
25
84
  const cacheDir = path_1.default.join(appRoot, '.react-client', 'deps');
26
- const pkgFile = path_1.default.join(appRoot, 'package.json');
27
85
  await fs_extra_1.default.ensureDir(cacheDir);
28
- // Detect entry file
86
+ const indexHtml = path_1.default.join(appRoot, 'index.html');
87
+ const pkgFile = path_1.default.join(appRoot, 'package.json');
88
+ // Detect entry
29
89
  const possibleEntries = ['src/main.tsx', 'src/main.jsx'];
30
90
  const entry = possibleEntries.map((p) => path_1.default.join(appRoot, p)).find((p) => fs_extra_1.default.existsSync(p));
31
91
  if (!entry) {
32
92
  console.error(chalk_1.default.red('āŒ No entry found: src/main.tsx or src/main.jsx'));
33
93
  process.exit(1);
34
94
  }
35
- const indexHtml = path_1.default.join(appRoot, 'index.html');
36
95
  // Detect open port
37
- const availablePort = await (0, detect_port_1.default)(defaultPort);
38
- const port = availablePort;
39
- if (availablePort !== defaultPort) {
96
+ const port = await (0, detect_port_1.default)(defaultPort);
97
+ if (port !== defaultPort) {
40
98
  const res = await (0, prompts_1.default)({
41
99
  type: 'confirm',
42
100
  name: 'useNewPort',
43
- message: `Port ${defaultPort} is occupied. Use ${availablePort} instead?`,
101
+ message: `Port ${defaultPort} is occupied. Use ${port} instead?`,
44
102
  initial: true,
45
103
  });
46
- if (!res.useNewPort) {
47
- console.log('šŸ›‘ Dev server cancelled.');
104
+ if (!res.useNewPort)
48
105
  process.exit(0);
49
- }
50
106
  }
51
107
  // Ensure react-refresh installed
52
108
  try {
53
109
  require.resolve('react-refresh/runtime');
54
110
  }
55
111
  catch {
56
- console.warn(chalk_1.default.yellow('āš ļø react-refresh not found — installing...'));
57
- (0, child_process_1.execSync)('npm install react-refresh --no-audit --no-fund --silent', {
58
- cwd: root,
59
- stdio: 'inherit',
60
- });
61
- console.log(chalk_1.default.green('āœ… react-refresh installed successfully.'));
112
+ console.log(chalk_1.default.yellow('Installing react-refresh...'));
113
+ (0, child_process_1.execSync)('npm i react-refresh --silent', { cwd: root, stdio: 'inherit' });
114
+ }
115
+ // Ensure Node polyfills installed
116
+ const missing = Object.keys(NODE_POLYFILLS).filter((m) => {
117
+ try {
118
+ require.resolve(m, { paths: [appRoot] });
119
+ return false;
120
+ }
121
+ catch {
122
+ return true;
123
+ }
124
+ });
125
+ if (missing.length > 0) {
126
+ console.log(chalk_1.default.yellow('Installing missing polyfills...'));
127
+ (0, child_process_1.execSync)(`npm i ${missing.join(' ')} --silent`, { cwd: appRoot, stdio: 'inherit' });
62
128
  }
63
129
  // --- Plugins
64
130
  const corePlugins = [
@@ -82,43 +148,26 @@ async function dev() {
82
148
  const userPlugins = Array.isArray(userConfig.plugins) ? userConfig.plugins : [];
83
149
  const plugins = [...corePlugins, ...userPlugins];
84
150
  const app = (0, connect_1.default)();
151
+ const server = http_1.default.createServer(app);
152
+ const broadcaster = new broadcastManager_1.BroadcastManager(server);
85
153
  const transformCache = new Map();
86
- // 🧠 Deep dependency graph analyzer
87
- async function analyzeGraph(file, seen = new Set()) {
88
- if (seen.has(file))
89
- return seen;
90
- seen.add(file);
91
- const code = await fs_extra_1.default.readFile(file, 'utf8');
92
- const matches = [
93
- ...code.matchAll(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g),
94
- ...code.matchAll(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g),
95
- ];
96
- for (const m of matches) {
97
- const dep = m[1];
98
- if (!dep || dep.startsWith('.') || dep.startsWith('/'))
99
- continue;
100
- try {
101
- const resolved = require.resolve(dep, { paths: [appRoot] });
102
- await analyzeGraph(resolved, seen);
103
- }
104
- catch {
105
- seen.add(dep); // bare dependency
106
- }
107
- }
108
- return seen;
109
- }
110
- // šŸ“¦ Smart prebundle cache (parallelized)
111
- async function prebundleDeps(deps) {
112
- if (!deps.size)
154
+ // --- Prebundle deps with gzip/brotli caching
155
+ async function prebundleDeps() {
156
+ if (!(await fs_extra_1.default.pathExists(pkgFile)))
113
157
  return;
114
- const cached = (await fs_extra_1.default.readdir(cacheDir)).map((f) => f.replace('.js', ''));
115
- const missing = [...deps].filter((d) => !cached.includes(d));
116
- if (!missing.length) {
117
- console.log(chalk_1.default.green('āœ… All dependencies already prebundled.'));
158
+ const pkg = JSON.parse(await fs_extra_1.default.readFile(pkgFile, 'utf8'));
159
+ const deps = Object.keys(pkg.dependencies || {});
160
+ if (!deps.length)
118
161
  return;
119
- }
120
- console.log(chalk_1.default.cyan('šŸ“¦ Prebundling:'), missing.join(', '));
121
- await Promise.all(missing.map(async (dep) => {
162
+ const hash = computeHash(JSON.stringify(deps));
163
+ const metaFile = path_1.default.join(cacheDir, '_meta.json');
164
+ let prevHash = null;
165
+ if (await fs_extra_1.default.pathExists(metaFile))
166
+ prevHash = (await fs_extra_1.default.readJSON(metaFile)).hash;
167
+ if (prevHash === hash)
168
+ return;
169
+ console.log(chalk_1.default.cyan('šŸ“¦ Rebuilding prebundle cache...'));
170
+ await Promise.all(deps.map(async (dep) => {
122
171
  try {
123
172
  const entryPath = require.resolve(dep, { paths: [appRoot] });
124
173
  const outFile = path_1.default.join(cacheDir, dep + '.js');
@@ -127,138 +176,135 @@ async function dev() {
127
176
  bundle: true,
128
177
  platform: 'browser',
129
178
  format: 'esm',
179
+ target: 'es2020',
130
180
  outfile: outFile,
131
181
  write: true,
132
- target: 'es2020',
133
182
  });
183
+ const content = await fs_extra_1.default.readFile(outFile);
184
+ await fs_extra_1.default.writeFile(outFile + '.gz', zlib_1.default.gzipSync(content));
185
+ await fs_extra_1.default.writeFile(outFile + '.br', zlib_1.default.brotliCompressSync(content));
134
186
  console.log(chalk_1.default.green(`āœ… Cached ${dep}`));
135
187
  }
136
- catch (err) {
137
- const e = err;
138
- console.warn(chalk_1.default.yellow(`āš ļø Skipped ${dep}: ${e.message}`));
188
+ catch (e) {
189
+ const err = e;
190
+ console.warn(chalk_1.default.yellow(`āš ļø Failed ${dep}: ${err.message}`));
139
191
  }
140
192
  }));
193
+ await fs_extra_1.default.writeJSON(metaFile, { hash });
141
194
  }
142
- // 🧩 Prewarm Graph
143
- const deps = await analyzeGraph(entry);
144
- await prebundleDeps(deps);
145
- // Auto rebuild on package.json change
146
- chokidar_1.default.watch(pkgFile).on('change', async () => {
147
- console.log(chalk_1.default.yellow('šŸ“¦ package.json changed — rebuilding prebundle cache...'));
148
- const newDeps = await analyzeGraph(entry);
149
- await prebundleDeps(newDeps);
150
- });
195
+ await prebundleDeps();
196
+ chokidar_1.default.watch(pkgFile).on('change', prebundleDeps);
151
197
  // --- Serve /@modules/
152
198
  app.use('/@modules/', async (req, res, next) => {
153
199
  const id = req.url?.replace(/^\/(@modules\/)?/, '');
154
200
  if (!id)
155
201
  return next();
202
+ const base = path_1.default.join(cacheDir, id.replace(/\//g, '_') + '.js');
203
+ const gz = base + '.gz';
204
+ const br = base + '.br';
205
+ const accept = req.headers['accept-encoding'] || '';
156
206
  try {
157
- const cacheFile = path_1.default.join(cacheDir, id.replace(/\//g, '_') + '.js');
158
- if (await fs_extra_1.default.pathExists(cacheFile)) {
159
- res.setHeader('Content-Type', 'application/javascript');
160
- return res.end(await fs_extra_1.default.readFile(cacheFile));
207
+ let buf = null;
208
+ let encoding = null;
209
+ if (/\bbr\b/.test(accept) && (await fs_extra_1.default.pathExists(br))) {
210
+ buf = await fs_extra_1.default.readFile(br);
211
+ encoding = 'br';
161
212
  }
162
- let entryPath = null;
163
- try {
164
- entryPath = require.resolve(id, { paths: [appRoot] });
213
+ else if (/\bgzip\b/.test(accept) && (await fs_extra_1.default.pathExists(gz))) {
214
+ buf = await fs_extra_1.default.readFile(gz);
215
+ encoding = 'gzip';
165
216
  }
166
- catch {
167
- if (id === 'react')
168
- entryPath = require.resolve('react');
169
- else if (id === 'react-dom' || id === 'react-dom/client')
170
- entryPath = require.resolve('react-dom');
217
+ else if (await fs_extra_1.default.pathExists(base)) {
218
+ buf = await fs_extra_1.default.readFile(base);
171
219
  }
172
- if (!entryPath)
173
- throw new Error(`Module ${id} not found.`);
174
- const result = await esbuild_1.default.build({
175
- entryPoints: [entryPath],
176
- bundle: true,
177
- platform: 'browser',
178
- format: 'esm',
179
- target: 'es2020',
180
- write: false,
181
- });
182
- let code = result.outputFiles[0].text;
183
- // 🩹 Fixed react-dom/client shim (no duplicate export)
184
- if (id === 'react-dom/client') {
185
- code += `
186
- // React Client auto-shim for React 18+
187
- import * as ReactDOMClient from '/@modules/react-dom';
188
- if (!('createRoot' in ReactDOMClient) && ReactDOMClient.default) {
189
- Object.assign(ReactDOMClient, ReactDOMClient.default);
190
- }
191
- export const createRoot =
192
- ReactDOMClient.createRoot ||
193
- ReactDOMClient.default?.createRoot ||
194
- (() => { throw new Error('ReactDOM.createRoot not found'); });
195
- export default ReactDOMClient;
196
- `;
220
+ else {
221
+ const entryPath = require.resolve(id, { paths: [appRoot] });
222
+ const result = await esbuild_1.default.build({
223
+ entryPoints: [entryPath],
224
+ bundle: true,
225
+ platform: 'browser',
226
+ format: 'esm',
227
+ write: false,
228
+ });
229
+ buf = Buffer.from(result.outputFiles[0].text);
230
+ await fs_extra_1.default.writeFile(base, buf);
231
+ }
232
+ const etag = `"${computeHash(buf)}"`;
233
+ if (req.headers['if-none-match'] === etag) {
234
+ res.writeHead(304);
235
+ return res.end();
197
236
  }
198
- await fs_extra_1.default.writeFile(cacheFile, code, 'utf8');
199
237
  res.setHeader('Content-Type', 'application/javascript');
200
- res.end(code);
238
+ res.setHeader('ETag', etag);
239
+ res.setHeader('Cache-Control', 'no-cache');
240
+ if (encoding)
241
+ res.setHeader('Content-Encoding', encoding);
242
+ res.end(buf);
201
243
  }
202
- catch (err) {
203
- const e = err;
204
- console.error(chalk_1.default.red(`āŒ Failed to load module ${id}: ${e.message}`));
244
+ catch (e) {
245
+ const err = e;
205
246
  res.writeHead(500);
206
- res.end(`// Failed to resolve module ${id}: ${e.message}`);
247
+ res.end(`// Failed to resolve module ${id}: ${err.message}`);
207
248
  }
208
249
  });
209
- // --- Serve /src files dynamically
250
+ // --- Serve /src/ files
210
251
  app.use(async (req, res, next) => {
211
252
  if (!req.url || (!req.url.startsWith('/src/') && !req.url.endsWith('.css')))
212
253
  return next();
213
- const rawPath = decodeURIComponent(req.url.split('?')[0]);
214
- let filePath = path_1.default.join(appRoot, rawPath);
215
- const possibleExts = ['', '.tsx', '.ts', '.jsx', '.js'];
216
- for (const ext of possibleExts) {
217
- if (await fs_extra_1.default.pathExists(filePath + ext)) {
218
- filePath += ext;
219
- break;
220
- }
221
- }
254
+ const filePath = path_1.default.join(appRoot, decodeURIComponent(req.url.split('?')[0]));
222
255
  if (!(await fs_extra_1.default.pathExists(filePath)))
223
256
  return next();
224
- try {
225
- let code = await fs_extra_1.default.readFile(filePath, 'utf8');
226
- // Rewrite bare imports → /@modules/*
227
- code = code
228
- .replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_match, dep) => `from "/@modules/${dep}"`)
229
- .replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (_match, dep) => `import("/@modules/${dep}")`);
230
- for (const p of plugins)
231
- if (p.onTransform)
232
- code = await p.onTransform(code, filePath);
233
- const ext = path_1.default.extname(filePath);
234
- let loader = 'js';
235
- if (ext === '.ts')
236
- loader = 'ts';
237
- else if (ext === '.tsx')
238
- loader = 'tsx';
239
- else if (ext === '.jsx')
240
- loader = 'jsx';
241
- const result = await esbuild_1.default.transform(code, {
242
- loader,
243
- sourcemap: 'inline',
244
- target: 'es2020',
245
- });
246
- transformCache.set(filePath, result.code);
247
- res.setHeader('Content-Type', 'application/javascript');
248
- res.end(result.code);
249
- }
250
- catch (err) {
251
- const e = err;
252
- console.error(chalk_1.default.red(`āš ļø Transform failed: ${e.message}`));
253
- res.writeHead(500);
254
- res.end(`// Error: ${e.message}`);
257
+ let code = await fs_extra_1.default.readFile(filePath, 'utf8');
258
+ code = code
259
+ .replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_m, dep) => `from "/@modules/${dep}"`)
260
+ .replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (_m, dep) => `import("/@modules/${dep}")`);
261
+ for (const p of plugins)
262
+ if (p.onTransform)
263
+ code = await p.onTransform(code, filePath);
264
+ const loader = filePath.endsWith('.tsx')
265
+ ? 'tsx'
266
+ : filePath.endsWith('.ts')
267
+ ? 'ts'
268
+ : filePath.endsWith('.jsx')
269
+ ? 'jsx'
270
+ : 'js';
271
+ const result = await esbuild_1.default.transform(code, { loader, sourcemap: 'inline', target: 'es2020' });
272
+ res.setHeader('Content-Type', 'application/javascript');
273
+ res.end(result.code);
274
+ });
275
+ // --- Serve static assets (favicon, /public, etc.)
276
+ app.use(async (req, res, next) => {
277
+ if (!req.url)
278
+ return next();
279
+ const assetPath = decodeURIComponent(req.url.split('?')[0]);
280
+ const publicDir = path_1.default.join(appRoot, 'public');
281
+ const rootFile = path_1.default.join(appRoot, assetPath);
282
+ const publicFile = path_1.default.join(publicDir, assetPath);
283
+ let targetFile = null;
284
+ if (await fs_extra_1.default.pathExists(publicFile))
285
+ targetFile = publicFile;
286
+ else if (await fs_extra_1.default.pathExists(rootFile))
287
+ targetFile = rootFile;
288
+ if (!targetFile)
289
+ return next();
290
+ const stat = await fs_extra_1.default.stat(targetFile);
291
+ if (!stat.isFile())
292
+ return next();
293
+ const etag = `"${stat.size}-${stat.mtimeMs}"`;
294
+ if (req.headers['if-none-match'] === etag) {
295
+ res.writeHead(304);
296
+ return res.end();
255
297
  }
298
+ res.setHeader('Cache-Control', 'public, max-age=3600');
299
+ res.setHeader('ETag', etag);
300
+ res.setHeader('Content-Type', getMimeType(targetFile));
301
+ fs_extra_1.default.createReadStream(targetFile).pipe(res);
256
302
  });
257
- // --- Serve index.html + overlay + HMR
303
+ // --- Serve index.html with overlay + HMR
258
304
  app.use(async (req, res, next) => {
259
305
  if (req.url !== '/' && req.url !== '/index.html')
260
306
  return next();
261
- if (!fs_extra_1.default.existsSync(indexHtml)) {
307
+ if (!(await fs_extra_1.default.pathExists(indexHtml))) {
262
308
  res.writeHead(404);
263
309
  return res.end('index.html not found');
264
310
  }
@@ -269,9 +315,10 @@ async function dev() {
269
315
  const style = document.createElement('style');
270
316
  style.textContent = \`
271
317
  .rc-overlay {
272
- position: fixed; top: 0; left: 0; width: 100%; height: 100%;
273
- background: rgba(0,0,0,0.9); color: #ff5555;
274
- font-family: monospace; padding: 2rem; overflow:auto; z-index: 999999;
318
+ position: fixed; inset: 0;
319
+ background: rgba(0,0,0,0.9); color:#fff;
320
+ font-family: monospace; padding:2rem; overflow:auto;
321
+ z-index:999999; white-space:pre-wrap;
275
322
  }
276
323
  \`;
277
324
  document.head.appendChild(style);
@@ -302,20 +349,21 @@ async function dev() {
302
349
  res.setHeader('Content-Type', 'text/html');
303
350
  res.end(html);
304
351
  });
305
- // --- WebSocket + HMR
306
- const server = http_1.default.createServer(app);
307
- const broadcaster = new broadcastManager_1.BroadcastManager(server);
308
- chokidar_1.default.watch(path_1.default.join(appRoot, 'src'), { ignoreInitial: true }).on('change', async (file) => {
352
+ // --- Watchers for HMR + favicon reload
353
+ chokidar_1.default.watch(path_1.default.join(appRoot, 'src'), { ignoreInitial: true }).on('change', (file) => {
309
354
  console.log(chalk_1.default.yellow(`šŸ”„ Changed: ${file}`));
310
355
  transformCache.delete(file);
311
- for (const p of plugins) {
312
- await p.onHotUpdate?.(file, { broadcast: (msg) => broadcaster.broadcast(msg) });
313
- }
314
356
  broadcaster.broadcast({
315
357
  type: 'update',
316
358
  path: '/' + path_1.default.relative(appRoot, file).replace(/\\/g, '/'),
317
359
  });
318
360
  });
361
+ chokidar_1.default
362
+ .watch(path_1.default.join(appRoot, 'public', 'favicon.ico'), { ignoreInitial: true })
363
+ .on('change', () => {
364
+ broadcaster.broadcast({ type: 'reload' });
365
+ });
366
+ // --- Start server
319
367
  server.listen(port, async () => {
320
368
  const url = `http://localhost:${port}`;
321
369
  console.log(chalk_1.default.cyan.bold('\nšŸš€ React Client Dev Server'));
@@ -51,7 +51,7 @@ export default defineConfig({
51
51
 
52
52
  // ⚔ Dev server settings
53
53
  server: {
54
- port: 5173,
54
+ port: 2202,
55
55
  },
56
56
 
57
57
  // šŸ—ļø Build options
@@ -19,7 +19,7 @@ async function preview() {
19
19
  const config = await (0, loadConfig_1.loadReactClientConfig)(root);
20
20
  const appRoot = path_1.default.resolve(root, config.root || '.');
21
21
  const outDir = path_1.default.join(appRoot, config.build?.outDir || '.react-client/build');
22
- const defaultPort = config.server?.port || 5173;
22
+ const defaultPort = config.server?.port || 2202;
23
23
  if (!fs_extra_1.default.existsSync(outDir)) {
24
24
  console.error(chalk_1.default.red(`āŒ Build output not found at: ${outDir}`));
25
25
  console.log(chalk_1.default.gray('Please run `react-client build` first.'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-client",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
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",