react-client 1.0.21 → 1.0.22

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.
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
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
3
+ * šŸš€ React Client Dev Server — Final Version
4
+ * ------------------------------------------
5
+ * āœ… Local overlay-runtime.js (Prism + stack mapping)
6
+ * āœ… Dynamic /@runtime/overlay-runtime.js alias
7
+ * āœ… Automatic HTML injection for overlay + HMR
8
+ * āœ… Prebundle cache (.react-client/deps)
9
+ * āœ… CSS HMR, relative & bare import handling
10
+ * āœ… Favicon & public assets serving
11
11
  */
12
12
  var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -29,24 +29,6 @@ const zlib_1 = __importDefault(require("zlib"));
29
29
  const crypto_1 = __importDefault(require("crypto"));
30
30
  const loadConfig_1 = require("../../utils/loadConfig");
31
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
32
  const computeHash = (content) => crypto_1.default.createHash('sha1').update(content).digest('hex');
51
33
  const getMimeType = (file) => {
52
34
  const ext = path_1.default.extname(file).toLowerCase();
@@ -64,35 +46,26 @@ const getMimeType = (file) => {
64
46
  '.mjs': 'application/javascript',
65
47
  '.css': 'text/css',
66
48
  '.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
49
  };
74
50
  return mime[ext] || 'application/octet-stream';
75
51
  };
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))));
79
52
  async function dev() {
80
53
  const root = process.cwd();
81
54
  const userConfig = await (0, loadConfig_1.loadReactClientConfig)(root);
82
55
  const appRoot = path_1.default.resolve(root, userConfig.root || '.');
83
56
  const defaultPort = userConfig.server?.port || 5173;
84
57
  const cacheDir = path_1.default.join(appRoot, '.react-client', 'deps');
85
- await fs_extra_1.default.ensureDir(cacheDir);
86
- const indexHtml = path_1.default.join(appRoot, 'index.html');
87
58
  const pkgFile = path_1.default.join(appRoot, 'package.json');
88
- // Detect entry
59
+ const indexHtml = path_1.default.join(appRoot, 'index.html');
60
+ await fs_extra_1.default.ensureDir(cacheDir);
61
+ // āœ… Detect entry
89
62
  const possibleEntries = ['src/main.tsx', 'src/main.jsx'];
90
63
  const entry = possibleEntries.map((p) => path_1.default.join(appRoot, p)).find((p) => fs_extra_1.default.existsSync(p));
91
64
  if (!entry) {
92
65
  console.error(chalk_1.default.red('āŒ No entry found: src/main.tsx or src/main.jsx'));
93
66
  process.exit(1);
94
67
  }
95
- // Detect open port
68
+ // āœ… Detect free port
96
69
  const port = await (0, detect_port_1.default)(defaultPort);
97
70
  if (port !== defaultPort) {
98
71
  const res = await (0, prompts_1.default)({
@@ -104,7 +77,7 @@ async function dev() {
104
77
  if (!res.useNewPort)
105
78
  process.exit(0);
106
79
  }
107
- // Ensure react-refresh installed
80
+ // āœ… Ensure react-refresh
108
81
  try {
109
82
  require.resolve('react-refresh/runtime');
110
83
  }
@@ -112,21 +85,7 @@ async function dev() {
112
85
  console.log(chalk_1.default.yellow('Installing react-refresh...'));
113
86
  (0, child_process_1.execSync)('npm i react-refresh --silent', { cwd: root, stdio: 'inherit' });
114
87
  }
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' });
128
- }
129
- // --- Plugins
88
+ // āœ… Core + user plugins
130
89
  const corePlugins = [
131
90
  {
132
91
  name: 'css-hmr',
@@ -151,7 +110,7 @@ async function dev() {
151
110
  const server = http_1.default.createServer(app);
152
111
  const broadcaster = new broadcastManager_1.BroadcastManager(server);
153
112
  const transformCache = new Map();
154
- // --- Prebundle deps with gzip/brotli caching
113
+ // 🧱 Persistent prebundle cache
155
114
  async function prebundleDeps() {
156
115
  if (!(await fs_extra_1.default.pathExists(pkgFile)))
157
116
  return;
@@ -187,120 +146,139 @@ async function dev() {
187
146
  }
188
147
  catch (e) {
189
148
  const err = e;
190
- console.warn(chalk_1.default.yellow(`āš ļø Failed ${dep}: ${err.message}`));
149
+ console.warn(chalk_1.default.yellow(`āš ļø Skipped ${dep}: ${err.message}`));
191
150
  }
192
151
  }));
193
152
  await fs_extra_1.default.writeJSON(metaFile, { hash });
194
153
  }
195
154
  await prebundleDeps();
196
155
  chokidar_1.default.watch(pkgFile).on('change', prebundleDeps);
197
- // --- Serve /@modules/
156
+ // 🧩 Serve local overlay runtime
157
+ app.use('/@runtime/overlay-runtime.js', async (req, res) => {
158
+ const overlayPath = path_1.default.join(appRoot, 'src/runtime/overlay-runtime.js');
159
+ try {
160
+ if (!(await fs_extra_1.default.pathExists(overlayPath))) {
161
+ res.writeHead(404);
162
+ return res.end(`// Overlay runtime not found: ${overlayPath}`);
163
+ }
164
+ let code = await fs_extra_1.default.readFile(overlayPath, 'utf8');
165
+ // Transform bare imports → /@modules/*
166
+ code = code
167
+ .replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_m, dep) => `from "/@modules/${dep}"`)
168
+ .replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (_m, dep) => `import("/@modules/${dep}")`);
169
+ const result = await esbuild_1.default.transform(code, {
170
+ loader: 'js',
171
+ sourcemap: 'inline',
172
+ target: 'es2020',
173
+ });
174
+ res.setHeader('Content-Type', 'application/javascript');
175
+ res.end(result.code);
176
+ }
177
+ catch (err) {
178
+ const e = err;
179
+ console.error(chalk_1.default.red(`āŒ Failed to load overlay runtime: ${e.message}`));
180
+ res.writeHead(500);
181
+ res.end(`// Failed to load overlay runtime: ${e.message}`);
182
+ }
183
+ });
184
+ // 🧠 Serve /@modules/
198
185
  app.use('/@modules/', async (req, res, next) => {
199
186
  const id = req.url?.replace(/^\/(@modules\/)?/, '');
200
187
  if (!id)
201
188
  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'] || '';
206
189
  try {
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';
212
- }
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';
216
- }
217
- else if (await fs_extra_1.default.pathExists(base)) {
218
- buf = await fs_extra_1.default.readFile(base);
219
- }
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();
190
+ const cacheFile = path_1.default.join(cacheDir, id.replace(/\//g, '_') + '.js');
191
+ if (await fs_extra_1.default.pathExists(cacheFile)) {
192
+ res.setHeader('Content-Type', 'application/javascript');
193
+ return res.end(await fs_extra_1.default.readFile(cacheFile));
236
194
  }
195
+ const entryPath = require.resolve(id, { paths: [appRoot] });
196
+ const result = await esbuild_1.default.build({
197
+ entryPoints: [entryPath],
198
+ bundle: true,
199
+ platform: 'browser',
200
+ format: 'esm',
201
+ target: 'es2020',
202
+ write: false,
203
+ });
204
+ const code = result.outputFiles[0].text;
205
+ await fs_extra_1.default.writeFile(cacheFile, code, 'utf8');
237
206
  res.setHeader('Content-Type', 'application/javascript');
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);
207
+ res.end(code);
243
208
  }
244
209
  catch (e) {
245
210
  const err = e;
211
+ console.error(chalk_1.default.red(`āŒ Failed to load module ${id}: ${err.message}`));
246
212
  res.writeHead(500);
247
213
  res.end(`// Failed to resolve module ${id}: ${err.message}`);
248
214
  }
249
215
  });
