@cfp-studio/iframe 18.3.0 → 18.4.1
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/README.md +6 -0
- package/package.json +2 -2
- package/preview/__previewjs_internal__/copy-cut-paste.ts +78 -72
- package/preview/__previewjs_internal__/error-message.ts +34 -34
- package/preview/__previewjs_internal__/index.ts +70 -71
- package/preview/__previewjs_internal__/links.ts +27 -27
- package/preview/__previewjs_internal__/logs.ts +94 -94
- package/preview/__previewjs_internal__/object-inspect.js +458 -540
- package/preview/__previewjs_internal__/overlay.spec.ts +86 -0
- package/preview/__previewjs_internal__/overlay.ts +197 -6
- package/preview/__previewjs_internal__/renderer/index.tsx +2 -2
- package/preview/__previewjs_internal__/run-renderer.ts +94 -97
- package/preview/__previewjs_internal__/runtime-bridge.ts +183 -1
- package/preview/__previewjs_internal__/state.ts +2 -2
- package/preview/__previewjs_internal__/vite-hmr-listener.ts +67 -66
- package/preview/index.html +78 -79
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type RuntimeNode,
|
|
21
21
|
type RuntimeNodeDetail,
|
|
22
22
|
type RuntimeSnapshot,
|
|
23
|
+
defaultStyleKeys,
|
|
23
24
|
summarizeComputedStyle,
|
|
24
25
|
type RuntimeSourceLocation,
|
|
25
26
|
} from "@cfp-studio/fiber-reader";
|
|
@@ -27,6 +28,9 @@ import {
|
|
|
27
28
|
import {
|
|
28
29
|
clearPageOverlay,
|
|
29
30
|
isOverlayChrome,
|
|
31
|
+
overlayActionAt,
|
|
32
|
+
runOverlayAction,
|
|
33
|
+
setOverlayActionHandler,
|
|
30
34
|
setPageInteractionShield,
|
|
31
35
|
setPageOverlay,
|
|
32
36
|
type PageOverlayPayload,
|
|
@@ -82,6 +86,13 @@ const SCROLL_KEYS = new Set([
|
|
|
82
86
|
|
|
83
87
|
let initialized = false;
|
|
84
88
|
let inspectEnabled = false;
|
|
89
|
+
/**
|
|
90
|
+
* 当前渲染面能否「截取单个元素」。
|
|
91
|
+
*
|
|
92
|
+
* 由宿主在 `cfp:set-inspect` 里带下来:browser 模式有 CDP 可裁切,embedded
|
|
93
|
+
* 是 iframe、跨域拿不到像素,所以不该显示那个按钮(点了只会失败)。
|
|
94
|
+
*/
|
|
95
|
+
let inspectScreenshotAvailable = false;
|
|
85
96
|
const nodeElements = new Map<string, Element>();
|
|
86
97
|
|
|
87
98
|
export function setupRuntimeBridge(): void {
|
|
@@ -89,10 +100,23 @@ export function setupRuntimeBridge(): void {
|
|
|
89
100
|
return;
|
|
90
101
|
}
|
|
91
102
|
initialized = true;
|
|
103
|
+
// 选中框上的快捷按钮:把「对哪个 runtime 节点做什么」发给宿主执行。
|
|
104
|
+
setOverlayActionHandler((actionId) => {
|
|
105
|
+
const runtimeNodeId = selectedRuntimeNodeId();
|
|
106
|
+
if (!runtimeNodeId) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
publishToHost({ kind: "cfp-selection-action", action: actionId, runtimeNodeId });
|
|
110
|
+
});
|
|
92
111
|
window.addEventListener("message", (event) => {
|
|
93
112
|
if (event.data?.kind === "cfp:runtime-request") {
|
|
94
113
|
publishRuntimeSnapshot();
|
|
114
|
+
} else if (event.data?.kind === "cfp:select-runtime-node") {
|
|
115
|
+
// 宿主(Runtime Tree 点选)要求把某个节点标成「已选中」:
|
|
116
|
+
// 面板里选中与预览里的选中框要双向同步。
|
|
117
|
+
selectRuntimeNodeById(typeof event.data.runtimeNodeId === "string" ? event.data.runtimeNodeId : undefined);
|
|
95
118
|
} else if (event.data?.kind === "cfp:set-inspect") {
|
|
119
|
+
inspectScreenshotAvailable = event.data.screenshot === true;
|
|
96
120
|
setInspectEnabled(Boolean(event.data.enabled));
|
|
97
121
|
} else if (event.data?.kind === "cfp:overlay") {
|
|
98
122
|
const layer = typeof event.data.layer === "string" ? event.data.layer : "default";
|
|
@@ -146,6 +170,8 @@ function setInspectEnabled(enabled: boolean): void {
|
|
|
146
170
|
setPageInteractionShield(enabled);
|
|
147
171
|
if (!enabled) {
|
|
148
172
|
clearPageOverlay("inspect");
|
|
173
|
+
clearPageOverlay("inspect-selected");
|
|
174
|
+
selectedElement = undefined;
|
|
149
175
|
return;
|
|
150
176
|
}
|
|
151
177
|
// 键盘焦点可能还留在应用里(tab 聚焦的输入框):inspect 时不应该继续往里输入。
|
|
@@ -155,17 +181,132 @@ function setInspectEnabled(enabled: boolean): void {
|
|
|
155
181
|
}
|
|
156
182
|
}
|
|
157
183
|
|
|
184
|
+
/**
|
|
185
|
+
* inspect 的选中态:点选后保留一个实线框,与 hover 的虚线框区分。
|
|
186
|
+
*
|
|
187
|
+
* 两层 overlay 各自独立(`inspect` / `inspect-selected`),所以 hover 移动时
|
|
188
|
+
* 不会把选中框冲掉。
|
|
189
|
+
*/
|
|
190
|
+
let selectedElement: Element | undefined;
|
|
191
|
+
|
|
158
192
|
function handleInspectClick(event: MouseEvent): void {
|
|
159
193
|
if (!inspectEnabled) {
|
|
160
194
|
return;
|
|
161
195
|
}
|
|
196
|
+
// 先判按钮:本监听挂在 document 的**捕获阶段**,比按钮自己的 click 先跑,
|
|
197
|
+
// 而 `document.elementsFromPoint` 看不到 shadow 内部 —— 不先查这一下,
|
|
198
|
+
// 点按钮会被当成点页面(选中父元素),按钮永远点不响。
|
|
199
|
+
const actionId = overlayActionAt(event.clientX, event.clientY);
|
|
200
|
+
if (actionId) {
|
|
201
|
+
event.preventDefault();
|
|
202
|
+
event.stopPropagation();
|
|
203
|
+
// **直接调处理函数,不要 `button.click()`**:
|
|
204
|
+
// 合成事件的 clientX/Y 是 0,会被本监听器再次命中
|
|
205
|
+
// (`overlayActionAt(0, 0)` 找不到按钮 → 当成点了页面 → 选中左上角元素),
|
|
206
|
+
// 而且 `stopPropagation` 会让按钮自己的监听器永远收不到 click。
|
|
207
|
+
runOverlayAction(actionId);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
162
210
|
const element = elementUnderPointer(event.clientX, event.clientY);
|
|
163
211
|
const source = element ? getElementSource(element) : undefined;
|
|
164
212
|
event.preventDefault();
|
|
165
213
|
event.stopPropagation();
|
|
214
|
+
selectedElement = element ?? undefined;
|
|
215
|
+
renderSelectedOverlay();
|
|
166
216
|
publishToHost({ kind: "cfp-runtime-select", source });
|
|
167
217
|
}
|
|
168
218
|
|
|
219
|
+
/** 当前选中元素对应的 runtimeNodeId(宿主执行动作时要用它定位节点)。 */
|
|
220
|
+
function selectedRuntimeNodeId(): string | undefined {
|
|
221
|
+
if (!selectedElement) {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
for (const [id, element] of nodeElements) {
|
|
225
|
+
if (element === selectedElement) {
|
|
226
|
+
return id;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** 选中框:实线 + 更淡的填充,与 hover 的虚线区分开。 */
|
|
233
|
+
function renderSelectedOverlay(): void {
|
|
234
|
+
// **不依赖 inspect 模式**:Runtime Tree 点选时通常没开 inspect,
|
|
235
|
+
// 而「树 → 预览页」的选中框正是那时要显示的。
|
|
236
|
+
// (早期这里是 `!inspectEnabled` 就清空,导致反向同步永远看不到效果。)
|
|
237
|
+
if (!selectedElement || !selectedElement.isConnected) {
|
|
238
|
+
clearPageOverlay("inspect-selected");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const rect = selectedElement.getBoundingClientRect();
|
|
242
|
+
const fiber = getElementFiberUpward(selectedElement);
|
|
243
|
+
const componentName = getComponentName(fiber);
|
|
244
|
+
const tag = selectedElement.tagName.toLowerCase();
|
|
245
|
+
const label = componentName ? `${componentName} <${tag}>` : `<${tag}>`;
|
|
246
|
+
setPageOverlay("inspect-selected", {
|
|
247
|
+
visible: true,
|
|
248
|
+
items: [
|
|
249
|
+
{
|
|
250
|
+
x: rect.left,
|
|
251
|
+
y: rect.top,
|
|
252
|
+
width: rect.width,
|
|
253
|
+
height: rect.height,
|
|
254
|
+
color: "#f5a623",
|
|
255
|
+
// 1px 足够:2px 在 iframe 内会显得笨重(实测反馈)。
|
|
256
|
+
borderWidth: 1,
|
|
257
|
+
borderStyle: "solid",
|
|
258
|
+
backgroundAlpha: 0.06,
|
|
259
|
+
label,
|
|
260
|
+
// 两个快捷动作挂在标签右侧:截图当前元素 / 复制 Selection Context Packet。
|
|
261
|
+
// 截图只在能截的渲染面显示:embedded(iframe)跨域拿不到像素,
|
|
262
|
+
// 给了按钮也只会失败,所以直接不渲染。
|
|
263
|
+
actions: [
|
|
264
|
+
...(inspectScreenshotAvailable
|
|
265
|
+
? [
|
|
266
|
+
{
|
|
267
|
+
id: "screenshot",
|
|
268
|
+
title: "Screenshot this element",
|
|
269
|
+
// 相机:机身 + 顶部凸起 + 镜头圆(描边版,12px 下轮廓清晰)。
|
|
270
|
+
iconMarkup: '<path d="M1.5 5.5h2.8l1-1.5h5.4l1 1.5h2.8v8h-13z"/><circle cx="8" cy="9" r="2.4"/>',
|
|
271
|
+
},
|
|
272
|
+
]
|
|
273
|
+
: []),
|
|
274
|
+
{
|
|
275
|
+
id: "copy-context",
|
|
276
|
+
title: "Copy selection context",
|
|
277
|
+
// 剪贴板:板身 + 顶部夹子(夹子用填充,否则 12px 下会糊成一片)。
|
|
278
|
+
iconMarkup:
|
|
279
|
+
'<rect x="3" y="3" width="10" height="11.5" rx="1"/><rect x="5.8" y="1.5" width="4.4" height="2.6" rx="0.8" fill="currentColor"/>',
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 宿主侧让某个节点成为「选中态」(Runtime Tree 点选时用)。
|
|
289
|
+
*
|
|
290
|
+
* 通过 `cfp:runtime-detail` 之外的消息传不进来,所以用 `Runtime.evaluate`
|
|
291
|
+
* 直接调这个全局钩子(浏览器模式)/ postMessage(embedded 模式)。
|
|
292
|
+
*/
|
|
293
|
+
function selectRuntimeNodeById(runtimeNodeId: string | undefined): void {
|
|
294
|
+
if (!runtimeNodeId) {
|
|
295
|
+
selectedElement = undefined;
|
|
296
|
+
clearPageOverlay("inspect-selected");
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const element = nodeElements.get(runtimeNodeId);
|
|
300
|
+
if (!element) {
|
|
301
|
+
// 树里的节点可能不在当前 DOM(未渲染的分支)——静默,不清掉已有选中。
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
selectedElement = element;
|
|
305
|
+
// 元素可能不在视口内:先滚动到可见位置,再按最终位置画框。
|
|
306
|
+
element.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
|
307
|
+
renderSelectedOverlay();
|
|
308
|
+
}
|
|
309
|
+
|
|
169
310
|
function handleInspectMove(event: MouseEvent): void {
|
|
170
311
|
if (!inspectEnabled) {
|
|
171
312
|
return;
|
|
@@ -191,6 +332,8 @@ function handleInspectMove(event: MouseEvent): void {
|
|
|
191
332
|
width: rect.width,
|
|
192
333
|
height: rect.height,
|
|
193
334
|
label,
|
|
335
|
+
// hover 用虚线:与点选后的实线区分,避免「哪个是当前选中」看不出来。
|
|
336
|
+
borderStyle: "dashed",
|
|
194
337
|
},
|
|
195
338
|
],
|
|
196
339
|
});
|
|
@@ -332,6 +475,41 @@ function walk(element: Element | null, parentId: string | undefined, path: strin
|
|
|
332
475
|
return truncated;
|
|
333
476
|
}
|
|
334
477
|
|
|
478
|
+
/**
|
|
479
|
+
* 初始值基线缓存:tag → 参考元素的 computed style 摘要。
|
|
480
|
+
*
|
|
481
|
+
* 参考元素放在 shadow root 里(作者样式匹配不到影子树内部),并显式 `all: initial`,
|
|
482
|
+
* 因此拿到的是**纯 CSS 初始值**:不继承页面、也不被任何作者规则命中。
|
|
483
|
+
*/
|
|
484
|
+
const baselineCache = new Map<string, Record<string, string>>();
|
|
485
|
+
let baselineHost: HTMLElement | undefined;
|
|
486
|
+
|
|
487
|
+
function measureBaseline(tagName: string): Record<string, string> {
|
|
488
|
+
const tag = tagName.toLowerCase();
|
|
489
|
+
const cached = baselineCache.get(tag);
|
|
490
|
+
if (cached) {
|
|
491
|
+
return cached;
|
|
492
|
+
}
|
|
493
|
+
if (!baselineHost) {
|
|
494
|
+
baselineHost = document.createElement("div");
|
|
495
|
+
baselineHost.setAttribute("aria-hidden", "true");
|
|
496
|
+
baselineHost.style.cssText =
|
|
497
|
+
"position:absolute;left:-9999px;top:0;width:0;height:0;overflow:hidden;visibility:hidden;pointer-events:none;";
|
|
498
|
+
document.body.appendChild(baselineHost);
|
|
499
|
+
}
|
|
500
|
+
const root = baselineHost.shadowRoot ?? baselineHost.attachShadow({ mode: "open" });
|
|
501
|
+
root.replaceChildren();
|
|
502
|
+
const reference = document.createElement(tag);
|
|
503
|
+
// `all: initial` 让参考元素回到 **CSS 初始值**(不继承宿主、也不被作者规则命中):
|
|
504
|
+
// 否则作者写 `div { letter-spacing: 2px }` 时,参考元素会通过继承拿到同一个值,
|
|
505
|
+
// 把用户真正设置的属性误判成默认值。
|
|
506
|
+
reference.style.all = "initial";
|
|
507
|
+
root.appendChild(reference);
|
|
508
|
+
const summary = summarizeComputedStyle(window.getComputedStyle(reference));
|
|
509
|
+
baselineCache.set(tag, summary);
|
|
510
|
+
return summary;
|
|
511
|
+
}
|
|
512
|
+
|
|
335
513
|
function collectRuntimeNodeDetail(runtimeNodeId: string): RuntimeNodeDetail | undefined {
|
|
336
514
|
const element = nodeElements.get(runtimeNodeId);
|
|
337
515
|
if (!element) {
|
|
@@ -345,6 +523,7 @@ function collectRuntimeNodeDetail(runtimeNodeId: string): RuntimeNodeDetail | un
|
|
|
345
523
|
}
|
|
346
524
|
const text = (element.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
347
525
|
const outerHTML = element.outerHTML;
|
|
526
|
+
const computedStyle = summarizeComputedStyle(window.getComputedStyle(element));
|
|
348
527
|
return {
|
|
349
528
|
runtimeNodeId,
|
|
350
529
|
bounds: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
@@ -355,7 +534,8 @@ function collectRuntimeNodeDetail(runtimeNodeId: string): RuntimeNodeDetail | un
|
|
|
355
534
|
scrollY: window.scrollY,
|
|
356
535
|
devicePixelRatio: window.devicePixelRatio,
|
|
357
536
|
},
|
|
358
|
-
computedStyle
|
|
537
|
+
computedStyle,
|
|
538
|
+
computedStyleDefaults: defaultStyleKeys(computedStyle, measureBaseline(element.tagName)),
|
|
359
539
|
dom: {
|
|
360
540
|
tag: element.tagName.toLowerCase(),
|
|
361
541
|
id: element.id || undefined,
|
|
@@ -408,6 +588,8 @@ function publishComputedStyle(subscription: ComputedStyleSubscription): void {
|
|
|
408
588
|
kind: "cfp-computed-style",
|
|
409
589
|
runtimeNodeId: subscription.runtimeNodeId,
|
|
410
590
|
computedStyle,
|
|
591
|
+
computedStyleDefaults:
|
|
592
|
+
element && computedStyle ? defaultStyleKeys(computedStyle, measureBaseline(element.tagName)) : [],
|
|
411
593
|
capturedAt: Date.now(),
|
|
412
594
|
});
|
|
413
595
|
}
|
|
@@ -3,9 +3,9 @@ import type { RenderOptions } from "../../src";
|
|
|
3
3
|
let state: RenderOptions | null = null;
|
|
4
4
|
|
|
5
5
|
export function getState() {
|
|
6
|
-
|
|
6
|
+
return state;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export function setState(newState: RenderOptions) {
|
|
10
|
-
|
|
10
|
+
state = newState;
|
|
11
11
|
}
|
|
@@ -1,74 +1,75 @@
|
|
|
1
1
|
/// <reference types="vite/client" />
|
|
2
2
|
import type { ErrorPayload, UpdatePayload } from "vite/types/hmrPayload";
|
|
3
|
+
|
|
3
4
|
import { generateMessageFromError } from "./error-message";
|
|
4
5
|
|
|
5
6
|
const IGNORED_UPDATE_PATH = "/__previewjs_internal__/preview.js?";
|
|
6
7
|
|
|
7
8
|
export function setupViteHmrListener() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
9
|
+
const hmr = import.meta.hot;
|
|
10
|
+
if (!hmr) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
let error: ErrorPayload | null = null;
|
|
14
|
+
let isFirstUpdate = true;
|
|
15
|
+
hmr.on("vite:beforeFullReload", () => {
|
|
16
|
+
window.__PREVIEWJS_IFRAME__.reportEvent({
|
|
17
|
+
kind: "vite-before-reload",
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
hmr.on("vite:error", (payload: ErrorPayload) => {
|
|
21
|
+
error = payload;
|
|
22
|
+
if (typeof payload.err?.message !== "string") {
|
|
23
|
+
// This error doesn't match the expected payload.
|
|
24
|
+
// For example, this can happen with invalid CSS with the
|
|
25
|
+
// vite-plugin-vue2 plugin.
|
|
26
|
+
// Block this to prevent crashes down the track.
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
window.__PREVIEWJS_IFRAME__.reportEvent({
|
|
30
|
+
kind: "error",
|
|
31
|
+
source: "vite",
|
|
32
|
+
message: generateMessageFromError(payload.err.message, payload.err.stack),
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
hmr.on("vite:beforeUpdate", (payload: UpdatePayload) => {
|
|
36
|
+
if (payload.updates.every((u) => u.path.startsWith(IGNORED_UPDATE_PATH))) {
|
|
37
|
+
// Ignore.
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// This is a copy of Vite logic that is disabled when the error overlay is off:
|
|
41
|
+
// https://github.com/vitejs/vite/blob/f3d15f106f378c3850b62fbebd69fc8f7c7f944b/packages/vite/src/client/client.ts#L61
|
|
42
|
+
if (error && isFirstUpdate) {
|
|
43
|
+
window.location.reload();
|
|
44
|
+
} else {
|
|
45
|
+
error = null;
|
|
46
|
+
isFirstUpdate = false;
|
|
47
|
+
}
|
|
48
|
+
if (window.__PREVIEWJS_IFRAME__.lastRenderFailed) {
|
|
49
|
+
window.location.reload();
|
|
50
|
+
} else {
|
|
51
|
+
window.__PREVIEWJS_IFRAME__.reportEvent({
|
|
52
|
+
kind: "vite-before-update",
|
|
53
|
+
payload,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
hmr.on("vite:afterUpdate", (payload: UpdatePayload) => {
|
|
58
|
+
if (payload.updates.every((u) => u.path.startsWith(IGNORED_UPDATE_PATH))) {
|
|
59
|
+
// Ignore.
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
window.__PREVIEWJS_IFRAME__.reportEvent({
|
|
63
|
+
kind: "vite-after-update",
|
|
64
|
+
payload,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
hmr.on("vite:invalidate", () => {
|
|
68
|
+
window.__PREVIEWJS_IFRAME__.reportEvent({
|
|
69
|
+
kind: "vite-invalidate",
|
|
70
|
+
});
|
|
71
|
+
window.__PREVIEWJS_IFRAME__.refresh({
|
|
72
|
+
refetchPreviewableModule: true,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
74
75
|
}
|
package/preview/index.html
CHANGED
|
@@ -1,83 +1,82 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
<head>
|
|
4
|
+
<link rel="icon" href="/logo.png" />
|
|
5
|
+
<script>
|
|
6
|
+
// 将全局对象 globalThis 赋值给 window.global,确保在不同环境下全局对象的一致性
|
|
7
|
+
window.global = globalThis;
|
|
8
|
+
// 将 window.module 设置为 undefined,避免模块冲突
|
|
9
|
+
window.module = undefined;
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
11
|
+
// 定义一个全局对象 __PREVIEWJS_IFRAME__,用于管理预览框架的状态和事件
|
|
12
|
+
window.__PREVIEWJS_IFRAME__ = {
|
|
13
|
+
// 初始化 lastRenderFailed 为 false,表示初始渲染未失败
|
|
14
|
+
lastRenderFailed: false,
|
|
15
|
+
toggleInspect: false,
|
|
16
|
+
/**
|
|
17
|
+
* 报告预览事件
|
|
18
|
+
* @param {Object} event - 事件对象
|
|
19
|
+
* @param {string} event.kind - 事件类型,如 "rendered" 或 "error"
|
|
20
|
+
* @param {string} [event.source] - 事件源,如 "renderer"
|
|
21
|
+
*/
|
|
22
|
+
reportEvent: (event) => {
|
|
23
|
+
// 如果事件类型为 "rendered",则将 lastRenderFailed 设为 false
|
|
24
|
+
if (event.kind === "rendered") {
|
|
25
|
+
window.__PREVIEWJS_IFRAME__.lastRenderFailed = false;
|
|
26
|
+
}
|
|
27
|
+
// 如果事件类型为 "error" 且事件源为 "renderer",则将 lastRenderFailed 设为 true
|
|
28
|
+
else if (event.kind === "error" && event.source === "renderer") {
|
|
29
|
+
window.__PREVIEWJS_IFRAME__.lastRenderFailed = true;
|
|
30
|
+
}
|
|
31
|
+
// 调用控制器的 onPreviewEvent 方法处理事件.
|
|
32
|
+
// 跨域 iframe 无法直接读取 parent 的属性,此时走 postMessage.
|
|
33
|
+
let delivered = false;
|
|
34
|
+
try {
|
|
35
|
+
const controller =
|
|
36
|
+
// 注意: 与 core/src/html-error.ts 中的代码保持同步.
|
|
37
|
+
window.__PREVIEWJS_CONTROLLER__ || window.parent.__PREVIEWJS_CONTROLLER__;
|
|
38
|
+
if (controller) {
|
|
39
|
+
controller.onPreviewEvent(event);
|
|
40
|
+
delivered = true;
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// cross-origin parent; fall through to postMessage
|
|
44
|
+
}
|
|
45
|
+
if (!delivered && window.parent !== window) {
|
|
46
|
+
window.parent.postMessage({ kind: "preview-event", event }, "*");
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
/**
|
|
50
|
+
* 刷新预览
|
|
51
|
+
* 目前为空函数,可根据需求实现具体逻辑
|
|
52
|
+
*/
|
|
53
|
+
refresh: () => {},
|
|
54
|
+
};
|
|
55
|
+
// 报告启动事件,表明预览框架正在初始化
|
|
56
|
+
window.__PREVIEWJS_IFRAME__.reportEvent({
|
|
57
|
+
kind: "bootstrapping",
|
|
58
|
+
});
|
|
59
|
+
</script>
|
|
60
|
+
<!-- %OPTIONAL_HEAD_CONTENT% -->
|
|
61
|
+
<style>
|
|
62
|
+
body {
|
|
63
|
+
padding: 1rem;
|
|
64
|
+
font-family:
|
|
65
|
+
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans",
|
|
66
|
+
"Droid Sans", "Helvetica Neue", sans-serif;
|
|
67
|
+
-webkit-font-smoothing: antialiased;
|
|
68
|
+
-moz-osx-font-smoothing: grayscale;
|
|
69
|
+
}
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
</body>
|
|
71
|
+
code,
|
|
72
|
+
.code {
|
|
73
|
+
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
|
|
74
|
+
}
|
|
75
|
+
</style>
|
|
76
|
+
<meta charset="utf-8" />
|
|
77
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
78
|
+
</head>
|
|
79
|
+
<body>
|
|
80
|
+
<div id="root"></div>
|
|
81
|
+
</body>
|
|
83
82
|
</html>
|