react-client 1.0.14 ā 1.0.16
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 +170 -200
- package/dist/server/broadcastManager.js +59 -0
- package/package.json +1 -1
package/dist/cli/commands/dev.js
CHANGED
|
@@ -7,24 +7,24 @@ exports.default = dev;
|
|
|
7
7
|
const esbuild_1 = __importDefault(require("esbuild"));
|
|
8
8
|
const connect_1 = __importDefault(require("connect"));
|
|
9
9
|
const http_1 = __importDefault(require("http"));
|
|
10
|
-
const ws_1 = require("ws");
|
|
11
10
|
const chokidar_1 = __importDefault(require("chokidar"));
|
|
12
11
|
const detect_port_1 = __importDefault(require("detect-port"));
|
|
13
12
|
const prompts_1 = __importDefault(require("prompts"));
|
|
14
13
|
const path_1 = __importDefault(require("path"));
|
|
15
14
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
16
|
-
const loadConfig_1 = require("../../utils/loadConfig");
|
|
17
15
|
const open_1 = __importDefault(require("open"));
|
|
18
16
|
const child_process_1 = require("child_process");
|
|
19
17
|
const chalk_1 = __importDefault(require("chalk"));
|
|
18
|
+
const loadConfig_1 = require("../../utils/loadConfig");
|
|
19
|
+
const broadcastManager_1 = require("../../server/broadcastManager");
|
|
20
20
|
async function dev() {
|
|
21
21
|
const root = process.cwd();
|
|
22
|
-
// š§© Load config
|
|
23
22
|
const userConfig = await (0, loadConfig_1.loadReactClientConfig)(root);
|
|
24
23
|
const appRoot = path_1.default.resolve(root, userConfig.root || '.');
|
|
25
24
|
const defaultPort = userConfig.server?.port || 5173;
|
|
26
|
-
const
|
|
27
|
-
|
|
25
|
+
const cacheDir = path_1.default.join(appRoot, '.react-client', 'deps');
|
|
26
|
+
await fs_extra_1.default.ensureDir(cacheDir);
|
|
27
|
+
// Detect entry dynamically (main.tsx or main.jsx)
|
|
28
28
|
const possibleEntries = ['src/main.tsx', 'src/main.jsx'];
|
|
29
29
|
const entry = possibleEntries.map((p) => path_1.default.join(appRoot, p)).find((p) => fs_extra_1.default.existsSync(p));
|
|
30
30
|
if (!entry) {
|
|
@@ -32,8 +32,7 @@ async function dev() {
|
|
|
32
32
|
process.exit(1);
|
|
33
33
|
}
|
|
34
34
|
const indexHtml = path_1.default.join(appRoot, 'index.html');
|
|
35
|
-
|
|
36
|
-
// š§ Detect available port
|
|
35
|
+
// Detect open port
|
|
37
36
|
const availablePort = await (0, detect_port_1.default)(defaultPort);
|
|
38
37
|
const port = availablePort;
|
|
39
38
|
if (availablePort !== defaultPort) {
|
|
@@ -48,122 +47,143 @@ async function dev() {
|
|
|
48
47
|
process.exit(0);
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
|
-
// ā”
|
|
50
|
+
// ā” React-refresh runtime auto install
|
|
52
51
|
function safeResolveReactRefresh() {
|
|
53
52
|
try {
|
|
54
53
|
return require.resolve('react-refresh/runtime');
|
|
55
54
|
}
|
|
56
55
|
catch {
|
|
57
|
-
console.warn(chalk_1.default.yellow('ā ļø react-refresh not found ā
|
|
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
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
65
|
+
const _reactRefreshRuntime = safeResolveReactRefresh();
|
|
66
|
+
// --- Plugins (core + user)
|
|
67
|
+
const corePlugins = [
|
|
68
|
+
{
|
|
69
|
+
name: 'css-hmr',
|
|
70
|
+
async onTransform(code, id) {
|
|
71
|
+
if (id.endsWith('.css')) {
|
|
72
|
+
const escaped = JSON.stringify(code);
|
|
73
|
+
return `
|
|
74
|
+
const css = ${escaped};
|
|
75
|
+
const style = document.createElement('style');
|
|
76
|
+
style.textContent = css;
|
|
77
|
+
document.head.appendChild(style);
|
|
78
|
+
import.meta.hot && import.meta.hot.accept();
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
return code;
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
const userPlugins = Array.isArray(userConfig.plugins) ? userConfig.plugins : [];
|
|
86
|
+
const plugins = [...corePlugins, ...userPlugins];
|
|
87
|
+
// š§± Connect app
|
|
88
|
+
const app = (0, connect_1.default)();
|
|
89
|
+
const transformCache = new Map();
|
|
90
|
+
// --- Prebundle persistent deps
|
|
91
|
+
async function prebundleDeps() {
|
|
92
|
+
const pkgFile = path_1.default.join(appRoot, 'package.json');
|
|
93
|
+
if (!fs_extra_1.default.existsSync(pkgFile))
|
|
94
|
+
return;
|
|
95
|
+
const pkg = JSON.parse(await fs_extra_1.default.readFile(pkgFile, 'utf8'));
|
|
96
|
+
const deps = Object.keys(pkg.dependencies || {});
|
|
97
|
+
if (!deps.length)
|
|
98
|
+
return;
|
|
99
|
+
const cached = await fs_extra_1.default.readdir(cacheDir);
|
|
100
|
+
const missing = deps.filter((d) => !cached.includes(d + '.js'));
|
|
101
|
+
if (!missing.length)
|
|
102
|
+
return;
|
|
103
|
+
console.log(chalk_1.default.cyan('š¦ Prebundling:'), missing.join(', '));
|
|
104
|
+
for (const dep of missing) {
|
|
58
105
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
106
|
+
const entryPath = require.resolve(dep, { paths: [appRoot] });
|
|
107
|
+
const outFile = path_1.default.join(cacheDir, dep + '.js');
|
|
108
|
+
await esbuild_1.default.build({
|
|
109
|
+
entryPoints: [entryPath],
|
|
110
|
+
bundle: true,
|
|
111
|
+
platform: 'browser',
|
|
112
|
+
format: 'esm',
|
|
113
|
+
outfile: outFile,
|
|
114
|
+
write: true,
|
|
115
|
+
target: 'es2020',
|
|
62
116
|
});
|
|
63
|
-
console.log(chalk_1.default.green(
|
|
64
|
-
return require.resolve('react-refresh/runtime');
|
|
117
|
+
console.log(chalk_1.default.green(`ā
Cached ${dep}`));
|
|
65
118
|
}
|
|
66
119
|
catch (err) {
|
|
67
120
|
const msg = err instanceof Error ? err.message : String(err);
|
|
68
|
-
console.
|
|
69
|
-
console.error(chalk_1.default.red('ā Failed to install react-refresh automatically.'));
|
|
70
|
-
console.error('Please run: npm install react-refresh');
|
|
71
|
-
process.exit(1);
|
|
121
|
+
console.warn(chalk_1.default.yellow(`ā ļø Skipped ${dep}: ${msg}`));
|
|
72
122
|
}
|
|
73
123
|
}
|
|
74
124
|
}
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
const deps = new Map(); // dependency ā importers
|
|
78
|
-
const transformCache = new Map();
|
|
79
|
-
async function resolveFile(basePath) {
|
|
80
|
-
if (await fs_extra_1.default.pathExists(basePath))
|
|
81
|
-
return basePath;
|
|
82
|
-
const exts = ['.tsx', '.ts', '.jsx', '.js'];
|
|
83
|
-
for (const ext of exts) {
|
|
84
|
-
const candidate = basePath + ext;
|
|
85
|
-
if (await fs_extra_1.default.pathExists(candidate))
|
|
86
|
-
return candidate;
|
|
87
|
-
}
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
// š connect server
|
|
91
|
-
const app = (0, connect_1.default)();
|
|
92
|
-
// š” Security headers
|
|
93
|
-
app.use((_req, res, next) => {
|
|
94
|
-
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
|
|
95
|
-
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
|
|
96
|
-
next();
|
|
97
|
-
});
|
|
98
|
-
// 1ļøā£ Serve react-refresh runtime with browser shim
|
|
99
|
-
app.use('/@react-refresh', async (_req, res) => {
|
|
100
|
-
const runtime = await fs_extra_1.default.readFile(reactRefreshRuntime, 'utf8');
|
|
101
|
-
const shim = `
|
|
102
|
-
window.process = window.process || { env: { NODE_ENV: 'development' } };
|
|
103
|
-
window.module = { exports: {} };
|
|
104
|
-
window.global = window;
|
|
105
|
-
window.require = () => window.module.exports;
|
|
106
|
-
`;
|
|
107
|
-
res.setHeader('Content-Type', 'application/javascript');
|
|
108
|
-
res.end(shim + '\n' + runtime);
|
|
109
|
-
});
|
|
110
|
-
// 2ļøā£ Serve bare modules dynamically (/@modules/)
|
|
125
|
+
await prebundleDeps();
|
|
126
|
+
// --- Serve prebundled modules
|
|
111
127
|
app.use('/@modules/', async (req, res, next) => {
|
|
112
|
-
|
|
128
|
+
const id = req.url?.replace(/^\/@modules\//, '');
|
|
113
129
|
if (!id)
|
|
114
130
|
return next();
|
|
115
|
-
id = id.replace(/^\/+/, ''); // normalize
|
|
116
131
|
try {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
132
|
+
const cacheFile = path_1.default.join(cacheDir, id.replace(/\//g, '_') + '.js');
|
|
133
|
+
if (await fs_extra_1.default.pathExists(cacheFile)) {
|
|
134
|
+
res.setHeader('Content-Type', 'application/javascript');
|
|
135
|
+
return res.end(await fs_extra_1.default.readFile(cacheFile));
|
|
136
|
+
}
|
|
137
|
+
const entryPath = require.resolve(id, { paths: [appRoot] });
|
|
138
|
+
const result = await esbuild_1.default.build({
|
|
139
|
+
entryPoints: [entryPath],
|
|
120
140
|
bundle: true,
|
|
121
|
-
write: false,
|
|
122
141
|
platform: 'browser',
|
|
123
142
|
format: 'esm',
|
|
124
143
|
target: 'es2020',
|
|
144
|
+
write: false,
|
|
125
145
|
});
|
|
146
|
+
let code = result.outputFiles[0].text;
|
|
147
|
+
if (id === 'react-dom/client') {
|
|
148
|
+
code += `
|
|
149
|
+
import * as ReactDOMClient from '/@modules/react-dom';
|
|
150
|
+
export const createRoot = ReactDOMClient.createRoot || ReactDOMClient.default?.createRoot;
|
|
151
|
+
export default ReactDOMClient.default || ReactDOMClient;
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
await fs_extra_1.default.writeFile(cacheFile, code, 'utf8');
|
|
126
155
|
res.setHeader('Content-Type', 'application/javascript');
|
|
127
|
-
res.end(
|
|
156
|
+
res.end(code);
|
|
128
157
|
}
|
|
129
158
|
catch (err) {
|
|
130
159
|
const msg = err instanceof Error ? err.message : String(err);
|
|
131
|
-
console.error(`Failed to resolve module ${id}:`, msg);
|
|
132
160
|
res.writeHead(500);
|
|
133
|
-
res.end(`//
|
|
161
|
+
res.end(`// Failed to resolve module ${id}: ${msg}`);
|
|
134
162
|
}
|
|
135
163
|
});
|
|
136
|
-
//
|
|
164
|
+
// --- Serve /src files dynamically
|
|
137
165
|
app.use(async (req, res, next) => {
|
|
138
|
-
if (!req.url || !req.url.startsWith('/src/'))
|
|
166
|
+
if (!req.url || (!req.url.startsWith('/src/') && !req.url.endsWith('.css')))
|
|
167
|
+
return next();
|
|
168
|
+
const filePath = path_1.default.join(appRoot, decodeURIComponent(req.url.split('?')[0]));
|
|
169
|
+
if (!(await fs_extra_1.default.pathExists(filePath)))
|
|
139
170
|
return next();
|
|
140
171
|
try {
|
|
141
|
-
|
|
142
|
-
const filePath = path_1.default.join(appRoot, requestPath);
|
|
143
|
-
const resolvedFile = await resolveFile(filePath);
|
|
144
|
-
if (!resolvedFile)
|
|
145
|
-
return next();
|
|
146
|
-
if (transformCache.has(resolvedFile)) {
|
|
172
|
+
if (transformCache.has(filePath)) {
|
|
147
173
|
res.setHeader('Content-Type', 'application/javascript');
|
|
148
|
-
res.end(transformCache.get(
|
|
149
|
-
return;
|
|
174
|
+
return res.end(transformCache.get(filePath));
|
|
150
175
|
}
|
|
151
|
-
let code = await fs_extra_1.default.readFile(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const importer = path_1.default.relative(appRoot, resolvedFile);
|
|
161
|
-
const importedFile = path_1.default.resolve(path_1.default.dirname(resolvedFile), rel);
|
|
162
|
-
const depFile = (await resolveFile(importedFile)) ?? importedFile;
|
|
163
|
-
if (!deps.has(depFile))
|
|
164
|
-
deps.set(depFile, new Set());
|
|
165
|
-
deps.get(depFile).add(importer);
|
|
176
|
+
let code = await fs_extra_1.default.readFile(filePath, 'utf8');
|
|
177
|
+
// š§© Rewrite bare imports (react, react-dom, etc.) to /@modules/*
|
|
178
|
+
code = code
|
|
179
|
+
.replace(/\bfrom\s+['"]([^'".\/][^'"]*)['"]/g, (_match, dep) => `from "/@modules/${dep}"`)
|
|
180
|
+
.replace(/\bimport\(['"]([^'".\/][^'"]*)['"]\)/g, (_match, dep) => `import("/@modules/${dep}")`);
|
|
181
|
+
// Run plugin transforms
|
|
182
|
+
for (const p of plugins) {
|
|
183
|
+
if (p.onTransform)
|
|
184
|
+
code = await p.onTransform(code, filePath);
|
|
166
185
|
}
|
|
186
|
+
const ext = path_1.default.extname(filePath);
|
|
167
187
|
let loader = 'js';
|
|
168
188
|
if (ext === '.ts')
|
|
169
189
|
loader = 'ts';
|
|
@@ -171,146 +191,96 @@ async function dev() {
|
|
|
171
191
|
loader = 'tsx';
|
|
172
192
|
else if (ext === '.jsx')
|
|
173
193
|
loader = 'jsx';
|
|
174
|
-
const
|
|
194
|
+
const result = await esbuild_1.default.transform(code, {
|
|
175
195
|
loader,
|
|
176
196
|
sourcemap: 'inline',
|
|
177
|
-
sourcefile: req.url,
|
|
178
197
|
target: 'es2020',
|
|
179
|
-
jsxFactory: 'React.createElement',
|
|
180
|
-
jsxFragment: 'React.Fragment',
|
|
181
198
|
});
|
|
182
|
-
transformCache.set(
|
|
199
|
+
transformCache.set(filePath, result.code);
|
|
183
200
|
res.setHeader('Content-Type', 'application/javascript');
|
|
184
|
-
res.end(
|
|
201
|
+
res.end(result.code);
|
|
185
202
|
}
|
|
186
203
|
catch (err) {
|
|
187
204
|
const msg = err instanceof Error ? err.message : String(err);
|
|
188
|
-
console.error('Error serving /src file:', msg);
|
|
189
205
|
res.writeHead(500);
|
|
190
206
|
res.end(`// Error: ${msg}`);
|
|
191
207
|
}
|
|
192
208
|
});
|
|
193
|
-
//
|
|
209
|
+
// --- Serve index.html with overlay + HMR client
|
|
194
210
|
app.use(async (req, res, next) => {
|
|
195
|
-
if (req.url
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
width: 100vw; height: 100vh;
|
|
212
|
-
background: rgba(0, 0, 0, 0.92);
|
|
213
|
-
color: #ff5555;
|
|
214
|
-
font-family: monospace;
|
|
215
|
-
padding: 2rem;
|
|
216
|
-
overflow: auto;
|
|
217
|
-
z-index: 999999;
|
|
218
|
-
white-space: pre-wrap;
|
|
219
|
-
}
|
|
220
|
-
.rc-overlay h2 {
|
|
221
|
-
color: #ff7575;
|
|
222
|
-
font-size: 1.2rem;
|
|
223
|
-
margin-bottom: 1rem;
|
|
224
|
-
}
|
|
225
|
-
\`;
|
|
226
|
-
document.head.appendChild(style);
|
|
227
|
-
|
|
228
|
-
window.showErrorOverlay = (err) => {
|
|
229
|
-
window.clearErrorOverlay?.();
|
|
230
|
-
const overlay = document.createElement('div');
|
|
231
|
-
overlay.className = 'rc-overlay';
|
|
232
|
-
overlay.innerHTML = '<h2>šØ React Client Error</h2>' +
|
|
233
|
-
(err.message || err.error || err) + '\\n\\n' + (err.stack || '');
|
|
234
|
-
document.body.appendChild(overlay);
|
|
235
|
-
window.__reactClientOverlay = overlay;
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
window.clearErrorOverlay = () => {
|
|
239
|
-
const overlay = window.__reactClientOverlay;
|
|
240
|
-
if (overlay) overlay.remove();
|
|
241
|
-
window.__reactClientOverlay = null;
|
|
242
|
-
};
|
|
243
|
-
})();
|
|
244
|
-
</script>
|
|
245
|
-
|
|
246
|
-
<script type="module">
|
|
247
|
-
import "/@react-refresh";
|
|
248
|
-
const ws = new WebSocket("ws://" + location.host);
|
|
249
|
-
ws.onmessage = async (e) => {
|
|
250
|
-
const msg = JSON.parse(e.data);
|
|
251
|
-
if (msg.type === "error") {
|
|
252
|
-
console.error(msg);
|
|
253
|
-
return window.showErrorOverlay?.(msg);
|
|
254
|
-
}
|
|
255
|
-
if (msg.type === "update") {
|
|
256
|
-
try {
|
|
257
|
-
await import(msg.path + "?t=" + Date.now());
|
|
258
|
-
window.clearErrorOverlay?.();
|
|
259
|
-
window.$RefreshRuntime?.performReactRefresh?.();
|
|
260
|
-
} catch (err) {
|
|
261
|
-
window.showErrorOverlay?.(err);
|
|
262
|
-
}
|
|
211
|
+
if (req.url !== '/' && req.url !== '/index.html')
|
|
212
|
+
return next();
|
|
213
|
+
if (!fs_extra_1.default.existsSync(indexHtml)) {
|
|
214
|
+
res.writeHead(404);
|
|
215
|
+
return res.end('index.html not found');
|
|
216
|
+
}
|
|
217
|
+
let html = await fs_extra_1.default.readFile(indexHtml, 'utf8');
|
|
218
|
+
html = html.replace('</body>', `
|
|
219
|
+
<script>
|
|
220
|
+
(() => {
|
|
221
|
+
const style = document.createElement('style');
|
|
222
|
+
style.textContent = \`
|
|
223
|
+
.rc-overlay {
|
|
224
|
+
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
|
225
|
+
background: rgba(0,0,0,0.9); color: #ff5555;
|
|
226
|
+
font-family: monospace; padding: 2rem; overflow:auto; z-index: 999999;
|
|
263
227
|
}
|
|
264
|
-
|
|
228
|
+
\`;
|
|
229
|
+
document.head.appendChild(style);
|
|
230
|
+
window.showErrorOverlay = (err) => {
|
|
231
|
+
window.clearErrorOverlay?.();
|
|
232
|
+
const el = document.createElement('div');
|
|
233
|
+
el.className = 'rc-overlay';
|
|
234
|
+
el.innerHTML = '<h2>šØ Error</h2><pre>' + (err.message || err) + '</pre>';
|
|
235
|
+
document.body.appendChild(el);
|
|
236
|
+
window.__overlay = el;
|
|
265
237
|
};
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
238
|
+
window.clearErrorOverlay = () => window.__overlay?.remove();
|
|
239
|
+
})();
|
|
240
|
+
</script>
|
|
241
|
+
<script type="module">
|
|
242
|
+
const ws = new WebSocket("ws://" + location.host);
|
|
243
|
+
ws.onmessage = (e) => {
|
|
244
|
+
const msg = JSON.parse(e.data);
|
|
245
|
+
if (msg.type === "reload") location.reload();
|
|
246
|
+
if (msg.type === "error") return window.showErrorOverlay?.(msg);
|
|
247
|
+
if (msg.type === "update") {
|
|
248
|
+
window.clearErrorOverlay?.();
|
|
249
|
+
import(msg.path + "?t=" + Date.now());
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
</script>
|
|
253
|
+
</body>`);
|
|
254
|
+
res.setHeader('Content-Type', 'text/html');
|
|
255
|
+
res.end(html);
|
|
273
256
|
});
|
|
274
|
-
//
|
|
257
|
+
// --- WebSocket + HMR via BroadcastManager
|
|
275
258
|
const server = http_1.default.createServer(app);
|
|
276
|
-
const
|
|
277
|
-
const broadcast = (data) => {
|
|
278
|
-
const json = JSON.stringify(data);
|
|
279
|
-
wss.clients.forEach((c) => c.readyState === 1 && c.send(json));
|
|
280
|
-
};
|
|
259
|
+
const broadcaster = new broadcastManager_1.BroadcastManager(server);
|
|
281
260
|
chokidar_1.default.watch(path_1.default.join(appRoot, 'src'), { ignoreInitial: true }).on('change', async (file) => {
|
|
282
|
-
console.log(`š
|
|
261
|
+
console.log(chalk_1.default.yellow(`š Changed: ${file}`));
|
|
283
262
|
transformCache.delete(file);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
while (queue.length > 0) {
|
|
289
|
-
const dep = queue.pop();
|
|
290
|
-
const importers = deps.get(dep);
|
|
291
|
-
if (!importers)
|
|
292
|
-
continue;
|
|
293
|
-
for (const importer of importers) {
|
|
294
|
-
if (visited.has(importer))
|
|
295
|
-
continue;
|
|
296
|
-
visited.add(importer);
|
|
297
|
-
console.log(chalk_1.default.yellow(`āŖļø Updating importer: ${importer}`));
|
|
298
|
-
transformCache.delete(path_1.default.join(appRoot, importer));
|
|
299
|
-
broadcast({ type: 'update', path: '/' + importer.replace(/\\/g, '/') });
|
|
300
|
-
queue.push(path_1.default.join(appRoot, importer));
|
|
301
|
-
}
|
|
263
|
+
for (const p of plugins) {
|
|
264
|
+
p.onHotUpdate?.(file, {
|
|
265
|
+
broadcast: (msg) => broadcaster.broadcast(msg),
|
|
266
|
+
});
|
|
302
267
|
}
|
|
268
|
+
broadcaster.broadcast({
|
|
269
|
+
type: 'update',
|
|
270
|
+
path: '/' + path_1.default.relative(appRoot, file).replace(/\\/g, '/'),
|
|
271
|
+
});
|
|
303
272
|
});
|
|
273
|
+
// š Launch
|
|
304
274
|
server.listen(port, async () => {
|
|
305
275
|
const url = `http://localhost:${port}`;
|
|
306
276
|
console.log(chalk_1.default.cyan.bold('\nš React Client Dev Server'));
|
|
307
|
-
console.log(chalk_1.default.gray('
|
|
277
|
+
console.log(chalk_1.default.gray('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
308
278
|
console.log(chalk_1.default.green(`ā” Running at: ${url}`));
|
|
309
279
|
await (0, open_1.default)(url, { newInstance: true });
|
|
310
280
|
});
|
|
311
|
-
process.on('SIGINT',
|
|
281
|
+
process.on('SIGINT', () => {
|
|
312
282
|
console.log(chalk_1.default.red('\nš Shutting down...'));
|
|
313
|
-
|
|
283
|
+
broadcaster.close();
|
|
314
284
|
process.exit(0);
|
|
315
285
|
});
|
|
316
286
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BroadcastManager = void 0;
|
|
7
|
+
const ws_1 = require("ws");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
/**
|
|
10
|
+
* BroadcastManager ā Shared WebSocket utility for dev, preview, and SSR servers.
|
|
11
|
+
* Generic over message type T which defaults to HMRMessage.
|
|
12
|
+
*/
|
|
13
|
+
class BroadcastManager {
|
|
14
|
+
constructor(server) {
|
|
15
|
+
this.clients = new Set();
|
|
16
|
+
this.wss = new ws_1.WebSocketServer({ server });
|
|
17
|
+
this.wss.on('connection', (ws) => {
|
|
18
|
+
this.clients.add(ws);
|
|
19
|
+
console.log(chalk_1.default.gray('š Client connected'));
|
|
20
|
+
ws.on('close', () => {
|
|
21
|
+
this.clients.delete(ws);
|
|
22
|
+
console.log(chalk_1.default.gray('ā Client disconnected'));
|
|
23
|
+
});
|
|
24
|
+
ws.on('error', (err) => {
|
|
25
|
+
console.error(chalk_1.default.red('ā ļø WebSocket error:'), err.message);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
broadcast(msg) {
|
|
30
|
+
const data = JSON.stringify(msg);
|
|
31
|
+
for (const client of this.clients) {
|
|
32
|
+
if (client.readyState === ws_1.WebSocket.OPEN) {
|
|
33
|
+
client.send(data);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
send(ws, msg) {
|
|
38
|
+
if (ws.readyState === ws_1.WebSocket.OPEN) {
|
|
39
|
+
ws.send(JSON.stringify(msg));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
getClientCount() {
|
|
43
|
+
return this.clients.size;
|
|
44
|
+
}
|
|
45
|
+
close() {
|
|
46
|
+
console.log(chalk_1.default.red('š Closing WebSocket connections...'));
|
|
47
|
+
this.wss.close();
|
|
48
|
+
for (const ws of this.clients) {
|
|
49
|
+
try {
|
|
50
|
+
ws.close();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// ignore
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
this.clients.clear();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.BroadcastManager = BroadcastManager;
|