250
- // --- Serve /src/ files
216
+ // 🧩 Serve /src/ and .css files dynamically
251
217
  app.use(async (req, res, next) => {
252
218
  if (!req.url || (!req.url.startsWith('/src/') && !req.url.endsWith('.css')))
253
219
  return next();
254
- const filePath = path_1.default.join(appRoot, decodeURIComponent(req.url.split('?')[0]));
255
- if (!(await fs_extra_1.default.pathExists(filePath)))
256
- return next();
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);
220
+ const rawPath = decodeURIComponent(req.url.split('?')[0]);
221
+ const filePath = path_1.default.join(appRoot, rawPath);
222
+ const possibleExts = ['', '.tsx', '.ts', '.jsx', '.js'];
223
+ let resolvedPath = null;
224
+ for (const ext of possibleExts) {
225
+ const candidate = filePath + ext;
226
+ if (await fs_extra_1.default.pathExists(candidate)) {
227
+ resolvedPath = candidate;
228
+ break;
229
+ }
230
+ }
231
+ if (!resolvedPath) {
232
+ res.writeHead(404);
233
+ return res.end(`// File not found: ${filePath}`);
234
+ }
235
+ try {
236
+ let code = await fs_extra_1.default.readFile(resolvedPath, 'utf8');
237
+ // Rewrite bare imports → /@modules/*
238
+ code = code
239
+ .replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_m, dep) => `from "/@modules/${dep}"`)
240
+ .replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (_m, dep) => `import("/@modules/${dep}")`);
241
+ for (const p of plugins)
242
+ if (p.onTransform)
243
+ code = await p.onTransform(code, resolvedPath);
244
+ const ext = path_1.default.extname(resolvedPath);
245
+ let loader = 'js';
246
+ if (ext === '.ts')
247
+ loader = 'ts';
248
+ else if (ext === '.tsx')
249
+ loader = 'tsx';
250
+ else if (ext === '.jsx')
251
+ loader = 'jsx';
252
+ const result = await esbuild_1.default.transform(code, {
253
+ loader,
254
+ sourcemap: 'inline',
255
+ target: 'es2020',
256
+ });
257
+ res.setHeader('Content-Type', 'application/javascript');
258
+ res.end(result.code);
259
+ }
260
+ catch (err) {
261
+ const e = err;
262
+ console.error(chalk_1.default.red(`āš ļø Transform failed: ${e.message}`));
263
+ res.writeHead(500);
264
+ res.end(`// Error: ${e.message}`);
265
+ }
274
266
  });
