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.
- package/dist/cli/commands/dev.js +88 -63
- package/package.json +1 -1
package/dist/cli/commands/dev.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
//
|
|
84
|
+
// Connect app
|
|
88
85
|
const app = (0, connect_1.default)();
|
|
89
86
|
const transformCache = new Map();
|
|
90
|
-
//
|
|
91
|
-
async function
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
121
|
-
console.warn(chalk_1.default.yellow(`⚠️ Skipped ${dep}: ${
|
|
125
|
+
const e = err;
|
|
126
|
+
console.warn(chalk_1.default.yellow(`⚠️ Skipped ${dep}: ${e.message}`));
|
|
122
127
|
}
|
|
123
|
-
}
|
|
128
|
+
}));
|
|
124
129
|
}
|
|
125
|
-
|
|
126
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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}: ${
|
|
187
|
+
res.end(`// Failed to resolve module ${id}: ${e.message}`);
|
|
162
188
|
}
|
|
163
189
|
});
|
|
164
|
-
// --- Serve
|
|
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
|
|
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
|
-
|
|
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
|
|
231
|
+
const e = err;
|
|
232
|
+
console.error(chalk_1.default.red(`⚠️ Transform failed: ${e.message}`));
|
|
205
233
|
res.writeHead(500);
|
|
206
|
-
res.end(`// Error: ${
|
|
234
|
+
res.end(`// Error: ${e.message}`);
|
|
207
235
|
}
|
|
208
236
|
});
|
|
209
|
-
// ---
|
|
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
|
|
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'));
|