react-client 1.0.18 → 1.0.20

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.
@@ -17,15 +17,48 @@ const child_process_1 = require("child_process");
17
17
  const chalk_1 = __importDefault(require("chalk"));
18
18
  const loadConfig_1 = require("../../utils/loadConfig");
19
19
  const broadcastManager_1 = require("../../server/broadcastManager");
20
+ // 🧠 Browser polyfills for Node built-ins
21
+ const NODE_POLYFILLS = {
22
+ buffer: 'buffer/',
23
+ process: 'process/browser',
24
+ path: 'path-browserify',
25
+ fs: 'browserify-fs',
26
+ os: 'os-browserify/browser',
27
+ stream: 'stream-browserify',
28
+ util: 'util/',
29
+ url: 'url/',
30
+ assert: 'assert/',
31
+ crypto: 'crypto-browserify',
32
+ events: 'events/',
33
+ constants: 'constants-browserify',
34
+ querystring: 'querystring-es3',
35
+ zlib: 'browserify-zlib',
36
+ };
37
+ // List of NPM packages required for polyfills
38
+ const POLYFILL_PACKAGES = [
39
+ 'buffer',
40
+ 'process',
41
+ 'path-browserify',
42
+ 'browserify-fs',
43
+ 'os-browserify',
44
+ 'stream-browserify',
45
+ 'util',
46
+ 'url',
47
+ 'assert',
48
+ 'crypto-browserify',
49
+ 'events',
50
+ 'constants-browserify',
51
+ 'querystring-es3',
52
+ 'browserify-zlib',
53
+ ];
20
54
  async function dev() {
21
55
  const root = process.cwd();
22
56
  const userConfig = await (0, loadConfig_1.loadReactClientConfig)(root);
23
57
  const appRoot = path_1.default.resolve(root, userConfig.root || '.');
24
58
  const defaultPort = userConfig.server?.port || 5173;
25
59
  const cacheDir = path_1.default.join(appRoot, '.react-client', 'deps');
26
- const pkgFile = path_1.default.join(appRoot, 'package.json');
27
60
  await fs_extra_1.default.ensureDir(cacheDir);
28
- // Detect entry
61
+ // Detect entry file
29
62
  const possibleEntries = ['src/main.tsx', 'src/main.jsx'];
30
63
  const entry = possibleEntries.map((p) => path_1.default.join(appRoot, p)).find((p) => fs_extra_1.default.existsSync(p));
31
64
  if (!entry) {
@@ -33,7 +66,7 @@ async function dev() {
33
66
  process.exit(1);
34
67
  }
35
68
  const indexHtml = path_1.default.join(appRoot, 'index.html');
36
- // Detect port
69
+ // Detect available port
37
70
  const availablePort = await (0, detect_port_1.default)(defaultPort);
38
71
  const port = availablePort;
39
72
  if (availablePort !== defaultPort) {
@@ -48,7 +81,7 @@ async function dev() {
48
81
  process.exit(0);
49
82
  }
50
83
  }
51
- // Ensure react-refresh installed
84
+ // 🧩 Auto-install react-refresh
52
85
  try {
53
86
  require.resolve('react-refresh/runtime');
54
87
  }
@@ -60,7 +93,33 @@ async function dev() {
60
93
  });
61
94
  console.log(chalk_1.default.green('āœ… react-refresh installed successfully.'));
62
95
  }
