react-client 1.0.16 → 1.0.18

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.
@@ -23,8 +23,9 @@ async function dev() {
23
23
  const appRoot = path_1.default.resolve(root, userConfig.root || '.');
24
24
  const defaultPort = userConfig.server?.port || 5173;
25
25
  const cacheDir = path_1.default.join(appRoot, '.react-client', 'deps');
26
+ const pkgFile = path_1.default.join(appRoot, 'package.json');
26
27
  await fs_extra_1.default.ensureDir(cacheDir);
27
- // Detect entry dynamically (main.tsx or main.jsx)
28
+ // Detect entry
28
29
  const possibleEntries = ['src/main.tsx', 'src/main.jsx'];
29
30
  const entry = possibleEntries.map((p) => path_1.default.join(appRoot, p)).find((p) => fs_extra_1.default.existsSync(p));
30
31
  if (!entry) {
@@ -32,7 +33,7 @@ async function dev() {
32
33
  process.exit(1);
33
34
  }
34
35
  const indexHtml = path_1.default.join(appRoot, 'index.html');
35
- // Detect open port
36
+ // Detect port
36
37
  const availablePort = await (0, detect_port_1.default)(defaultPort);
37
38
  const port = availablePort;
38
39
  if (availablePort !== defaultPort) {
@@ -47,23 +48,19 @@ async function dev() {
47
48
  process.exit(0);
48
49
  }
49
50
  }
50
- // React-refresh runtime auto install
51
- function safeResolveReactRefresh() {
52
- try {
53
- return require.resolve('react-refresh/runtime');
54
- }
55
- catch {
56
- console.warn(chalk_1.default.yellow('⚠️ react-refresh not found installing...'));
57
- (0, child_process_1.execSync)('npm install react-refresh --no-audit --no-fund --silent', {
58
- cwd: root,
59
- stdio: 'inherit',
60
- });
61
- return require.resolve('react-refresh/runtime');
62
- }
51
+ // Ensure react-refresh installed
52
+ try {
53
+ require.resolve('react-refresh/runtime');
54
+ }
55
+ catch {
56
+ console.warn(chalk_1.default.yellow('⚠️ react-refresh not found — installing...'));
57
+ (0, child_process_1.execSync)('npm install react-refresh --no-audit --no-fund --silent', {
58
+ cwd: root,
59
+ stdio: 'inherit',
60
+ });
61
+ console.log(chalk_1.default.green('✅ react-refresh installed successfully.'));
63
62
  }
64
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
65
- const _reactRefreshRuntime = safeResolveReactRefresh();
66
- // --- Plugins (core + user)
63
+ // Core plugin: CSS HMR
67
64
  const corePlugins = [
68
65
  {
69
66
  name: 'css-hmr',
@@ -71,12 +68,12 @@ async function dev() {
71
68
  if (id.endsWith('.css')) {
72
69
  const escaped = JSON.stringify(code);
73
70
  return `
74
- const css = ${escaped};
75
- const style = document.createElement('style');
76
- style.textContent = css;
77
- document.head.appendChild(style);
78
- import.meta.hot && import.meta.hot.accept();
79
- `;
71
+ const css = ${escaped};
72
+ const style = document.createElement('style');
73
+ style.textContent = css;
74
+ document.head.appendChild(style);
75
+ import.meta.hot && import.meta.hot.accept();
76
+ `;
80
77
  }
81
78
  return code;
82
79
  },
@@ -84,24 +81,32 @@ async function dev() {
84
81
  ];
85
82
  const userPlugins = Array.isArray(userConfig.plugins) ? userConfig.plugins : [];
86
83
  const plugins = [...corePlugins, ...userPlugins];
87
- // 🧱 Connect app
84
+ // Connect app
88
85
  const app = (0, connect_1.default)();
89
86
  const transformCache = new Map();
90
- // --- Prebundle persistent deps
91
- async function prebundleDeps() {
92
- const pkgFile = path_1.default.join(appRoot, 'package.json');
93
- if (!fs_extra_1.default.existsSync(pkgFile))
94
- return;
95
- const pkg = JSON.parse(await fs_extra_1.default.readFile(pkgFile, 'utf8'));
96
- const deps = Object.keys(pkg.dependencies || {});
87
+ // 🧠 Prewarm: analyze imports before first run
88
+ async function analyzeImports(entryFile) {
89
+ const entryCode = await fs_extra_1.default.readFile(entryFile, 'utf8');
90
+ const importMatches = [
91
+ ...entryCode.matchAll(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g),
92
+ ...entryCode.matchAll(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g),
93
+ ];
94
+ const deps = [...new Set(importMatches.map((m) => m[1]))];
95
+ console.log(chalk_1.default.gray(`🧩 Found ${deps.length} direct imports:`), deps.join(', '));
96
+ return deps;
97
+ }
98
+ // 📦 Smart prebundle cache
99
+ async function prebundleDeps(deps) {
97
100
  if (!deps.length)
98
101
  return;
99
- const cached = await fs_extra_1.default.readdir(cacheDir);
100
- const missing = deps.filter((d) => !cached.includes(d + '.js'));
101
- if (!missing.length)
102
+ const cached = (await fs_extra_1.default.readdir(cacheDir)).map((f) => f.replace('.js', ''));
103
+ const missing = deps.filter((d) => !cached.includes(d));
104
+ if (!missing.length) {
105
+ console.log(chalk_1.default.green('✅ All dependencies already prebundled.'));
102
106
  return;
107
+ }
103
108
  console.log(chalk_1.default.cyan('📦 Prebundling:'), missing.join(', '));
104
- for (const dep of missing) {
109
+ await Promise.all(missing.map(async (dep) => {
105
110
  try {
106
111
  const entryPath = require.resolve(dep, { paths: [appRoot] });
107
112
  const outFile = path_1.default.join(cacheDir, dep + '.js');
@@ -117,15 +122,23 @@ async function dev() {
117
122
  console.log(chalk_1.default.green(`✅ Cached ${dep}`));
118
123
  }
119
124
  catch (err) {
120
- const msg = err instanceof Error ? err.message : String(err);
121
- console.warn(chalk_1.default.yellow(`⚠️ Skipped ${dep}: ${msg}`));
125
+ const e = err;
126
+ console.warn(chalk_1.default.yellow(`⚠️ Skipped ${dep}: ${e.message}`));
122
127
  }
123
- }
128
+ }));
124
129
  }
125
- await prebundleDeps();
126
- // --- Serve prebundled modules
130
+ // 🧩 Smart rebuild trigger when package.json changes
131
+ chokidar_1.default.watch(pkgFile).on('change', async () => {
132
+ console.log(chalk_1.default.yellow('📦 Detected package.json change — rebuilding prebundles...'));
133
+ const deps = await analyzeImports(entry);
134
+ await prebundleDeps(deps);
135
+ });
136
+ // Initial prewarm
137
+ const initialDeps = await analyzeImports(entry);
138
+ await prebundleDeps(initialDeps);
139
+ // --- Serve prebundled / node_modules
127
140
  app.use('/@modules/', async (req, res, next) => {
128
- const id = req.url?.replace(/^\/@modules\//, '');
141
+ const id = req.url?.replace(/^\/(@modules\/)?/, '');
129
142
  if (!id)
130
143
  return next();
131
144
  try {
@@ -134,7 +147,18 @@ async function dev() {
134
147
  res.setHeader('Content-Type', 'application/javascript');
135
148
  return res.end(await fs_extra_1.default.readFile(cacheFile));
136
149
  }
137
- const entryPath = require.resolve(id, { paths: [appRoot] });
150
+ let entryPath = null;
151
+ try {
152
+ entryPath = require.resolve(id, { paths: [path_1.default.join(appRoot, 'node_modules')] });
153
+ }
154
+ catch {
155
+ if (id === 'react')
156
+ entryPath = require.resolve('react');
157
+ else if (id === 'react-dom' || id === 'react-dom/client')
158
+ entryPath = require.resolve('react-dom');
159
+ }
160
+ if (!entryPath)
161
+ throw new Error(`Module ${id} not found.`);
138
162
  const result = await esbuild_1.default.build({
139
163
  entryPoints: [entryPath],
140
164
  bundle: true,
@@ -144,6 +168,7 @@ async function dev() {
144
168
  write: false,
145
169
  });
146
170
  let code = result.outputFiles[0].text;
171
+ // Fix for react-dom/client exports
147
172
  if (id === 'react-dom/client') {
148
173
  code += `
149
174
  import * as ReactDOMClient from '/@modules/react-dom';
@@ -156,33 +181,35 @@ async function dev() {
156
181
  res.end(code);
157
182
  }
158
183
  catch (err) {
159
- const msg = err instanceof Error ? err.message : String(err);
184
+ const e = err;
185
+ console.error(chalk_1.default.red(`❌ Failed to load module ${id}: ${e.message}`));
160
186
  res.writeHead(500);
161
- res.end(`// Failed to resolve module ${id}: ${msg}`);
187
+ res.end(`// Failed to resolve module ${id}: ${e.message}`);
162
188
  }
163
189
  });
164
- // --- Serve /src files dynamically
190
+ // --- Serve src files + HMR
165
191
  app.use(async (req, res, next) => {
166
192
  if (!req.url || (!req.url.startsWith('/src/') && !req.url.endsWith('.css')))
167
193
  return next();
168
- const filePath = path_1.default.join(appRoot, decodeURIComponent(req.url.split('?')[0]));
194
+ const rawPath = decodeURIComponent(req.url.split('?')[0]);
195
+ let filePath = path_1.default.join(appRoot, rawPath);
196
+ const possibleExts = ['', '.tsx', '.ts', '.jsx', '.js'];
197
+ for (const ext of possibleExts) {
198
+ if (await fs_extra_1.default.pathExists(filePath + ext)) {
199
+ filePath += ext;
200
+ break;
201
+ }
202
+ }
169
203
  if (!(await fs_extra_1.default.pathExists(filePath)))
170
204
  return next();
171
205
  try {
172
- if (transformCache.has(filePath)) {
173
- res.setHeader('Content-Type', 'application/javascript');
174
- return res.end(transformCache.get(filePath));
175
- }
176
206
  let code = await fs_extra_1.default.readFile(filePath, 'utf8');
177
- // 🧩 Rewrite bare imports (react, react-dom, etc.) to /@modules/*
178
207
  code = code
179
208
  .replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_match, dep) => `from "/@modules/${dep}"`)
180
209
  .replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (_match, dep) => `import("/@modules/${dep}")`);
181
- // Run plugin transforms
182
- for (const p of plugins) {
210
+ for (const p of plugins)
183
211
  if (p.onTransform)
184
212
  code = await p.onTransform(code, filePath);
185
- }
186
213
  const ext = path_1.default.extname(filePath);
187
214
  let loader = 'js';
188
215
  if (ext === '.ts')
@@ -201,12 +228,13 @@ async function dev() {
201
228
  res.end(result.code);
202
229
  }
203
230
  catch (err) {
204
- const msg = err instanceof Error ? err.message : String(err);
231
+ const e = err;
232
+ console.error(chalk_1.default.red(`⚠️ Transform failed: ${e.message}`));
205
233
  res.writeHead(500);
206
- res.end(`// Error: ${msg}`);
234
+ res.end(`// Error: ${e.message}`);
207
235
  }
208
236
  });
209
- // --- Serve index.html with overlay + HMR client
237
+ // --- index.html + overlay
210
238
  app.use(async (req, res, next) => {
211
239
  if (req.url !== '/' && req.url !== '/index.html')
212
240
  return next();
@@ -254,23 +282,20 @@ async function dev() {
254
282
  res.setHeader('Content-Type', 'text/html');
255
283
  res.end(html);
256
284
  });
257
- // --- WebSocket + HMR via BroadcastManager
285
+ // --- WebSocket + HMR
258
286
  const server = http_1.default.createServer(app);
259
287
  const broadcaster = new broadcastManager_1.BroadcastManager(server);
260
288
  chokidar_1.default.watch(path_1.default.join(appRoot, 'src'), { ignoreInitial: true }).on('change', async (file) => {
261
289
  console.log(chalk_1.default.yellow(`🔄 Changed: ${file}`));
262
290
  transformCache.delete(file);
263
291
  for (const p of plugins) {
264
- p.onHotUpdate?.(file, {
265
- broadcast: (msg) => broadcaster.broadcast(msg),
266
- });
292
+ await p.onHotUpdate?.(file, { broadcast: (msg) => broadcaster.broadcast(msg) });
267
293
  }
268
294
  broadcaster.broadcast({
269
295
  type: 'update',
270
296
  path: '/' + path_1.default.relative(appRoot, file).replace(/\\/g, '/'),
271
297
  });
272
298
  });
273
- // 🚀 Launch
274
299
  server.listen(port, async () => {
275
300
  const url = `http://localhost:${port}`;
276
301
  console.log(chalk_1.default.cyan.bold('\n🚀 React Client Dev Server'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-client",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
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",