275
- // --- Serve static assets (favicon, /public, etc.)
267
+ // šŸ–¼ļø Serve static assets (favicon + public)
276
268
  app.use(async (req, res, next) => {
277
269
  if (!req.url)
278
270
  return next();
279
- const assetPath = decodeURIComponent(req.url.split('?')[0]);
280
271
  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)
272
+ const targetFile = path_1.default.join(publicDir, decodeURIComponent(req.url.split('?')[0]));
273
+ if (!(await fs_extra_1.default.pathExists(targetFile)))
289
274
  return next();
290
275
  const stat = await fs_extra_1.default.stat(targetFile);
291
276
  if (!stat.isFile())
292
277
  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();
297
- }
298
- res.setHeader('Cache-Control', 'public, max-age=3600');
299
- res.setHeader('ETag', etag);
300
278
  res.setHeader('Content-Type', getMimeType(targetFile));
301
279
  fs_extra_1.default.createReadStream(targetFile).pipe(res);
302
280
  });
303
- // --- Serve index.html with overlay + HMR
281
+ // 🧩 Serve index.html + overlay + HMR
304
282
  app.use(async (req, res, next) => {
305
283
  if (req.url !== '/' && req.url !== '/index.html')
306
284
  return next();
@@ -310,29 +288,7 @@ async function dev() {
310
288
  }
311
289
  let html = await fs_extra_1.default.readFile(indexHtml, 'utf8');
312
290
  html = html.replace('</body>', `
313
- <script>
314
- (() => {
315
- const style = document.createElement('style');
316
- style.textContent = \`
317
- .rc-overlay {
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;
322
- }
323
- \`;
324
- document.head.appendChild(style);
325
- window.showErrorOverlay = (err) => {
326
- window.clearErrorOverlay?.();
327
- const el = document.createElement('div');
328
- el.className = 'rc-overlay';
329
- el.innerHTML = '<h2>🚨 Error</h2><pre>' + (err.message || err) + '</pre>';
330
- document.body.appendChild(el);
331
- window.__overlay = el;
332
- };
333
- window.clearErrorOverlay = () => window.__overlay?.remove();
334
- })();
335
- </script>
291
+ <script type="module" src="/@runtime/overlay-runtime.js"></script>
336
292
  <script type="module">
337
293
  const ws = new WebSocket("ws://" + location.host);
338
294
  ws.onmessage = (e) => {
@@ -349,7 +305,7 @@ async function dev() {
349
305
  res.setHeader('Content-Type', 'text/html');
350
306
  res.end(html);
351
307
  });
352
- // --- Watchers for HMR + favicon reload
308
+ // ā™»ļø Watchers
353
309
  chokidar_1.default.watch(path_1.default.join(appRoot, 'src'), { ignoreInitial: true }).on('change', (file) => {
354
310
  console.log(chalk_1.default.yellow(`šŸ”„ Changed: ${file}`));
355
311
  transformCache.delete(file);
@@ -359,11 +315,11 @@ async function dev() {
359
315
  });
360
316
  });
361
317
  chokidar_1.default
362
- .watch(path_1.default.join(appRoot, 'public', 'favicon.ico'), { ignoreInitial: true })
318
+ .watch(path_1.default.join(appRoot, 'src/runtime/overlay-runtime.js'), { ignoreInitial: true })
363
319
  .on('change', () => {
320
+ console.log(chalk_1.default.magenta('ā™»ļø Overlay runtime updated — reloading browser...'));
364
321
  broadcaster.broadcast({ type: 'reload' });
365
322
  });
366
- // --- Start server
367
323
  server.listen(port, async () => {
368
324
  const url = `http://localhost:${port}`;
369
325
  console.log(chalk_1.default.cyan.bold('\nšŸš€ React Client Dev Server'));
@@ -4,54 +4,161 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.default = preview;
7
- const connect_1 = __importDefault(require("connect"));
8
- const serve_static_1 = __importDefault(require("serve-static"));
9
7
  const http_1 = __importDefault(require("http"));
10
8
  const path_1 = __importDefault(require("path"));
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
11
  const detect_port_1 = __importDefault(require("detect-port"));
12
12
  const prompts_1 = __importDefault(require("prompts"));
13
- const chalk_1 = __importDefault(require("chalk"));
14
13
  const open_1 = __importDefault(require("open"));
15
- const fs_extra_1 = __importDefault(require("fs-extra"));
16
14
  const loadConfig_1 = require("../../utils/loadConfig");
15
+ const MIME = {
16
+ '.html': 'text/html; charset=utf-8',
17
+ '.js': 'application/javascript; charset=utf-8',
18
+ '.mjs': 'application/javascript; charset=utf-8',
19
+ '.json': 'application/json; charset=utf-8',
20
+ '.css': 'text/css; charset=utf-8',
21
+ '.png': 'image/png',
22
+ '.jpg': 'image/jpeg',
23
+ '.jpeg': 'image/jpeg',
24
+ '.gif': 'image/gif',
25
+ '.svg': 'image/svg+xml',
26
+ '.ico': 'image/x-icon',
27
+ '.woff': 'font/woff',
28
+ '.woff2': 'font/woff2',
29
+ '.ttf': 'font/ttf',
30
+ '.map': 'application/octet-stream',
31
+ '.txt': 'text/plain; charset=utf-8',
32
+ };
33
+ function contentType(file) {
34
+ return MIME[path_1.default.extname(file).toLowerCase()] || 'application/octet-stream';
35
+ }
36
+ function setCachingHeaders(res, stat) {
37
+ // Short cache for preview by default, but set ETag/Last-Modified so browsers behave nicely
38
+ const etag = `${stat.size}-${Date.parse(stat.mtime.toString())}`;
39
+ res.setHeader('ETag', etag);
40
+ res.setHeader('Last-Modified', stat.mtime.toUTCString());
41
+ res.setHeader('Cache-Control', 'public, max-age=0, must-revalidate');
42
+ }
17
43
  async function preview() {
18
- const root = process.cwd();
19
- const config = await (0, loadConfig_1.loadReactClientConfig)(root);
20
- const appRoot = path_1.default.resolve(root, config.root || '.');
21
- const outDir = path_1.default.join(appRoot, config.build?.outDir || '.react-client/build');
22
- const defaultPort = config.server?.port || 2202;
23
- if (!fs_extra_1.default.existsSync(outDir)) {
24
- console.error(chalk_1.default.red(`āŒ Build output not found at: ${outDir}`));
25
- console.log(chalk_1.default.gray('Please run `react-client build` first.'));
44
+ const cwd = process.cwd();
45
+ const config = await (0, loadConfig_1.loadReactClientConfig)(cwd);
46
+ const appRoot = path_1.default.resolve(cwd, config.root || '.');
47
+ const outDir = path_1.default.join(appRoot, config.build?.outDir || 'dist');
48
+ const indexHtml = path_1.default.join(outDir, 'index.html');
49
+ if (!(await fs_extra_1.default.pathExists(outDir))) {
50
+ console.error(chalk_1.default.red(`āŒ Preview directory not found: ${outDir}`));
26
51
  process.exit(1);
27
52
  }
28
- const availablePort = await (0, detect_port_1.default)(defaultPort);
29
- const port = availablePort;
30
- if (availablePort !== defaultPort) {
31
- const res = await (0, prompts_1.default)({
53
+ if (!(await fs_extra_1.default.pathExists(indexHtml))) {
54
+ console.warn(chalk_1.default.yellow(`āš ļø index.html not found in ${outDir}. SPA fallback will be disabled.`));
55
+ }
56
+ const defaultPort = config.server?.port || 4173;
57
+ const port = await (0, detect_port_1.default)(defaultPort);
58
+ if (port !== defaultPort) {
59
+ const r = await (0, prompts_1.default)({
32
60
  type: 'confirm',
33
61
  name: 'useNewPort',
34
- message: `Port ${defaultPort} is occupied. Use ${availablePort} instead?`,
35
62
  initial: true,
63
+ message: `Port ${defaultPort} is occupied. Use ${port} instead?`,
36
64
  });
37
- if (!res.useNewPort) {
38
- console.log(chalk_1.default.red('šŸ›‘ Preview server cancelled.'));
65
+ if (!r.useNewPort) {
66
+ console.log('šŸ›‘ Preview cancelled.');
39
67
  process.exit(0);
40
68
  }
41
69
  }
42
- const app = (0, connect_1.default)();
43
- app.use((0, serve_static_1.default)(outDir));
44
- const server = http_1.default.createServer(app);
70
+ const server = http_1.default.createServer(async (req, res) => {
71
+ try {
72
+ const url = req.url || '/';
73
+ // normalize and protect
74
+ const relPath = decodeURIComponent(url.split('?')[0]);
75
+ if (relPath.includes('..')) {
76
+ res.writeHead(400);
77
+ return res.end('Invalid request');
78
+ }
79
+ // handle root -> index.html
80
+ let filePath = path_1.default.join(outDir, relPath);
81
+ const tryIndexFallback = async () => {
82
+ if (await fs_extra_1.default.pathExists(indexHtml)) {
83
+ const stat = await fs_extra_1.default.stat(indexHtml);
84
+ setCachingHeaders(res, stat);
85
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
86
+ return fs_extra_1.default.createReadStream(indexHtml).pipe(res);
87
+ }
88
+ else {
89
+ res.writeHead(404);
90
+ return res.end('Not found');
91
+ }
92
+ };
93
+ // If the request path is a directory, try index.html inside it
94
+ if (relPath.endsWith('/')) {
95
+ const candidate = path_1.default.join(filePath, 'index.html');
96
+ if (await fs_extra_1.default.pathExists(candidate)) {
97
+ filePath = candidate;
98
+ }
99
+ else {
100
+ return tryIndexFallback();
101
+ }
102
+ }
103
+ // If file doesn't exist, fallback to index.html for SPA routes
104
+ if (!(await fs_extra_1.default.pathExists(filePath))) {
105
+ // If request appears to be a static asset (has extension), return 404
106
+ if (path_1.default.extname(filePath)) {
107
+ res.writeHead(404);
108
+ return res.end('Not found');
109
+ }
110
+ return tryIndexFallback();
111
+ }
112
+ const stat = await fs_extra_1.default.stat(filePath);
113
+ if (!stat.isFile()) {
114
+ return tryIndexFallback();
115
+ }
116
+ // Compression/Precompressed support: prefer brotli -> gzip -> raw
117
+ const accept = (req.headers['accept-encoding'] || '');
118
+ const tryPrecompressed = async () => {
119
+ if (accept.includes('br') && (await fs_extra_1.default.pathExists(filePath + '.br'))) {
120
+ res.setHeader('Content-Encoding', 'br');
121
+ res.setHeader('Content-Type', contentType(filePath));
122
+ setCachingHeaders(res, stat);
123
+ return fs_extra_1.default.createReadStream(filePath + '.br').pipe(res);
124
+ }
125
+ if (accept.includes('gzip') && (await fs_extra_1.default.pathExists(filePath + '.gz'))) {
126
+ res.setHeader('Content-Encoding', 'gzip');
127
+ res.setHeader('Content-Type', contentType(filePath));
128
+ setCachingHeaders(res, stat);
129
+ return fs_extra_1.default.createReadStream(filePath + '.gz').pipe(res);
130
+ }
131
+ // default
132
+ res.setHeader('Content-Type', contentType(filePath));
133
+ setCachingHeaders(res, stat);
134
+ return fs_extra_1.default.createReadStream(filePath).pipe(res);
135
+ };
136
+ // ETag / If-None-Match handling
137
+ const etag = `${stat.size}-${Date.parse(stat.mtime.toString())}`;
138
+ const inm = req.headers['if-none-match'];
139
+ if (inm && inm.toString() === etag) {
140
+ res.writeHead(304);
141
+ return res.end();
142
+ }
143
+ return tryPrecompressed();
144
+ }
145
+ catch (err) {
146
+ const e = err;
147
+ console.error('Preview server error:', e);
148
+ res.writeHead(500);
149
+ res.end('Internal Server Error');
150
+ }
151
+ });
45
152
  server.listen(port, async () => {
46
153
  const url = `http://localhost:${port}`;
47
- console.log(chalk_1.default.green(`\n🌐 Preview server running at ${url}`));
48
- if (port !== defaultPort) {
49
- console.log(chalk_1.default.yellow(`āš ļø Using alternate port (default ${defaultPort} was occupied).`));
50
- }
154
+ console.log(chalk_1.default.cyan.bold('\nšŸ”Ž react-client preview'));
155
+ console.log(chalk_1.default.gray('────────────────────────'));
156
+ console.log(chalk_1.default.green(`Serving: ${outDir}`));
157
+ console.log(chalk_1.default.green(`Open: ${url}`));
51
158
  await (0, open_1.default)(url, { newInstance: true });
52
159
  });
53
160
  process.on('SIGINT', () => {
54
- console.log(chalk_1.default.red('\nšŸ›‘ Shutting down preview server...'));
161
+ console.log(chalk_1.default.red('\nšŸ›‘ Shutting down preview...'));
55
162
  server.close();
56
163
  process.exit(0);
57
164
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-client",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
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",