react-client 1.0.19 → 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,13 +17,46 @@ 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
61
  // Detect entry file
29
62
  const possibleEntries = ['src/main.tsx', 'src/main.jsx'];
@@ -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 open 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,6 +93,32 @@ async function dev() {
60
93
  });
61
94
  console.log(chalk_1.default.green('āœ… react-refresh installed successfully.'));
62
95
  }
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
+ }
63
122
  // --- Plugins
64
123
  const corePlugins = [
65
124
  {
@@ -83,119 +142,73 @@ async function dev() {
83
142
  const plugins = [...corePlugins, ...userPlugins];
84
143
  const app = (0, connect_1.default)();
85
144
  const transformCache = new Map();
86
- // 🧠 Deep dependency graph analyzer
87
- async function analyzeGraph(file, seen = new Set()) {
88
- if (seen.has(file))
89
- return seen;
90
- seen.add(file);
91
- const code = await fs_extra_1.default.readFile(file, 'utf8');
92
- const matches = [
93
- ...code.matchAll(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g),
94
- ...code.matchAll(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g),
95
- ];
96
- for (const m of matches) {
97
- const dep = m[1];
98
- if (!dep || dep.startsWith('.') || dep.startsWith('/'))
99
- continue;
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;
165
+ }
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];
100
173
  try {
101
- const resolved = require.resolve(dep, { paths: [appRoot] });
102
- await analyzeGraph(resolved, seen);
174
+ entryPath = require.resolve(base, { paths: [appRoot] });
103
175
  }
104
176
  catch {
105
- seen.add(dep); // bare dependency
177
+ entryPath = null;
106
178
  }
107
179
  }
108
- return seen;
109
- }
110
- // šŸ“¦ Smart prebundle cache (parallelized)
111
- async function prebundleDeps(deps) {
112
- if (!deps.size)
113
- return;
114
- const cached = (await fs_extra_1.default.readdir(cacheDir)).map((f) => f.replace('.js', ''));
115
- const missing = [...deps].filter((d) => !cached.includes(d));
116
- if (!missing.length) {
117
- console.log(chalk_1.default.green('āœ… All dependencies already prebundled.'));
118
- return;
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
+ `;
119
201
  }
120
- console.log(chalk_1.default.cyan('šŸ“¦ Prebundling:'), missing.join(', '));
121
- await Promise.all(missing.map(async (dep) => {
122
- try {
123
- const entryPath = require.resolve(dep, { paths: [appRoot] });
124
- const outFile = path_1.default.join(cacheDir, dep + '.js');
125
- await esbuild_1.default.build({
126
- entryPoints: [entryPath],
127
- bundle: true,
128
- platform: 'browser',
129
- format: 'esm',
130
- outfile: outFile,
131
- write: true,
132
- target: 'es2020',
133
- });
134
- console.log(chalk_1.default.green(`āœ… Cached ${dep}`));
135
- }
136
- catch (err) {
137
- const e = err;
138
- console.warn(chalk_1.default.yellow(`āš ļø Skipped ${dep}: ${e.message}`));
139
- }
140
- }));
202
+ await fs_extra_1.default.writeFile(cacheFile, finalCode, 'utf8');
203
+ return finalCode;
141
204
  }
142
- // 🧩 Prewarm Graph
143
- const deps = await analyzeGraph(entry);
144
- await prebundleDeps(deps);
145
- // Auto rebuild on package.json change
146
- chokidar_1.default.watch(pkgFile).on('change', async () => {
147
- console.log(chalk_1.default.yellow('šŸ“¦ package.json changed — rebuilding prebundle cache...'));
148
- const newDeps = await analyzeGraph(entry);
149
- await prebundleDeps(newDeps);
150
- });
151
- // --- Serve /@modules/
205
+ // --- /@modules/
152
206
  app.use('/@modules/', async (req, res, next) => {
153
207
  const id = req.url?.replace(/^\/(@modules\/)?/, '');
154
208
  if (!id)
155
209
  return next();
156
210
  try {
157
- const cacheFile = path_1.default.join(cacheDir, id.replace(/\//g, '_') + '.js');
158
- if (await fs_extra_1.default.pathExists(cacheFile)) {
159
- res.setHeader('Content-Type', 'application/javascript');
160
- return res.end(await fs_extra_1.default.readFile(cacheFile));
161
- }
162
- let entryPath = null;
163
- try {
164
- entryPath = require.resolve(id, { paths: [appRoot] });
165
- }
166
- catch {
167
- if (id === 'react')
168
- entryPath = require.resolve('react');
169
- else if (id === 'react-dom' || id === 'react-dom/client')
170
- entryPath = require.resolve('react-dom');
171
- }
172
- if (!entryPath)
173
- throw new Error(`Module ${id} not found.`);
174
- const result = await esbuild_1.default.build({
175
- entryPoints: [entryPath],
176
- bundle: true,
177
- platform: 'browser',
178
- format: 'esm',
179
- target: 'es2020',
180
- write: false,
181
- });
182
- let code = result.outputFiles[0].text;
183
- // 🩹 Fixed react-dom/client shim (no duplicate export)
184
- if (id === 'react-dom/client') {
185
- code += `
186
- // React Client auto-shim for React 18+
187
- import * as ReactDOMClient from '/@modules/react-dom';
188
- if (!('createRoot' in ReactDOMClient) && ReactDOMClient.default) {
189
- Object.assign(ReactDOMClient, ReactDOMClient.default);
190
- }
191
- export const createRoot =
192
- ReactDOMClient.createRoot ||
193
- ReactDOMClient.default?.createRoot ||
194
- (() => { throw new Error('ReactDOM.createRoot not found'); });
195
- export default ReactDOMClient;
196
- `;
197
- }
198
- await fs_extra_1.default.writeFile(cacheFile, code, 'utf8');
211
+ const code = await buildModuleWithSafeWrapper(id);
199
212
  res.setHeader('Content-Type', 'application/javascript');
200
213
  res.end(code);
201
214
  }
@@ -206,13 +219,13 @@ async function dev() {
206
219
  res.end(`// Failed to resolve module ${id}: ${e.message}`);
207
220
  }
208
221
  });
