@cfp-studio/iframe 18.4.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 CHANGED
@@ -3,6 +3,7 @@
3
3
  CFP Studio 预览 iframe 通信与渲染核心包,负责 iframe 与主应用/设计器的消息通信、状态同步、渲染控制、HMR 支持等。
4
4
 
5
5
  ## 特性
6
+
6
7
  - 提供 iframe 与父窗口的高效通信协议
7
8
  - 支持渲染状态、事件、错误、刷新等多种消息类型
8
9
  - 支持 HMR(热模块替换)与自动刷新
@@ -10,12 +11,14 @@ CFP Studio 预览 iframe 通信与渲染核心包,负责 iframe 与主应用/
10
11
  - 支持全局状态、事件上报、调试等
11
12
 
12
13
  ## 架构与工作原理
14
+
13
15
  - 通过全局 `__PREVIEWJS_IFRAME__` 对象暴露渲染、刷新、事件上报等能力
14
16
  - 预览页面(preview/index.html)自动注入全局对象与通信桥
15
17
  - 支持与主应用/设计器的双向事件流与状态同步
16
18
  - 支持多渲染器、错误处理、调试工具等扩展
17
19
 
18
20
  ## 主要 API/模块
21
+
19
22
  - `src/index.ts`:iframe 通信与状态主入口
20
23
  - `preview/index.html`:预览页面模板,注入全局对象
21
24
  - `preview/__previewjs_internal__/`:内部工具与渲染控制
@@ -26,12 +29,15 @@ CFP Studio 预览 iframe 通信与渲染核心包,负责 iframe 与主应用/
26
29
  集成于 CFP Studio 设计器/主应用,无需单独调用。自定义渲染器可参考 preview/renderer/
27
30
 
28
31
  ## 依赖
32
+
29
33
  - `assert-never`、`immer`、`typescript`、`vite`、`tsdown`
30
34
 
31
35
  ## 构建
36
+
32
37
  ```sh
33
38
  pnpm build
34
39
  ```
35
40
 
36
41
  ## 许可证
