react-client 1.0.17 ā 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 +57 -56
- 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
|
-
//
|
|
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
|
-
//
|
|
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,22 +48,19 @@ async function dev() {
|
|
|
47
48
|
process.exit(0);
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return 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
|
-
return require.resolve('react-refresh/runtime');
|
|
62
|
-
}
|
|
51
|
+
// Ensure react-refresh installed
|
|
52
|
+
try {
|
|
53
|
+
require.resolve('react-refresh/runtime');
|
|
63
54
|
}
|
|
64
|
-
|
|
65
|
-
|
|
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.'));
|
|
62
|
+
}
|
|
63
|
+
// Core plugin: CSS HMR
|
|
66
64
|
const corePlugins = [
|
|
67
65
|
{
|
|
68
66
|
name: 'css-hmr',
|
|
@@ -83,24 +81,32 @@ async function dev() {
|
|
|
83
81
|
];
|
|
84
82
|
const userPlugins = Array.isArray(userConfig.plugins) ? userConfig.plugins : [];
|
|
85
83
|
const plugins = [...corePlugins, ...userPlugins];
|
|
86
|
-
//
|
|
84
|
+
// Connect app
|
|
87
85
|
const app = (0, connect_1.default)();
|
|
88
86
|
const transformCache = new Map();
|
|
89
|
-
//
|
|
90
|
-
async function
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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) {
|
|
96
100
|
if (!deps.length)
|
|
97
101
|
return;
|
|
98
|
-
const cached = await fs_extra_1.default.readdir(cacheDir);
|
|
99
|
-
const missing = deps.filter((d) => !cached.includes(d
|
|
100
|
-
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.'));
|
|
101
106
|
return;
|
|
107
|
+
}
|
|
102
108
|
console.log(chalk_1.default.cyan('š¦ Prebundling:'), missing.join(', '));
|
|
103
|
-
|
|
109
|
+
await Promise.all(missing.map(async (dep) => {
|
|
104
110
|
try {
|
|
105
111
|
const entryPath = require.resolve(dep, { paths: [appRoot] });
|
|
106
112
|
const outFile = path_1.default.join(cacheDir, dep + '.js');
|
|
@@ -119,12 +125,20 @@ async function dev() {
|
|
|
119
125
|
const e = err;
|
|
120
126
|
console.warn(chalk_1.default.yellow(`ā ļø Skipped ${dep}: ${e.message}`));
|
|
121
127
|
}
|
|
122
|
-
}
|
|
128
|
+
}));
|
|
123
129
|
}
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
126
140
|
app.use('/@modules/', async (req, res, next) => {
|
|
127
|
-
const id = req.url?.replace(
|
|
141
|
+
const id = req.url?.replace(/^\/(@modules\/)?/, '');
|
|
128
142
|
if (!id)
|
|
129
143
|
return next();
|
|
130
144
|
try {
|
|
@@ -138,18 +152,13 @@ async function dev() {
|
|
|
138
152
|
entryPath = require.resolve(id, { paths: [path_1.default.join(appRoot, 'node_modules')] });
|
|
139
153
|
}
|
|
140
154
|
catch {
|
|
141
|
-
|
|
142
|
-
entryPath = require.resolve(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (id === 'react')
|
|
146
|
-
entryPath = require.resolve('react');
|
|
147
|
-
else if (id === 'react-dom' || id === 'react-dom/client')
|
|
148
|
-
entryPath = require.resolve('react-dom');
|
|
149
|
-
}
|
|
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');
|
|
150
159
|
}
|
|
151
160
|
if (!entryPath)
|
|
152
|
-
throw new Error(`
|
|
161
|
+
throw new Error(`Module ${id} not found.`);
|
|
153
162
|
const result = await esbuild_1.default.build({
|
|
154
163
|
entryPoints: [entryPath],
|
|
155
164
|
bundle: true,
|
|
@@ -159,7 +168,7 @@ async function dev() {
|
|
|
159
168
|
write: false,
|
|
160
169
|
});
|
|
161
170
|
let code = result.outputFiles[0].text;
|
|
162
|
-
//
|
|
171
|
+
// Fix for react-dom/client exports
|
|
163
172
|
if (id === 'react-dom/client') {
|
|
164
173
|
code += `
|
|
165
174
|
import * as ReactDOMClient from '/@modules/react-dom';
|
|
@@ -178,7 +187,7 @@ async function dev() {
|
|
|
178
187
|
res.end(`// Failed to resolve module ${id}: ${e.message}`);
|
|
179
188
|
}
|
|
180
189
|
});
|
|
181
|
-
// --- Serve
|
|
190
|
+
// --- Serve src files + HMR
|
|
182
191
|
app.use(async (req, res, next) => {
|
|
183
192
|
if (!req.url || (!req.url.startsWith('/src/') && !req.url.endsWith('.css')))
|
|
184
193
|
return next();
|
|
@@ -194,12 +203,7 @@ async function dev() {
|
|
|
194
203
|
if (!(await fs_extra_1.default.pathExists(filePath)))
|
|
195
204
|
return next();
|
|
196
205
|
try {
|
|
197
|
-
if (transformCache.has(filePath)) {
|
|
198
|
-
res.setHeader('Content-Type', 'application/javascript');
|
|
199
|
-
return res.end(transformCache.get(filePath));
|
|
200
|
-
}
|
|
201
206
|
let code = await fs_extra_1.default.readFile(filePath, 'utf8');
|
|
202
|
-
// Rewrite bare imports ā /@modules/*
|
|
203
207
|
code = code
|
|
204
208
|
.replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_match, dep) => `from "/@modules/${dep}"`)
|
|
205
209
|
.replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (_match, dep) => `import("/@modules/${dep}")`);
|
|
@@ -230,7 +234,7 @@ async function dev() {
|
|
|
230
234
|
res.end(`// Error: ${e.message}`);
|
|
231
235
|
}
|
|
232
236
|
});
|
|
233
|
-
// ---
|
|
237
|
+
// --- index.html + overlay
|
|
234
238
|
app.use(async (req, res, next) => {
|
|
235
239
|
if (req.url !== '/' && req.url !== '/index.html')
|
|
236
240
|
return next();
|
|
@@ -278,23 +282,20 @@ async function dev() {
|
|
|
278
282
|
res.setHeader('Content-Type', 'text/html');
|
|
279
283
|
res.end(html);
|
|
280
284
|
});
|
|
281
|
-
// --- WebSocket + HMR
|
|
285
|
+
// --- WebSocket + HMR
|
|
282
286
|
const server = http_1.default.createServer(app);
|
|
283
287
|
const broadcaster = new broadcastManager_1.BroadcastManager(server);
|
|
284
288
|
chokidar_1.default.watch(path_1.default.join(appRoot, 'src'), { ignoreInitial: true }).on('change', async (file) => {
|
|
285
289
|
console.log(chalk_1.default.yellow(`š Changed: ${file}`));
|
|
286
290
|
transformCache.delete(file);
|
|
287
291
|
for (const p of plugins) {
|
|
288
|
-
await p.onHotUpdate?.(file, {
|
|
289
|
-
broadcast: (msg) => broadcaster.broadcast(msg),
|
|
290
|
-
});
|
|
292
|
+
await p.onHotUpdate?.(file, { broadcast: (msg) => broadcaster.broadcast(msg) });
|
|
291
293
|
}
|
|
292
294
|
broadcaster.broadcast({
|
|
293
295
|
type: 'update',
|
|
294
296
|
path: '/' + path_1.default.relative(appRoot, file).replace(/\\/g, '/'),
|
|
295
297
|
});
|
|
296
298
|
});
|
|
297
|
-
// š Start server
|
|
298
299
|
server.listen(port, async () => {
|
|
299
300
|
const url = `http://localhost:${port}`;
|
|
300
301
|
console.log(chalk_1.default.cyan.bold('\nš React Client Dev Server'));
|