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.
- package/dist/cli/commands/dev.js +135 -119
- package/package.json +1 -1
package/dist/cli/commands/dev.js
CHANGED
|
@@ -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
|
|
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,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
|
-
//
|
|
87
|
-
async function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
102
|
-
await analyzeGraph(resolved, seen);
|
|
174
|
+
entryPath = require.resolve(base, { paths: [appRoot] });
|
|
103
175
|
}
|
|
104
176
|
catch {
|
|
105
|
-
|
|
177
|
+
entryPath = null;
|
|
106
178
|
}
|
|
107
179
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
// ---
|
|
222
|
+
// --- Universal transform for all project files
|
|
210
223
|
app.use(async (req, res, next) => {
|
|
211
|
-
|
|
224
|
+
const urlPath = decodeURIComponent(req.url.split('?')[0]);
|
|
225
|
+
if (urlPath.includes('node_modules'))
|
|
212
226
|
return next();
|
|
213
|
-
|
|
214
|
-
|
|
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
|
|
239
|
+
// Rewrite bare imports
|
|
227
240
|
code = code
|
|
228
|
-
.replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (
|
|
229
|
-
.replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (
|
|
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
|
-
// ---
|
|
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;
|
|
273
|
-
|
|
274
|
-
|
|
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(
|
|
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', () => {
|