42
+
37
43
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfp-studio/iframe",
3
- "version": "18.4.0",
3
+ "version": "18.4.1",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "dist",
@@ -1,75 +1,81 @@
1
1
  export function overrideCopyCutPaste() {
2
- if (
3
- !navigator.userAgent.includes(" Code/") &&
4
- !navigator.userAgent.includes(" Code-Insiders/")
5
- ) {
6
- // We're not running in VS Code.
7
- return;
8
- }
2
+ if (!navigator.userAgent.includes(" Code/") && !navigator.userAgent.includes(" Code-Insiders/")) {
3
+ // We're not running in VS Code.
4
+ return;
5
+ }
9
6
 
10
- document.addEventListener("keydown", (e) => {
11
- const hasMeta = e.ctrlKey || e.metaKey;
12
- if (!hasMeta) {
13
- return;
14
- }
15
- switch (e.key.toLowerCase()) {
16
- case "c": {
17
- // 使用现代的 Clipboard API 替代已弃用的 document.execCommand
18
- const textToCopy = window.getSelection()?.toString();
19
- if (textToCopy) {
20
- navigator.clipboard.writeText(textToCopy).then(() => {
21
- console.log('Copied to clipboard successfully!');
22
- }).catch((err) => {
23
- console.error('Failed to copy text: ', err);
24
- });
25
- }
26
- e.preventDefault();
27
- break;
28
- }
29
- case "x": {
30
- // 使用现代的 Clipboard API 替代已弃用的 document.execCommand
31
- const textToCut = window.getSelection()?.toString();
32
- if (textToCut) {
33
- navigator.clipboard.writeText(textToCut).then(() => {
34
- console.log('Cut to clipboard successfully!');
35
- // 移除选中的文本
36
- const range = document.createRange();
37
- range.selectNodeContents(document.getSelection()?.anchorNode?.parentNode as Node);
38
- range.deleteContents();
39
- }).catch((err) => {
40
- console.error('Failed to cut text: ', err);
41
- });
42
- }
43
- e.preventDefault();
44
- break;
45
- }
46
- case "v":
47
- // 使用现代的 Clipboard API 替代已弃用的 document.execCommand
48
- navigator.clipboard.readText().then((text) => {
49
- const selection = window.getSelection();
50
- if (selection) {
51
- const range = selection.getRangeAt(0);
52
- range.deleteContents();
53
- range.insertNode(document.createTextNode(text));
54
- console.log('Pasted from clipboard successfully!');
55
- }
56
- }).catch((err) => {
57
- console.error('Failed to paste text: ', err);
58
- });
59
- e.preventDefault();
60
- break;
61
- case "a": {
62
- // 使用现代的 Range 和 Selection API 替代已弃用的 document.execCommand
63
- const selection = window.getSelection();
64
- if (selection) {
65
- const range = document.createRange();
66
- range.selectNodeContents(document.body);
67
- selection.removeAllRanges();
68
- selection.addRange(range);
69
- }
70
- e.preventDefault();
71
- break;
72
- }
73
- }
74
- });
7
+ document.addEventListener("keydown", (e) => {
8
+ const hasMeta = e.ctrlKey || e.metaKey;
9
+ if (!hasMeta) {
10
+ return;
11
+ }
12
+ switch (e.key.toLowerCase()) {
13
+ case "c": {
14
+ // 使用现代的 Clipboard API 替代已弃用的 document.execCommand
15
+ const textToCopy = window.getSelection()?.toString();
16
+ if (textToCopy) {
17
+ navigator.clipboard
18
+ .writeText(textToCopy)
19
+ .then(() => {
20
+ console.log("Copied to clipboard successfully!");
21
+ })
22
+ .catch((err) => {
23
+ console.error("Failed to copy text: ", err);
24
+ });
25
+ }
26
+ e.preventDefault();
27
+ break;
28
+ }
29
+ case "x": {
30
+ // 使用现代的 Clipboard API 替代已弃用的 document.execCommand
31
+ const textToCut = window.getSelection()?.toString();
32
+ if (textToCut) {
33
+ navigator.clipboard
34
+ .writeText(textToCut)
35
+ .then(() => {
36
+ console.log("Cut to clipboard successfully!");
37
+ // 移除选中的文本
38
+ const range = document.createRange();
39
+ range.selectNodeContents(document.getSelection()?.anchorNode?.parentNode as Node);
40
+ range.deleteContents();
41
+ })
42
+ .catch((err) => {
43
+ console.error("Failed to cut text: ", err);
44
+ });
45
+ }
46
+ e.preventDefault();
47
+ break;
48
+ }
49
+ case "v":
50
+ // 使用现代的 Clipboard API 替代已弃用的 document.execCommand
51
+ navigator.clipboard
52
+ .readText()
53
+ .then((text) => {
54
+ const selection = window.getSelection();
55
+ if (selection) {
56
+ const range = selection.getRangeAt(0);
57
+ range.deleteContents();
58
+ range.insertNode(document.createTextNode(text));
59
+ console.log("Pasted from clipboard successfully!");
60
+ }
61
+ })
62
+ .catch((err) => {
63
+ console.error("Failed to paste text: ", err);
64
+ });
65
+ e.preventDefault();
66
+ break;
67
+ case "a": {
68
+ // 使用现代的 Range 和 Selection API 替代已弃用的 document.execCommand
69
+ const selection = window.getSelection();
70
+ if (selection) {
71
+ const range = document.createRange();
72
+ range.selectNodeContents(document.body);
73
+ selection.removeAllRanges();
74
+ selection.addRange(range);
75
+ }
76
+ e.preventDefault();
77
+ break;
78
+ }
79
+ }
80
+ });
75
81
  }
