react-client 1.0.22 ā 1.0.23
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 -140
- package/package.json +1 -1
package/dist/cli/commands/dev.js
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* š React Client Dev Server ā Final Version
|
|
4
|
-
* ------------------------------------------
|
|
5
|
-
* ā
Local overlay-runtime.js (Prism + stack mapping)
|
|
6
|
-
* ā
Dynamic /@runtime/overlay-runtime.js alias
|
|
7
|
-
* ā
Automatic HTML injection for overlay + HMR
|
|
8
|
-
* ā
Prebundle cache (.react-client/deps)
|
|
9
|
-
* ā
CSS HMR, relative & bare import handling
|
|
10
|
-
* ā
Favicon & public assets serving
|
|
11
|
-
*/
|
|
12
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
4
|
};
|
|
@@ -25,30 +15,8 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
|
25
15
|
const open_1 = __importDefault(require("open"));
|
|
26
16
|
const child_process_1 = require("child_process");
|
|
27
17
|
const chalk_1 = __importDefault(require("chalk"));
|
|
28
|
-
const zlib_1 = __importDefault(require("zlib"));
|
|
29
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
30
18
|
const loadConfig_1 = require("../../utils/loadConfig");
|
|
31
19
|
const broadcastManager_1 = require("../../server/broadcastManager");
|
|
32
|
-
const computeHash = (content) => crypto_1.default.createHash('sha1').update(content).digest('hex');
|
|
33
|
-
const getMimeType = (file) => {
|
|
34
|
-
const ext = path_1.default.extname(file).toLowerCase();
|
|
35
|
-
const mime = {
|
|
36
|
-
'.ico': 'image/x-icon',
|
|
37
|
-
'.png': 'image/png',
|
|
38
|
-
'.jpg': 'image/jpeg',
|
|
39
|
-
'.jpeg': 'image/jpeg',
|
|
40
|
-
'.gif': 'image/gif',
|
|
41
|
-
'.svg': 'image/svg+xml',
|
|
42
|
-
'.webp': 'image/webp',
|
|
43
|
-
'.json': 'application/json',
|
|
44
|
-
'.txt': 'text/plain',
|
|
45
|
-
'.js': 'application/javascript',
|
|
46
|
-
'.mjs': 'application/javascript',
|
|
47
|
-
'.css': 'text/css',
|
|
48
|
-
'.html': 'text/html',
|
|
49
|
-
};
|
|
50
|
-
return mime[ext] || 'application/octet-stream';
|
|
51
|
-
};
|
|
52
20
|
async function dev() {
|
|
53
21
|
const root = process.cwd();
|
|
54
22
|
const userConfig = await (0, loadConfig_1.loadReactClientConfig)(root);
|
|
@@ -56,36 +24,43 @@ async function dev() {
|
|
|
56
24
|
const defaultPort = userConfig.server?.port || 5173;
|
|
57
25
|
const cacheDir = path_1.default.join(appRoot, '.react-client', 'deps');
|
|
58
26
|
const pkgFile = path_1.default.join(appRoot, 'package.json');
|
|
59
|
-
const indexHtml = path_1.default.join(appRoot, 'index.html');
|
|
60
27
|
await fs_extra_1.default.ensureDir(cacheDir);
|
|
61
|
-
// ā
Detect entry
|
|
28
|
+
// ā
Detect entry (main.tsx or main.jsx)
|
|
62
29
|
const possibleEntries = ['src/main.tsx', 'src/main.jsx'];
|
|
63
30
|
const entry = possibleEntries.map((p) => path_1.default.join(appRoot, p)).find((p) => fs_extra_1.default.existsSync(p));
|
|
64
31
|
if (!entry) {
|
|
65
32
|
console.error(chalk_1.default.red('ā No entry found: src/main.tsx or src/main.jsx'));
|
|
66
33
|
process.exit(1);
|
|
67
34
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
35
|
+
const indexHtml = path_1.default.join(appRoot, 'index.html');
|
|
36
|
+
// ā
Detect open port
|
|
37
|
+
const availablePort = await (0, detect_port_1.default)(defaultPort);
|
|
38
|
+
const port = availablePort;
|
|
39
|
+
if (availablePort !== defaultPort) {
|
|
71
40
|
const res = await (0, prompts_1.default)({
|
|
72
41
|
type: 'confirm',
|
|
73
42
|
name: 'useNewPort',
|
|
74
|
-
message: `Port ${defaultPort} is occupied. Use ${
|
|
43
|
+
message: `Port ${defaultPort} is occupied. Use ${availablePort} instead?`,
|
|
75
44
|
initial: true,
|
|
76
45
|
});
|
|
77
|
-
if (!res.useNewPort)
|
|
46
|
+
if (!res.useNewPort) {
|
|
47
|
+
console.log('š Dev server cancelled.');
|
|
78
48
|
process.exit(0);
|
|
49
|
+
}
|
|
79
50
|
}
|
|
80
|
-
// ā
Ensure react-refresh
|
|
51
|
+
// ā
Ensure react-refresh installed
|
|
81
52
|
try {
|
|
82
53
|
require.resolve('react-refresh/runtime');
|
|
83
54
|
}
|
|
84
55
|
catch {
|
|
85
|
-
console.
|
|
86
|
-
(0, child_process_1.execSync)('npm
|
|
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.'));
|
|
87
62
|
}
|
|
88
|
-
// ā
Core +
|
|
63
|
+
// ā
Core + User Plugins
|
|
89
64
|
const corePlugins = [
|
|
90
65
|
{
|
|
91
66
|
name: 'css-hmr',
|
|
@@ -107,26 +82,43 @@ async function dev() {
|
|
|
107
82
|
const userPlugins = Array.isArray(userConfig.plugins) ? userConfig.plugins : [];
|
|
108
83
|
const plugins = [...corePlugins, ...userPlugins];
|
|
109
84
|
const app = (0, connect_1.default)();
|
|
110
|
-
const server = http_1.default.createServer(app);
|
|
111
|
-
const broadcaster = new broadcastManager_1.BroadcastManager(server);
|
|
112
85
|
const transformCache = new Map();
|
|
113
|
-
//
|
|
114
|
-
async function
|
|
115
|
-
if (
|
|
116
|
-
return;
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
86
|
+
// ā
Analyze dependency graph recursively
|
|
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;
|
|
100
|
+
try {
|
|
101
|
+
const resolved = require.resolve(dep, { paths: [appRoot] });
|
|
102
|
+
await analyzeGraph(resolved, seen);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
seen.add(dep);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return seen;
|
|
109
|
+
}
|
|
110
|
+
// ā
Smart prebundling cache
|
|
111
|
+
async function prebundleDeps(deps) {
|
|
112
|
+
if (!deps.size)
|
|
120
113
|
return;
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
prevHash = (await fs_extra_1.default.readJSON(metaFile)).hash;
|
|
126
|
-
if (prevHash === hash)
|
|
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.'));
|
|
127
118
|
return;
|
|
128
|
-
|
|
129
|
-
|
|
119
|
+
}
|
|
120
|
+
console.log(chalk_1.default.cyan('š¦ Prebundling:'), missing.join(', '));
|
|
121
|
+
await Promise.all(missing.map(async (dep) => {
|
|
130
122
|
try {
|
|
131
123
|
const entryPath = require.resolve(dep, { paths: [appRoot] });
|
|
132
124
|
const outFile = path_1.default.join(cacheDir, dep + '.js');
|
|
@@ -135,53 +127,28 @@ async function dev() {
|
|
|
135
127
|
bundle: true,
|
|
136
128
|
platform: 'browser',
|
|
137
129
|
format: 'esm',
|
|
138
|
-
target: 'es2020',
|
|
139
130
|
outfile: outFile,
|
|
140
131
|
write: true,
|
|
132
|
+
target: 'es2020',
|
|
141
133
|
});
|
|
142
|
-
const content = await fs_extra_1.default.readFile(outFile);
|
|
143
|
-
await fs_extra_1.default.writeFile(outFile + '.gz', zlib_1.default.gzipSync(content));
|
|
144
|
-
await fs_extra_1.default.writeFile(outFile + '.br', zlib_1.default.brotliCompressSync(content));
|
|
145
134
|
console.log(chalk_1.default.green(`ā
Cached ${dep}`));
|
|
146
135
|
}
|
|
147
|
-
catch (
|
|
148
|
-
const
|
|
149
|
-
console.warn(chalk_1.default.yellow(`ā ļø Skipped ${dep}: ${
|
|
136
|
+
catch (err) {
|
|
137
|
+
const e = err;
|
|
138
|
+
console.warn(chalk_1.default.yellow(`ā ļø Skipped ${dep}: ${e.message}`));
|
|
150
139
|
}
|
|
151
140
|
}));
|
|
152
|
-
await fs_extra_1.default.writeJSON(metaFile, { hash });
|
|
153
141
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
return res.end(`// Overlay runtime not found: ${overlayPath}`);
|
|
163
|
-
}
|
|
164
|
-
let code = await fs_extra_1.default.readFile(overlayPath, 'utf8');
|
|
165
|
-
// Transform bare imports ā /@modules/*
|
|
166
|
-
code = code
|
|
167
|
-
.replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_m, dep) => `from "/@modules/${dep}"`)
|
|
168
|
-
.replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (_m, dep) => `import("/@modules/${dep}")`);
|
|
169
|
-
const result = await esbuild_1.default.transform(code, {
|
|
170
|
-
loader: 'js',
|
|
171
|
-
sourcemap: 'inline',
|
|
172
|
-
target: 'es2020',
|
|
173
|
-
});
|
|
174
|
-
res.setHeader('Content-Type', 'application/javascript');
|
|
175
|
-
res.end(result.code);
|
|
176
|
-
}
|
|
177
|
-
catch (err) {
|
|
178
|
-
const e = err;
|
|
179
|
-
console.error(chalk_1.default.red(`ā Failed to load overlay runtime: ${e.message}`));
|
|
180
|
-
res.writeHead(500);
|
|
181
|
-
res.end(`// Failed to load overlay runtime: ${e.message}`);
|
|
182
|
-
}
|
|
142
|
+
// ā
Initial dependency prebundle
|
|
143
|
+
const deps = await analyzeGraph(entry);
|
|
144
|
+
await prebundleDeps(deps);
|
|
145
|
+
// ā
Auto re-prebundle when package.json changes
|
|
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);
|
|
183
150
|
});
|
|
184
|
-
//
|
|
151
|
+
// ā
Serve /@modules/
|
|
185
152
|
app.use('/@modules/', async (req, res, next) => {
|
|
186
153
|
const id = req.url?.replace(/^\/(@modules\/)?/, '');
|
|
187
154
|
if (!id)
|
|
@@ -206,42 +173,38 @@ async function dev() {
|
|
|
206
173
|
res.setHeader('Content-Type', 'application/javascript');
|
|
207
174
|
res.end(code);
|
|
208
175
|
}
|
|
209
|
-
catch (
|
|
210
|
-
const
|
|
211
|
-
console.error(chalk_1.default.red(`ā Failed to load module ${id}: ${
|
|
176
|
+
catch (err) {
|
|
177
|
+
const e = err;
|
|
178
|
+
console.error(chalk_1.default.red(`ā Failed to load module ${id}: ${e.message}`));
|
|
212
179
|
res.writeHead(500);
|
|
213
|
-
res.end(`// Failed to resolve module ${id}: ${
|
|
180
|
+
res.end(`// Failed to resolve module ${id}: ${e.message}`);
|
|
214
181
|
}
|
|
215
182
|
});
|
|
216
|
-
//
|
|
183
|
+
// ā
Serve /src files dynamically
|
|
217
184
|
app.use(async (req, res, next) => {
|
|
218
185
|
if (!req.url || (!req.url.startsWith('/src/') && !req.url.endsWith('.css')))
|
|
219
186
|
return next();
|
|
220
187
|
const rawPath = decodeURIComponent(req.url.split('?')[0]);
|
|
221
|
-
|
|
188
|
+
let filePath = path_1.default.join(appRoot, rawPath);
|
|
222
189
|
const possibleExts = ['', '.tsx', '.ts', '.jsx', '.js'];
|
|
223
|
-
let resolvedPath = null;
|
|
224
190
|
for (const ext of possibleExts) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
resolvedPath = candidate;
|
|
191
|
+
if (await fs_extra_1.default.pathExists(filePath + ext)) {
|
|
192
|
+
filePath += ext;
|
|
228
193
|
break;
|
|
229
194
|
}
|
|
230
195
|
}
|
|
231
|
-
if (!
|
|
232
|
-
|
|
233
|
-
return res.end(`// File not found: ${filePath}`);
|
|
234
|
-
}
|
|
196
|
+
if (!(await fs_extra_1.default.pathExists(filePath)))
|
|
197
|
+
return next();
|
|
235
198
|
try {
|
|
236
|
-
let code = await fs_extra_1.default.readFile(
|
|
199
|
+
let code = await fs_extra_1.default.readFile(filePath, 'utf8');
|
|
237
200
|
// Rewrite bare imports ā /@modules/*
|
|
238
201
|
code = code
|
|
239
202
|
.replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_m, dep) => `from "/@modules/${dep}"`)
|
|
240
203
|
.replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (_m, dep) => `import("/@modules/${dep}")`);
|
|
241
204
|
for (const p of plugins)
|
|
242
205
|
if (p.onTransform)
|
|
243
|
-
code = await p.onTransform(code,
|
|
244
|
-
const ext = path_1.default.extname(
|
|
206
|
+
code = await p.onTransform(code, filePath);
|
|
207
|
+
const ext = path_1.default.extname(filePath);
|
|
245
208
|
let loader = 'js';
|
|
246
209
|
if (ext === '.ts')
|
|
247
210
|
loader = 'ts';
|
|
@@ -254,6 +217,7 @@ async function dev() {
|
|
|
254
217
|
sourcemap: 'inline',
|
|
255
218
|
target: 'es2020',
|
|
256
219
|
});
|
|
220
|
+
transformCache.set(filePath, result.code);
|
|
257
221
|
res.setHeader('Content-Type', 'application/javascript');
|
|
258
222
|
res.end(result.code);
|
|
259
223
|
}
|
|
@@ -264,31 +228,17 @@ async function dev() {
|
|
|
264
228
|
res.end(`// Error: ${e.message}`);
|
|
265
229
|
}
|
|
266
230
|
});
|
|
267
|
-
//
|
|
268
|
-
app.use(async (req, res, next) => {
|
|
269
|
-
if (!req.url)
|
|
270
|
-
return next();
|
|
271
|
-
const publicDir = path_1.default.join(appRoot, 'public');
|
|
272
|
-
const targetFile = path_1.default.join(publicDir, decodeURIComponent(req.url.split('?')[0]));
|
|
273
|
-
if (!(await fs_extra_1.default.pathExists(targetFile)))
|
|
274
|
-
return next();
|
|
275
|
-
const stat = await fs_extra_1.default.stat(targetFile);
|
|
276
|
-
if (!stat.isFile())
|
|
277
|
-
return next();
|
|
278
|
-
res.setHeader('Content-Type', getMimeType(targetFile));
|
|
279
|
-
fs_extra_1.default.createReadStream(targetFile).pipe(res);
|
|
280
|
-
});
|
|
281
|
-
// š§© Serve index.html + overlay + HMR
|
|
231
|
+
// ā
Serve index.html + overlay + runtime
|
|
282
232
|
app.use(async (req, res, next) => {
|
|
283
233
|
if (req.url !== '/' && req.url !== '/index.html')
|
|
284
234
|
return next();
|
|
285
|
-
if (!
|
|
235
|
+
if (!fs_extra_1.default.existsSync(indexHtml)) {
|
|
286
236
|
res.writeHead(404);
|
|
287
237
|
return res.end('index.html not found');
|
|
288
238
|
}
|
|
289
239
|
let html = await fs_extra_1.default.readFile(indexHtml, 'utf8');
|
|
290
240
|
html = html.replace('</body>', `
|
|
291
|
-
<script type="module" src="
|
|
241
|
+
<script type="module" src="/src/runtime/overlay-runtime.js"></script>
|
|
292
242
|
<script type="module">
|
|
293
243
|
const ws = new WebSocket("ws://" + location.host);
|
|
294
244
|
ws.onmessage = (e) => {
|
|
@@ -305,21 +255,19 @@ async function dev() {
|
|
|
305
255
|
res.setHeader('Content-Type', 'text/html');
|
|
306
256
|
res.end(html);
|
|
307
257
|
});
|
|
308
|
-
//
|
|
309
|
-
|
|
258
|
+
// ā
WebSocket + HMR
|
|
259
|
+
const server = http_1.default.createServer(app);
|
|
260
|
+
const broadcaster = new broadcastManager_1.BroadcastManager(server);
|
|
261
|
+
chokidar_1.default.watch(path_1.default.join(appRoot, 'src'), { ignoreInitial: true }).on('change', async (file) => {
|
|
310
262
|
console.log(chalk_1.default.yellow(`š Changed: ${file}`));
|
|
311
263
|
transformCache.delete(file);
|
|
264
|
+
for (const p of plugins)
|
|
265
|
+
await p.onHotUpdate?.(file, { broadcast: (msg) => broadcaster.broadcast(msg) });
|
|
312
266
|
broadcaster.broadcast({
|
|
313
267
|
type: 'update',
|
|
314
268
|
path: '/' + path_1.default.relative(appRoot, file).replace(/\\/g, '/'),
|
|
315
269
|
});
|
|
316
270
|
});
|
|
317
|
-
chokidar_1.default
|
|
318
|
-
.watch(path_1.default.join(appRoot, 'src/runtime/overlay-runtime.js'), { ignoreInitial: true })
|
|
319
|
-
.on('change', () => {
|
|
320
|
-
console.log(chalk_1.default.magenta('ā»ļø Overlay runtime updated ā reloading browser...'));
|
|
321
|
-
broadcaster.broadcast({ type: 'reload' });
|
|
322
|
-
});
|
|
323
271
|
server.listen(port, async () => {
|
|
324
272
|
const url = `http://localhost:${port}`;
|
|
325
273
|
console.log(chalk_1.default.cyan.bold('\nš React Client Dev Server'));
|