63
- // Core plugin: CSS HMR
96
+ // 🧩 Auto-install missing polyfill packages
97
+ const missingPolyfills = POLYFILL_PACKAGES.filter((pkg) => {
98
+ try {
99
+ require.resolve(pkg, { paths: [appRoot] });
100
+ return false;
101
+ }
102
+ catch {
103
+ return true;
104
+ }
105
+ });
106
+ if (missingPolyfills.length > 0) {
107
+ console.log(chalk_1.default.yellow('āš™ļø Installing missing polyfill packages...'));
108
+ console.log(chalk_1.default.gray('šŸ“¦ ' + missingPolyfills.join(', ')));
109
+ try {
110
+ (0, child_process_1.execSync)(`npm install ${missingPolyfills.join(' ')} --no-audit --no-fund --silent`, {
111
+ cwd: appRoot,
112
+ stdio: 'inherit',
113
+ });
114
+ console.log(chalk_1.default.green('āœ… Polyfills installed successfully.'));
115
+ }
116
+ catch (err) {
117
+ console.error(chalk_1.default.red('āŒ Failed to install polyfills automatically.'));
118
+ console.error(err);
119
+ process.exit(1);
120
+ }
121
+ }
122
+ // --- Plugins
64
123
  const corePlugins = [
65
124
  {
66
125
  name: 'css-hmr',
@@ -81,102 +140,75 @@ async function dev() {
81
140
  ];
82
141
  const userPlugins = Array.isArray(userConfig.plugins) ? userConfig.plugins : [];
83
142
  const plugins = [...corePlugins, ...userPlugins];
84
- // Connect app
85
143
  const app = (0, connect_1.default)();
86
144
  const transformCache = new Map();
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) {
100
- if (!deps.length)
101
- return;
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.'));
106
- return;
145
+ // 🧱 Polyfilled module builder
146
+ async function buildModuleWithSafeWrapper(id) {
147
+ const cacheFile = path_1.default.join(cacheDir, id.replace(/\//g, '_') + '.js');
148
+ if (await fs_extra_1.default.pathExists(cacheFile))
149
+ return fs_extra_1.default.readFile(cacheFile, 'utf8');
150
+ // 🧠 Polyfill detection
151
+ const polyId = NODE_POLYFILLS[id];
152
+ if (polyId) {
153
+ console.log(chalk_1.default.gray(`🧩 Using polyfill for ${id}: ${polyId}`));
154
+ const result = await esbuild_1.default.build({
155
+ entryPoints: [require.resolve(polyId, { paths: [appRoot] })],
156
+ bundle: true,
157
+ platform: 'browser',
158
+ format: 'esm',
159
+ target: 'es2020',
160
+ write: false,
161
+ });
162
+ const polyCode = result.outputFiles[0].text;
163
+ await fs_extra_1.default.writeFile(cacheFile, polyCode, 'utf8');
164
+ return polyCode;
107
165
  }
108
- console.log(chalk_1.default.cyan('šŸ“¦ Prebundling:'), missing.join(', '));
109
- await Promise.all(missing.map(async (dep) => {
166
+ // 🧱 Normal dependency
167
+ let entryPath = null;
168
+ try {
169
+ entryPath = require.resolve(id, { paths: [appRoot] });
170
+ }
171
+ catch {
172
+ const base = id.split('/')[0];
110
173
  try {
111
- const entryPath = require.resolve(dep, { paths: [appRoot] });
112
- const outFile = path_1.default.join(cacheDir, dep + '.js');
113
- await esbuild_1.default.build({
114
- entryPoints: [entryPath],
115
- bundle: true,
116
- platform: 'browser',
117
- format: 'esm',
118
- outfile: outFile,
119
- write: true,
120
- target: 'es2020',
121
- });
122
- console.log(chalk_1.default.green(`āœ… Cached ${dep}`));
174
+ entryPath = require.resolve(base, { paths: [appRoot] });
123
175
  }
124
- catch (err) {
125
- const e = err;
126
- console.warn(chalk_1.default.yellow(`āš ļø Skipped ${dep}: ${e.message}`));
176
+ catch {
177
+ entryPath = null;
127
178
  }
128
- }));
179
+ }
180
+ if (!entryPath)
181
+ throw new Error(`Module ${id} not found (resolve failed)`);
182
+ const result = await esbuild_1.default.build({
183
+ entryPoints: [entryPath],
184
+ bundle: true,
185
+ platform: 'browser',
186
+ format: 'esm',
187
+ target: 'es2020',
188
+ write: false,
189
+ });
190
+ const originalCode = result.outputFiles[0].text;
191
+ const isSubpath = id.includes('/');
192
+ let finalCode = originalCode;
193
+ if (isSubpath) {
194
+ const base = id.split('/')[0];
195
+ finalCode += `
196
+ // --- react-client auto wrapper for subpath: ${id}
197
+ import * as __base from '/@modules/${base}';
198
+ export const __rc_dynamic = __base;
199
+ export default __base.default || __base;
200
+ `;
201
+ }
202
+ await fs_extra_1.default.writeFile(cacheFile, finalCode, 'utf8');
203
+ return finalCode;
129
204
  }
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
205
+ // --- /@modules/
140
206
  app.use('/@modules/', async (req, res, next) => {
141
207
  const id = req.url?.replace(/^\/(@modules\/)?/, '');
142
208
  if (!id)
143
209
  return next();
144
210
  try {
145
- const cacheFile = path_1.default.join(cacheDir, id.replace(/\//g, '_') + '.js');
146
- if (await fs_extra_1.default.pathExists(cacheFile)) {
147
- res.setHeader('Content-Type', 'application/javascript');
148
- return res.end(await fs_extra_1.default.readFile(cacheFile));
149
- }
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.`);
162
- const result = await esbuild_1.default.build({
163
- entryPoints: [entryPath],
164
- bundle: true,
165
- platform: 'browser',
166
- format: 'esm',
167
- target: 'es2020',
168
- write: false,
169
- });
170
- let code = result.outputFiles[0].text;
171
- // Fix for react-dom/client exports
172
- if (id === 'react-dom/client') {
173
- code += `
174
- import * as ReactDOMClient from '/@modules/react-dom';
175
- export const createRoot = ReactDOMClient.createRoot || ReactDOMClient.default?.createRoot;
176
- export default ReactDOMClient.default || ReactDOMClient;
177
- `;
178
- }
179
- await fs_extra_1.default.writeFile(cacheFile, code, 'utf8');
211
+ const code = await buildModuleWithSafeWrapper(id);
180
212
  res.setHeader('Content-Type', 'application/javascript');
181
213
  res.end(code);
182
214
  }
@@ -187,13 +219,13 @@ async function dev() {
187
219
  res.end(`// Failed to resolve module ${id}: ${e.message}`);
188
220
  }
189
221
  });
190
- // --- Serve src files + HMR
222
+ // --- Universal transform for all project files
191
223
  app.use(async (req, res, next) => {
192
- if (!req.url || (!req.url.startsWith('/src/') && !req.url.endsWith('.css')))
224
+ const urlPath = decodeURIComponent(req.url.split('?')[0]);
225
+ if (urlPath.includes('node_modules'))
193
226
  return next();
194
- const rawPath = decodeURIComponent(req.url.split('?')[0]);
195
- let filePath = path_1.default.join(appRoot, rawPath);
196
- const possibleExts = ['', '.tsx', '.ts', '.jsx', '.js'];
227
+ let filePath = path_1.default.join(appRoot, urlPath);
228
+ const possibleExts = ['', '.tsx', '.ts', '.jsx', '.js', '.css'];
197
229
  for (const ext of possibleExts) {
198
230
  if (await fs_extra_1.default.pathExists(filePath + ext)) {
199
231
  filePath += ext;
@@ -204,9 +236,10 @@ async function dev() {
204
236
  return next();
205
237
  try {
206
238
  let code = await fs_extra_1.default.readFile(filePath, 'utf8');
239
+ // Rewrite bare imports
207
240
  code = code
208
- .replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_match, dep) => `from "/@modules/${dep}"`)
209
- .replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (_match, dep) => `import("/@modules/${dep}")`);
241
+ .replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_m, dep) => `from "/@modules/${dep}"`)
242
+ .replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (_m, dep) => `import("/@modules/${dep}")`);
210
243
  for (const p of plugins)
211
244
  if (p.onTransform)
212
245
  code = await p.onTransform(code, filePath);
@@ -234,7 +267,7 @@ async function dev() {
234
267
  res.end(`// Error: ${e.message}`);
235
268
  }
236
269
  });
237
- // --- index.html + overlay
270
+ // --- index.html + overlay + HMR
238
271
  app.use(async (req, res, next) => {
239
272
  if (req.url !== '/' && req.url !== '/index.html')
240
273
  return next();
@@ -249,9 +282,9 @@ async function dev() {
249
282
  const style = document.createElement('style');
250
283
  style.textContent = \`
251
284
  .rc-overlay {
252
- position: fixed; top: 0; left: 0; width: 100%; height: 100%;
253
- background: rgba(0,0,0,0.9); color: #ff5555;
254
- font-family: monospace; padding: 2rem; overflow:auto; z-index: 999999;
285
+ position: fixed; inset: 0; background: rgba(0,0,0,0.9);
286
+ color: #ff5555; font-family: monospace;
287
+ padding: 2rem; overflow:auto; z-index: 999999;
255
288
  }
256
289
  \`;
257
290
  document.head.appendChild(style);
@@ -285,12 +318,13 @@ async function dev() {
285
318
  // --- WebSocket + HMR
286
319
  const server = http_1.default.createServer(app);
287
320
  const broadcaster = new broadcastManager_1.BroadcastManager(server);
288
- chokidar_1.default.watch(path_1.default.join(appRoot, 'src'), { ignoreInitial: true }).on('change', async (file) => {
321
+ chokidar_1.default.watch(appRoot, { ignoreInitial: true }).on('change', async (file) => {
322
+ if (file.includes('node_modules') || file.includes('.react-client'))
323
+ return;
289
324
  console.log(chalk_1.default.yellow(`šŸ”„ Changed: ${file}`));
290
325
  transformCache.delete(file);
291
- for (const p of plugins) {
326
+ for (const p of plugins)
292
327
  await p.onHotUpdate?.(file, { broadcast: (msg) => broadcaster.broadcast(msg) });
293
- }
294
328
  broadcaster.broadcast({
295
329
  type: 'update',
296
330
  path: '/' + path_1.default.relative(appRoot, file).replace(/\\/g, '/'),
@@ -301,6 +335,8 @@ async function dev() {
301
335
  console.log(chalk_1.default.cyan.bold('\nšŸš€ React Client Dev Server'));
302
336
  console.log(chalk_1.default.gray('──────────────────────────────'));
303
337
  console.log(chalk_1.default.green(`⚔ Running at: ${url}`));
338
+ if (port !== defaultPort)
339
+ console.log(chalk_1.default.yellow(`āš ļø Using alternate port (default ${defaultPort} occupied)`));
304
340
  await (0, open_1.default)(url, { newInstance: true });
305
341
  });
306
342
  process.on('SIGINT', () => {
@@ -16,10 +16,8 @@ class BroadcastManager {
16
16
  this.wss = new ws_1.WebSocketServer({ server });
17
17
  this.wss.on('connection', (ws) => {
18
18
  this.clients.add(ws);
19
- console.log(chalk_1.default.gray('šŸ”Œ Client connected'));
20
19
  ws.on('close', () => {
21
20
  this.clients.delete(ws);
22
- console.log(chalk_1.default.gray('āŽ Client disconnected'));
23
21
  });
24
22
  ws.on('error', (err) => {
25
23
  console.error(chalk_1.default.red('āš ļø WebSocket error:'), err.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-client",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
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",