209
- // --- Serve /src files dynamically
222
+ // --- Universal transform for all project files
210
223
  app.use(async (req, res, next) => {
211
- 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'))
212
226
  return next();
213
- const rawPath = decodeURIComponent(req.url.split('?')[0]);
214
- let filePath = path_1.default.join(appRoot, rawPath);
215
- const possibleExts = ['', '.tsx', '.ts', '.jsx', '.js'];
227
+ let filePath = path_1.default.join(appRoot, urlPath);
228
+ const possibleExts = ['', '.tsx', '.ts', '.jsx', '.js', '.css'];
216
229
  for (const ext of possibleExts) {
217
230
  if (await fs_extra_1.default.pathExists(filePath + ext)) {
218
231
  filePath += ext;
@@ -223,10 +236,10 @@ async function dev() {
223
236
  return next();
224
237
  try {
225
238
  let code = await fs_extra_1.default.readFile(filePath, 'utf8');
226
- // Rewrite bare imports → /@modules/*
239
+ // Rewrite bare imports
227
240
  code = code
228
- .replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_match, dep) => `from "/@modules/${dep}"`)
229
- .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}")`);
230
243
  for (const p of plugins)
231
244
  if (p.onTransform)
232
245
  code = await p.onTransform(code, filePath);
@@ -254,7 +267,7 @@ async function dev() {
254
267
  res.end(`// Error: ${e.message}`);
255
268
  }
256
269
  });
257
- // --- Serve index.html + overlay + HMR
270
+ // --- index.html + overlay + HMR
258
271
  app.use(async (req, res, next) => {
259
272
  if (req.url !== '/' && req.url !== '/index.html')
260
273
  return next();
@@ -269,9 +282,9 @@ async function dev() {
269
282
  const style = document.createElement('style');
270
283
  style.textContent = \`
271
284
  .rc-overlay {
272
- position: fixed; top: 0; left: 0; width: 100%; height: 100%;
273
- background: rgba(0,0,0,0.9); color: #ff5555;
274
- 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;
275
288
  }
276
289
  \`;
277
290
  document.head.appendChild(style);
@@ -305,12 +318,13 @@ async function dev() {
305
318
  // --- WebSocket + HMR
306
319
  const server = http_1.default.createServer(app);
307
320
  const broadcaster = new broadcastManager_1.BroadcastManager(server);
308
- 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;
309
324
  console.log(chalk_1.default.yellow(`šŸ”„ Changed: ${file}`));
310
325
  transformCache.delete(file);
311
- for (const p of plugins) {
326
+ for (const p of plugins)
312
327
  await p.onHotUpdate?.(file, { broadcast: (msg) => broadcaster.broadcast(msg) });
313
- }
314
328
  broadcaster.broadcast({
315
329
  type: 'update',
316
330
  path: '/' + path_1.default.relative(appRoot, file).replace(/\\/g, '/'),
@@ -321,6 +335,8 @@ async function dev() {
321
335
  console.log(chalk_1.default.cyan.bold('\nšŸš€ React Client Dev Server'));
322
336
  console.log(chalk_1.default.gray('──────────────────────────────'));
323
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)`));
324
340
  await (0, open_1.default)(url, { newInstance: true });
325
341
  });
326
342
  process.on('SIGINT', () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-client",
3
- "version": "1.0.19",
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",