@cfp-studio/iframe 18.4.4 → 18.4.6

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.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- //#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.1_13c54df9746541450cb8a36e10ae9cdd/node_modules/vite/types/hmrPayload.d.ts
1
+ //#region ../../../../node_modules/.pnpm/vite@6.4.5_@types+node@24.1_d5dfe5fcbde62b2ff1de970aa6c74477/node_modules/vite/types/hmrPayload.d.ts
2
2
  interface UpdatePayload {
3
3
  type: 'update';
4
4
  updates: Update[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfp-studio/iframe",
3
- "version": "18.4.4",
3
+ "version": "18.4.6",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "dist",
@@ -10,6 +10,7 @@
10
10
  "main": "./dist/index.mjs",
11
11
  "types": "./dist/index.d.mts",
12
12
  "dependencies": {
13
+ "@cfp-studio/analyzer-api": "0.1.16",
13
14
  "@cfp-studio/fiber-reader": "0.3.0"
14
15
  },
15
16
  "devDependencies": {
@@ -1,3 +1,5 @@
1
+ import { clearErrorPanel } from "@cfp-studio/analyzer-api/error-panel";
2
+
1
3
  import { overrideCopyCutPaste } from "./copy-cut-paste";
2
4
  import { setUpLinkInterception } from "./links";
3
5
  import { setUpLogInterception } from "./logs";
@@ -67,11 +69,20 @@ export function initPreview({
67
69
  shouldAbortRender: () => renderId !== thisRenderId,
68
70
  loadRenderer,
69
71
  });
72
+ // 渲染成功:上一次的错误覆盖层该撤了。
73
+ //
74
+ // 面板现在挂在 `document.body` 的独立覆盖层上(不占 `#root`),所以不会被
75
+ // `root.innerHTML` 覆写带走 —— 必须显式清 —— 否则改好代码后面板会永久赖着。
76
+ clearErrorPanel();
70
77
  } catch (error: any) {
71
- if (root.innerHTML !== rootHtml) {
72
- // 重新调用前一次渲染,以确保预览不会白屏。
73
- root.innerHTML = rootHtml;
74
- }
78
+ // **不再 `root.innerHTML = rootHtml` 回填快照**。
79
+ //
80
+ // 旧行为(为了「不白屏」而回滚)实测症状:「错误面板闪现一下,然后被正常页面
81
+ // 覆盖」,用户看不到真正原因。现在改成:保留错误覆盖层(半透明遮罩盖住旧画面),
82
+ // 同时继续 `reportEvent` 给宿主侧。
83
+ //
84
+ // `rootHtml` 仍用于判定:没变化说明连上一次的 DOM 都没建立,无需特别处理。
85
+ void rootHtml;
75
86
  window.__PREVIEWJS_IFRAME__.reportEvent({
76
87
  kind: "error",
77
88
  source: "renderer",
@@ -2,6 +2,7 @@
2
2
  import type { LogLevel } from "../../src";
3
3
  // @ts-ignore
4
4
  import inspect from "./object-inspect";
5
+ import { postToParent } from "./runtime-bridge";
5
6
 
6
7
  // 注意: 请与 vite 保持同步:
7
8
  // https://github.com/vitejs/vite/blob/2de425d0288bfae345c5ced5c84cf67ffccaef48/packages/vite/src/client/client.ts#L117
@@ -31,12 +32,21 @@ export function setUpLogInterception() {
31
32
  return;
32
33
  }
33
34
  }
35
+ const message = formatLogMessage(...args);
34
36
  window.__PREVIEWJS_IFRAME__.reportEvent({
35
37
  kind: "log-message",
36
38
  level,
37
- message: formatLogMessage(...args),
39
+ message,
38
40
  timestamp: Date.now(),
39
41
  });
42
+ // 同时往上广播一份给宿主(`docs/todo/38` 的 P1)。
43
+ //
44
+ // 为什么需要两条路:`reportEvent` 只落到 iframe 自己的 `state.logs`,
45
+ // 而 `app/client/src/main.ts` 的 `onStateUpdate` 从未消费它 ——
46
+ // 组件的 console 输出在宿主完全不可见,用户只能开 Webview DevTools。
47
+ // 这里走 `cfp-*` 前缀是为了复用已有的转发链:
48
+ // iframe → app/client 的通用转发 → webview 的 classifyShellMessage → 宿主
49
+ publishLogToHost(level, message);
40
50
  } finally {
41
51
  console[level] = self;
42
52
  }
@@ -129,3 +139,58 @@ function formatValue(value: any) {
129
139
  }
130
140
  return formatted;
131
141
  }
142
+
143
+ /**
144
+ * 把一条 console 消息广播给宿主。
145
+ *
146
+ * ## 为什么单独一个函数
147
+ *
148
+ * `reportEvent` 那条路(`state.logs`)与这条(`cfp-log`)**消费者不重叠**:
149
+ * 前者留在 iframe 内部供 UI 用,后者往上传。和 `cfp-rendered` 的双通路是同一个模式,
150
+ * 见 `docs/analysis/preview-event-channels.md`。
151
+ *
152
+ * ## 为什么要限流
153
+ *
154
+ * 组件在 render 里打 `console.log` 时,一次刷新可能几十条。不设上限的话
155
+ * `OutputChannel` 会瞬间被刷满、`postMessage` 也会拖慢渲染。这里按
156
+ * {@link LOG_BURST_WINDOW_MS} 的窗口做**简单丢弃计数**:超限后只统计,
157
+ * 窗口结束时补一条汇总。
158
+ *
159
+ * 不追求精确(不需要漏桶/令牌桶):排查时要的是「知道有过量日志 + 看到样本」,
160
+ * 不是一份完整的日志归档。
161
+ */
162
+ const LOG_BURST_WINDOW_MS = 1_000;
163
+ /** 一个窗口内最多转发多少条。超出部分计数后丢弃。 */
164
+ const LOG_BURST_LIMIT = 50;
165
+
166
+ let burstWindowStart = 0;
167
+ let burstCount = 0;
168
+ let droppedInWindow = 0;
169
+
170
+ function publishLogToHost(level: LogLevel, message: string): void {
171
+ try {
172
+ const now = Date.now();
173
+ if (now - burstWindowStart > LOG_BURST_WINDOW_MS) {
174
+ // 上一个窗口结束时若有丢弃,补一条汇总(用 warn,确保宿主默认看得见)。
175
+ if (droppedInWindow > 0) {
176
+ postToParent({
177
+ kind: "cfp-log",
178
+ level: "warn",
179
+ message: `[预览页日志过多] 上一秒另有 ${droppedInWindow} 条被丢弃(上限 ${LOG_BURST_LIMIT}/秒)`,
180
+ timestamp: now,
181
+ });
182
+ }
183
+ burstWindowStart = now;
184
+ burstCount = 0;
185
+ droppedInWindow = 0;
186
+ }
187
+ if (burstCount >= LOG_BURST_LIMIT) {
188
+ droppedInWindow += 1;
189
+ return;
190
+ }
191
+ burstCount += 1;
192
+ postToParent({ kind: "cfp-log", level, message, timestamp: now });
193
+ } catch {
194
+ // 上报失败绝不能影响页面本身 —— console 拦截已经改了全局行为,这里必须静默。
195
+ }
196
+ }
@@ -46,7 +46,15 @@ export function setupViteHmrListener() {
46
46
  isFirstUpdate = false;
47
47
  }
48
48
  if (window.__PREVIEWJS_IFRAME__.lastRenderFailed) {
49
- window.location.reload();
49
+ // **不要 `location.reload()`**。
50
+ //
51
+ // 旧行为:上一次渲染失败时整页重载。实测症状(真实反馈):
52
+ // 「保存后闪现错误页面,然后被白屏覆盖」—— 错误面板刚画出来就被重载抹了,
53
+ // 重载后 `runNewRender` 又在 `loadRenderer` 阶段抛错,用户什么都看不到。
54
+ //
55
+ // 现在:面板是独立覆盖层(见 `analyzer-api` 的 `renderErrorPanel`),
56
+ // 只要重新渲染就行 —— 成功时 `runNewRender` 会自己 `clearErrorPanel()`。
57
+ window.__PREVIEWJS_IFRAME__.refresh({ refetchPreviewableModule: true });
50
58
  } else {
51
59
  window.__PREVIEWJS_IFRAME__.reportEvent({
52
60
  kind: "vite-before-update",