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.
- package/dist/cli/commands/dev.js +139 -103
- package/dist/server/broadcastManager.js +0 -2
- package/package.json +1 -1
package/dist/cli/commands/dev.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
88
|
-
async function
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
];
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
|
125
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
// ---
|
|
222
|
+
// --- Universal transform for all project files
|
|
191
223
|
app.use(async (req, res, next) => {
|
|
192
|
-
|
|
224
|
+
const urlPath = decodeURIComponent(req.url.split('?')[0]);
|
|
225
|
+
if (urlPath.includes('node_modules'))
|
|
193
226
|
return next();
|
|
194
|
-
|
|
195
|
-
|
|
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, (
|
|
209
|
-
.replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (
|
|
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;
|
|
253
|
-
|
|
254
|
-
|
|
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(
|
|
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);
|