@dev-to/react-plugin 0.1.0

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/index.js ADDED
@@ -0,0 +1,1723 @@
1
+ import { DEV_TO_REACT_BASE_PATH, DEV_TO_REACT_CONTRACT_KEY, DEV_TO_REACT_CONTRACT_PATH, DEV_TO_REACT_DEBUG_HTML_PATH, DEV_TO_REACT_DEBUG_JSON_PATH, DEV_TO_REACT_DEBUG_STATE_KEY, DEV_TO_REACT_DID_OPEN_BROWSER_KEY, DEV_TO_REACT_EVENT_FULL_RELOAD, DEV_TO_REACT_EVENT_HMR_UPDATE, DEV_TO_REACT_INIT_PATH, DEV_TO_REACT_NAMESPACE, DEV_TO_REACT_ORIGIN_KEY, DEV_TO_REACT_REACT_RUNTIME_PATH, DEV_TO_REACT_RESOLVE_ASSET_KEY } from "@dev-to/react-shared";
2
+ import node_fs from "node:fs";
3
+ import node_path from "node:path";
4
+ import picocolors from "picocolors";
5
+ import { mergeConfig } from "vite";
6
+ import { exec } from "node:child_process";
7
+ import node_os from "node:os";
8
+ import { pathToFileURL } from "node:url";
9
+ import typescript from "typescript";
10
+ const PLUGIN_NAME = DEV_TO_REACT_NAMESPACE;
11
+ const PLUGIN_LOG_PREFIX = `[${PLUGIN_NAME}]`;
12
+ const STABLE_BASE_PATH = DEV_TO_REACT_BASE_PATH;
13
+ const STABLE_CONTRACT_PATH = DEV_TO_REACT_CONTRACT_PATH;
14
+ const STABLE_INIT_PATH = DEV_TO_REACT_INIT_PATH;
15
+ const STABLE_REACT_RUNTIME_PATH = DEV_TO_REACT_REACT_RUNTIME_PATH;
16
+ const STABLE_DEBUG_HTML_PATH = DEV_TO_REACT_DEBUG_HTML_PATH;
17
+ const STABLE_DEBUG_JSON_PATH = DEV_TO_REACT_DEBUG_JSON_PATH;
18
+ const EVENT_FULL_RELOAD = DEV_TO_REACT_EVENT_FULL_RELOAD;
19
+ const EVENT_HMR_UPDATE = DEV_TO_REACT_EVENT_HMR_UPDATE;
20
+ function renderDebugHtml(params) {
21
+ const { resolvedDevComponentMap, entryPathMap = {}, audit, stats, originCandidates, actualPort, configFilePath } = params;
22
+ const { defaultEntryAbs, defaultEntryExists, componentMapCount } = audit;
23
+ const hasConfig = Object.keys(resolvedDevComponentMap).length > 0;
24
+ const isWildcardOnly = hasConfig && 1 === Object.keys(resolvedDevComponentMap).length && resolvedDevComponentMap['*'];
25
+ const projectRoot = configFilePath ? node_path.dirname(configFilePath) : process.cwd();
26
+ const projectRootDisplay = projectRoot.replace(/\\/g, '/');
27
+ const getShortPath = (absPath)=>{
28
+ try {
29
+ const rel = node_path.relative(projectRoot, absPath).replace(/\\/g, '/');
30
+ return rel.startsWith('.') ? rel : `./${rel}`;
31
+ } catch {
32
+ return absPath;
33
+ }
34
+ };
35
+ const toVsCodeUrl = (p)=>`vscode://file/${p.replace(/\\/g, '/')}`;
36
+ const escapeHtml = (s)=>s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
37
+ const annotatedConfigHtml = (()=>{
38
+ if (!configFilePath || !node_fs.existsSync(configFilePath)) return '';
39
+ try {
40
+ const content = node_fs.readFileSync(configFilePath, 'utf-8');
41
+ const lines = [];
42
+ const dim = (s)=>`<span class="cmt-dim">${escapeHtml(s)}</span>`;
43
+ const map = (s)=>`<span class="cmt-mapping">${escapeHtml(s)}</span>`;
44
+ lines.push(dim('/**'));
45
+ lines.push(dim(` * 💡 ${PLUGIN_NAME} 解析结果:`));
46
+ lines.push(dim(` * - 默认入口: ${getShortPath(defaultEntryAbs)} (${defaultEntryExists ? '存在' : '缺失'})`));
47
+ lines.push(dim(' * - 组件映射解析 (Resolved Component Map):'));
48
+ Object.entries(resolvedDevComponentMap).forEach(([name, entry])=>{
49
+ lines.push(map(` * - ${name} -> ${entry}`));
50
+ });
51
+ lines.push(dim(' */'));
52
+ return `${lines.join('\n')}\n\n${escapeHtml(content)}`;
53
+ } catch (e) {
54
+ return escapeHtml(`// ${PLUGIN_LOG_PREFIX} 无法读取配置文件: ${e}`);
55
+ }
56
+ })();
57
+ return `<!doctype html>
58
+ <html lang="zh-CN">
59
+ <head>
60
+ <meta charset="utf-8">
61
+ <meta name="viewport" content="width=device-width,initial-scale=1">
62
+ <title>${PLUGIN_NAME} Debug</title>
63
+ <style>
64
+ :root { --p: #3b82f6; --t: #1e293b; --m: #64748b; --b: #e2e8f0; --r: 12px; }
65
+ * { box-sizing: border-box; }
66
+ body { font-family: -apple-system, system-ui, sans-serif; background: #f8fafc; color: #1a202c; margin: 0; padding: 24px; line-height: 1.6; }
67
+ .container { max-width: 1000px; margin: 0 auto; }
68
+ .header, .card { background: #fff; border-radius: var(--r); border: 1px solid var(--b); margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
69
+ .header { padding: 20px 32px; display: flex; justify-content: space-between; align-items: center; }
70
+ .card { padding: 24px; }
71
+ .header h1 { margin: 0; font-size: 24px; color: var(--t); }
72
+ .header p, .muted { color: var(--m); font-size: 14px; margin: 4px 0 0; }
73
+ .header-status { display: flex; gap: 12px; flex-wrap: wrap; }
74
+ .status-pill { border: 1px solid var(--b); font-weight: 500; display: flex; align-items: center; background: #f8fafc; padding: 6px 16px; border-radius: 20px; font-size: 13px; color: #475569; }
75
+ .status-pill b { color: var(--t); margin-left: 6px; }
76
+ .card h3 { margin: 0 0 20px; font-size: 16px; font-weight: 600; display: flex; align-items: center; gap: 8px; color: var(--t); }
77
+ .card h3::before { content: ''; width: 3px; height: 16px; background: var(--p); border-radius: 2px; }
78
+
79
+ .alert { padding: 12px 16px; border-radius: 8px; margin-bottom: 20px; font-size: 13px; display: flex; align-items: center; gap: 10px; border: 1px solid transparent; }
80
+ .alert-info { background: #f0f9ff; color: #0369a1; border-color: #e0f2fe; }
81
+ .alert-error { background: #fef2f2; color: #991b1b; border-color: #fee2e2; }
82
+ .alert-warning { background: #fffbeb; color: #92400e; border-color: #fef3c7; }
83
+
84
+ .setup-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 10px; margin: 12px 0 20px; }
85
+ .setup-card {
86
+ background: #fff; border: 1.5px solid var(--b); border-radius: 8px; padding: 10px 14px;
87
+ transition: all .2s cubic-bezier(0.4, 0, 0.2, 1); cursor: pointer; display: flex; flex-direction: column;
88
+ }
89
+ .setup-card:hover { border-color: var(--p); background: #f8faff; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
90
+ .setup-card.active { border-color: var(--p); background: #f0f7ff; }
91
+ .setup-card .type { font-size: 9px; text-transform: uppercase; color: var(--m); font-weight: 700; margin-bottom: 6px; display: inline-flex; background: #f1f5f9; padding: 1px 6px; border-radius: 4px; width: fit-content; }
92
+ .setup-card.active .type { background: #dbeafe; color: var(--p); }
93
+ .setup-card .url { font-family: SFMono-Regular, Consolas, monospace; font-size: 13px; color: var(--t); font-weight: 600; margin-bottom: 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; width: 100%; }
94
+ .setup-card .action { font-size: 11px; color: var(--m); display: flex; align-items: center; gap: 4px; margin-top: auto; }
95
+ .setup-card:hover .action, .setup-card.active .action { color: var(--p); }
96
+
97
+ .manual-box { background: #fcfdfe; border: 1px solid var(--b); border-radius: 8px; margin-top: 16px; overflow: hidden; }
98
+ .manual-header { padding: 10px 16px; display: flex; justify-content: space-between; align-items: center; color: var(--m); font-size: 11px; font-weight: 600; background: #f8fafc; border-bottom: 1px solid var(--b); }
99
+ .copy-btn-link { background: #fff; border: 1px solid var(--b); color: var(--t); padding: 3px 10px; border-radius: 4px; cursor: pointer; font-size: 11px; transition: .2s; }
100
+ .copy-btn-link:hover { border-color: var(--p); color: var(--p); }
101
+ #fullCmdPreview { margin: 0; padding: 14px 16px; border: none; background: transparent; }
102
+
103
+ .info-grid { display: grid; grid-template-columns: 100px 1fr; gap: 12px 16px; margin-bottom: 24px; align-items: baseline; font-size: 13px; }
104
+ .info-label { color: var(--m); font-weight: 500; }
105
+
106
+ table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 14px; border: 1px solid #f1f5f9; border-radius: 8px; overflow: hidden; }
107
+ th, td { text-align: left; padding: 14px 16px; border-bottom: 1px solid #f1f5f9; }
108
+ th { background: #f8fafc; color: var(--m); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .05em; }
109
+ code, pre { font-family: SFMono-Regular, Consolas, monospace; font-size: .9em; }
110
+ code { background: #f1f5f9; padding: 3px 6px; border-radius: 4px; color: #475569; font-weight: 500; }
111
+ .code-name { color: #4f46e5; background: #eef2ff; border: 1px solid #e0e7ff; }
112
+ .link-code { color: #2563eb; text-decoration: none; padding: 2px 6px; border-radius: 4px; transition: .2s; display: inline-flex; align-items: center; gap: 4px; background: #f0f9ff; border: 1px solid #bae6fd; }
113
+ .link-code:hover { background: #e0f2fe; color: #1d4ed8; border-color: #7dd3fc; }
114
+ .link-code code { background: 0 0; padding: 0; color: inherit; }
115
+ .link-code::after { content: '↗'; font-size: 11px; opacity: .6; }
116
+ details { margin-top: 16px; border: 1px solid #f1f5f9; border-radius: 10px; padding: 12px 16px; background: #fafbfc; }
117
+ summary { cursor: pointer; color: #475569; font-size: 14px; font-weight: 600; outline: 0; }
118
+ summary:hover { color: var(--p); }
119
+
120
+ pre { background: #ebf8ff; color: #2d3748; padding: 14px 16px; border-radius: 8px; font-size: 12px; border: 1px solid var(--b); margin: 12px 0; line-height: 1.6; overflow-x: auto; }
121
+ .cmt { color: #718096; font-style: italic; }
122
+ .kw { color: #2563eb; font-weight: 600; }
123
+ .str { color: #059669; }
124
+ .val { color: #d97706; }
125
+
126
+ .stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
127
+ .stat-card { background: #f8fafc; padding: 20px; border-radius: 10px; text-align: center; border: 1px solid #f1f5f9; }
128
+ .stat-card .value { font-size: 24px; font-weight: 700; color: var(--p); margin-bottom: 4px; }
129
+ .stat-card .label { font-size: 12px; color: var(--m); text-transform: uppercase; font-weight: 600; }
130
+ .parameter-item { margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px dashed var(--b); }
131
+ .parameter-item:last-child { border-bottom: none; }
132
+ .parameter-name { font-weight: 600; color: var(--t); margin-bottom: 6px; display: block; font-size: 14px; }
133
+ .info-value, .parameter-info { font-size: 13px; color: #4a5568; line-height: 1.7; }
134
+ .cmt-dim { opacity: 0.4; }
135
+ .cmt-mapping { color: var(--p); font-weight: 600; }
136
+
137
+ /* 响应式优化:移动端 (480px以下) */
138
+ @media (max-width: 480px) {
139
+ body { padding: 12px; }
140
+ .header { padding: 16px; flex-direction: column; align-items: flex-start; gap: 16px; }
141
+ .header-status { width: 100%; gap: 8px; }
142
+ .status-pill { padding: 4px 12px; font-size: 12px; }
143
+ .card { padding: 16px; }
144
+ .info-grid { grid-template-columns: 1fr; gap: 4px; }
145
+ .info-label { font-size: 12px; margin-bottom: 2px; }
146
+ .stats-grid { grid-template-columns: 1fr; }
147
+ .stat-card { padding: 12px; }
148
+ .stat-card .value { font-size: 20px; }
149
+ table { display: table; width: 100%; border-radius: 6px; }
150
+ th, td { padding: 10px 8px; font-size: 12px; white-space: normal; overflow-wrap: anywhere; word-break: normal; }
151
+ th br, th .muted { display: none; }
152
+ pre { padding: 10px; font-size: 11px; }
153
+ .setup-grid { grid-template-columns: 1fr 1fr; gap: 8px; }
154
+ .setup-card { padding: 8px 10px; }
155
+ .setup-card .url { font-size: 12px; margin-bottom: 4px; }
156
+ .setup-card .action { font-size: 10px; }
157
+ .build-grid { grid-template-columns: 1fr !important; }
158
+ }
159
+
160
+ .build-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 16px; }
161
+ .build-card { background:#f8fafc; padding:16px; border-radius:10px; border:1px solid #edf2f7; }
162
+ </style>
163
+ </head>
164
+ <body>
165
+ <div class="container">
166
+ <div class="header">
167
+ <div class="header-main">
168
+ <h1>🚀 ${PLUGIN_NAME}</h1>
169
+ <p>用于 Electron 宿主接入 Vite ESM + HMR 的调试面板</p>
170
+ </div>
171
+ <div class="header-status">
172
+ <div class="status-pill">组件<b>${componentMapCount}</b></div>
173
+ <div class="status-pill">入口<b style="color: ${defaultEntryExists ? '#10b981' : '#ef4444'}">${defaultEntryExists ? '✓' : '✗'}</b></div>
174
+ <div class="status-pill">端口<b id="actualPortDisplay">-</b></div>
175
+ </div>
176
+ </div>
177
+
178
+ <div class="card">
179
+ <h3>🌐 环境快速设置</h3>
180
+ <div class="alert alert-info">
181
+ <span>💡</span>
182
+ <div>在宿主 Electron 的控制台 (DevTools Console) 执行下方卡片中的命令,即可完成环境切换。</div>
183
+ </div>
184
+
185
+ <div id="originGrid" class="setup-grid"></div>
186
+
187
+ <div class="manual-box">
188
+ <div class="manual-header">
189
+ <span>手动复制完整脚本</span>
190
+ <button id="copyFullCmd" class="copy-btn-link">复制原始命令</button>
191
+ </div>
192
+ <pre id="fullCmdPreview"></pre>
193
+ </div>
194
+ </div>
195
+
196
+ <div class="card">
197
+ <h3>📋 当前组件配置</h3>
198
+
199
+ <div class="info-grid">
200
+ <div class="info-label">项目目录:</div>
201
+ <div class="info-value">
202
+ <a href="${toVsCodeUrl(projectRoot)}" class="link-code" title="点击在 IDE 中打开"><code>${escapeHtml(projectRootDisplay)}</code></a>
203
+ </div>
204
+ <div class="info-label">配置文件:</div>
205
+ <div class="info-value">
206
+ ${configFilePath ? `
207
+ <a href="${toVsCodeUrl(configFilePath)}" class="link-code" title="点击在 IDE 中打开"><code>${escapeHtml(node_path.basename(configFilePath))}</code></a>
208
+ ${annotatedConfigHtml ? `
209
+ <details style="margin-top: 8px; border: none; padding: 0; background: transparent; box-shadow: none;">
210
+ <summary style="font-size: 12px; color: var(--p); font-weight: 500;">查看配置源码与解析结果</summary>
211
+ <pre style="margin-top: 8px; max-height: 400px; overflow: auto; background: #f1f5f9; border-color: #cbd5e1; font-size: 11px; padding: 12px; border-radius: 6px;">${annotatedConfigHtml}</pre>
212
+ </details>
213
+ ` : ''}
214
+ ` : '<span class="muted">未找到</span>'}
215
+ </div>
216
+ </div>
217
+
218
+ ${!hasConfig || isWildcardOnly ? `
219
+ <div class="alert alert-info">
220
+ <span>💡</span>
221
+ <div>
222
+ <b>全局通配模式已启用</b>
223
+ <div style="font-size: 13px; margin-top: 2px;">Map 中包含通配符 "*"。所有组件请求将默认加载入口。构建前请在 <code>vite.config.ts</code> 中显式指定组件映射。</div>
224
+ </div>
225
+ </div>
226
+ ` : ''}
227
+
228
+ ${!defaultEntryExists ? `
229
+ <div class="alert alert-error">
230
+ <span>⚠️</span>
231
+ <div>
232
+ <b>默认入口文件缺失</b>
233
+ <div style="font-size: 13px; margin-top: 2px;">找不到路径:<a href="${toVsCodeUrl(defaultEntryAbs)}" class="link-code"><code>${escapeHtml(getShortPath(defaultEntryAbs))}</code></a></div>
234
+ </div>
235
+ </div>
236
+ ` : ''}
237
+
238
+ ${hasConfig ? `
239
+ <table>
240
+ <thead><tr><th>组件名称 <small class="muted">(Component Name)</small></th><th>映射入口 <small class="muted">(Short Path)</small></th></tr></thead>
241
+ <tbody>
242
+ ${Object.entries(resolvedDevComponentMap).map(([name, entry])=>{
243
+ const abs = entryPathMap[name];
244
+ const displayPath = abs ? getShortPath(abs) : entry;
245
+ return `<tr>
246
+ <td><code class="code-name">${name}</code></td>
247
+ <td>
248
+ ${abs ? `<a href="${toVsCodeUrl(abs)}" class="link-code" title="点击在 IDE 中打开"><code>${escapeHtml(displayPath)}</code></a>` : `<code>${escapeHtml(entry)}</code>`}
249
+ </td>
250
+ </tr>`;
251
+ }).join('')}
252
+ </tbody>
253
+ </table>
254
+ ` : '<div class="alert alert-warning">未发现任何配置组件</div>'}
255
+
256
+ <details>
257
+ <summary>插件参数与配置说明 (Plugin API)</summary>
258
+ <div class="parameter-desc">
259
+ <div style="margin-bottom: 24px;">
260
+ <pre style="background: #ebf8ff; color: #2c5282; border-color: #bee3f8; font-size: 14px; font-weight: 600;">reactHmrHostPlugin(devComponentMap?, options?)</pre>
261
+ <div class="muted" style="margin-top: 8px;">
262
+ 支持单组件简写、对象全量映射,以及透传 Vite 原生配置。
263
+ </div>
264
+ </div>
265
+
266
+ <div class="parameter-item">
267
+ <span class="parameter-name">1. devComponentMap (第一个参数)</span>
268
+ <div class="parameter-info">
269
+ 定义组件名与本地入口文件的映射:
270
+ <ul style="margin-top: 8px;">
271
+ <li><b>单组件简写</b>:<code class="val">'Demo'</code> ➔ 自动关联 <code>{ Demo: '/' }</code>。</li>
272
+ <li><b>通配符映射</b>:<code class="val">'*'</code> ➔ 匹配所有组件名。支持 <code class="val">'/'</code> (默认入口) 或具体的相对/绝对路径。</li>
273
+ <li><b>多组件映射</b>:支持具体的相对/绝对路径。</li>
274
+ </ul>
275
+ <pre><span class="cmt">// Option 1: Shorthand (Default)</span>
276
+ reactHmrHostPlugin(<span class="str">'Demo'</span>)
277
+
278
+ <span class="cmt">// Option 2: Explicit Mapping with Wildcard</span>
279
+ reactHmrHostPlugin({
280
+ <span class="str">'*'</span>: <span class="str">'/'</span>, <span class="cmt">// Wildcard to default entry</span>
281
+ <span class="str">'Card'</span>: <span class="str">'src/Card.tsx'</span> <span class="cmt">// Specific file</span>
282
+ })</pre>
283
+ <div class="muted" style="font-size: 12px; margin-top: 8px; background: #fffbeb; padding: 8px 12px; border-radius: 6px; border: 1px solid #fef3c7; color: #92400e;">
284
+ 💡 <b>关于默认入口 (/)</b>:表示使用工程默认入口文件。查找顺序:优先 <code>src/App.{tsx,jsx}</code>,其次 <code>src/index.{tsx,jsx}</code>。
285
+ </div>
286
+ </div>
287
+ </div>
288
+ <div class="parameter-item">
289
+ <span class="parameter-name">2. options (第二个参数)</span>
290
+ <div class="parameter-info">
291
+ 高级配置(深度合并):
292
+ <ul style="margin-top: 8px;">
293
+ <li><code class="kw">css</code>:
294
+ <ul>
295
+ <li><b>默认值:</b><code>{ modules: { generateScopedName: <span class="str">'[name]__[local]___[hash:base64:5]'</span> } }</code>。</li>
296
+ <li>传 <code class="kw">false</code> 禁用配置;传对象则进行深度合并。</li>
297
+ <li>详细配置请参考 <a href="https://cn.vite.dev/config/shared-options#css-modules" target="_blank" style="color:#3b82f6;">Vite CSS 官方文档 ↗</a></li>
298
+ </ul>
299
+ <pre><span class="cmt">// Disable plugin CSS config or provide custom overrides</span>
300
+ reactHmrHostPlugin(<span class="str">'Demo'</span>, { css: <span class="kw">false</span> })
301
+ reactHmrHostPlugin(<span class="str">'Demo'</span>, { css: { ... } })</pre>
302
+ </li>
303
+ <li style="margin-top: 12px;"><code class="kw">build</code>:
304
+ <ul>
305
+ <li><b>仅在 lib 构建模式下生效</b>。内置默认值:</li>
306
+ <pre style="font-size: 11px; color: #4a5568;">formats: [<span class="str">'umd'</span>], fileName: <span class="str">'[name].js'</span>, inlineDynamicImports: <span class="kw">true</span>,
307
+ external: [<span class="str">'react'</span>, <span class="str">'react-dom'</span>, <span class="str">'react-dom/client'</span>, <span class="str">'typescript'</span>],
308
+ globals: { react: <span class="str">'React'</span>, <span class="str">'react-dom'</span>: <span class="str">'ReactDOM'</span>, ... }</pre>
309
+ <li>合并规则:用户配置覆盖默认项。</li>
310
+ <li>详细配置请参考 <a href="https://cn.vite.dev/config/build-options" target="_blank" style="color:#3b82f6;">Vite 构建官方文档 ↗</a></li>
311
+ </ul>
312
+ <pre><span class="cmt">// Example: Disable asset inlining during build</span>
313
+ reactHmrHostPlugin(<span class="str">'Demo'</span>, {
314
+ build: { assetsInlineLimit: <span class="val">0</span> }
315
+ })</pre>
316
+ </li>
317
+ <li style="margin-top: 12px;"><code class="kw">open</code>:
318
+ <ul>
319
+ <li><b>默认值:</b><code class="kw">false</code>。</li>
320
+ <li>是否在启动 Vite 开发服务器后自动在浏览器中打开此调试面板。</li>
321
+ </ul>
322
+ <pre><span class="cmt">// Enable auto-open</span>
323
+ reactHmrHostPlugin(<span class="str">'Demo'</span>, { open: <span class="kw">true</span> })</pre>
324
+ </li>
325
+ </ul>
326
+ </div>
327
+ </div>
328
+ </div>
329
+ </details>
330
+ </div>
331
+
332
+ <div class="card">
333
+ <h3>📦 构建与部署</h3>
334
+ <p class="muted">执行 <code>vite build --mode lib</code> 将组件打包为 UMD 格式以供发布。</p>
335
+
336
+ <div class="build-grid">
337
+ <div class="build-card">
338
+ <div style="font-weight:600; font-size:13px; margin-bottom:8px; color: var(--t);">输出结构 (Output)</div>
339
+ <pre style="margin:0; padding:0; background:transparent; border:none; font-size:12px; color:#4a5568;">
340
+ JS: <span class="str">dist/&lt;name&gt;/&lt;name&gt;.js</span>
341
+ CSS: <span class="str">dist/&lt;name&gt;/&lt;name&gt;.css</span></pre>
342
+ </div>
343
+ <div class="build-card">
344
+ <div style="font-weight:600; font-size:13px; margin-bottom:8px; color: var(--t);">外部依赖 (External)</div>
345
+ <pre style="margin:0; padding:0; background:transparent; border:none; font-size:12px; color:#4a5568;">
346
+ <span class="kw">react</span> ➔ <span class="val">React</span>
347
+ <span class="kw">react-dom</span> ➔ <span class="val">ReactDOM</span></pre>
348
+ </div>
349
+ </div>
350
+
351
+ <details>
352
+ <summary>构建导出 (Export) 智能分析逻辑</summary>
353
+ <div style="margin-top: 12px; font-size: 13px;">
354
+ <p class="muted">插件会使用 AST 分析入口文件,确保 UMD 包具备正确的导出:</p>
355
+ <ul class="muted" style="line-height: 1.8;">
356
+ <li>如果有 <code>export default</code>,直接作为组件入口。</li>
357
+ <li>如果没有 Default 但只有一个命名导出,自动将其关联为 Default。</li>
358
+ <li>如果有多个命名导出,必须有一个与 <code>componentName</code> 同名,否则会报错提醒。</li>
359
+ </ul>
360
+ </div>
361
+ </details>
362
+ </div>
363
+
364
+ <div class="card">
365
+ <h3>📊 运行指标 & 参考</h3>
366
+ <div class="stats-grid">
367
+ <div class="stat-card"><div class="value">${stats.contract.count}</div><div class="label">Contract 请求</div></div>
368
+ <div class="stat-card"><div class="value">${stats.init.count}</div><div class="label">Init 注入</div></div>
369
+ <div class="stat-card"><div class="value">${stats.runtime.count}</div><div class="label">Runtime 加载</div></div>
370
+ </div>
371
+
372
+ <details>
373
+ <summary>技术端点与 HMR 事件 (Internal Reference)</summary>
374
+ <div style="margin-top: 12px;">
375
+ <pre style="font-size: 12px; line-height: 1.7;">
376
+ <span class="kw">Endpoints:</span>
377
+ - Contract: <span class="str">${STABLE_CONTRACT_PATH}</span>
378
+ - Init: <span class="str">${STABLE_INIT_PATH}</span>
379
+ - Runtime: <span class="str">${STABLE_REACT_RUNTIME_PATH}</span>
380
+
381
+ <span class="kw">HMR Events:</span>
382
+ - Reload: <span class="val">${EVENT_FULL_RELOAD}</span>
383
+ - Update: <span class="val">${EVENT_HMR_UPDATE}</span></pre>
384
+ <p class="muted" style="font-size: 12px; margin-top: 12px; background: #fffbeb; padding: 10px 14px; border-radius: 6px; border: 1px solid #fef3c7; color: #92400e;">
385
+ 💡 <b>重要提示:</b>在 Electron 环境下,静态资源必须通过 <code>import</code> 引入才能被桥接插件正确拦截和路径重写。
386
+ </p>
387
+ </div>
388
+ </details>
389
+ </div>
390
+
391
+ <div style="text-align: center; margin-top: 32px; padding-bottom: 24px;">
392
+ <a href="${STABLE_DEBUG_JSON_PATH}" target="_blank" style="font-size: 13px; color: #3b82f6; text-decoration: none; font-weight: 500;">查看原始协议 JSON 数据 ➔</a>
393
+ </div>
394
+ </div>
395
+
396
+ <script>
397
+ (function() {
398
+ const serverOrigins = ${JSON.stringify(originCandidates)};
399
+ const current = location.origin;
400
+ const origins = [...serverOrigins];
401
+
402
+ // 确保当前访问地址也在候选列表中
403
+ if (!origins.includes(current)) origins.unshift(current);
404
+
405
+ const seen = new Set();
406
+ const uniqueOrigins = origins.filter(o => {
407
+ if (seen.has(o)) return false;
408
+ seen.add(o);
409
+ return true;
410
+ });
411
+
412
+ const grid = document.getElementById('originGrid');
413
+ const fullCmdPreview = document.getElementById('fullCmdPreview');
414
+ const copyFullBtn = document.getElementById('copyFullCmd');
415
+
416
+ function makeCmd(origin) {
417
+ return "localStorage.setItem('VITE_DEV_SERVER_ORIGIN', '" + origin + "'); location.reload();";
418
+ }
419
+
420
+ function selectOrigin(origin, card) {
421
+ // 更新卡片激活状态
422
+ document.querySelectorAll('.setup-card').forEach(c => c.classList.remove('active'));
423
+ if (card) card.classList.add('active');
424
+
425
+ // 更新下方预览脚本
426
+ fullCmdPreview.textContent = makeCmd(origin);
427
+ }
428
+
429
+ function copy(text, successCb) {
430
+ const ta = document.createElement('textarea');
431
+ ta.value = text;
432
+ document.body.appendChild(ta);
433
+ ta.select();
434
+ document.execCommand('copy');
435
+ document.body.removeChild(ta);
436
+ if (successCb) successCb();
437
+ }
438
+
439
+ uniqueOrigins.forEach(origin => {
440
+ const isLocal = origin.includes('localhost') || origin.includes('127.0.0.1');
441
+ const displayUrl = origin.indexOf('://') > -1 ? origin.split('://')[1] : origin;
442
+ const card = document.createElement('div');
443
+ card.className = 'setup-card' + (origin === current ? ' active' : '');
444
+ card.innerHTML =
445
+ '<span class="type">' + (isLocal ? '本地回路 (Local)' : '局域网 (LAN)') + '</span>' +
446
+ '<span class="url">' + displayUrl + '</span>' +
447
+ '<div class="action">点击复制切换命令</div>';
448
+ card.onclick = () => {
449
+ selectOrigin(origin, card);
450
+ copy(makeCmd(origin), () => {
451
+ const actionEl = card.querySelector('.action');
452
+ const originalAction = actionEl.innerHTML;
453
+ actionEl.innerHTML = '<span>✅</span> 命令已复制成功';
454
+ card.style.borderColor = '#10b981';
455
+ setTimeout(() => {
456
+ actionEl.innerHTML = originalAction;
457
+ card.style.borderColor = '';
458
+ }, 2000);
459
+ });
460
+ };
461
+ grid.appendChild(card);
462
+ });
463
+
464
+ selectOrigin(current, null); // 初始化预览
465
+ copyFullBtn.onclick = () => copy(fullCmdPreview.textContent, () => {
466
+ copyFullBtn.textContent = '✓ 已成功复制';
467
+ setTimeout(() => { copyFullBtn.textContent = '复制原始命令'; }, 2000);
468
+ });
469
+
470
+ const serverActualPort = ${'number' == typeof actualPort ? actualPort : 'null'};
471
+ document.getElementById('actualPortDisplay').textContent = serverActualPort || location.port || '-';
472
+ })();
473
+ </script>
474
+ </body>
475
+ </html>`;
476
+ }
477
+ function getLanIPv4Hosts() {
478
+ const nets = node_os.networkInterfaces();
479
+ const out = new Set();
480
+ for (const items of Object.values(nets))if (items) {
481
+ for (const info of items)if ('IPv4' === info.family && !info.internal) out.add(info.address);
482
+ }
483
+ return Array.from(out);
484
+ }
485
+ function toFsPathFromViteEntry(entry) {
486
+ if (!entry.startsWith('/@fs')) return null;
487
+ let p = entry.slice(4);
488
+ if (p.startsWith('/') && /\/[A-Za-z]:\//.test(p)) p = p.slice(1);
489
+ return p;
490
+ }
491
+ function tryResolveWithExtensions(p) {
492
+ const exts = [
493
+ '.tsx',
494
+ '.jsx',
495
+ '.ts',
496
+ '.js'
497
+ ];
498
+ if (node_fs.existsSync(p)) return p;
499
+ const parsed = node_path.parse(p);
500
+ if (parsed.ext) for (const ext of exts){
501
+ const cand = node_path.join(parsed.dir, `${parsed.name}${ext}`);
502
+ if (node_fs.existsSync(cand)) return cand;
503
+ }
504
+ else for (const ext of exts){
505
+ const cand = `${p}${ext}`;
506
+ if (node_fs.existsSync(cand)) return cand;
507
+ }
508
+ return null;
509
+ }
510
+ function resolveEntryAbsPath(rootDir, entry, defaultEntryAbs, fallbackRoot) {
511
+ const tryResolveWithBase = (baseDir)=>{
512
+ if ('/' === entry) {
513
+ if (!defaultEntryAbs) throw new Error(`${PLUGIN_LOG_PREFIX} 使用 / 作为入口时,必须提供 defaultEntryAbs 参数`);
514
+ return defaultEntryAbs;
515
+ }
516
+ const fsPath = toFsPathFromViteEntry(entry);
517
+ if (fsPath) return tryResolveWithExtensions(fsPath);
518
+ if (node_path.isAbsolute(entry)) return tryResolveWithExtensions(entry);
519
+ if (entry.startsWith('/')) {
520
+ const maybe = node_path.resolve(baseDir, entry.slice(1));
521
+ return tryResolveWithExtensions(maybe);
522
+ }
523
+ const rel = node_path.resolve(baseDir, entry);
524
+ return tryResolveWithExtensions(rel);
525
+ };
526
+ const resolved = tryResolveWithBase(rootDir);
527
+ if (resolved) return resolved;
528
+ if (fallbackRoot && fallbackRoot !== rootDir) return tryResolveWithBase(fallbackRoot);
529
+ return resolved;
530
+ }
531
+ function openBrowser(url) {
532
+ const bridgePath = STABLE_DEBUG_HTML_PATH;
533
+ if ('darwin' === process.platform) {
534
+ const script = `
535
+ try
536
+ tell application "Google Chrome"
537
+ repeat with w in windows
538
+ repeat with t in tabs of w
539
+ if URL of t contains "${bridgePath}" then
540
+ set URL of t to "${url}"
541
+ set active tab index of w to (get index of t)
542
+ set index of w to 1
543
+ activate
544
+ return "found"
545
+ end if
546
+ end repeat
547
+ end repeat
548
+ end tell
549
+ end try
550
+
551
+ try
552
+ tell application "Microsoft Edge"
553
+ repeat with w in windows
554
+ repeat with t in tabs of w
555
+ if URL of t contains "${bridgePath}" then
556
+ set URL of t to "${url}"
557
+ set active tab index of w to (get index of t)
558
+ set index of w to 1
559
+ activate
560
+ return "found"
561
+ end if
562
+ end repeat
563
+ end repeat
564
+ end tell
565
+ end try
566
+
567
+ try
568
+ tell application "Safari"
569
+ repeat with w in windows
570
+ repeat with t in tabs of w
571
+ if URL of t contains "${bridgePath}" then
572
+ set URL of t to "${url}"
573
+ set current tab of w to t
574
+ set index of w to 1
575
+ activate
576
+ return "found"
577
+ end if
578
+ end repeat
579
+ end repeat
580
+ end tell
581
+ end try
582
+
583
+ return "not_found"
584
+ `.replace(/\n/g, ' ');
585
+ exec(`osascript -e '${script}'`, (err, stdout)=>{
586
+ if (err || 'found' !== stdout.trim()) exec(`open "${url}"`);
587
+ });
588
+ return;
589
+ }
590
+ if ('win32' === process.platform) return void exec(`start "" "${url}"`);
591
+ exec(`xdg-open "${url}"`);
592
+ }
593
+ const globalState = globalThis;
594
+ let didOpenBrowser = Boolean(globalState[DEV_TO_REACT_DID_OPEN_BROWSER_KEY]);
595
+ function installDebugTools(server, ctx, state) {
596
+ const printStartupDebugUrl = ()=>{
597
+ if (state.didPrintStartupDebugUrl) return;
598
+ state.didPrintStartupDebugUrl = true;
599
+ const isHttps = !!server.config.server.https;
600
+ const proto = isHttps ? 'https' : 'http';
601
+ const addr = server.httpServer?.address();
602
+ const actualPort = addr && 'object' == typeof addr ? addr.port : void 0;
603
+ const port = (()=>{
604
+ if ('number' == typeof actualPort) return actualPort;
605
+ if ('number' == typeof server.config.server.port) return server.config.server.port;
606
+ return 5173;
607
+ })();
608
+ const lanHosts = getLanIPv4Hosts();
609
+ const candidateHosts = [
610
+ 'localhost',
611
+ '127.0.0.1',
612
+ ...lanHosts
613
+ ];
614
+ const urls = candidateHosts.map((h)=>`${proto}://${h}:${port}${STABLE_DEBUG_HTML_PATH}`);
615
+ const logger = server.config.logger;
616
+ const info = 'function' == typeof logger?.info ? logger.info.bind(logger) : console.log;
617
+ info('');
618
+ info(`${PLUGIN_LOG_PREFIX} Debug panel:`);
619
+ urls.forEach((u)=>info(` ${u}`));
620
+ info(` JSON: ${proto}://localhost:${port}${STABLE_DEBUG_JSON_PATH}`);
621
+ info('');
622
+ if (ctx.open && !didOpenBrowser) {
623
+ didOpenBrowser = true;
624
+ globalState[DEV_TO_REACT_DID_OPEN_BROWSER_KEY] = true;
625
+ openBrowser(urls[0]);
626
+ }
627
+ };
628
+ try {
629
+ if (server.httpServer) {
630
+ server.httpServer.once('listening', printStartupDebugUrl);
631
+ if (server.httpServer.listening) printStartupDebugUrl();
632
+ }
633
+ } catch {}
634
+ server.middlewares.use((req, res, next)=>{
635
+ const url = req.url || '';
636
+ const pathname = String(url).split('?')[0];
637
+ const now = Date.now();
638
+ if (pathname === STABLE_CONTRACT_PATH) {
639
+ ctx.stats.contract.count += 1;
640
+ ctx.stats.contract.lastAt = now;
641
+ } else if (pathname === STABLE_INIT_PATH) {
642
+ ctx.stats.init.count += 1;
643
+ ctx.stats.init.lastAt = now;
644
+ } else if (pathname === STABLE_REACT_RUNTIME_PATH) {
645
+ ctx.stats.runtime.count += 1;
646
+ ctx.stats.runtime.lastAt = now;
647
+ }
648
+ if (url === STABLE_BASE_PATH || url === `${STABLE_BASE_PATH}/`) {
649
+ res.statusCode = 302;
650
+ res.setHeader('Location', STABLE_DEBUG_HTML_PATH);
651
+ res.end();
652
+ return;
653
+ }
654
+ if (url.startsWith(STABLE_DEBUG_JSON_PATH)) {
655
+ const isHttps = !!server.config.server.https;
656
+ const proto = isHttps ? 'https' : 'http';
657
+ const hostHeader = String(req.headers.host || '');
658
+ const addr = server.httpServer?.address();
659
+ const actualPort = addr && 'object' == typeof addr ? addr.port : void 0;
660
+ const lanHosts = getLanIPv4Hosts();
661
+ const candidateHosts = [
662
+ 'localhost',
663
+ '127.0.0.1',
664
+ ...lanHosts
665
+ ];
666
+ const originCandidates = candidateHosts.map((h)=>`${proto}://${h}${actualPort ? `:${actualPort}` : ''}`);
667
+ const requestOrigin = hostHeader ? `${proto}://${hostHeader}` : originCandidates[0];
668
+ const componentNames = Object.keys(ctx.contract?.dev?.componentMap || {});
669
+ const libComponentExample = componentNames.slice(0, 2).join(',') || 'Demo';
670
+ res.statusCode = 200;
671
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
672
+ res.end(JSON.stringify({
673
+ contract: ctx.contract,
674
+ stats: ctx.stats,
675
+ audit: ctx.audit,
676
+ server: {
677
+ protocol: proto,
678
+ hostHeader,
679
+ actualPort,
680
+ config: {
681
+ host: server.config.server.host,
682
+ port: server.config.server.port,
683
+ strictPort: server.config.server.strictPort,
684
+ cors: server.config.server.cors,
685
+ https: !!server.config.server.https
686
+ }
687
+ },
688
+ originCandidates,
689
+ usage: {
690
+ localStorageKey: 'VITE_DEV_SERVER_ORIGIN',
691
+ suggested: requestOrigin,
692
+ snippet: `localStorage.setItem('VITE_DEV_SERVER_ORIGIN', '${requestOrigin}'); location.reload();`,
693
+ libBuild: {
694
+ command: 'vite build --mode lib',
695
+ env: {
696
+ DEV_TO_REACT_LIB_SECTION: libComponentExample
697
+ },
698
+ output: {
699
+ dir: 'dist/<component>/',
700
+ js: '<component>.js (UMD, 尽量单文件 bundle)',
701
+ css: '<component>.css (如有样式)'
702
+ },
703
+ externals: [
704
+ 'react',
705
+ 'react-dom',
706
+ 'react-dom/client'
707
+ ],
708
+ umdGlobals: {
709
+ react: 'React',
710
+ 'react-dom': 'ReactDOM',
711
+ 'react-dom/client': 'ReactDOMClient'
712
+ },
713
+ jsx: 'classic (React.createElement)',
714
+ customizable: 'options.css (dev/build; false disables plugin-injected CSS config; object deep-merged) and options.build (lib mode only; deep-merged with internal defaults, user wins)'
715
+ }
716
+ },
717
+ tips: [
718
+ '宿主侧需设置 localStorage.VITE_DEV_SERVER_ORIGIN(可从 originCandidates 里选择一个可访问的 origin)。',
719
+ 'devComponentMap 的 key 必须与后端返回的 componentName 完全一致(严格匹配)。',
720
+ 'Fast Refresh 关键:必须先 import init.js(安装 react-refresh preamble),再 import react-dom/client。',
721
+ '如需产出可分发 UMD 包:使用 `vite build --mode lib`(仅构建 componentMap 指定的组件,输出到 dist/<component>/)。'
722
+ ]
723
+ }, null, 2));
724
+ return;
725
+ }
726
+ if (url.startsWith(STABLE_DEBUG_HTML_PATH)) {
727
+ const addr = server.httpServer?.address();
728
+ const actualPort = addr && 'object' == typeof addr ? addr.port : void 0;
729
+ const lanHosts = getLanIPv4Hosts();
730
+ const isHttps = !!server.config.server.https;
731
+ const proto = isHttps ? 'https' : 'http';
732
+ const candidateHosts = [
733
+ 'localhost',
734
+ '127.0.0.1',
735
+ ...lanHosts
736
+ ];
737
+ const originCandidates = candidateHosts.map((h)=>`${proto}://${h}${actualPort ? `:${actualPort}` : ''}`);
738
+ const serverConfigLite = {
739
+ host: server.config.server.host,
740
+ port: server.config.server.port,
741
+ strictPort: server.config.server.strictPort,
742
+ cors: server.config.server.cors,
743
+ https: !!server.config.server.https
744
+ };
745
+ let configFilePath;
746
+ const rootDir = server.config.root || process.cwd();
747
+ configFilePath = server.config.configFile || void 0;
748
+ if (!configFilePath) try {
749
+ const files = node_fs.readdirSync(rootDir);
750
+ const configFile = files.find((file)=>/^vite\.config\.(ts|js|mjs|cjs|cts)$/.test(file));
751
+ if (configFile) configFilePath = node_path.resolve(rootDir, configFile);
752
+ } catch {
753
+ const configFiles = [
754
+ 'vite.config.ts',
755
+ 'vite.config.js',
756
+ 'vite.config.mjs',
757
+ 'vite.config.cjs',
758
+ 'vite.config.cts'
759
+ ];
760
+ for (const file of configFiles){
761
+ const fullPath = node_path.resolve(rootDir, file);
762
+ if (node_fs.existsSync(fullPath)) {
763
+ configFilePath = fullPath;
764
+ break;
765
+ }
766
+ }
767
+ }
768
+ const entryPathMap = {};
769
+ for (const [componentName, entry] of Object.entries(ctx.contract.dev.componentMap)){
770
+ if ('*' === componentName) {
771
+ entryPathMap[componentName] = ctx.audit.defaultEntryAbs;
772
+ continue;
773
+ }
774
+ if (entry.startsWith('/@fs')) {
775
+ const fsPath = toFsPathFromViteEntry(entry);
776
+ if (fsPath) entryPathMap[componentName] = fsPath;
777
+ } else if ('/' === entry) entryPathMap[componentName] = ctx.audit.defaultEntryAbs;
778
+ else if (!entry.startsWith('http://') && !entry.startsWith('https://') && !entry.startsWith('/')) entryPathMap[componentName] = node_path.resolve(rootDir, entry);
779
+ }
780
+ const html = renderDebugHtml({
781
+ resolvedDevComponentMap: ctx.contract.dev.componentMap,
782
+ entryPathMap,
783
+ audit: ctx.audit,
784
+ stats: ctx.stats,
785
+ serverConfigLite,
786
+ originCandidates,
787
+ actualPort: 'number' == typeof actualPort ? actualPort : void 0,
788
+ configFilePath
789
+ });
790
+ res.statusCode = 200;
791
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
792
+ res.end(html);
793
+ return;
794
+ }
795
+ next();
796
+ });
797
+ }
798
+ function toViteFsPath(filePath) {
799
+ const normalized = filePath.replace(/\\/g, '/');
800
+ return normalized.startsWith('/') ? `/@fs${normalized}` : `/@fs/${normalized}`;
801
+ }
802
+ function resolveDefaultEntryAbs(rootDir) {
803
+ const appCandidates = [
804
+ node_path.resolve(rootDir, 'src/App.tsx'),
805
+ node_path.resolve(rootDir, 'src/App.jsx'),
806
+ node_path.resolve(rootDir, 'src/App.ts'),
807
+ node_path.resolve(rootDir, 'src/App.js')
808
+ ];
809
+ const foundApp = appCandidates.find((p)=>node_fs.existsSync(p));
810
+ if (foundApp) return foundApp;
811
+ const indexCandidates = [
812
+ node_path.resolve(rootDir, 'src/index.ts'),
813
+ node_path.resolve(rootDir, 'src/index.tsx'),
814
+ node_path.resolve(rootDir, 'src/index.jsx'),
815
+ node_path.resolve(rootDir, 'src/index.js')
816
+ ];
817
+ const foundIndex = indexCandidates.find((p)=>node_fs.existsSync(p));
818
+ if (foundIndex) return foundIndex;
819
+ return appCandidates[0];
820
+ }
821
+ function buildDevComponentMapFromRecord(rootDir, input, defaultEntryAbs, convertAt = false, fallbackRoot) {
822
+ const out = {};
823
+ for (const [componentName, entry] of Object.entries(input)){
824
+ if (!componentName || !entry) continue;
825
+ if ('/' === entry) {
826
+ out[componentName] = convertAt ? toViteFsPath(defaultEntryAbs) : '/';
827
+ continue;
828
+ }
829
+ if (entry.startsWith('http://') || entry.startsWith('https://') || entry.startsWith('/')) {
830
+ out[componentName] = entry;
831
+ continue;
832
+ }
833
+ const abs = node_path.isAbsolute(entry) ? entry : node_path.resolve(rootDir, entry);
834
+ let resolved = tryResolveWithExtensions(abs);
835
+ if (!resolved && fallbackRoot && fallbackRoot !== rootDir) {
836
+ const fallbackAbs = node_path.isAbsolute(entry) ? entry : node_path.resolve(fallbackRoot, entry);
837
+ resolved = tryResolveWithExtensions(fallbackAbs);
838
+ }
839
+ resolved = resolved || abs;
840
+ out[componentName] = toViteFsPath(resolved);
841
+ }
842
+ return out;
843
+ }
844
+ function getFsPathFromViteEntry(entry) {
845
+ if (!entry.startsWith('/@fs')) return null;
846
+ let p = entry.slice(4);
847
+ if (p.startsWith('/') && /\/[A-Za-z]:\//.test(p)) p = p.slice(1);
848
+ return p;
849
+ }
850
+ function resolveDevComponentConfig(rootDir, devComponentMap, fallbackRoot) {
851
+ const defaultEntryAbs = resolveDefaultEntryAbs(rootDir);
852
+ const defaultEntry = toViteFsPath(defaultEntryAbs);
853
+ let resolvedDevComponentMap = {};
854
+ if ('string' == typeof devComponentMap) {
855
+ const name = devComponentMap.trim();
856
+ resolvedDevComponentMap = name ? buildDevComponentMapFromRecord(rootDir, {
857
+ [name]: '/'
858
+ }, defaultEntryAbs, false, fallbackRoot) : {
859
+ '*': '/'
860
+ };
861
+ } else {
862
+ const input = devComponentMap ?? {};
863
+ resolvedDevComponentMap = 0 === Object.keys(input).length ? {
864
+ '*': '/'
865
+ } : buildDevComponentMapFromRecord(rootDir, input, defaultEntryAbs, false, fallbackRoot);
866
+ }
867
+ const audit = (()=>{
868
+ const missing = [];
869
+ for (const [componentName, entry] of Object.entries(resolvedDevComponentMap)){
870
+ const fsPath = getFsPathFromViteEntry(entry);
871
+ if (fsPath) {
872
+ const resolved = tryResolveWithExtensions(fsPath);
873
+ if (!resolved || !node_fs.existsSync(resolved)) missing.push({
874
+ componentName,
875
+ filePath: fsPath
876
+ });
877
+ }
878
+ }
879
+ return {
880
+ defaultEntryAbs,
881
+ defaultEntryExists: node_fs.existsSync(defaultEntryAbs),
882
+ componentMapCount: Object.keys(resolvedDevComponentMap).length,
883
+ missingEntries: missing
884
+ };
885
+ })();
886
+ return {
887
+ defaultEntryAbs,
888
+ defaultEntry,
889
+ componentMap: resolvedDevComponentMap,
890
+ audit
891
+ };
892
+ }
893
+ function isLibBuild(env) {
894
+ return env?.command === 'build' && env?.mode === 'lib';
895
+ }
896
+ function toSafeOutDirName(componentName) {
897
+ return componentName.replace(/[\\/]/g, '_').replace(/\.\./g, '_');
898
+ }
899
+ function toSafeUmdName(componentName) {
900
+ let s = componentName.replace(/[^A-Za-z0-9_$]+/g, '_');
901
+ if (!s) s = 'ViteDevComponent';
902
+ if (/^\d/.test(s)) s = `_${s}`;
903
+ return s;
904
+ }
905
+ function isValidJsIdentifier(name) {
906
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
907
+ }
908
+ function analyzeExports(filePath) {
909
+ if (!node_fs.existsSync(filePath)) return {
910
+ hasDefault: false,
911
+ namedExports: []
912
+ };
913
+ const content = node_fs.readFileSync(filePath, 'utf-8');
914
+ const namedExports = [];
915
+ let hasDefault = false;
916
+ let scriptKind = typescript.ScriptKind.TS;
917
+ const ext = node_path.extname(filePath).toLowerCase();
918
+ if ('.tsx' === ext) scriptKind = typescript.ScriptKind.TSX;
919
+ else if ('.jsx' === ext) scriptKind = typescript.ScriptKind.JSX;
920
+ else if ('.js' === ext) scriptKind = typescript.ScriptKind.JS;
921
+ else if ('.ts' === ext) scriptKind = typescript.ScriptKind.TS;
922
+ let sourceFile;
923
+ try {
924
+ sourceFile = typescript.createSourceFile(filePath, content, typescript.ScriptTarget.Latest, true, scriptKind);
925
+ } catch (parseError) {
926
+ throw new Error(`${PLUGIN_LOG_PREFIX} 无法解析入口文件 "${filePath}"。\n解析错误: ${parseError instanceof Error ? parseError.message : String(parseError)}\n请确保文件是有效的 TypeScript/JavaScript 文件。`);
927
+ }
928
+ typescript.getPreEmitDiagnostics(typescript.createProgram([
929
+ filePath
930
+ ], {
931
+ target: typescript.ScriptTarget.Latest,
932
+ module: typescript.ModuleKind.ESNext,
933
+ jsx: scriptKind === typescript.ScriptKind.TSX || scriptKind === typescript.ScriptKind.JSX ? typescript.JsxEmit.React : void 0
934
+ }));
935
+ function visit(node) {
936
+ if (typescript.isExportAssignment(node)) {
937
+ if (true !== node.isExportEquals) hasDefault = true;
938
+ }
939
+ if (typescript.isFunctionDeclaration(node) || typescript.isClassDeclaration(node) || typescript.isVariableStatement(node) || typescript.isInterfaceDeclaration(node) || typescript.isTypeAliasDeclaration(node) || typescript.isEnumDeclaration(node)) {
940
+ const modifiers = typescript.getModifiers(node);
941
+ if (modifiers?.some((m)=>m.kind === typescript.SyntaxKind.ExportKeyword)) if (modifiers.some((m)=>m.kind === typescript.SyntaxKind.DefaultKeyword)) hasDefault = true;
942
+ else {
943
+ if (typescript.isFunctionDeclaration(node) && node.name) namedExports.push(node.name.text);
944
+ if (typescript.isClassDeclaration(node) && node.name) namedExports.push(node.name.text);
945
+ if (typescript.isVariableStatement(node)) node.declarationList.declarations.forEach((decl)=>{
946
+ if (typescript.isIdentifier(decl.name)) namedExports.push(decl.name.text);
947
+ });
948
+ if (typescript.isInterfaceDeclaration(node) && node.name) namedExports.push(node.name.text);
949
+ if (typescript.isTypeAliasDeclaration(node) && node.name) namedExports.push(node.name.text);
950
+ if (typescript.isEnumDeclaration(node) && node.name) namedExports.push(node.name.text);
951
+ }
952
+ }
953
+ if (typescript.isExportDeclaration(node) && node.exportClause) {
954
+ if (typescript.isNamedExports(node.exportClause)) node.exportClause.elements.forEach((element)=>{
955
+ if (element.name) {
956
+ const exportName = element.name.text;
957
+ const { propertyName } = element;
958
+ if (propertyName && 'default' === propertyName.text) namedExports.push(exportName);
959
+ else if ('default' === exportName) hasDefault = true;
960
+ else namedExports.push(exportName);
961
+ }
962
+ });
963
+ else if (typescript.isNamespaceExport(node.exportClause)) namedExports.push(node.exportClause.name.text);
964
+ }
965
+ typescript.isExportDeclaration(node) && node.exportClause;
966
+ typescript.forEachChild(node, visit);
967
+ }
968
+ visit(sourceFile);
969
+ const uniqueExports = Array.from(new Set(namedExports));
970
+ if (!hasDefault && 0 === uniqueExports.length) {
971
+ const hasDefaultRegex = /export\s+default\s+/;
972
+ const hasNamedRegex = /export\s+(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
973
+ const regexHasDefault = hasDefaultRegex.test(content);
974
+ const regexNamedMatches = [];
975
+ let match;
976
+ while(null !== (match = hasNamedRegex.exec(content)))regexNamedMatches.push(match[1]);
977
+ if (regexHasDefault || regexNamedMatches.length > 0) {
978
+ console.warn(`${PLUGIN_LOG_PREFIX} 警告:AST 分析未检测到导出,但正则检测到:\n 文件: ${filePath}\n 正则检测 default: ${regexHasDefault}\n 正则检测命名导出: ${regexNamedMatches.join(', ') || '无'}\n 这可能是 AST 解析问题,将尝试继续构建。`);
979
+ if (regexHasDefault) hasDefault = true;
980
+ if (regexNamedMatches.length > 0) namedExports.push(...regexNamedMatches);
981
+ }
982
+ }
983
+ return {
984
+ hasDefault,
985
+ namedExports: Array.from(new Set(namedExports))
986
+ };
987
+ }
988
+ function generateLibVirtualEntryCode(params) {
989
+ const { defaultEntryAbs, componentName } = params;
990
+ if (!node_fs.existsSync(defaultEntryAbs)) throw new Error(`${PLUGIN_LOG_PREFIX} 入口文件不存在: "${defaultEntryAbs}"\n请检查文件路径是否正确。`);
991
+ const actualFile = tryResolveWithExtensions(defaultEntryAbs) || defaultEntryAbs;
992
+ if (!node_fs.existsSync(actualFile)) throw new Error(`${PLUGIN_LOG_PREFIX} 入口文件不存在: "${defaultEntryAbs}"\n尝试解析后的路径: "${actualFile}"\n请检查文件路径是否正确。`);
993
+ const importTarget = pathToFileURL(actualFile).href;
994
+ let exports;
995
+ try {
996
+ exports = analyzeExports(actualFile);
997
+ } catch (error) {
998
+ const errorMsg = error instanceof Error ? error.message : String(error);
999
+ throw new Error(`${PLUGIN_LOG_PREFIX} 分析入口文件 "${actualFile}" 的导出时出错:\n${errorMsg}\n\n请检查文件:\n 1. 文件是否存在且可读\n 2. 文件是否有语法错误\n 3. 文件是否有导出(export default 或命名导出)\n\n原始路径: "${defaultEntryAbs}"\n实际解析路径: "${actualFile}"`);
1000
+ }
1001
+ let code;
1002
+ if (exports.hasDefault || 0 !== exports.namedExports.length) if (exports.hasDefault) {
1003
+ let prefer = '';
1004
+ if (exports.namedExports.includes(componentName)) prefer = isValidJsIdentifier(componentName) ? `mod.${componentName}` : `mod[${JSON.stringify(componentName)}]`;
1005
+ const pickedExpr = prefer ? `${prefer} || mod.default || mod` : 'mod.default || mod';
1006
+ code = `/** AUTO-GENERATED by ${PLUGIN_NAME} */
1007
+ import * as mod from ${JSON.stringify(importTarget)};
1008
+ const picked = ${pickedExpr};
1009
+ const Card = picked && picked.default ? picked.default : picked;
1010
+ export default Card;
1011
+ export * from ${JSON.stringify(importTarget)};
1012
+ `;
1013
+ } else if (1 === exports.namedExports.length) {
1014
+ const singleExport = exports.namedExports[0];
1015
+ const exportAccess = isValidJsIdentifier(singleExport) ? `mod.${singleExport}` : `mod[${JSON.stringify(singleExport)}]`;
1016
+ code = `/** AUTO-GENERATED by ${PLUGIN_NAME} */
1017
+ import * as mod from ${JSON.stringify(importTarget)};
1018
+ const Card = ${exportAccess};
1019
+ export default Card;
1020
+ export * from ${JSON.stringify(importTarget)};
1021
+ `;
1022
+ } else {
1023
+ const hasComponentNameExport = exports.namedExports.some((exp)=>exp === componentName);
1024
+ if (hasComponentNameExport) {
1025
+ const exportAccess = isValidJsIdentifier(componentName) ? `mod.${componentName}` : `mod[${JSON.stringify(componentName)}]`;
1026
+ code = `/** AUTO-GENERATED by ${PLUGIN_NAME} */
1027
+ import * as mod from ${JSON.stringify(importTarget)};
1028
+ const Card = ${exportAccess};
1029
+ export default Card;
1030
+ export * from ${JSON.stringify(importTarget)};
1031
+ `;
1032
+ } else throw new Error(`${PLUGIN_LOG_PREFIX} Entry file "${defaultEntryAbs}" has multiple named exports (${exports.namedExports.join(', ')}), but none match componentName "${componentName}".\n\nPlease resolve this by:\n\n 1. Adding a default export (Recommended):\n export default function ${componentName}() { ... }\n\n 2. Adding a named export called "${componentName}":\n export function ${componentName}() { ... }\n\n 3. Keeping only one named export (it will be used automatically).\n\nCurrent componentName: "${componentName}"\nCurrent named exports: ${exports.namedExports.join(', ')}`);
1033
+ }
1034
+ else throw new Error(`${PLUGIN_LOG_PREFIX} Entry file "${defaultEntryAbs}" does not have any exports.\n\nPlease ensure the file has one of the following:\n\n 1. export default (Recommended):\n export default function ${componentName}() { ... }\n or\n const ${componentName} = () => { ... };\n export default ${componentName};\n\n 2. Named export matching componentName:\n export function ${componentName}() { ... }\n or\n export const ${componentName} = () => { ... };\n\n 3. A single named export (any name):\n export function MyComponent() { ... }\n // If there is only one named export, it will be used automatically.\n\nCurrent componentName: "${componentName}"`);
1035
+ return code;
1036
+ }
1037
+ function getLibVirtualEntryPath(componentName) {
1038
+ return `virtual:${PLUGIN_NAME}-lib-entry:${componentName}`;
1039
+ }
1040
+ function normalizeLibCss(outDir, baseName) {
1041
+ if (!node_fs.existsSync(outDir)) return;
1042
+ const target = node_path.join(outDir, `${baseName}.css`);
1043
+ if (node_fs.existsSync(target)) return;
1044
+ const cssCandidates = [];
1045
+ const scanDir = (dir, depth)=>{
1046
+ if (depth < 0 || !node_fs.existsSync(dir)) return;
1047
+ const entries = node_fs.readdirSync(dir, {
1048
+ withFileTypes: true
1049
+ });
1050
+ for (const e of entries){
1051
+ const full = node_path.join(dir, e.name);
1052
+ if (e.isDirectory()) scanDir(full, depth - 1);
1053
+ else if (e.isFile() && e.name.endsWith('.css')) cssCandidates.push(full);
1054
+ }
1055
+ };
1056
+ scanDir(outDir, 2);
1057
+ if (1 !== cssCandidates.length) return;
1058
+ const from = cssCandidates[0];
1059
+ try {
1060
+ node_fs.renameSync(from, target);
1061
+ } catch {
1062
+ try {
1063
+ node_fs.copyFileSync(from, target);
1064
+ node_fs.unlinkSync(from);
1065
+ } catch {}
1066
+ }
1067
+ }
1068
+ function resolveBuildTargets(params) {
1069
+ const { componentMap, requestedRaw, defaultEntryAbs } = params;
1070
+ const componentNames = Object.keys(componentMap);
1071
+ const requestedList = requestedRaw ? requestedRaw.split(',').map((s)=>s.trim()).filter(Boolean) : [];
1072
+ const actualConfiguredNames = componentNames.filter((n)=>'*' !== n);
1073
+ if (actualConfiguredNames.length > 0) {
1074
+ if (requestedList.length > 0) {
1075
+ const picked = requestedList.filter((n)=>actualConfiguredNames.includes(n));
1076
+ if (0 === picked.length) throw new Error(`${PLUGIN_LOG_PREFIX} 指定的 component 不在配置列表中:${requestedRaw}`);
1077
+ return picked;
1078
+ }
1079
+ return actualConfiguredNames;
1080
+ }
1081
+ if (requestedList.length > 0) return requestedList;
1082
+ const fallbackName = node_path.parse(defaultEntryAbs || 'index').name || 'index';
1083
+ return [
1084
+ fallbackName
1085
+ ];
1086
+ }
1087
+ function generateLibBuildNextConfig(params) {
1088
+ const { rootDir, picked, componentMap, resolvedConfig, options, userConfig, configDir } = params;
1089
+ const outBase = toSafeOutDirName(picked);
1090
+ const outDir = node_path.resolve(rootDir, 'dist', outBase);
1091
+ let resolvedEntryAbs = resolvedConfig.defaultEntryAbs;
1092
+ const entryAbs = (()=>{
1093
+ const entryFromMap = componentMap[picked];
1094
+ if (entryFromMap) {
1095
+ const abs = resolveEntryAbsPath(rootDir, entryFromMap, resolvedConfig.defaultEntryAbs, configDir);
1096
+ if (!abs) throw new Error(`${PLUGIN_LOG_PREFIX} 无法解析入口:component="${picked}", entry="${entryFromMap}"`);
1097
+ resolvedEntryAbs = abs;
1098
+ }
1099
+ return getLibVirtualEntryPath(picked);
1100
+ })();
1101
+ const virtualEntryCode = (()=>{
1102
+ const entryFromMap = componentMap[picked];
1103
+ if (entryFromMap) {
1104
+ const abs = resolveEntryAbsPath(rootDir, entryFromMap, resolvedConfig.defaultEntryAbs, configDir);
1105
+ if (!abs) throw new Error(`${PLUGIN_LOG_PREFIX} 无法解析入口:component="${picked}", entry="${entryFromMap}"`);
1106
+ resolvedEntryAbs = abs;
1107
+ return generateLibVirtualEntryCode({
1108
+ rootDir,
1109
+ defaultEntryAbs: abs,
1110
+ componentName: picked
1111
+ });
1112
+ }
1113
+ resolvedEntryAbs = resolvedConfig.defaultEntryAbs;
1114
+ return generateLibVirtualEntryCode({
1115
+ rootDir,
1116
+ defaultEntryAbs: resolvedConfig.defaultEntryAbs,
1117
+ componentName: picked
1118
+ });
1119
+ })();
1120
+ const next = {
1121
+ root: rootDir,
1122
+ define: {
1123
+ ...userConfig.define || {},
1124
+ 'process.env.NODE_ENV': JSON.stringify('production')
1125
+ },
1126
+ build: {
1127
+ outDir,
1128
+ emptyOutDir: true,
1129
+ cssCodeSplit: false,
1130
+ lib: {
1131
+ entry: entryAbs,
1132
+ name: toSafeUmdName(picked),
1133
+ formats: [
1134
+ 'umd'
1135
+ ],
1136
+ fileName: ()=>`${outBase}.js`
1137
+ },
1138
+ rollupOptions: {
1139
+ external: [
1140
+ 'react',
1141
+ 'react-dom',
1142
+ 'react-dom/client'
1143
+ ],
1144
+ output: {
1145
+ inlineDynamicImports: true,
1146
+ exports: 'named',
1147
+ globals: {
1148
+ react: 'React',
1149
+ 'react-dom': 'ReactDOM',
1150
+ 'react-dom/client': 'ReactDOMClient'
1151
+ },
1152
+ assetFileNames: (assetInfo)=>{
1153
+ const name = assetInfo?.name || '';
1154
+ if (name.endsWith('.css')) return `${outBase}.css`;
1155
+ return 'assets/[name]-[hash][extname]';
1156
+ }
1157
+ }
1158
+ }
1159
+ }
1160
+ };
1161
+ if (options.build) {
1162
+ const merged = mergeConfig({
1163
+ build: next.build
1164
+ }, {
1165
+ build: options.build
1166
+ });
1167
+ next.build = merged.build;
1168
+ }
1169
+ return {
1170
+ next,
1171
+ outDir: next.build?.outDir || outDir,
1172
+ outBase,
1173
+ buildTargets: [],
1174
+ virtualEntryCode,
1175
+ resolvedEntryAbs
1176
+ };
1177
+ }
1178
+ function injectReactImport(code, id) {
1179
+ const cleanId = id.split('?')[0];
1180
+ if (cleanId.includes('/node_modules/')) return null;
1181
+ if (!/\.([tj])sx$/.test(cleanId)) return null;
1182
+ const hasReactRuntimeImport = /^\s*import\s+(?!type)(?:\*\s+as\s+React|React)\b[\s\S]*?\bfrom\s*['"]react['"]\s*;?/m.test(code);
1183
+ if (!hasReactRuntimeImport) return {
1184
+ code: `import * as React from 'react'\n${code}`,
1185
+ map: null
1186
+ };
1187
+ return null;
1188
+ }
1189
+ function transformAssetUrl(code, id) {
1190
+ if (id.includes('?import') || /\.(svg|png|jpg|jpeg|gif|webp|ico|bmp|tiff|woff|woff2|ttf|eot|otf)$/.test(id)) {
1191
+ const relativePathMatch = code.match(/export\s+default\s+["']([^"']+)["']/);
1192
+ if (relativePathMatch) {
1193
+ const relativePath = relativePathMatch[1];
1194
+ if (relativePath.startsWith('/') && !relativePath.startsWith('http')) {
1195
+ const transformedCode = code.replace(/export\s+default\s+["']([^"']+)["']/, `export default (() => {
1196
+ const path = "${relativePath}";
1197
+ if (typeof window !== 'undefined' && window[${JSON.stringify(DEV_TO_REACT_RESOLVE_ASSET_KEY)}]) {
1198
+ return window[${JSON.stringify(DEV_TO_REACT_RESOLVE_ASSET_KEY)}](path);
1199
+ }
1200
+ try {
1201
+ const ORIGIN = new URL(import.meta.url).origin;
1202
+ return path.startsWith('http') ? path : ORIGIN + path;
1203
+ } catch (e) {
1204
+ console.warn('${PLUGIN_LOG_PREFIX} Failed to resolve static asset URL:', path, e);
1205
+ return path;
1206
+ }
1207
+ })()`);
1208
+ return {
1209
+ code: transformedCode,
1210
+ map: null
1211
+ };
1212
+ }
1213
+ }
1214
+ }
1215
+ return null;
1216
+ }
1217
+ function transformViteDevCssAssetUrls(code, id) {
1218
+ const cleanId = id.split('?')[0];
1219
+ if (!/\.(css|less|sass|scss|styl|stylus)$/.test(cleanId)) return null;
1220
+ if (!code.includes('__vite__updateStyle') || !code.includes('const __vite__css')) return null;
1221
+ if (!/url\(\s*['"]?\//.test(code)) return null;
1222
+ if (code.includes('__dev_to_react_css')) return null;
1223
+ const updateCallRE = /__vite__updateStyle\(\s*__vite__id\s*,\s*__vite__css\s*\)/;
1224
+ if (!updateCallRE.test(code)) return null;
1225
+ const injected = `
1226
+ const __dev_to_react_resolveAsset = (path) => {
1227
+ if (!path) return path;
1228
+ if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('data:') || path.startsWith('blob:')) {
1229
+ return path;
1230
+ }
1231
+ try {
1232
+ const g = typeof globalThis !== 'undefined' ? globalThis : window;
1233
+ const fn = g && g[${JSON.stringify(DEV_TO_REACT_RESOLVE_ASSET_KEY)}];
1234
+ if (typeof fn === 'function') return fn(path);
1235
+ } catch {
1236
+ // ignore
1237
+ }
1238
+ try {
1239
+ const origin = new URL(import.meta.url).origin;
1240
+ return path.startsWith('/') ? origin + path : origin + '/' + path;
1241
+ } catch (e) {
1242
+ console.warn('${PLUGIN_LOG_PREFIX} Failed to resolve CSS asset URL:', path, e);
1243
+ return path;
1244
+ }
1245
+ };
1246
+ const __dev_to_react_css = __vite__css.replace(/url\\(\\s*(['"]?)(\\/(?!\\/)[^'")]+)\\1\\s*\\)/g, (_m, q, p) => {
1247
+ const next = __dev_to_react_resolveAsset(p);
1248
+ return 'url(' + q + next + q + ')';
1249
+ });
1250
+ `;
1251
+ const nextCode = code.replace(updateCallRE, `${injected}__vite__updateStyle(__vite__id, __dev_to_react_css)`);
1252
+ return {
1253
+ code: nextCode,
1254
+ map: null
1255
+ };
1256
+ }
1257
+ function createContractVirtualModuleCode(contract) {
1258
+ return `
1259
+ const CONTRACT = ${JSON.stringify(contract)};
1260
+ const ORIGIN = new URL(import.meta.url).origin;
1261
+ const G = typeof globalThis !== 'undefined' ? globalThis : window;
1262
+ const DEBUG_KEY = ${JSON.stringify(DEV_TO_REACT_DEBUG_STATE_KEY)};
1263
+ const STATE = (G[DEBUG_KEY] ||= { logged: {} });
1264
+ if (!STATE.logged.contract) {
1265
+ STATE.logged.contract = true;
1266
+ console.groupCollapsed('${PLUGIN_LOG_PREFIX} contract loaded');
1267
+ console.log('Origin:', ORIGIN);
1268
+ console.log('Paths:', CONTRACT.paths);
1269
+ console.log('Events:', CONTRACT.events);
1270
+ console.log('Dev mode:', CONTRACT?.dev?.mode);
1271
+ console.log('Default entry:', CONTRACT?.dev?.defaultEntry);
1272
+ console.log('devComponentMap keys:', Object.keys(CONTRACT?.dev?.componentMap || {}));
1273
+ console.log('Tip: open', ORIGIN + '${STABLE_DEBUG_HTML_PATH}');
1274
+ console.groupEnd();
1275
+ }
1276
+ export const DEV_TO_REACT_CONTRACT = CONTRACT;
1277
+ export default CONTRACT;
1278
+ `;
1279
+ }
1280
+ function createInitVirtualModuleCode() {
1281
+ const contractExportName = 'DEV_TO_REACT_CONTRACT';
1282
+ const globalKey = DEV_TO_REACT_CONTRACT_KEY;
1283
+ return `
1284
+ import "/@vite/client";
1285
+ import RefreshRuntime from "/@react-refresh";
1286
+
1287
+ import CONTRACT, { ${contractExportName} as CONTRACT_NAMED } from "${STABLE_CONTRACT_PATH}";
1288
+
1289
+ if (typeof window !== 'undefined' && !window.__vite_plugin_react_preamble_installed__) {
1290
+ RefreshRuntime.injectIntoGlobalHook(window);
1291
+ window.$RefreshReg$ = (type, id) => RefreshRuntime.register(type, id);
1292
+ window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
1293
+ window.__vite_plugin_react_preamble_installed__ = true;
1294
+ console.log('${PLUGIN_LOG_PREFIX} React Refresh preamble installed.');
1295
+ }
1296
+
1297
+ {
1298
+ const ORIGIN = new URL(import.meta.url).origin;
1299
+ const G = typeof globalThis !== 'undefined' ? globalThis : window;
1300
+ const DEBUG_KEY = ${JSON.stringify(DEV_TO_REACT_DEBUG_STATE_KEY)};
1301
+ const STATE = (G[DEBUG_KEY] ||= { logged: {} });
1302
+ if (!STATE.logged.init) {
1303
+ STATE.logged.init = true;
1304
+ console.groupCollapsed('${PLUGIN_LOG_PREFIX} init loaded (HMR enabled)');
1305
+ console.log('Origin:', ORIGIN);
1306
+ console.log('This module imports /@vite/client and installs react-refresh preamble.');
1307
+ console.log('Important: init must run BEFORE importing react-dom/client in the host.');
1308
+ console.log('Tip: open', ORIGIN + '${STABLE_DEBUG_HTML_PATH}');
1309
+ console.groupEnd();
1310
+ }
1311
+
1312
+ // 设置全局 Vite 服务器 origin,用于静态资源 URL 转换
1313
+ // 在宿主页面(file:///Electron)中,静态资源的相对路径需要转换为完整 URL
1314
+ if (typeof window !== 'undefined') {
1315
+ window[${JSON.stringify(DEV_TO_REACT_ORIGIN_KEY)}] = ORIGIN;
1316
+ // 添加全局辅助函数,用于将相对路径转换为完整的 Vite 服务器 URL
1317
+ window[${JSON.stringify(DEV_TO_REACT_RESOLVE_ASSET_KEY)}] = (path) => {
1318
+ if (!path) return path;
1319
+ if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('data:')) {
1320
+ return path;
1321
+ }
1322
+ const origin = window[${JSON.stringify(DEV_TO_REACT_ORIGIN_KEY)}] || ORIGIN;
1323
+ return path.startsWith('/') ? origin + path : origin + '/' + path;
1324
+ };
1325
+ }
1326
+ }
1327
+
1328
+ if (import.meta.hot) {
1329
+ import.meta.hot.accept();
1330
+ import.meta.hot.on('vite:beforeFullReload', () => {
1331
+ window.dispatchEvent(new CustomEvent(CONTRACT.events.fullReload));
1332
+ });
1333
+ import.meta.hot.on('vite:afterUpdate', (payload) => {
1334
+ payload.updates.forEach(update => {
1335
+ window.dispatchEvent(new CustomEvent(CONTRACT.events.hmrUpdate, {
1336
+ detail: { file: update.path, timestamp: payload.timestamp }
1337
+ }));
1338
+ });
1339
+ });
1340
+ }
1341
+
1342
+ export const ${contractExportName} = CONTRACT_NAMED || CONTRACT;
1343
+ if (typeof window !== 'undefined') {
1344
+ window[${JSON.stringify(globalKey)}] = CONTRACT;
1345
+ }
1346
+ export default CONTRACT;
1347
+ `;
1348
+ }
1349
+ function createReactRuntimeVirtualModuleCode() {
1350
+ return `
1351
+ import React from "react";
1352
+ import * as ReactDOMClient from "react-dom/client";
1353
+ {
1354
+ const ORIGIN = new URL(import.meta.url).origin;
1355
+ const G = typeof globalThis !== 'undefined' ? globalThis : window;
1356
+ const DEBUG_KEY = ${JSON.stringify(DEV_TO_REACT_DEBUG_STATE_KEY)};
1357
+ const STATE = (G[DEBUG_KEY] ||= { logged: {} });
1358
+ if (!STATE.logged.runtime) {
1359
+ STATE.logged.runtime = true;
1360
+ console.groupCollapsed('${PLUGIN_LOG_PREFIX} react-runtime loaded');
1361
+ console.log('Origin:', ORIGIN);
1362
+ console.log('React.version:', React?.version);
1363
+ console.log('ReactDOMClient keys:', Object.keys(ReactDOMClient || {}));
1364
+ console.log('Tip: open', ORIGIN + '${STABLE_DEBUG_HTML_PATH}');
1365
+ console.groupEnd();
1366
+ }
1367
+ }
1368
+ export { React, ReactDOMClient };
1369
+ export default React;
1370
+ `;
1371
+ }
1372
+ const devToReactPlugin = (devComponentMap = {}, options = {})=>{
1373
+ const stats = {
1374
+ contract: {
1375
+ count: 0,
1376
+ lastAt: 0
1377
+ },
1378
+ init: {
1379
+ count: 0,
1380
+ lastAt: 0
1381
+ },
1382
+ runtime: {
1383
+ count: 0,
1384
+ lastAt: 0
1385
+ }
1386
+ };
1387
+ const debugState = {
1388
+ didPrintStartupDebugUrl: false
1389
+ };
1390
+ let configDir = process.cwd();
1391
+ let currentRootDir = configDir;
1392
+ let resolvedConfig = resolveDevComponentConfig(currentRootDir, devComponentMap, configDir);
1393
+ let version = '0.0.0';
1394
+ try {
1395
+ const pkgPath = node_path.join(configDir, 'package.json');
1396
+ if (node_fs.existsSync(pkgPath)) {
1397
+ const pkg = JSON.parse(node_fs.readFileSync(pkgPath, 'utf-8'));
1398
+ version = pkg.version || version;
1399
+ }
1400
+ } catch {}
1401
+ const createContract = (componentMap, defaultEntryAbs, isDevMode = false)=>{
1402
+ const processedComponentMap = {};
1403
+ for (const [key, value] of Object.entries(componentMap))if ('/' === value && isDevMode) processedComponentMap[key] = toViteFsPath(defaultEntryAbs);
1404
+ else processedComponentMap[key] = value;
1405
+ return {
1406
+ paths: {
1407
+ contract: STABLE_CONTRACT_PATH,
1408
+ initClient: STABLE_INIT_PATH,
1409
+ reactRuntime: STABLE_REACT_RUNTIME_PATH
1410
+ },
1411
+ events: {
1412
+ fullReload: EVENT_FULL_RELOAD,
1413
+ hmrUpdate: EVENT_HMR_UPDATE
1414
+ },
1415
+ dev: {
1416
+ componentMap: processedComponentMap
1417
+ }
1418
+ };
1419
+ };
1420
+ let contract = createContract(resolvedConfig.componentMap, resolvedConfig.defaultEntryAbs, true);
1421
+ let viteResolved = null;
1422
+ const libBuildState = {
1423
+ enabled: false,
1424
+ isChild: false,
1425
+ currentComponent: '',
1426
+ currentOutDir: '',
1427
+ currentOutBase: '',
1428
+ currentEntryAbs: '',
1429
+ remainingComponents: [],
1430
+ virtualEntryCode: '',
1431
+ startAt: 0,
1432
+ totalComponents: 0,
1433
+ currentIndex: 1
1434
+ };
1435
+ const shortenPath = (p)=>{
1436
+ if (!p) return '';
1437
+ const normalize = (abs)=>`/${node_path.relative(configDir, abs).replace(/\\/g, '/')}`;
1438
+ if (p.startsWith('/@fs')) return normalize(p.slice(4));
1439
+ if (node_path.isAbsolute(p)) return normalize(p);
1440
+ return p;
1441
+ };
1442
+ const stripAnsi = (input)=>{
1443
+ let out = '';
1444
+ for(let i = 0; i < input.length; i += 1){
1445
+ if (27 === input.charCodeAt(i) && '[' === input[i + 1]) {
1446
+ i += 2;
1447
+ while(i < input.length && 'm' !== input[i])i += 1;
1448
+ continue;
1449
+ }
1450
+ out += input[i];
1451
+ }
1452
+ return out;
1453
+ };
1454
+ let lastComponentPrinted = '';
1455
+ const printLibBanner = (step, component, extra = {})=>{
1456
+ const lines = [];
1457
+ const push = (s)=>lines.push(s);
1458
+ if ('spawn' === step) return;
1459
+ {
1460
+ const isNewComponent = lastComponentPrinted !== component;
1461
+ if (isNewComponent) {
1462
+ if (process.stdout.isTTY) {
1463
+ const progressStr = libBuildState.totalComponents > 1 ? picocolors.dim(` [${libBuildState.currentIndex}/${libBuildState.totalComponents}]`) : '';
1464
+ process.stdout.write(`\r ✅ ${picocolors.dim('Prepared component:')} ${picocolors.bold(picocolors.magenta(component))}${progressStr} \n`);
1465
+ }
1466
+ if (lastComponentPrinted) push('\n\n');
1467
+ lastComponentPrinted = component;
1468
+ const progress = libBuildState.totalComponents > 1 ? picocolors.dim(` [${libBuildState.currentIndex}/${libBuildState.totalComponents}]`) : '';
1469
+ const title = ` 📦 Building Component: ${component}${progress} `;
1470
+ const border = '━'.repeat(stripAnsi(title).length);
1471
+ push(picocolors.cyan(`┏${border}┓`));
1472
+ push(`${picocolors.cyan('┃')}${picocolors.bold(picocolors.magenta(title))}${picocolors.cyan('┃')}`);
1473
+ push(picocolors.cyan(`┗${border}┛`));
1474
+ }
1475
+ if ('prepare' === step) {
1476
+ const root = picocolors.dim(shortenPath(extra.root));
1477
+ const name = picocolors.bold(picocolors.cyan(component));
1478
+ const src = picocolors.blue(shortenPath(extra.entry));
1479
+ const out = extra.out ? picocolors.dim(' ➔ ') + picocolors.yellow(shortenPath(extra.out)) : '';
1480
+ push(`${picocolors.cyan(PLUGIN_NAME)} ${picocolors.dim(`v${version}`)} ${picocolors.dim('building')} ${name}${picocolors.dim(':')} ${root} ${picocolors.dim('➔')} ${src}${out}`);
1481
+ } else if ('done' === step) push('\n');
1482
+ }
1483
+ const msg = lines.join('\n');
1484
+ if (msg.trim() || 'done' === step && '\n' === msg) if (viteResolved?.logger) viteResolved.logger.info(msg);
1485
+ else console.log(msg);
1486
+ };
1487
+ const corePlugin = {
1488
+ name: '@dev-to/react-plugin',
1489
+ enforce: 'pre',
1490
+ configureServer (server) {
1491
+ contract = createContract(resolvedConfig.componentMap, resolvedConfig.defaultEntryAbs, true);
1492
+ if (1 === Object.keys(resolvedConfig.componentMap).length && '/' === resolvedConfig.componentMap['*']) {
1493
+ const warn = server.config.logger?.warn?.bind(server.config.logger) ?? console.warn;
1494
+ warn('');
1495
+ warn(`⚠️ ${PLUGIN_LOG_PREFIX} No componentName configured. This works in dev mode but will fail in library build (--mode lib).`);
1496
+ warn(' Please use devToReactPlugin({ ComponentName: "src/ComponentName.tsx" }) or devToReactPlugin({ ComponentName: "/" }) to specify components.');
1497
+ warn(' Or use wildcard: devToReactPlugin({ "*": "/" }) or devToReactPlugin("*")');
1498
+ warn('');
1499
+ }
1500
+ installDebugTools(server, {
1501
+ contract,
1502
+ stats,
1503
+ audit: resolvedConfig.audit,
1504
+ open: options.open
1505
+ }, debugState);
1506
+ },
1507
+ config (userConfig, env) {
1508
+ const rootDir = configDir;
1509
+ if (rootDir !== currentRootDir) {
1510
+ currentRootDir = rootDir;
1511
+ resolvedConfig = resolveDevComponentConfig(rootDir, devComponentMap, configDir);
1512
+ const isDev = !isLibBuild(env);
1513
+ contract = createContract(resolvedConfig.componentMap, resolvedConfig.defaultEntryAbs, isDev);
1514
+ }
1515
+ const next = {
1516
+ server: {
1517
+ host: userConfig.server?.host ?? true,
1518
+ cors: userConfig.server?.cors ?? true
1519
+ },
1520
+ css: {
1521
+ modules: {
1522
+ generateScopedName: '[name]__[local]___[hash:base64:5]'
1523
+ }
1524
+ }
1525
+ };
1526
+ if (false === options.css) next.css = void 0;
1527
+ else if (options.css) next.css = mergeConfig({
1528
+ css: next.css
1529
+ }, {
1530
+ css: options.css
1531
+ }).css;
1532
+ if (isLibBuild(env)) {
1533
+ const actualNames = Object.keys(contract.dev.componentMap).filter((n)=>'*' !== n);
1534
+ if (0 === actualNames.length && !process.env.DEV_TO_REACT_LIB_SECTION) throw new Error(` ${PLUGIN_LOG_PREFIX} Library build (--mode lib) requires at least one explicit componentName for identification and distribution.\nCurrent configuration is in "global fallback mode", which cannot determine build targets.\n\nPlease use one of the following to specify components:\n - devToReactPlugin('ComponentName')\n - devToReactPlugin({ ComponentName: '/' })\n - devToReactPlugin({ ComponentName: 'src/ComponentName.tsx' })\n\n💡 Tip: Wildcards are convenient for development, but explicit naming is required for production builds.`);
1535
+ const isChild = '1' === process.env.DEV_TO_REACT_LIB_CHILD;
1536
+ const buildTargets = resolveBuildTargets({
1537
+ componentMap: contract.dev.componentMap,
1538
+ requestedRaw: process.env.DEV_TO_REACT_LIB_SECTION || '',
1539
+ defaultEntryAbs: resolvedConfig.defaultEntryAbs
1540
+ });
1541
+ const totalComponents = isChild ? parseInt(process.env.DEV_TO_REACT_LIB_TOTAL || '1', 10) : buildTargets.length;
1542
+ const currentIndex = parseInt(process.env.DEV_TO_REACT_LIB_INDEX || '1', 10);
1543
+ if (!isChild) {
1544
+ console.log(`\n${picocolors.bgCyan(picocolors.black(picocolors.bold(' 🏗️ Starting Library Build Process ')))}`);
1545
+ console.log(`${picocolors.cyan('┃')} Total components to build: ${picocolors.bold(picocolors.magenta(totalComponents))}`);
1546
+ console.log(`${picocolors.cyan('┃')} Build targets: ${picocolors.dim(buildTargets.join(', '))}\n`);
1547
+ }
1548
+ const picked = buildTargets[0];
1549
+ if (process.stdout.isTTY) {
1550
+ const progressStr = totalComponents > 1 ? picocolors.dim(` [${currentIndex}/${totalComponents}]`) : '';
1551
+ process.stdout.write(` ⌛ ${picocolors.dim('Preparing component:')} ${picocolors.bold(picocolors.magenta(picked))}${progressStr}...`);
1552
+ } else console.log(` ${picocolors.yellow('⌛')} Preparing component: ${picked}...`);
1553
+ const libCfg = generateLibBuildNextConfig({
1554
+ rootDir,
1555
+ configDir,
1556
+ picked,
1557
+ componentMap: contract.dev.componentMap,
1558
+ resolvedConfig,
1559
+ options,
1560
+ userConfig
1561
+ });
1562
+ Object.assign(libBuildState, {
1563
+ enabled: true,
1564
+ isChild,
1565
+ currentComponent: picked,
1566
+ currentOutDir: libCfg.outDir,
1567
+ currentOutBase: libCfg.outBase,
1568
+ currentEntryAbs: libCfg.resolvedEntryAbs,
1569
+ remainingComponents: isChild ? [] : buildTargets.slice(1),
1570
+ virtualEntryCode: libCfg.virtualEntryCode,
1571
+ startAt: Date.now(),
1572
+ totalComponents,
1573
+ currentIndex
1574
+ });
1575
+ printLibBanner('prepare', picked, {
1576
+ root: rootDir,
1577
+ entry: libCfg.resolvedEntryAbs,
1578
+ out: libCfg.outDir
1579
+ });
1580
+ return mergeConfig(next, libCfg.next);
1581
+ }
1582
+ return next;
1583
+ },
1584
+ configResolved (resolved) {
1585
+ viteResolved = resolved;
1586
+ if (resolved.configFile) {
1587
+ const nextConfigDir = node_path.dirname(resolved.configFile);
1588
+ if (nextConfigDir !== configDir) {
1589
+ configDir = nextConfigDir;
1590
+ currentRootDir = configDir;
1591
+ resolvedConfig = resolveDevComponentConfig(currentRootDir, devComponentMap, configDir);
1592
+ const isDev = 'serve' === resolved.command;
1593
+ contract = createContract(resolvedConfig.componentMap, resolvedConfig.defaultEntryAbs, isDev);
1594
+ }
1595
+ }
1596
+ },
1597
+ buildStart () {
1598
+ if (libBuildState.enabled && !libBuildState.virtualEntryCode) throw new Error(`${PLUGIN_LOG_PREFIX} lib 构建模式已启用,但虚拟入口模块代码未生成。\n当前 component: "${libBuildState.currentComponent}"\n这可能是插件配置问题,请检查 vite.config.ts 中的配置。`);
1599
+ },
1600
+ async closeBundle () {
1601
+ if (!libBuildState.enabled) return;
1602
+ normalizeLibCss(libBuildState.currentOutDir, libBuildState.currentOutBase);
1603
+ printLibBanner('done', libBuildState.currentComponent, {
1604
+ outDir: libBuildState.currentOutDir,
1605
+ file: libBuildState.currentOutBase,
1606
+ entry: libBuildState.currentEntryAbs,
1607
+ took: libBuildState.startAt ? `${Date.now() - libBuildState.startAt}ms` : void 0
1608
+ });
1609
+ if (libBuildState.isChild || 0 === libBuildState.remainingComponents.length || !viteResolved?.configFile) return;
1610
+ const { build } = await import("vite");
1611
+ const prevChild = process.env.DEV_TO_REACT_LIB_CHILD;
1612
+ const prevSection = process.env.DEV_TO_REACT_LIB_SECTION;
1613
+ const prevTotal = process.env.DEV_TO_REACT_LIB_TOTAL;
1614
+ const prevIndex = process.env.DEV_TO_REACT_LIB_INDEX;
1615
+ let childIdx = libBuildState.currentIndex + 1;
1616
+ for (const componentName of libBuildState.remainingComponents){
1617
+ process.env.DEV_TO_REACT_LIB_CHILD = '1';
1618
+ process.env.DEV_TO_REACT_LIB_SECTION = componentName;
1619
+ process.env.DEV_TO_REACT_LIB_TOTAL = String(libBuildState.totalComponents);
1620
+ process.env.DEV_TO_REACT_LIB_INDEX = String(childIdx++);
1621
+ try {
1622
+ printLibBanner('spawn', componentName, {
1623
+ cfg: viteResolved.configFile
1624
+ });
1625
+ await build({
1626
+ configFile: viteResolved.configFile,
1627
+ mode: 'lib',
1628
+ clearScreen: false,
1629
+ logLevel: viteResolved.logLevel
1630
+ });
1631
+ } finally{
1632
+ process.env.DEV_TO_REACT_LIB_CHILD = prevChild;
1633
+ process.env.DEV_TO_REACT_LIB_SECTION = prevSection;
1634
+ process.env.DEV_TO_REACT_LIB_TOTAL = prevTotal;
1635
+ process.env.DEV_TO_REACT_LIB_INDEX = prevIndex;
1636
+ }
1637
+ }
1638
+ },
1639
+ resolveId (source) {
1640
+ if (source.includes(STABLE_CONTRACT_PATH)) return `\0virtual:${PLUGIN_NAME}-contract`;
1641
+ if (source.includes(STABLE_INIT_PATH)) return `\0virtual:${PLUGIN_NAME}-init`;
1642
+ if (source.includes(STABLE_REACT_RUNTIME_PATH)) return `\0virtual:${PLUGIN_NAME}-react-runtime`;
1643
+ if (source.includes(`virtual:${PLUGIN_NAME}-lib-entry:`)) {
1644
+ const pos = source.indexOf(`virtual:${PLUGIN_NAME}-lib-entry:`);
1645
+ const virtualSource = source.slice(pos);
1646
+ return `\0${virtualSource}`;
1647
+ }
1648
+ return null;
1649
+ },
1650
+ load (id) {
1651
+ if (id === `\0virtual:${PLUGIN_NAME}-contract`) return createContractVirtualModuleCode(contract);
1652
+ if (id === `\0virtual:${PLUGIN_NAME}-init`) return createInitVirtualModuleCode();
1653
+ if (id === `\0virtual:${PLUGIN_NAME}-react-runtime`) return createReactRuntimeVirtualModuleCode();
1654
+ if (id.startsWith(`\0virtual:${PLUGIN_NAME}-lib-entry:`)) {
1655
+ const componentName = id.replace(`\0virtual:${PLUGIN_NAME}-lib-entry:`, '');
1656
+ if (libBuildState.enabled) {
1657
+ if (libBuildState.virtualEntryCode) return libBuildState.virtualEntryCode;
1658
+ throw new Error(`${PLUGIN_LOG_PREFIX} 虚拟入口模块 "${id}" (componentName: "${componentName}") 的代码未生成。\n这可能是插件配置问题,请检查 vite.config.ts 中的配置。\n当前 libBuildState: ${JSON.stringify({
1659
+ enabled: libBuildState.enabled,
1660
+ currentComponent: libBuildState.currentComponent,
1661
+ hasCode: !!libBuildState.virtualEntryCode
1662
+ })}`);
1663
+ }
1664
+ if (resolvedConfig && resolvedConfig.componentMap) {
1665
+ const entryFromMap = resolvedConfig.componentMap[componentName];
1666
+ if (entryFromMap) {
1667
+ const abs = resolveEntryAbsPath(currentRootDir, entryFromMap, resolvedConfig.defaultEntryAbs, configDir);
1668
+ if (abs) try {
1669
+ const code = generateLibVirtualEntryCode({
1670
+ rootDir: currentRootDir,
1671
+ defaultEntryAbs: abs,
1672
+ componentName
1673
+ });
1674
+ libBuildState.virtualEntryCode = code;
1675
+ libBuildState.enabled = true;
1676
+ libBuildState.currentComponent = componentName;
1677
+ return code;
1678
+ } catch (error) {
1679
+ throw new Error(`${PLUGIN_LOG_PREFIX} 无法生成虚拟入口模块代码:${error instanceof Error ? error.message : String(error)}`);
1680
+ }
1681
+ }
1682
+ }
1683
+ throw new Error(`${PLUGIN_LOG_PREFIX} 虚拟入口模块 "${id}" (componentName: "${componentName}") 在插件配置完成之前被请求,且无法延迟生成代码。\n这可能是 Vite/Rollup 的内部时序问题。\n请尝试重新运行构建命令,如果问题持续,请检查 vite.config.ts 中的配置。`);
1684
+ }
1685
+ return null;
1686
+ },
1687
+ transform (code, id) {
1688
+ if (libBuildState.enabled) {
1689
+ const injected = injectReactImport(code, id);
1690
+ if (injected) return injected;
1691
+ }
1692
+ return transformAssetUrl(code, id);
1693
+ }
1694
+ };
1695
+ const devCssAssetPlugin = {
1696
+ name: '@dev-to/react-plugin:dev-css-asset-url',
1697
+ enforce: 'post',
1698
+ transform (code, id) {
1699
+ return transformViteDevCssAssetUrls(code, id);
1700
+ }
1701
+ };
1702
+ const libPostPlugin = {
1703
+ name: '@dev-to/react-plugin:lib-post',
1704
+ enforce: 'post',
1705
+ config (_userConfig, env) {
1706
+ if (!isLibBuild(env)) return null;
1707
+ return {
1708
+ esbuild: {
1709
+ jsx: 'transform',
1710
+ jsxFactory: 'React.createElement',
1711
+ jsxFragment: 'React.Fragment'
1712
+ }
1713
+ };
1714
+ }
1715
+ };
1716
+ return [
1717
+ corePlugin,
1718
+ devCssAssetPlugin,
1719
+ libPostPlugin
1720
+ ];
1721
+ };
1722
+ const viteHostReactBridgePlugin = devToReactPlugin;
1723
+ export { EVENT_FULL_RELOAD, EVENT_HMR_UPDATE, PLUGIN_LOG_PREFIX, PLUGIN_NAME, STABLE_BASE_PATH, STABLE_CONTRACT_PATH, STABLE_DEBUG_HTML_PATH, STABLE_DEBUG_JSON_PATH, STABLE_INIT_PATH, STABLE_REACT_RUNTIME_PATH, devToReactPlugin, viteHostReactBridgePlugin };