@@ -1,36 +1,36 @@
1
1
  export function generateMessageFromError(errorMessage: string, stack?: string) {
2
- let message = errorMessage + (stack ? `\n${stack}` : "");
3
- // Remove any redundant line breaks (but not spaces,
4
- // which could be useful indentation).
5
- message = message.replace(/^\n+/g, "\n").trim();
6
- const stripPrefix = "Internal server error: ";
7
- if (message.startsWith(stripPrefix)) {
8
- // Replace deprecated 'substr' with 'slice'
9
- message = message.slice(stripPrefix.length);
10
- }
11
- if (/^Transform failed with \d+ errors?:?\n.*/.test(message)) {
12
- const lineBreakPosition = message.indexOf("\n");
13
- message = message.substring(lineBreakPosition + 1);
14
- }
15
- const lineBreakPosition = message.indexOf("\n");
16
- let title: string;
17
- let rest: string;
18
- if (lineBreakPosition > -1) {
19
- // Replace deprecated 'substr' with 'slice'
20
- title = message.slice(0, lineBreakPosition).trim();
21
- // Replace deprecated 'substr' with 'slice'
22
- rest = message.slice(lineBreakPosition + 1);
23
- } else {
24
- title = message;
25
- rest = "";
26
- }
27
- if (title.endsWith(":") || title.endsWith(".")) {
28
- // Replace deprecated 'substr' with 'slice'
29
- title = title.slice(0, -1).trim();
30
- }
31
- // Note: this isn't relevant to all browsers.
32
- if (rest.startsWith(`Error: ${title}\n`)) {
33
- rest = rest.slice(rest.indexOf("\n") + 1);
34
- }
35
- return `${title}${rest ? `\n\n${rest}` : ""}`;
2
+ let message = errorMessage + (stack ? `\n${stack}` : "");
3
+ // Remove any redundant line breaks (but not spaces,
4
+ // which could be useful indentation).
5
+ message = message.replace(/^\n+/g, "\n").trim();
6
+ const stripPrefix = "Internal server error: ";
7
+ if (message.startsWith(stripPrefix)) {
8
+ // Replace deprecated 'substr' with 'slice'
9
+ message = message.slice(stripPrefix.length);
10
+ }
11
+ if (/^Transform failed with \d+ errors?:?\n.*/.test(message)) {
12
+ const lineBreakPosition = message.indexOf("\n");
13
+ message = message.substring(lineBreakPosition + 1);
14
+ }
15
+ const lineBreakPosition = message.indexOf("\n");
16
+ let title: string;
17
+ let rest: string;
18
+ if (lineBreakPosition > -1) {
19
+ // Replace deprecated 'substr' with 'slice'
20
+ title = message.slice(0, lineBreakPosition).trim();
21
+ // Replace deprecated 'substr' with 'slice'
22
+ rest = message.slice(lineBreakPosition + 1);
23
+ } else {
24
+ title = message;
25
+ rest = "";
26
+ }
27
+ if (title.endsWith(":") || title.endsWith(".")) {
28
+ // Replace deprecated 'substr' with 'slice'
29
+ title = title.slice(0, -1).trim();
30
+ }
31
+ // Note: this isn't relevant to all browsers.
32
+ if (rest.startsWith(`Error: ${title}\n`)) {
33
+ rest = rest.slice(rest.indexOf("\n") + 1);
34
+ }
35
+ return `${title}${rest ? `\n\n${rest}` : ""}`;
36
36
  }
@@ -3,8 +3,8 @@ import { setUpLinkInterception } from "./links";
3
3
  import { setUpLogInterception } from "./logs";
4
4
  import { loadRenderer } from "./renderer";
5
5
  import { runRenderer } from "./run-renderer";
6
- import { setState } from "./state";
7
6
  import { setupRuntimeBridge } from "./runtime-bridge";
7
+ import { setState } from "./state";
8
8
  import { setupViteHmrListener } from "./vite-hmr-listener";
9
9
  import { setupVSCodeControlListener } from "./vscode-control";
10
10
 
@@ -13,82 +13,81 @@ let listenersInitialized = false;
13
13
  // Important: initListeners() must be invoked before we try to load any modules
14
14
  // that might fail to load, such as a component, so we can intercept Vite errors.
15
15
  export function initListeners() {
16
- if (listenersInitialized) {
17
- return;
18
- }
19
- listenersInitialized = true;
20
- setupViteHmrListener();
21
- setUpLogInterception();
22
- setUpLinkInterception();
23
- overrideCopyCutPaste();
24
- setupVSCodeControlListener();
25
- setupRuntimeBridge();
16
+ if (listenersInitialized) {
17
+ return;
18
+ }
19
+ listenersInitialized = true;
20
+ setupViteHmrListener();
21
+ setUpLogInterception();
22
+ setUpLinkInterception();
23
+ overrideCopyCutPaste();
24
+ setupVSCodeControlListener();
25
+ setupRuntimeBridge();
26
26
  }
27
27
 
