react-client 1.0.28 → 1.0.31

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,6 +1,5 @@
1
- "use strict";
2
1
  /**
3
- * dev.ts — Vite-like dev server for react-client
2
+ * dev.ts — dev server for react-client
4
3
  *
5
4
  * - prebundles deps into .react-client/deps
6
5
  * - serves /@modules/<dep>
@@ -10,45 +9,187 @@
10
9
  *
11
10
  * Keep this file linted & typed. Avoids manual react-dom/client hacks.
12
11
  */
13
- var __importDefault = (this && this.__importDefault) || function (mod) {
14
- return (mod && mod.__esModule) ? mod : { "default": mod };
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.default = dev;
18
- const esbuild_1 = __importDefault(require("esbuild"));
19
- const connect_1 = __importDefault(require("connect"));
20
- const http_1 = __importDefault(require("http"));
21
- const chokidar_1 = __importDefault(require("chokidar"));
22
- const detect_port_1 = __importDefault(require("detect-port"));
23
- const prompts_1 = __importDefault(require("prompts"));
24
- const path_1 = __importDefault(require("path"));
25
- const fs_extra_1 = __importDefault(require("fs-extra"));
26
- const open_1 = __importDefault(require("open"));
27
- const chalk_1 = __importDefault(require("chalk"));
28
- const child_process_1 = require("child_process");
29
- const loadConfig_1 = require("../../utils/loadConfig");
30
- const broadcastManager_1 = require("../../server/broadcastManager");
31
- async function dev() {
12
+ import esbuild from 'esbuild';
13
+ import connect from 'connect';
14
+ import http from 'http';
15
+ import chokidar from 'chokidar';
16
+ import detectPort from 'detect-port';
17
+ import prompts from 'prompts';
18
+ import path from 'path';
19
+ import fs from 'fs-extra';
20
+ import open from 'open';
21
+ import chalk from 'chalk';
22
+ import { execSync } from 'child_process';
23
+ import { loadReactClientConfig } from '../../utils/loadConfig';
24
+ import { BroadcastManager } from '../../server/broadcastManager';
25
+ import { createRequire } from 'module';
26
+ const require = createRequire(import.meta.url);
27
+ const RUNTIME_OVERLAY_ROUTE = '/@runtime/overlay';
28
+ function jsContentType() {
29
+ return 'application/javascript; charset=utf-8';
30
+ }
31
+ /**
32
+ * Resolve any bare import id robustly:
33
+ * 1. try require.resolve(id)
34
+ * 2. try require.resolve(`${pkg}/${subpath}`)
35
+ * 3. try package.json exports field
36
+ * 4. try common fallback candidates
37
+ */
38
+ async function resolveModuleEntry(id, root) {
39
+ // quick resolution
40
+ try {
41
+ return require.resolve(id, { paths: [root] });
42
+ }
43
+ catch {
44
+ // continue
45
+ }
46
+ // split package root and subpath
47
+ const parts = id.split('/');
48
+ const pkgRoot = parts[0].startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
49
+ const subPath = parts.slice(pkgRoot.startsWith('@') ? 2 : 1).join('/');
50
+ let pkgJsonPath;
51
+ try {
52
+ pkgJsonPath = require.resolve(`${pkgRoot}/package.json`, { paths: [root] });
53
+ }
54
+ catch {
55
+ // No need to keep unused variable 'err'
56
+ throw new Error(`Package not found: ${pkgRoot}`);
57
+ }
58
+ const pkgDir = path.dirname(pkgJsonPath);
59
+ // Explicitly type pkgJson to avoid 'any'
60
+ let pkgJson = {};
61
+ try {
62
+ const pkgContent = await fs.readFile(pkgJsonPath, 'utf8');
63
+ pkgJson = JSON.parse(pkgContent);
64
+ }
65
+ catch {
66
+ // ignore parse or read errors gracefully
67
+ }
68
+ // If exports field exists, try to look up subpath (type-safe, supports conditional exports)
69
+ if (pkgJson.exports) {
70
+ const exportsField = pkgJson.exports;
71
+ // If exports is a plain string -> it's the entry
72
+ if (typeof exportsField === 'string') {
73
+ if (!subPath)
74
+ return path.resolve(pkgDir, exportsField);
75
+ }
76
+ else if (exportsField && typeof exportsField === 'object') {
77
+ // Normalize to a record so we can index it safely
78
+ const exportsMap = exportsField;
79
+ // Try candidates in order: explicit subpath, index, fallback
80
+ const keyCandidates = [];
81
+ if (subPath) {
82
+ keyCandidates.push(`./${subPath}`, `./${subPath}.js`, `./${subPath}.mjs`);
83
+ }
84
+ keyCandidates.push('.', './index.js', './index.mjs');
85
+ for (const key of keyCandidates) {
86
+ if (!(key in exportsMap))
87
+ continue;
88
+ const entry = exportsMap[key];
89
+ // entry may be string or object like { import: "...", require: "..." }
90
+ let target;
91
+ if (typeof entry === 'string') {
92
+ target = entry;
93
+ }
94
+ else if (entry && typeof entry === 'object') {
95
+ const entryObj = entry;
96
+ // Prefer "import" field for ESM consumers, then "default", then any string-ish value
97
+ if (typeof entryObj.import === 'string')
98
+ target = entryObj.import;
99
+ else if (typeof entryObj.default === 'string')
100
+ target = entryObj.default;
101
+ else {
102
+ // If the entry object itself is a conditional map (like {"node": "...", "browser": "..."}),
103
+ // attempt to pick any string value present.
104
+ for (const k of Object.keys(entryObj)) {
105
+ if (typeof entryObj[k] === 'string') {
106
+ target = entryObj[k];
107
+ break;
108
+ }
109
+ }
110
+ }
111
+ }
112
+ if (!target || typeof target !== 'string')
113
+ continue;
114
+ // Normalize relative paths in exports (remove leading ./)
115
+ const normalized = target.replace(/^\.\//, '');
116
+ const abs = path.isAbsolute(normalized)
117
+ ? normalized
118
+ : path.resolve(pkgDir, normalized);
119
+ if (await fs.pathExists(abs)) {
120
+ return abs;
121
+ }
122
+ }
123
+ }
124
+ }
125
+ // Try resolved subpath directly (pkg/subpath)
126
+ if (subPath) {
127
+ try {
128
+ const candidate = require.resolve(`${pkgRoot}/${subPath}`, { paths: [root] });
129
+ return candidate;
130
+ }
131
+ catch {
132
+ // fallback to searching common candidates under package dir
133
+ const candPaths = [
134
+ path.join(pkgDir, subPath),
135
+ path.join(pkgDir, subPath + '.js'),
136
+ path.join(pkgDir, subPath + '.mjs'),
137
+ path.join(pkgDir, subPath, 'index.js'),
138
+ path.join(pkgDir, subPath, 'index.mjs'),
139
+ ];
140
+ for (const c of candPaths) {
141
+ if (await fs.pathExists(c))
142
+ return c;
143
+ }
144
+ }
145
+ }
146
+ // Try package's main/module/browser fields safely (typed as string)
147
+ const candidateFields = [
148
+ typeof pkgJson.module === 'string' ? pkgJson.module : undefined,
149
+ typeof pkgJson.browser === 'string' ? pkgJson.browser : undefined,
150
+ typeof pkgJson.main === 'string' ? pkgJson.main : undefined,
151
+ ];
152
+ for (const field of candidateFields) {
153
+ if (!field)
154
+ continue;
155
+ const abs = path.isAbsolute(field) ? field : path.resolve(pkgDir, field);
156
+ if (await fs.pathExists(abs))
157
+ return abs;
158
+ }
159
+ throw new Error(`Could not resolve module entry for ${id}`);
160
+ }
161
+ /**
162
+ * Wrap the built module for subpath imports:
163
+ * For requests like "/@modules/react-dom/client" — we bundle the resolved file
164
+ * and return it. If the user requested the package root instead, the resolved
165
+ * bundle is returned directly.
166
+ *
167
+ * No hardcoded special cases.
168
+ */
169
+ function normalizeCacheKey(id) {
170
+ return id.replace(/[\\/]/g, '_');
171
+ }
172
+ export default async function dev() {
32
173
  const root = process.cwd();
33
- const userConfig = (await (0, loadConfig_1.loadReactClientConfig)(root));
34
- const appRoot = path_1.default.resolve(root, userConfig.root || '.');
174
+ const userConfig = (await loadReactClientConfig(root));
175
+ const appRoot = path.resolve(root, userConfig.root || '.');
35
176
  const defaultPort = userConfig.server?.port ?? 2202;
36
177
  // cache dir for prebundled deps
37
- const cacheDir = path_1.default.join(appRoot, '.react-client', 'deps');
38
- await fs_extra_1.default.ensureDir(cacheDir);
178
+ const cacheDir = path.join(appRoot, '.react-client', 'deps');
179
+ await fs.ensureDir(cacheDir);
39
180
  // Detect entry (main.tsx / main.jsx)
40
- const possible = ['src/main.tsx', 'src/main.jsx'].map((p) => path_1.default.join(appRoot, p));
41
- const entry = possible.find((p) => fs_extra_1.default.existsSync(p));
181
+ const possible = ['src/main.tsx', 'src/main.jsx'].map((p) => path.join(appRoot, p));
182
+ const entry = possible.find((p) => fs.existsSync(p));
42
183
  if (!entry) {
43
- console.error(chalk_1.default.red('❌ Entry not found: src/main.tsx or src/main.jsx'));
184
+ console.error(chalk.red('❌ Entry not found: src/main.tsx or src/main.jsx'));
44
185
  process.exit(1);
45
186
  }
46
- const indexHtml = path_1.default.join(appRoot, 'index.html');
187
+ const indexHtml = path.join(appRoot, 'index.html');
47
188
  // Select port
48
- const availablePort = await (0, detect_port_1.default)(defaultPort);
189
+ const availablePort = await detectPort(defaultPort);
49
190
  const port = availablePort;
50
191
  if (availablePort !== defaultPort) {
51
- const response = await (0, prompts_1.default)({
192
+ const response = await prompts({
52
193
  type: 'confirm',
53
194
  name: 'useNewPort',
54
195
  message: `Port ${defaultPort} is occupied. Use ${availablePort} instead?`,
@@ -64,11 +205,16 @@ async function dev() {
64
205
  require.resolve('react-refresh/runtime');
65
206
  }
66
207
  catch {
67
- console.warn(chalk_1.default.yellow('⚠️ react-refresh not found — installing react-refresh...'));
68
- (0, child_process_1.execSync)('npm install react-refresh --no-audit --no-fund --silent', {
69
- cwd: appRoot,
70
- stdio: 'inherit',
71
- });
208
+ console.warn(chalk.yellow('⚠️ react-refresh not found — installing react-refresh...'));
209
+ try {
210
+ execSync('npm install react-refresh --no-audit --no-fund --silent', {
211
+ cwd: appRoot,
212
+ stdio: 'inherit',
213
+ });
214
+ }
215
+ catch {
216
+ console.warn(chalk.yellow('⚠️ automatic install of react-refresh failed; continuing without it.'));
217
+ }
72
218
  }
73
219
  // Plugin system (core + user)
74
220
  const corePlugins = [
@@ -92,7 +238,7 @@ async function dev() {
92
238
  const userPlugins = Array.isArray(userConfig.plugins) ? userConfig.plugins : [];
93
239
  const plugins = [...corePlugins, ...userPlugins];
94
240
  // App + caches
95
- const app = (0, connect_1.default)();
241
+ const app = connect();
96
242
  const transformCache = new Map();
97
243
  // Helper: recursively analyze dependency graph for prebundling (bare imports)
98
244
  async function analyzeGraph(file, seen = new Set()) {
@@ -100,7 +246,7 @@ async function dev() {
100
246
  return seen;
101
247
  seen.add(file);
102
248
  try {
103
- const code = await fs_extra_1.default.readFile(file, 'utf8');
249
+ const code = await fs.readFile(file, 'utf8');
104
250
  const matches = [
105
251
  ...code.matchAll(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g),
106
252
  ...code.matchAll(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g),
@@ -128,17 +274,17 @@ async function dev() {
128
274
  async function prebundleDeps(deps) {
129
275
  if (!deps.size)
130
276
  return;
131
- const existingFiles = await fs_extra_1.default.readdir(cacheDir);
277
+ const existingFiles = await fs.readdir(cacheDir);
132
278
  const existing = new Set(existingFiles.map((f) => f.replace(/\.js$/, '')));
133
279
  const missing = [...deps].filter((d) => !existing.has(d));
134
280
  if (!missing.length)
135
281
  return;
136
- console.log(chalk_1.default.cyan('📦 Prebundling:'), missing.join(', '));
282
+ console.log(chalk.cyan('📦 Prebundling:'), missing.join(', '));
137
283
  await Promise.all(missing.map(async (dep) => {
138
284
  try {
139
285
  const entryPoint = require.resolve(dep, { paths: [appRoot] });
140
- const outFile = path_1.default.join(cacheDir, dep.replace(/\//g, '_') + '.js');
141
- await esbuild_1.default.build({
286
+ const outFile = path.join(cacheDir, normalizeCacheKey(dep) + '.js');
287
+ await esbuild.build({
142
288
  entryPoints: [entryPoint],
143
289
  bundle: true,
144
290
  platform: 'browser',
@@ -147,10 +293,10 @@ async function dev() {
147
293
  write: true,
148
294
  target: ['es2020'],
149
295
  });
150
- console.log(chalk_1.default.green(`✅ Cached ${dep}`));
296
+ console.log(chalk.green(`✅ Cached ${dep}`));
151
297
  }
152
298
  catch (err) {
153
- console.warn(chalk_1.default.yellow(`⚠️ Skipped ${dep}: ${err.message}`));
299
+ console.warn(chalk.yellow(`⚠️ Skipped ${dep}: ${err.message}`));
154
300
  }
155
301
  }));
156
302
  }
@@ -158,16 +304,16 @@ async function dev() {
158
304
  const depsSet = await analyzeGraph(entry);
159
305
  await prebundleDeps(depsSet);
160
306
  // Watch package.json for changes to re-prebundle
161
- const pkgPath = path_1.default.join(appRoot, 'package.json');
162
- if (await fs_extra_1.default.pathExists(pkgPath)) {
163
- chokidar_1.default.watch(pkgPath).on('change', async () => {
164
- console.log(chalk_1.default.yellow('📦 package.json changed — rebuilding prebundle...'));
307
+ const pkgPath = path.join(appRoot, 'package.json');
308
+ if (await fs.pathExists(pkgPath)) {
309
+ chokidar.watch(pkgPath).on('change', async () => {
310
+ console.log(chalk.yellow('📦 package.json changed — rebuilding prebundle...'));
165
311
  const newDeps = await analyzeGraph(entry);
166
312
  await prebundleDeps(newDeps);
167
313
  });
168
314
  }
169
315
  // --- Serve /@modules/<dep> (prebundled or on-demand esbuild bundle)
170
- app.use(async (req, res, next) => {
316
+ app.use((async (req, res, next) => {
171
317
  const url = req.url ?? '';
172
318
  if (!url.startsWith('/@modules/'))
173
319
  return next();
@@ -177,44 +323,14 @@ async function dev() {
177
323
  return res.end('// invalid module');
178
324
  }
179
325
  try {
180
- const cacheFile = path_1.default.join(cacheDir, id.replace(/[\\/]/g, '_') + '.js');
181
- if (await fs_extra_1.default.pathExists(cacheFile)) {
182
- res.setHeader('Content-Type', 'application/javascript');
183
- return res.end(await fs_extra_1.default.readFile(cacheFile, 'utf8'));
184
- }
185
- // 🧠 Handle subpath imports correctly (like react-dom/client)
186
- let entryFile = null;
187
- try {
188
- entryFile = require.resolve(id, { paths: [appRoot] });
326
+ const cacheFile = path.join(cacheDir, normalizeCacheKey(id) + '.js');
327
+ if (await fs.pathExists(cacheFile)) {
328
+ res.setHeader('Content-Type', jsContentType());
329
+ return res.end(await fs.readFile(cacheFile, 'utf8'));
189
330
  }
190
- catch {
191
- const parts = id.split('/');
192
- const pkgRoot = parts[0].startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
193
- const subPath = parts.slice(pkgRoot.startsWith('@') ? 2 : 1).join('/');
194
- const pkgJsonPath = require.resolve(`${pkgRoot}/package.json`, { paths: [appRoot] });
195
- const pkgDir = path_1.default.dirname(pkgJsonPath);
196
- // Special case: react-dom/client
197
- if (pkgRoot === 'react-dom' && subPath === 'client') {
198
- entryFile = path_1.default.join(pkgDir, 'client.js');
199
- }
200
- else {
201
- const candidates = [
202
- path_1.default.join(pkgDir, subPath),
203
- path_1.default.join(pkgDir, subPath, 'index.js'),
204
- path_1.default.join(pkgDir, subPath + '.js'),
205
- path_1.default.join(pkgDir, subPath + '.mjs'),
206
- ];
207
- for (const f of candidates) {
208
- if (await fs_extra_1.default.pathExists(f)) {
209
- entryFile = f;
210
- break;
211
- }
212
- }
213
- }
214
- }
215
- if (!entryFile)
216
- throw new Error(`Cannot resolve module: ${id}`);
217
- const result = await esbuild_1.default.build({
331
+ // Resolve the actual entry file (handles subpaths & package exports)
332
+ const entryFile = await resolveModuleEntry(id, appRoot);
333
+ const result = await esbuild.build({
218
334
  entryPoints: [entryFile],
219
335
  bundle: true,
220
336
  platform: 'browser',
@@ -223,108 +339,87 @@ async function dev() {
223
339
  target: ['es2020'],
224
340
  });
225
341
  const output = result.outputFiles?.[0]?.text ?? '';
226
- await fs_extra_1.default.writeFile(cacheFile, output, 'utf8');
227
- res.setHeader('Content-Type', 'application/javascript');
342
+ // Write cache and respond
343
+ await fs.writeFile(cacheFile, output, 'utf8');
344
+ res.setHeader('Content-Type', jsContentType());
228
345
  res.end(output);
229
346
  }
230
347
  catch (err) {
231
348
  res.writeHead(500);
232
349
  res.end(`// Failed to resolve module ${id}: ${err.message}`);
233
350
  }
234
- });
351
+ }));
235
352
  // --- Serve runtime overlay (inline, no external dependencies)
236
353
  const OVERLAY_RUNTIME = `
354
+ /* inline overlay runtime - served at ${RUNTIME_OVERLAY_ROUTE} */
355
+ ${(() => {
356
+ // small helper — embed as a string
357
+ return `
237
358
  const overlayId = "__rc_error_overlay__";
359
+ (function(){
360
+ const style = document.createElement("style");
361
+ style.textContent = \`
362
+ #\${overlayId}{position:fixed;inset:0;background:rgba(0,0,0,0.9);color:#fff;font-family:Menlo,Consolas,monospace;font-size:14px;z-index:999999;overflow:auto;padding:24px;}
363
+ #\${overlayId} h2{color:#ff6b6b;margin-bottom:16px;}
364
+ #\${overlayId} pre{background:rgba(255,255,255,0.06);padding:12px;border-radius:6px;overflow:auto;}
365
+ .frame-file{color:#ffa500;cursor:pointer;font-weight:bold;margin-bottom:4px;}
366
+ .line-number{opacity:0.6;margin-right:10px;display:inline-block;width:2em;text-align:right;}
367
+ \`;
368
+ document.head.appendChild(style);
238
369
 
239
- const style = document.createElement("style");
240
- style.textContent = \`
241
- #\${overlayId} {
242
- position: fixed;
243
- inset: 0;
244
- background: rgba(0, 0, 0, 0.9);
245
- color: #fff;
246
- font-family: Menlo, Consolas, monospace;
247
- font-size: 14px;
248
- z-index: 999999;
249
- overflow: auto;
250
- padding: 24px;
251
- animation: fadeIn 0.2s ease-out;
370
+ async function mapStackFrame(frame){
371
+ const m = frame.match(/(\\/src\\/[^\s:]+):(\\d+):(\\d+)/);
372
+ if(!m) return frame;
373
+ const [,file,line,col] = m;
374
+ try{
375
+ const resp = await fetch(\`/@source-map?file=\${file}&line=\${line}&column=\${col}\`);
376
+ if(!resp.ok) return frame;
377
+ const pos = await resp.json();
378
+ if(pos.source) return pos;
379
+ }catch(e){}
380
+ return frame;
252
381
  }
253
- @keyframes fadeIn { from {opacity: 0;} to {opacity: 1;} }
254
- #\${overlayId} h2 { color: #ff6b6b; margin-bottom: 16px; }
255
- #\${overlayId} pre { background: rgba(255,255,255,0.1); padding: 12px; border-radius: 6px; overflow-x: auto; }
256
- #\${overlayId} a { color: #9cf; text-decoration: underline; }
257
- #\${overlayId} .frame { margin: 12px 0; }
258
- #\${overlayId} .frame-file { color: #ffa500; cursor: pointer; font-weight: bold; margin-bottom: 4px; }
259
- .line-number { opacity: 0.5; margin-right: 10px; display: inline-block; width: 2em; text-align: right; }
260
- \`;
261
- document.head.appendChild(style);
262
382
 
263
- async function mapStackFrame(frame) {
264
- const m = frame.match(/(\\/src\\/[^\s:]+):(\\d+):(\\d+)/);
265
- if (!m) return frame;
266
- const [, file, line, col] = m;
267
- const resp = await fetch(\`/@source-map?file=\${file}&line=\${line}&column=\${col}\`);
268
- if (!resp.ok) return frame;
269
- const pos = await resp.json();
270
- if (pos.source) {
271
- return {
272
- file: pos.source,
273
- line: pos.line,
274
- column: pos.column,
275
- snippet: pos.snippet || ""
276
- };
383
+ function highlightSimple(s){
384
+ return s.replace(/(const|let|var|function|return|import|from|export|class|new|await|async|if|else|for|while|try|catch|throw)/g,'<span style="color:#ffb86c">$1</span>');
277
385
  }
278
- return frame;
279
- }
280
386
 
281
- // 🔹 minimal inline syntax highlighting (keywords only)
282
- function highlightJS(code) {
283
- return code
284
- .replace(/(const|let|var|function|return|import|from|export|class|new|await|async|if|else|for|while|try|catch|throw)/g, '<span style="color:#ffb86c;">$1</span>')
285
- .replace(/("[^"]*"|'[^']*')/g, '<span style="color:#8be9fd;">$1</span>')
286
- .replace(/(\\/\\/.*)/g, '<span style="opacity:0.6;">$1</span>');
287
- }
288
-
289
- async function renderOverlay(err) {
290
- const overlay =
291
- document.getElementById(overlayId) ||
292
- document.body.appendChild(Object.assign(document.createElement("div"), { id: overlayId }));
293
- overlay.innerHTML = "";
294
- const title = document.createElement("h2");
295
- title.textContent = "🔥 " + (err.message || "Error");
296
- overlay.appendChild(title);
297
-
298
- const frames = (err.stack || "").split("\\n").filter(l => /src\\//.test(l));
299
- for (const frame of frames) {
300
- const mapped = await mapStackFrame(frame);
301
- if (typeof mapped === "string") continue;
302
- const frameEl = document.createElement("div");
303
- frameEl.className = "frame";
304
-
305
- const link = document.createElement("div");
306
- link.className = "frame-file";
307
- link.textContent = \`\${mapped.file}:\${mapped.line}:\${mapped.column}\`;
308
- link.onclick = () =>
309
- window.open("vscode://file/" + location.origin.replace("http://", "") + mapped.file + ":" + mapped.line);
310
- frameEl.appendChild(link);
311
-
312
- if (mapped.snippet) {
313
- const pre = document.createElement("pre");
314
- pre.innerHTML = highlightJS(mapped.snippet);
315
- frameEl.appendChild(pre);
387
+ async function renderOverlay(err){
388
+ const overlay = document.getElementById(overlayId) || document.body.appendChild(Object.assign(document.createElement("div"),{id:overlayId}));
389
+ overlay.innerHTML = "";
390
+ const title = document.createElement("h2");
391
+ title.textContent = "🔥 " + (err.message || "Error");
392
+ overlay.appendChild(title);
393
+ const frames = (err.stack||"").split("\\n").filter(l => /src\\//.test(l));
394
+ for(const frame of frames){
395
+ const mapped = await mapStackFrame(frame);
396
+ if(typeof mapped === "string") continue;
397
+ const frameEl = document.createElement("div");
398
+ const link = document.createElement("div");
399
+ link.className = "frame-file";
400
+ link.textContent = \`\${mapped.source||mapped.file}:\${mapped.line}:\${mapped.column}\`;
401
+ link.onclick = ()=>window.open("vscode://file/"+(mapped.source||mapped.file)+":"+mapped.line);
402
+ frameEl.appendChild(link);
403
+ if(mapped.snippet){
404
+ const pre = document.createElement("pre");
405
+ pre.innerHTML = highlightSimple(mapped.snippet);
406
+ frameEl.appendChild(pre);
407
+ }
408
+ overlay.appendChild(frameEl);
316
409
  }
317
-
318
- overlay.appendChild(frameEl);
319
410
  }
320
- }
321
411
 
322
- window.showErrorOverlay = (err) => renderOverlay(err);
323
- window.clearErrorOverlay = () => document.getElementById(overlayId)?.remove();
412
+ window.showErrorOverlay = (err)=>renderOverlay(err);
413
+ window.clearErrorOverlay = ()=>document.getElementById(overlayId)?.remove();
414
+ window.addEventListener("error", e => window.showErrorOverlay?.(e.error || e));
415
+ window.addEventListener("unhandledrejection", e => window.showErrorOverlay?.(e.reason || e));
416
+ })();
417
+ `;
418
+ })()}
324
419
  `;
325
420
  app.use(async (req, res, next) => {
326
- if (req.url === '/@runtime/overlay') {
327
- res.setHeader('Content-Type', 'application/javascript');
421
+ if (req.url === RUNTIME_OVERLAY_ROUTE) {
422
+ res.setHeader('Content-Type', jsContentType());
328
423
  return res.end(OVERLAY_RUNTIME);
329
424
  }
330
425
  next();
@@ -334,10 +429,8 @@ window.clearErrorOverlay = () => document.getElementById(overlayId)?.remove();
334
429
  const url = req.url ?? '';
335
430
  if (!url.startsWith('/@source-map'))
336
431
  return next();
337
- // expected query: ?file=/src/xyz.tsx&line=12&column=3
338
432
  try {
339
- const full = req.url ?? '';
340
- const parsed = new URL(full, `http://localhost:${port}`);
433
+ const parsed = new URL(req.url ?? '', `http://localhost:${port}`);
341
434
  const file = parsed.searchParams.get('file') ?? '';
342
435
  const lineStr = parsed.searchParams.get('line') ?? '0';
343
436
  const lineNum = Number(lineStr) || 0;
@@ -345,12 +438,12 @@ window.clearErrorOverlay = () => document.getElementById(overlayId)?.remove();
345
438
  res.writeHead(400);
346
439
  return res.end('{}');
347
440
  }
348
- const filePath = path_1.default.join(appRoot, file.startsWith('/') ? file.slice(1) : file);
349
- if (!(await fs_extra_1.default.pathExists(filePath))) {
441
+ const filePath = path.join(appRoot, file.startsWith('/') ? file.slice(1) : file);
442
+ if (!(await fs.pathExists(filePath))) {
350
443
  res.writeHead(404);
351
444
  return res.end('{}');
352
445
  }
353
- const src = await fs_extra_1.default.readFile(filePath, 'utf8');
446
+ const src = await fs.readFile(filePath, 'utf8');
354
447
  const lines = src.split(/\r?\n/);
355
448
  const start = Math.max(0, lineNum - 3 - 1);
356
449
  const end = Math.min(lines.length, lineNum + 2);
@@ -377,12 +470,12 @@ window.clearErrorOverlay = () => document.getElementById(overlayId)?.remove();
377
470
  if (!url.startsWith('/src/') && !url.endsWith('.css'))
378
471
  return next();
379
472
  const raw = decodeURIComponent((req.url ?? '').split('?')[0]);
380
- const filePath = path_1.default.join(appRoot, raw.replace(/^\//, ''));
473
+ const filePath = path.join(appRoot, raw.replace(/^\//, ''));
381
474
  // Try file extensions if not exact file
382
475
  const exts = ['', '.tsx', '.ts', '.jsx', '.js', '.css'];
383
476
  let found = '';
384
477
  for (const ext of exts) {
385
- if (await fs_extra_1.default.pathExists(filePath + ext)) {
478
+ if (await fs.pathExists(filePath + ext)) {
386
479
  found = filePath + ext;
387
480
  break;
388
481
  }
@@ -390,7 +483,7 @@ window.clearErrorOverlay = () => document.getElementById(overlayId)?.remove();
390
483
  if (!found)
391
484
  return next();
392
485
  try {
393
- let code = await fs_extra_1.default.readFile(found, 'utf8');
486
+ let code = await fs.readFile(found, 'utf8');
394
487
  // rewrite bare imports -> /@modules/<dep>
395
488
  code = code
396
489
  .replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_m, dep) => `from "/@modules/${dep}"`)
@@ -398,24 +491,21 @@ window.clearErrorOverlay = () => document.getElementById(overlayId)?.remove();
398
491
  // run plugin transforms
399
492
  for (const p of plugins) {
400
493
  if (p.onTransform) {
401
- // plugin may return transformed code
402
- // keep typed as string
403
- // eslint-disable-next-line no-await-in-loop
404
494
  const out = await p.onTransform(code, found);
405
495
  if (typeof out === 'string')
406
496
  code = out;
407
497
  }
408
498
  }
409
499
  // choose loader by extension
410
- const ext = path_1.default.extname(found).toLowerCase();
500
+ const ext = path.extname(found).toLowerCase();
411
501
  const loader = ext === '.ts' ? 'ts' : ext === '.tsx' ? 'tsx' : ext === '.jsx' ? 'jsx' : 'js';
412
- const result = await esbuild_1.default.transform(code, {
502
+ const result = await esbuild.transform(code, {
413
503
  loader,
414
504
  sourcemap: 'inline',
415
505
  target: ['es2020'],
416
506
  });
417
507
  transformCache.set(found, result.code);
418
- res.setHeader('Content-Type', 'application/javascript');
508
+ res.setHeader('Content-Type', jsContentType());
419
509
  res.end(result.code);
420
510
  }
421
511
  catch (err) {
@@ -429,15 +519,15 @@ window.clearErrorOverlay = () => document.getElementById(overlayId)?.remove();
429
519
  const url = req.url ?? '';
430
520
  if (url !== '/' && url !== '/index.html')
431
521
  return next();
432
- if (!(await fs_extra_1.default.pathExists(indexHtml))) {
522
+ if (!(await fs.pathExists(indexHtml))) {
433
523
  res.writeHead(404);
434
524
  return res.end('index.html not found');
435
525
  }
436
526
  try {
437
- let html = await fs_extra_1.default.readFile(indexHtml, 'utf8');
527
+ let html = await fs.readFile(indexHtml, 'utf8');
438
528
  // inject overlay runtime and HMR client if not already present
439
- if (!html.includes('/@runtime/overlay')) {
440
- html = html.replace('</body>', `\n<script type="module" src="/@runtime/overlay"></script>\n<script type="module">
529
+ if (!html.includes(RUNTIME_OVERLAY_ROUTE)) {
530
+ html = html.replace('</body>', `\n<script type="module" src="${RUNTIME_OVERLAY_ROUTE}"></script>\n<script type="module">
441
531
  const ws = new WebSocket("ws://" + location.host);
442
532
  ws.onmessage = (e) => {
443
533
  const msg = JSON.parse(e.data);
@@ -450,7 +540,7 @@ window.clearErrorOverlay = () => document.getElementById(overlayId)?.remove();
450
540
  };
451
541
  </script>\n</body>`);
452
542
  }
453
- res.setHeader('Content-Type', 'text/html');
543
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
454
544
  res.end(html);
455
545
  }
456
546
  catch (err) {
@@ -459,29 +549,24 @@ window.clearErrorOverlay = () => document.getElementById(overlayId)?.remove();
459
549
  }
460
550
  }));
461
551
  // --- HMR WebSocket server
462
- const server = http_1.default.createServer(app);
463
- const broadcaster = new broadcastManager_1.BroadcastManager(server);
552
+ const server = http.createServer(app);
553
+ const broadcaster = new BroadcastManager(server);
464
554
  // Watch files and trigger plugin onHotUpdate + broadcast HMR message
465
- const watcher = chokidar_1.default.watch(path_1.default.join(appRoot, 'src'), { ignoreInitial: true });
555
+ const watcher = chokidar.watch(path.join(appRoot, 'src'), { ignoreInitial: true });
466
556
  watcher.on('change', async (file) => {
467
557
  transformCache.delete(file);
468
558
  // plugin hook onHotUpdate optionally
469
559
  for (const p of plugins) {
470
560
  if (p.onHotUpdate) {
471
561
  try {
472
- // allow plugin to broadcast via a simple function
473
- // plugin gets { broadcast }
474
- // plugin signature: onHotUpdate(file, { broadcast })
475
- // eslint-disable-next-line no-await-in-loop
476
562
  await p.onHotUpdate(file, {
563
+ // plugin only needs broadcast in most cases
477
564
  broadcast: (msg) => {
478
565
  broadcaster.broadcast(msg);
479
566
  },
480
567
  });
481
568
  }
482
569
  catch (err) {
483
- // plugin errors shouldn't crash server
484
- // eslint-disable-next-line no-console
485
570
  console.warn('plugin onHotUpdate error:', err.message);
486
571
  }
487
572
  }
@@ -489,18 +574,17 @@ window.clearErrorOverlay = () => document.getElementById(overlayId)?.remove();
489
574
  // default: broadcast update for changed file
490
575
  broadcaster.broadcast({
491
576
  type: 'update',
492
- path: '/' + path_1.default.relative(appRoot, file).replace(/\\/g, '/'),
577
+ path: '/' + path.relative(appRoot, file).replace(/\\/g, '/'),
493
578
  });
494
579
  });
495
580
  // start server
496
581
  server.listen(port, async () => {
497
582
  const url = `http://localhost:${port}`;
498
- console.log(chalk_1.default.cyan.bold('\n🚀 React Client Dev Server'));
499
- console.log(chalk_1.default.green(`⚡ Running at: ${url}`));
583
+ console.log(chalk.cyan.bold('\n🚀 React Client Dev Server'));
584
+ console.log(chalk.green(`⚡ Running at: ${url}`));
500
585
  if (userConfig.server?.open !== false) {
501
- // open default browser
502
586
  try {
503
- await (0, open_1.default)(url);
587
+ await open(url);
504
588
  }
505
589
  catch {
506
590
  // ignore open errors
@@ -509,8 +593,8 @@ window.clearErrorOverlay = () => document.getElementById(overlayId)?.remove();
509
593
  });
510
594
  // graceful shutdown
511
595
  process.on('SIGINT', async () => {
512
- console.log(chalk_1.default.red('\n🛑 Shutting down...'));
513
- watcher.close();
596
+ console.log(chalk.red('\n🛑 Shutting down...'));
597
+ await watcher.close();
514
598
  broadcaster.close();
515
599
  server.close();
516
600
  process.exit(0);