react-client 1.0.30 → 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.
@@ -9,15 +9,6 @@
9
9
  *
10
10
  * Keep this file linted & typed. Avoids manual react-dom/client hacks.
11
11
  */
12
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
14
- return new (P || (P = Promise))(function (resolve, reject) {
15
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
16
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
18
- step((generator = generator.apply(thisArg, _arguments || [])).next());
19
- });
20
- };
21
12
  import esbuild from 'esbuild';
22
13
  import connect from 'connect';
23
14
  import http from 'http';
@@ -44,130 +35,128 @@ function jsContentType() {
44
35
  * 3. try package.json exports field
45
36
  * 4. try common fallback candidates
46
37
  */
47
- function resolveModuleEntry(id, root) {
48
- return __awaiter(this, void 0, void 0, function* () {
49
- // quick resolution
50
- try {
51
- return require.resolve(id, { paths: [root] });
52
- }
53
- catch (_a) {
54
- // continue
55
- }
56
- // split package root and subpath
57
- const parts = id.split('/');
58
- const pkgRoot = parts[0].startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
59
- const subPath = parts.slice(pkgRoot.startsWith('@') ? 2 : 1).join('/');
60
- let pkgJsonPath;
61
- try {
62
- pkgJsonPath = require.resolve(`${pkgRoot}/package.json`, { paths: [root] });
63
- }
64
- catch (_b) {
65
- // No need to keep unused variable 'err'
66
- throw new Error(`Package not found: ${pkgRoot}`);
67
- }
68
- const pkgDir = path.dirname(pkgJsonPath);
69
- // Explicitly type pkgJson to avoid 'any'
70
- let pkgJson = {};
71
- try {
72
- const pkgContent = yield fs.readFile(pkgJsonPath, 'utf8');
73
- pkgJson = JSON.parse(pkgContent);
74
- }
75
- catch (_c) {
76
- // ignore parse or read errors gracefully
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);
77
75
  }
78
- // If exports field exists, try to look up subpath (type-safe, supports conditional exports)
79
- if (pkgJson.exports) {
80
- const exportsField = pkgJson.exports;
81
- // If exports is a plain string -> it's the entry
82
- if (typeof exportsField === 'string') {
83
- if (!subPath)
84
- return path.resolve(pkgDir, exportsField);
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`);
85
83
  }
86
- else if (exportsField && typeof exportsField === 'object') {
87
- // Normalize to a record so we can index it safely
88
- const exportsMap = exportsField;
89
- // Try candidates in order: explicit subpath, index, fallback
90
- const keyCandidates = [];
91
- if (subPath) {
92
- keyCandidates.push(`./${subPath}`, `./${subPath}.js`, `./${subPath}.mjs`);
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
93
  }
94
- keyCandidates.push('.', './index.js', './index.mjs');
95
- for (const key of keyCandidates) {
96
- if (!(key in exportsMap))
97
- continue;
98
- const entry = exportsMap[key];
99
- // entry may be string or object like { import: "...", require: "..." }
100
- let target;
101
- if (typeof entry === 'string') {
102
- target = entry;
103
- }
104
- else if (entry && typeof entry === 'object') {
105
- const entryObj = entry;
106
- // Prefer "import" field for ESM consumers, then "default", then any string-ish value
107
- if (typeof entryObj.import === 'string')
108
- target = entryObj.import;
109
- else if (typeof entryObj.default === 'string')
110
- target = entryObj.default;
111
- else {
112
- // If the entry object itself is a conditional map (like {"node": "...", "browser": "..."}),
113
- // attempt to pick any string value present.
114
- for (const k of Object.keys(entryObj)) {
115
- if (typeof entryObj[k] === 'string') {
116
- target = entryObj[k];
117
- break;
118
- }
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;
119
108
  }
120
109
  }
121
110
  }
122
- if (!target || typeof target !== 'string')
123
- continue;
124
- // Normalize relative paths in exports (remove leading ./)
125
- const normalized = target.replace(/^\.\//, '');
126
- const abs = path.isAbsolute(normalized)
127
- ? normalized
128
- : path.resolve(pkgDir, normalized);
129
- if (yield fs.pathExists(abs)) {
130
- return abs;
131
- }
132
111
  }
133
- }
134
- }
135
- // Try resolved subpath directly (pkg/subpath)
136
- if (subPath) {
137
- try {
138
- const candidate = require.resolve(`${pkgRoot}/${subPath}`, { paths: [root] });
139
- return candidate;
140
- }
141
- catch (_d) {
142
- // fallback to searching common candidates under package dir
143
- const candPaths = [
144
- path.join(pkgDir, subPath),
145
- path.join(pkgDir, subPath + '.js'),
146
- path.join(pkgDir, subPath + '.mjs'),
147
- path.join(pkgDir, subPath, 'index.js'),
148
- path.join(pkgDir, subPath, 'index.mjs'),
149
- ];
150
- for (const c of candPaths) {
151
- if (yield fs.pathExists(c))
152
- return c;
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;
153
121
  }
154
122
  }
155
123
  }
156
- // Try package's main/module/browser fields safely (typed as string)
157
- const candidateFields = [
158
- typeof pkgJson.module === 'string' ? pkgJson.module : undefined,
159
- typeof pkgJson.browser === 'string' ? pkgJson.browser : undefined,
160
- typeof pkgJson.main === 'string' ? pkgJson.main : undefined,
161
- ];
162
- for (const field of candidateFields) {
163
- if (!field)
164
- continue;
165
- const abs = path.isAbsolute(field) ? field : path.resolve(pkgDir, field);
166
- if (yield fs.pathExists(abs))
167
- return abs;
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;
168
130
  }
169
- throw new Error(`Could not resolve module entry for ${id}`);
170
- });
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}`);
171
160
  }
172
161
  /**
173
162
  * Wrap the built module for subpath imports:
@@ -180,201 +169,192 @@ function resolveModuleEntry(id, root) {
180
169
  function normalizeCacheKey(id) {
181
170
  return id.replace(/[\\/]/g, '_');
182
171
  }
183
- export default function dev() {
184
- return __awaiter(this, void 0, void 0, function* () {
185
- var _a, _b;
186
- const root = process.cwd();
187
- const userConfig = (yield loadReactClientConfig(root));
188
- const appRoot = path.resolve(root, userConfig.root || '.');
189
- const defaultPort = (_b = (_a = userConfig.server) === null || _a === void 0 ? void 0 : _a.port) !== null && _b !== void 0 ? _b : 2202;
190
- // cache dir for prebundled deps
191
- const cacheDir = path.join(appRoot, '.react-client', 'deps');
192
- yield fs.ensureDir(cacheDir);
193
- // Detect entry (main.tsx / main.jsx)
194
- const possible = ['src/main.tsx', 'src/main.jsx'].map((p) => path.join(appRoot, p));
195
- const entry = possible.find((p) => fs.existsSync(p));
196
- if (!entry) {
197
- console.error(chalk.red('āŒ Entry not found: src/main.tsx or src/main.jsx'));
198
- process.exit(1);
199
- }
200
- const indexHtml = path.join(appRoot, 'index.html');
201
- // Select port
202
- const availablePort = yield detectPort(defaultPort);
203
- const port = availablePort;
204
- if (availablePort !== defaultPort) {
205
- const response = yield prompts({
206
- type: 'confirm',
207
- name: 'useNewPort',
208
- message: `Port ${defaultPort} is occupied. Use ${availablePort} instead?`,
209
- initial: true,
210
- });
211
- if (!response.useNewPort) {
212
- console.log('šŸ›‘ Dev server cancelled.');
213
- process.exit(0);
214
- }
172
+ export default async function dev() {
173
+ const root = process.cwd();
174
+ const userConfig = (await loadReactClientConfig(root));
175
+ const appRoot = path.resolve(root, userConfig.root || '.');
176
+ const defaultPort = userConfig.server?.port ?? 2202;
177
+ // cache dir for prebundled deps
178
+ const cacheDir = path.join(appRoot, '.react-client', 'deps');
179
+ await fs.ensureDir(cacheDir);
180
+ // Detect entry (main.tsx / main.jsx)
181
+ const possible = ['src/main.tsx', 'src/main.jsx'].map((p) => path.join(appRoot, p));
182
+ const entry = possible.find((p) => fs.existsSync(p));
183
+ if (!entry) {
184
+ console.error(chalk.red('āŒ Entry not found: src/main.tsx or src/main.jsx'));
185
+ process.exit(1);
186
+ }
187
+ const indexHtml = path.join(appRoot, 'index.html');
188
+ // Select port
189
+ const availablePort = await detectPort(defaultPort);
190
+ const port = availablePort;
191
+ if (availablePort !== defaultPort) {
192
+ const response = await prompts({
193
+ type: 'confirm',
194
+ name: 'useNewPort',
195
+ message: `Port ${defaultPort} is occupied. Use ${availablePort} instead?`,
196
+ initial: true,
197
+ });
198
+ if (!response.useNewPort) {
199
+ console.log('šŸ›‘ Dev server cancelled.');
200
+ process.exit(0);
215
201
  }
216
- // Ensure react-refresh runtime available (used by many templates)
202
+ }
203
+ // Ensure react-refresh runtime available (used by many templates)
204
+ try {
205
+ require.resolve('react-refresh/runtime');
206
+ }
207
+ catch {
208
+ console.warn(chalk.yellow('āš ļø react-refresh not found — installing react-refresh...'));
217
209
  try {
218
- require.resolve('react-refresh/runtime');
210
+ execSync('npm install react-refresh --no-audit --no-fund --silent', {
211
+ cwd: appRoot,
212
+ stdio: 'inherit',
213
+ });
219
214
  }
220
- catch (_c) {
221
- console.warn(chalk.yellow('āš ļø react-refresh not found — installing react-refresh...'));
222
- try {
223
- execSync('npm install react-refresh --no-audit --no-fund --silent', {
224
- cwd: appRoot,
225
- stdio: 'inherit',
226
- });
227
- }
228
- catch (_d) {
229
- console.warn(chalk.yellow('āš ļø automatic install of react-refresh failed; continuing without it.'));
230
- }
215
+ catch {
216
+ console.warn(chalk.yellow('āš ļø automatic install of react-refresh failed; continuing without it.'));
231
217
  }
232
- // Plugin system (core + user)
233
- const corePlugins = [
234
- {
235
- name: 'css-hmr',
236
- onTransform(code, id) {
237
- return __awaiter(this, void 0, void 0, function* () {
238
- if (id.endsWith('.css')) {
239
- const escaped = JSON.stringify(code);
240
- return `
218
+ }
219
+ // Plugin system (core + user)
220
+ const corePlugins = [
221
+ {
222
+ name: 'css-hmr',
223
+ async onTransform(code, id) {
224
+ if (id.endsWith('.css')) {
225
+ const escaped = JSON.stringify(code);
226
+ return `
241
227
  const css = ${escaped};
242
228
  const style = document.createElement("style");
243
229
  style.textContent = css;
244
230
  document.head.appendChild(style);
245
231
  import.meta.hot?.accept();
246
232
  `;
247
- }
248
- return code;
249
- });
250
- },
233
+ }
234
+ return code;
251
235
  },
252
- ];
253
- const userPlugins = Array.isArray(userConfig.plugins) ? userConfig.plugins : [];
254
- const plugins = [...corePlugins, ...userPlugins];
255
- // App + caches
256
- const app = connect();
257
- const transformCache = new Map();
258
- // Helper: recursively analyze dependency graph for prebundling (bare imports)
259
- function analyzeGraph(file_1) {
260
- return __awaiter(this, arguments, void 0, function* (file, seen = new Set()) {
261
- if (seen.has(file))
262
- return seen;
263
- seen.add(file);
236
+ },
237
+ ];
238
+ const userPlugins = Array.isArray(userConfig.plugins) ? userConfig.plugins : [];
239
+ const plugins = [...corePlugins, ...userPlugins];
240
+ // App + caches
241
+ const app = connect();
242
+ const transformCache = new Map();
243
+ // Helper: recursively analyze dependency graph for prebundling (bare imports)
244
+ async function analyzeGraph(file, seen = new Set()) {
245
+ if (seen.has(file))
246
+ return seen;
247
+ seen.add(file);
248
+ try {
249
+ const code = await fs.readFile(file, 'utf8');
250
+ const matches = [
251
+ ...code.matchAll(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g),
252
+ ...code.matchAll(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g),
253
+ ];
254
+ for (const m of matches) {
255
+ const dep = m[1];
256
+ if (!dep || dep.startsWith('.') || dep.startsWith('/'))
257
+ continue;
264
258
  try {
265
- const code = yield fs.readFile(file, 'utf8');
266
- const matches = [
267
- ...code.matchAll(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g),
268
- ...code.matchAll(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g),
269
- ];
270
- for (const m of matches) {
271
- const dep = m[1];
272
- if (!dep || dep.startsWith('.') || dep.startsWith('/'))
273
- continue;
274
- try {
275
- const resolved = require.resolve(dep, { paths: [appRoot] });
276
- yield analyzeGraph(resolved, seen);
277
- }
278
- catch (_a) {
279
- // bare dependency (node_modules) - track name
280
- seen.add(dep);
281
- }
282
- }
259
+ const resolved = require.resolve(dep, { paths: [appRoot] });
260
+ await analyzeGraph(resolved, seen);
283
261
  }
284
- catch (_b) {
285
- // ignore unreadable files
262
+ catch {
263
+ // bare dependency (node_modules) - track name
264
+ seen.add(dep);
286
265
  }
287
- return seen;
288
- });
289
- }
290
- // Prebundle dependencies into cache dir (parallel)
291
- function prebundleDeps(deps) {
292
- return __awaiter(this, void 0, void 0, function* () {
293
- if (!deps.size)
294
- return;
295
- const existingFiles = yield fs.readdir(cacheDir);
296
- const existing = new Set(existingFiles.map((f) => f.replace(/\.js$/, '')));
297
- const missing = [...deps].filter((d) => !existing.has(d));
298
- if (!missing.length)
299
- return;
300
- console.log(chalk.cyan('šŸ“¦ Prebundling:'), missing.join(', '));
301
- yield Promise.all(missing.map((dep) => __awaiter(this, void 0, void 0, function* () {
302
- try {
303
- const entryPoint = require.resolve(dep, { paths: [appRoot] });
304
- const outFile = path.join(cacheDir, normalizeCacheKey(dep) + '.js');
305
- yield esbuild.build({
306
- entryPoints: [entryPoint],
307
- bundle: true,
308
- platform: 'browser',
309
- format: 'esm',
310
- outfile: outFile,
311
- write: true,
312
- target: ['es2020'],
313
- });
314
- console.log(chalk.green(`āœ… Cached ${dep}`));
315
- }
316
- catch (err) {
317
- console.warn(chalk.yellow(`āš ļø Skipped ${dep}: ${err.message}`));
318
- }
319
- })));
320
- });
266
+ }
321
267
  }
322
- // Build initial prebundle graph from entry
323
- const depsSet = yield analyzeGraph(entry);
324
- yield prebundleDeps(depsSet);
325
- // Watch package.json for changes to re-prebundle
326
- const pkgPath = path.join(appRoot, 'package.json');
327
- if (yield fs.pathExists(pkgPath)) {
328
- chokidar.watch(pkgPath).on('change', () => __awaiter(this, void 0, void 0, function* () {
329
- console.log(chalk.yellow('šŸ“¦ package.json changed — rebuilding prebundle...'));
330
- const newDeps = yield analyzeGraph(entry);
331
- yield prebundleDeps(newDeps);
332
- }));
268
+ catch {
269
+ // ignore unreadable files
333
270
  }
334
- // --- Serve /@modules/<dep> (prebundled or on-demand esbuild bundle)
335
- app.use(((req, res, next) => __awaiter(this, void 0, void 0, function* () {
336
- var _a, _b, _c, _d;
337
- const url = (_a = req.url) !== null && _a !== void 0 ? _a : '';
338
- if (!url.startsWith('/@modules/'))
339
- return next();
340
- const id = url.replace(/^\/@modules\//, '');
341
- if (!id) {
342
- res.writeHead(400);
343
- return res.end('// invalid module');
344
- }
271
+ return seen;
272
+ }
273
+ // Prebundle dependencies into cache dir (parallel)
274
+ async function prebundleDeps(deps) {
275
+ if (!deps.size)
276
+ return;
277
+ const existingFiles = await fs.readdir(cacheDir);
278
+ const existing = new Set(existingFiles.map((f) => f.replace(/\.js$/, '')));
279
+ const missing = [...deps].filter((d) => !existing.has(d));
280
+ if (!missing.length)
281
+ return;
282
+ console.log(chalk.cyan('šŸ“¦ Prebundling:'), missing.join(', '));
283
+ await Promise.all(missing.map(async (dep) => {
345
284
  try {
346
- const cacheFile = path.join(cacheDir, normalizeCacheKey(id) + '.js');
347
- if (yield fs.pathExists(cacheFile)) {
348
- res.setHeader('Content-Type', jsContentType());
349
- return res.end(yield fs.readFile(cacheFile, 'utf8'));
350
- }
351
- // Resolve the actual entry file (handles subpaths & package exports)
352
- const entryFile = yield resolveModuleEntry(id, appRoot);
353
- const result = yield esbuild.build({
354
- entryPoints: [entryFile],
285
+ const entryPoint = require.resolve(dep, { paths: [appRoot] });
286
+ const outFile = path.join(cacheDir, normalizeCacheKey(dep) + '.js');
287
+ await esbuild.build({
288
+ entryPoints: [entryPoint],
355
289
  bundle: true,
356
290
  platform: 'browser',
357
291
  format: 'esm',
358
- write: false,
292
+ outfile: outFile,
293
+ write: true,
359
294
  target: ['es2020'],
360
295
  });
361
- const output = (_d = (_c = (_b = result.outputFiles) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.text) !== null && _d !== void 0 ? _d : '';
362
- // Write cache and respond
363
- yield fs.writeFile(cacheFile, output, 'utf8');
364
- res.setHeader('Content-Type', jsContentType());
365
- res.end(output);
296
+ console.log(chalk.green(`āœ… Cached ${dep}`));
366
297
  }
367
298
  catch (err) {
368
- res.writeHead(500);
369
- res.end(`// Failed to resolve module ${id}: ${err.message}`);
299
+ console.warn(chalk.yellow(`āš ļø Skipped ${dep}: ${err.message}`));
370
300
  }
371
- })));
372
- // --- Serve runtime overlay (inline, no external dependencies)
373
- const OVERLAY_RUNTIME = `
301
+ }));
302
+ }
303
+ // Build initial prebundle graph from entry
304
+ const depsSet = await analyzeGraph(entry);
305
+ await prebundleDeps(depsSet);
306
+ // Watch package.json for changes to re-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...'));
311
+ const newDeps = await analyzeGraph(entry);
312
+ await prebundleDeps(newDeps);
313
+ });
314
+ }
315
+ // --- Serve /@modules/<dep> (prebundled or on-demand esbuild bundle)
316
+ app.use((async (req, res, next) => {
317
+ const url = req.url ?? '';
318
+ if (!url.startsWith('/@modules/'))
319
+ return next();
320
+ const id = url.replace(/^\/@modules\//, '');
321
+ if (!id) {
322
+ res.writeHead(400);
323
+ return res.end('// invalid module');
324
+ }
325
+ try {
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'));
330
+ }
331
+ // Resolve the actual entry file (handles subpaths & package exports)
332
+ const entryFile = await resolveModuleEntry(id, appRoot);
333
+ const result = await esbuild.build({
334
+ entryPoints: [entryFile],
335
+ bundle: true,
336
+ platform: 'browser',
337
+ format: 'esm',
338
+ write: false,
339
+ target: ['es2020'],
340
+ });
341
+ const output = result.outputFiles?.[0]?.text ?? '';
342
+ // Write cache and respond
343
+ await fs.writeFile(cacheFile, output, 'utf8');
344
+ res.setHeader('Content-Type', jsContentType());
345
+ res.end(output);
346
+ }
347
+ catch (err) {
348
+ res.writeHead(500);
349
+ res.end(`// Failed to resolve module ${id}: ${err.message}`);
350
+ }
351
+ }));
352
+ // --- Serve runtime overlay (inline, no external dependencies)
353
+ const OVERLAY_RUNTIME = `
374
354
  /* inline overlay runtime - served at ${RUNTIME_OVERLAY_ROUTE} */
375
355
  ${(() => {
376
- // small helper — embed as a string
377
- return `
356
+ // small helper — embed as a string
357
+ return `
378
358
  const overlayId = "__rc_error_overlay__";
379
359
  (function(){
380
360
  const style = document.createElement("style");
@@ -435,122 +415,119 @@ const overlayId = "__rc_error_overlay__";
435
415
  window.addEventListener("unhandledrejection", e => window.showErrorOverlay?.(e.reason || e));
436
416
  })();
437
417
  `;
438
- })()}
418
+ })()}
439
419
  `;
440
- app.use((req, res, next) => __awaiter(this, void 0, void 0, function* () {
441
- if (req.url === RUNTIME_OVERLAY_ROUTE) {
442
- res.setHeader('Content-Type', jsContentType());
443
- return res.end(OVERLAY_RUNTIME);
444
- }
445
- next();
446
- }));
447
- // --- minimal /@source-map: return snippet around requested line of original source file
448
- app.use(((req, res, next) => __awaiter(this, void 0, void 0, function* () {
449
- var _a, _b, _c, _d;
450
- const url = (_a = req.url) !== null && _a !== void 0 ? _a : '';
451
- if (!url.startsWith('/@source-map'))
452
- return next();
453
- try {
454
- const parsed = new URL((_b = req.url) !== null && _b !== void 0 ? _b : '', `http://localhost:${port}`);
455
- const file = (_c = parsed.searchParams.get('file')) !== null && _c !== void 0 ? _c : '';
456
- const lineStr = (_d = parsed.searchParams.get('line')) !== null && _d !== void 0 ? _d : '0';
457
- const lineNum = Number(lineStr) || 0;
458
- if (!file) {
459
- res.writeHead(400);
460
- return res.end('{}');
461
- }
462
- const filePath = path.join(appRoot, file.startsWith('/') ? file.slice(1) : file);
463
- if (!(yield fs.pathExists(filePath))) {
464
- res.writeHead(404);
465
- return res.end('{}');
466
- }
467
- const src = yield fs.readFile(filePath, 'utf8');
468
- const lines = src.split(/\r?\n/);
469
- const start = Math.max(0, lineNum - 3 - 1);
470
- const end = Math.min(lines.length, lineNum + 2);
471
- const snippet = lines
472
- .slice(start, end)
473
- .map((l, i) => {
474
- const ln = start + i + 1;
475
- return `<span class="line-number">${ln}</span> ${l
476
- .replace(/</g, '&lt;')
477
- .replace(/>/g, '&gt;')}`;
478
- })
479
- .join('\n');
480
- res.setHeader('Content-Type', 'application/json');
481
- res.end(JSON.stringify({ source: file, line: lineNum, column: 0, snippet }));
420
+ app.use(async (req, res, next) => {
421
+ if (req.url === RUNTIME_OVERLAY_ROUTE) {
422
+ res.setHeader('Content-Type', jsContentType());
423
+ return res.end(OVERLAY_RUNTIME);
424
+ }
425
+ next();
426
+ });
427
+ // --- minimal /@source-map: return snippet around requested line of original source file
428
+ app.use((async (req, res, next) => {
429
+ const url = req.url ?? '';
430
+ if (!url.startsWith('/@source-map'))
431
+ return next();
432
+ try {
433
+ const parsed = new URL(req.url ?? '', `http://localhost:${port}`);
434
+ const file = parsed.searchParams.get('file') ?? '';
435
+ const lineStr = parsed.searchParams.get('line') ?? '0';
436
+ const lineNum = Number(lineStr) || 0;
437
+ if (!file) {
438
+ res.writeHead(400);
439
+ return res.end('{}');
482
440
  }
483
- catch (err) {
484
- res.writeHead(500);
485
- res.end(JSON.stringify({ error: err.message }));
441
+ const filePath = path.join(appRoot, file.startsWith('/') ? file.slice(1) : file);
442
+ if (!(await fs.pathExists(filePath))) {
443
+ res.writeHead(404);
444
+ return res.end('{}');
486
445
  }
487
- })));
488
- // --- Serve /src/* files (on-the-fly transform + bare import rewrite)
489
- app.use(((req, res, next) => __awaiter(this, void 0, void 0, function* () {
490
- var _a, _b;
491
- const url = (_a = req.url) !== null && _a !== void 0 ? _a : '';
492
- if (!url.startsWith('/src/') && !url.endsWith('.css'))
493
- return next();
494
- const raw = decodeURIComponent(((_b = req.url) !== null && _b !== void 0 ? _b : '').split('?')[0]);
495
- const filePath = path.join(appRoot, raw.replace(/^\//, ''));
496
- // Try file extensions if not exact file
497
- const exts = ['', '.tsx', '.ts', '.jsx', '.js', '.css'];
498
- let found = '';
499
- for (const ext of exts) {
500
- if (yield fs.pathExists(filePath + ext)) {
501
- found = filePath + ext;
502
- break;
503
- }
446
+ const src = await fs.readFile(filePath, 'utf8');
447
+ const lines = src.split(/\r?\n/);
448
+ const start = Math.max(0, lineNum - 3 - 1);
449
+ const end = Math.min(lines.length, lineNum + 2);
450
+ const snippet = lines
451
+ .slice(start, end)
452
+ .map((l, i) => {
453
+ const ln = start + i + 1;
454
+ return `<span class="line-number">${ln}</span> ${l
455
+ .replace(/</g, '&lt;')
456
+ .replace(/>/g, '&gt;')}`;
457
+ })
458
+ .join('\n');
459
+ res.setHeader('Content-Type', 'application/json');
460
+ res.end(JSON.stringify({ source: file, line: lineNum, column: 0, snippet }));
461
+ }
462
+ catch (err) {
463
+ res.writeHead(500);
464
+ res.end(JSON.stringify({ error: err.message }));
465
+ }
466
+ }));
467
+ // --- Serve /src/* files (on-the-fly transform + bare import rewrite)
468
+ app.use((async (req, res, next) => {
469
+ const url = req.url ?? '';
470
+ if (!url.startsWith('/src/') && !url.endsWith('.css'))
471
+ return next();
472
+ const raw = decodeURIComponent((req.url ?? '').split('?')[0]);
473
+ const filePath = path.join(appRoot, raw.replace(/^\//, ''));
474
+ // Try file extensions if not exact file
475
+ const exts = ['', '.tsx', '.ts', '.jsx', '.js', '.css'];
476
+ let found = '';
477
+ for (const ext of exts) {
478
+ if (await fs.pathExists(filePath + ext)) {
479
+ found = filePath + ext;
480
+ break;
504
481
  }
505
- if (!found)
506
- return next();
507
- try {
508
- let code = yield fs.readFile(found, 'utf8');
509
- // rewrite bare imports -> /@modules/<dep>
510
- code = code
511
- .replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_m, dep) => `from "/@modules/${dep}"`)
512
- .replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (_m, dep) => `import("/@modules/${dep}")`);
513
- // run plugin transforms
514
- for (const p of plugins) {
515
- if (p.onTransform) {
516
- const out = yield p.onTransform(code, found);
517
- if (typeof out === 'string')
518
- code = out;
519
- }
482
+ }
483
+ if (!found)
484
+ return next();
485
+ try {
486
+ let code = await fs.readFile(found, 'utf8');
487
+ // rewrite bare imports -> /@modules/<dep>
488
+ code = code
489
+ .replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_m, dep) => `from "/@modules/${dep}"`)
490
+ .replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (_m, dep) => `import("/@modules/${dep}")`);
491
+ // run plugin transforms
492
+ for (const p of plugins) {
493
+ if (p.onTransform) {
494
+ const out = await p.onTransform(code, found);
495
+ if (typeof out === 'string')
496
+ code = out;
520
497
  }
521
- // choose loader by extension
522
- const ext = path.extname(found).toLowerCase();
523
- const loader = ext === '.ts' ? 'ts' : ext === '.tsx' ? 'tsx' : ext === '.jsx' ? 'jsx' : 'js';
524
- const result = yield esbuild.transform(code, {
525
- loader,
526
- sourcemap: 'inline',
527
- target: ['es2020'],
528
- });
529
- transformCache.set(found, result.code);
530
- res.setHeader('Content-Type', jsContentType());
531
- res.end(result.code);
532
- }
533
- catch (err) {
534
- const e = err;
535
- res.writeHead(500);
536
- res.end(`// transform error: ${e.message}`);
537
498
  }
538
- })));
539
- // --- Serve index.html with overlay + HMR client injection
540
- app.use(((req, res, next) => __awaiter(this, void 0, void 0, function* () {
541
- var _a;
542
- const url = (_a = req.url) !== null && _a !== void 0 ? _a : '';
543
- if (url !== '/' && url !== '/index.html')
544
- return next();
545
- if (!(yield fs.pathExists(indexHtml))) {
546
- res.writeHead(404);
547
- return res.end('index.html not found');
548
- }
549
- try {
550
- let html = yield fs.readFile(indexHtml, 'utf8');
551
- // inject overlay runtime and HMR client if not already present
552
- if (!html.includes(RUNTIME_OVERLAY_ROUTE)) {
553
- html = html.replace('</body>', `\n<script type="module" src="${RUNTIME_OVERLAY_ROUTE}"></script>\n<script type="module">
499
+ // choose loader by extension
500
+ const ext = path.extname(found).toLowerCase();
501
+ const loader = ext === '.ts' ? 'ts' : ext === '.tsx' ? 'tsx' : ext === '.jsx' ? 'jsx' : 'js';
502
+ const result = await esbuild.transform(code, {
503
+ loader,
504
+ sourcemap: 'inline',
505
+ target: ['es2020'],
506
+ });
507
+ transformCache.set(found, result.code);
508
+ res.setHeader('Content-Type', jsContentType());
509
+ res.end(result.code);
510
+ }
511
+ catch (err) {
512
+ const e = err;
513
+ res.writeHead(500);
514
+ res.end(`// transform error: ${e.message}`);
515
+ }
516
+ }));
517
+ // --- Serve index.html with overlay + HMR client injection
518
+ app.use((async (req, res, next) => {
519
+ const url = req.url ?? '';
520
+ if (url !== '/' && url !== '/index.html')
521
+ return next();
522
+ if (!(await fs.pathExists(indexHtml))) {
523
+ res.writeHead(404);
524
+ return res.end('index.html not found');
525
+ }
526
+ try {
527
+ let html = await fs.readFile(indexHtml, 'utf8');
528
+ // inject overlay runtime and HMR client if not already present
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">
554
531
  const ws = new WebSocket("ws://" + location.host);
555
532
  ws.onmessage = (e) => {
556
533
  const msg = JSON.parse(e.data);
@@ -562,66 +539,64 @@ const overlayId = "__rc_error_overlay__";
562
539
  }
563
540
  };
564
541
  </script>\n</body>`);
565
- }
566
- res.setHeader('Content-Type', 'text/html; charset=utf-8');
567
- res.end(html);
568
- }
569
- catch (err) {
570
- res.writeHead(500);
571
- res.end(`// html read error: ${err.message}`);
572
- }
573
- })));
574
- // --- HMR WebSocket server
575
- const server = http.createServer(app);
576
- const broadcaster = new BroadcastManager(server);
577
- // Watch files and trigger plugin onHotUpdate + broadcast HMR message
578
- const watcher = chokidar.watch(path.join(appRoot, 'src'), { ignoreInitial: true });
579
- watcher.on('change', (file) => __awaiter(this, void 0, void 0, function* () {
580
- transformCache.delete(file);
581
- // plugin hook onHotUpdate optionally
582
- for (const p of plugins) {
583
- if (p.onHotUpdate) {
584
- try {
585
- yield p.onHotUpdate(file, {
586
- // plugin only needs broadcast in most cases
587
- broadcast: (msg) => {
588
- broadcaster.broadcast(msg);
589
- },
590
- });
591
- }
592
- catch (err) {
593
- console.warn('plugin onHotUpdate error:', err.message);
594
- }
595
- }
596
542
  }
597
- // default: broadcast update for changed file
598
- broadcaster.broadcast({
599
- type: 'update',
600
- path: '/' + path.relative(appRoot, file).replace(/\\/g, '/'),
601
- });
602
- }));
603
- // start server
604
- server.listen(port, () => __awaiter(this, void 0, void 0, function* () {
605
- var _a;
606
- const url = `http://localhost:${port}`;
607
- console.log(chalk.cyan.bold('\nšŸš€ React Client Dev Server'));
608
- console.log(chalk.green(`⚔ Running at: ${url}`));
609
- if (((_a = userConfig.server) === null || _a === void 0 ? void 0 : _a.open) !== false) {
543
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
544
+ res.end(html);
545
+ }
546
+ catch (err) {
547
+ res.writeHead(500);
548
+ res.end(`// html read error: ${err.message}`);
549
+ }
550
+ }));
551
+ // --- HMR WebSocket server
552
+ const server = http.createServer(app);
553
+ const broadcaster = new BroadcastManager(server);
554
+ // Watch files and trigger plugin onHotUpdate + broadcast HMR message
555
+ const watcher = chokidar.watch(path.join(appRoot, 'src'), { ignoreInitial: true });
556
+ watcher.on('change', async (file) => {
557
+ transformCache.delete(file);
558
+ // plugin hook onHotUpdate optionally
559
+ for (const p of plugins) {
560
+ if (p.onHotUpdate) {
610
561
  try {
611
- yield open(url);
562
+ await p.onHotUpdate(file, {
563
+ // plugin only needs broadcast in most cases
564
+ broadcast: (msg) => {
565
+ broadcaster.broadcast(msg);
566
+ },
567
+ });
612
568
  }
613
- catch (_b) {
614
- // ignore open errors
569
+ catch (err) {
570
+ console.warn('plugin onHotUpdate error:', err.message);
615
571
  }
616
572
  }
617
- }));
618
- // graceful shutdown
619
- process.on('SIGINT', () => __awaiter(this, void 0, void 0, function* () {
620
- console.log(chalk.red('\nšŸ›‘ Shutting down...'));
621
- yield watcher.close();
622
- broadcaster.close();
623
- server.close();
624
- process.exit(0);
625
- }));
573
+ }
574
+ // default: broadcast update for changed file
575
+ broadcaster.broadcast({
576
+ type: 'update',
577
+ path: '/' + path.relative(appRoot, file).replace(/\\/g, '/'),
578
+ });
579
+ });
580
+ // start server
581
+ server.listen(port, async () => {
582
+ const url = `http://localhost:${port}`;
583
+ console.log(chalk.cyan.bold('\nšŸš€ React Client Dev Server'));
584
+ console.log(chalk.green(`⚔ Running at: ${url}`));
585
+ if (userConfig.server?.open !== false) {
586
+ try {
587
+ await open(url);
588
+ }
589
+ catch {
590
+ // ignore open errors
591
+ }
592
+ }
593
+ });
594
+ // graceful shutdown
595
+ process.on('SIGINT', async () => {
596
+ console.log(chalk.red('\nšŸ›‘ Shutting down...'));
597
+ await watcher.close();
598
+ broadcaster.close();
599
+ server.close();
600
+ process.exit(0);
626
601
  });
627
602
  }