28
28
  export function initPreview({
29
- previewableModule,
30
- previewableName,
31
- wrapperModule,
32
- wrapperName,
29
+ previewableModule,
30
+ previewableName,
31
+ wrapperModule,
32
+ wrapperName,
33
33
  }: {
34
- previewableModule: any;
35
- previewableName: string;
36
- wrapperModule: any;
37
- wrapperName: string;
34
+ previewableModule: any;
35
+ previewableName: string;
36
+ wrapperModule: any;
37
+ wrapperName: string;
38
38
  }): typeof window.__PREVIEWJS_IFRAME__.refresh {
39
- const root = document.getElementById("root");
40
- if (!root) {
41
- throw new Error("未能找到 #root 节点!");
42
- }
43
- let renderId = 0;
44
- window.__PREVIEWJS_IFRAME__.render = async (data) => {
45
- setState(data);
46
- await runNewRender();
47
- };
48
-
49
- window.__PREVIEWJS_IFRAME__.reportEvent({
50
- kind: "bootstrapped",
51
- });
39
+ const root = document.getElementById("root");
40
+ if (!root) {
41
+ throw new Error("未能找到 #root 节点!");
42
+ }
43
+ let renderId = 0;
44
+ window.__PREVIEWJS_IFRAME__.render = async (data) => {
45
+ setState(data);
46
+ await runNewRender();
47
+ };
52
48
 
53
- async function runNewRender() {
54
- if (!root) {
55
- throw new Error("未能找到 #root 节点!");
56
- }
57
- const rootHtml = root.innerHTML;
58
- try {
59
- renderId += 1;
60
- const thisRenderId = renderId;
61
- await runRenderer({
62
- wrapperModule,
63
- wrapperName,
64
- previewableModule,
65
- previewableName,
66
- renderId,
67
- shouldAbortRender: () => renderId !== thisRenderId,
68
- loadRenderer,
69
- });
70
- } catch (error: any) {
71
- if (root.innerHTML !== rootHtml) {
72
- // 重新调用前一次渲染,以确保预览不会白屏。
73
- root.innerHTML = rootHtml;
74
- }
75
- window.__PREVIEWJS_IFRAME__.reportEvent({
76
- kind: "error",
77
- source: "renderer",
78
- message: error.stack || error.message,
79
- });
80
- }
81
- }
49
+ window.__PREVIEWJS_IFRAME__.reportEvent({
50
+ kind: "bootstrapped",
51
+ });
82
52
 
53
+ async function runNewRender() {
54
+ if (!root) {
55
+ throw new Error("未能找到 #root 节点!");
56
+ }
57
+ const rootHtml = root.innerHTML;
58
+ try {
59
+ renderId += 1;
60
+ const thisRenderId = renderId;
61
+ await runRenderer({
62
+ wrapperModule,
63
+ wrapperName,
64
+ previewableModule,
65
+ previewableName,
66
+ renderId,
67
+ shouldAbortRender: () => renderId !== thisRenderId,
68
+ loadRenderer,
69
+ });
70
+ } catch (error: any) {
71
+ if (root.innerHTML !== rootHtml) {
72
+ // 重新调用前一次渲染,以确保预览不会白屏。
73
+ root.innerHTML = rootHtml;
74
+ }
75
+ window.__PREVIEWJS_IFRAME__.reportEvent({
76
+ kind: "error",
77
+ source: "renderer",
78
+ message: error.stack || error.message,
79
+ });
80
+ }
81
+ }
83
82
 
84
- return (options = {}) => {
85
- if (options.previewableModule) {
86
- previewableModule = options.previewableModule;
87
- }
88
- if (options.wrapperModule) {
89
- wrapperModule = options.wrapperModule;
90
- }
91
- // eslint-disable-next-line no-console
92
- runNewRender().catch(console.error);
93
- };
83
+ return (options = {}) => {
84
+ if (options.previewableModule) {
85
+ previewableModule = options.previewableModule;
86
+ }
87
+ if (options.wrapperModule) {
88
+ wrapperModule = options.wrapperModule;
89
+ }
90
+ // eslint-disable-next-line no-console
91
+ runNewRender().catch(console.error);
92
+ };
94
93
  }
@@ -1,29 +1,29 @@
1
1
  export function setUpLinkInterception() {
2
- document.addEventListener(
3
- "click",
4
- (event) => {
5
- let node = event?.target;
6
- while (node) {
7
- if (!(node instanceof Node)) {
8
- break;
9
- }
10
- if (node.nodeName === "A") {
11
- const url = (node as any).href;
12
- // Handle click here by posting data back to VS Code
13
- // for your extension to handle
14
- if (url) {
15
- window.__PREVIEWJS_IFRAME__.reportEvent({
16
- kind: "action",
17
- type: "url",
18
- path: url,
19
- });
20
- }
21
- event.preventDefault();
22
- return;
23
- }
24
- node = node.parentNode;
25
- }
26
- },
27
- true
28
- );
2
+ document.addEventListener(
3
+ "click",
4
+ (event) => {
5
+ let node = event?.target;
6
+ while (node) {
7
+ if (!(node instanceof Node)) {
8
+ break;
9
+ }
10
+ if (node.nodeName === "A") {
11
+ const url = (node as any).href;
12
+ // Handle click here by posting data back to VS Code
13
+ // for your extension to handle
14
+ if (url) {
15
+ window.__PREVIEWJS_IFRAME__.reportEvent({
16
+ kind: "action",
17
+ type: "url",
18
+ path: url,
19
+ });
20
+ }
21
+ event.preventDefault();
22
+ return;
23
+ }
24
+ node = node.parentNode;
25
+ }
26
+ },
27
+ true,
28
+ );
29
29
  }