@cfp-studio/iframe 18.0.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/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@cfp-studio/iframe",
3
+ "version": "18.0.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "preview"
11
+ ],
12
+ "devDependencies": {
13
+ "assert-never": "1.4.0",
14
+ "immer": "10.1.1",
15
+ "typescript": "5.7.3",
16
+ "unbuild": "2.0.0",
17
+ "vite": "6.2.0"
18
+ },
19
+ "scripts": {
20
+ "build": "tsc && unbuild"
21
+ }
22
+ }
@@ -0,0 +1,75 @@
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
+ }
9
+
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
+ });
75
+ }
@@ -0,0 +1,36 @@
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}` : ""}`;
36
+ }
@@ -0,0 +1,95 @@
1
+ import { overrideCopyCutPaste } from "./copy-cut-paste";
2
+ import { setUpLinkInterception } from "./links";
3
+ import { setUpLogInterception } from "./logs";
4
+ import { loadRenderer } from "./renderer";
5
+ import { runRenderer } from "./run-renderer";
6
+ import { setState } from "./state";
7
+ import { setupViteHmrListener } from "./vite-hmr-listener";
8
+ import { setupVSCodeControlListener } from "./vscode-control";
9
+
10
+ let listenersInitialized = false;
11
+
12
+ // Important: initListeners() must be invoked before we try to load any modules
13
+ // that might fail to load, such as a component, so we can intercept Vite errors.
14
+ export function initListeners() {
15
+ if (listenersInitialized) {
16
+ return;
17
+ }
18
+ listenersInitialized = true;
19
+ setupViteHmrListener();
20
+ setUpLogInterception();
21
+ setUpLinkInterception();
22
+ overrideCopyCutPaste();
23
+ setupVSCodeControlListener();
24
+ }
25
+
26
+ export function initPreview({
27
+ previewableModule,
28
+ previewableName,
29
+ wrapperModule,
30
+ wrapperName,
31
+ }: {
32
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
33
+ previewableModule: any;
34
+ previewableName: string;
35
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
36
+ wrapperModule: any;
37
+ wrapperName: string;
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
+ });
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
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
71
+ } catch (error: any) {
72
+ if (root.innerHTML !== rootHtml) {
73
+ // 重新调用前一次渲染,以确保预览不会白屏。
74
+ root.innerHTML = rootHtml;
75
+ }
76
+ window.__PREVIEWJS_IFRAME__.reportEvent({
77
+ kind: "error",
78
+ source: "renderer",
79
+ message: error.stack || error.message,
80
+ });
81
+ }
82
+ }
83
+
84
+
85
+ return (options = {}) => {
86
+ if (options.previewableModule) {
87
+ previewableModule = options.previewableModule;
88
+ }
89
+ if (options.wrapperModule) {
90
+ wrapperModule = options.wrapperModule;
91
+ }
92
+ // eslint-disable-next-line no-console
93
+ runNewRender().catch(console.error);
94
+ };
95
+ }
@@ -0,0 +1,30 @@
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
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
12
+ const url = (node as any).href;
13
+ // Handle click here by posting data back to VS Code
14
+ // for your extension to handle
15
+ if (url) {
16
+ window.__PREVIEWJS_IFRAME__.reportEvent({
17
+ kind: "action",
18
+ type: "url",
19
+ path: url,
20
+ });
21
+ }
22
+ event.preventDefault();
23
+ return;
24
+ }
25
+ node = node.parentNode;
26
+ }
27
+ },
28
+ true
29
+ );
30
+ }
@@ -0,0 +1,131 @@
1
+ /* eslint-disable no-console */
2
+ import type { LogLevel } from "../../src";
3
+ // @ts-ignore
4
+ import inspect from "./object-inspect";
5
+
6
+ // 注意: 请与 vite 保持同步:
7
+ // https://github.com/vitejs/vite/blob/2de425d0288bfae345c5ced5c84cf67ffccaef48/packages/vite/src/client/client.ts#L117
8
+ const HMR_FAILED_UPDATE_REGEX =
9
+ /^\[hmr\] Failed to reload (.+)\. This could be due to syntax errors or importing non-existent modules\. \(see errors above\)$/;
10
+
11
+ /**
12
+ * 设置日志拦截器。
13
+ */
14
+ export function setUpLogInterception() {
15
+ const makeLogger =
16
+ (level: LogLevel, defaultFn: typeof console.log) =>
17
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
18
+ (...args: any[]) => {
19
+ // Prevent an infinite call stack size if anything here logs.
20
+ const self = console[level];
21
+ console[level] = defaultFn;
22
+ defaultFn(...args);
23
+ try {
24
+ const firstArg = args[0];
25
+ if (typeof firstArg === "string") {
26
+ if (firstArg.startsWith("[vite]")) {
27
+ // Silence.
28
+ return;
29
+ }
30
+ const hmrMatch = firstArg.match(HMR_FAILED_UPDATE_REGEX);
31
+ if (hmrMatch) {
32
+ return;
33
+ }
34
+ }
35
+ window.__PREVIEWJS_IFRAME__.reportEvent({
36
+ kind: "log-message",
37
+ level,
38
+ message: formatLogMessage(...args),
39
+ timestamp: Date.now(),
40
+ });
41
+ } finally {
42
+ console[level] = self;
43
+ }
44
+ };
45
+ console.log = makeLogger("log", console.log);
46
+ console.info = makeLogger("info", console.info);
47
+ console.warn = makeLogger("warn", console.warn);
48
+ console.error = makeLogger("error", console.error);
49
+ window.onerror = (message, source, lineno, colno, error) => {
50
+ window.__PREVIEWJS_IFRAME__.reportEvent({
51
+ kind: "error",
52
+ source: "renderer",
53
+ message: formatError(error, message),
54
+ });
55
+ };
56
+ window.onunhandledrejection = (event) => {
57
+ const message = formatError(event.reason);
58
+ window.__PREVIEWJS_IFRAME__.reportEvent({
59
+ kind: "error",
60
+ source:
61
+ message.includes("Failed to fetch dynamically imported module") ||
62
+ message.includes("Failed to reload")
63
+ ? "vite"
64
+ : "renderer",
65
+ message,
66
+ });
67
+ };
68
+ }
69
+
70
+ /**
71
+ * 格式化错误信息。
72
+ * @param error
73
+ * @param message
74
+ * @returns
75
+ */
76
+
77
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
78
+ function formatError(error?: any, message?: any): string {
79
+ // 如果 error 对象存在且包含 stack 和 message 属性
80
+ if (error?.stack && error.message) {
81
+ let stackMessage = error.stack;
82
+ // 确保 stack 信息包含错误消息
83
+ if (!stackMessage.includes(error.message)) {
84
+ stackMessage = `${error.message}\n${stackMessage}`;
85
+ }
86
+ return stackMessage;
87
+ } if (message) {
88
+ return `${message}`;
89
+ }
90
+ return `${error}`;
91
+ }
92
+
93
+ /**
94
+ * 格式化日志消息。
95
+ * @param args
96
+ * @returns
97
+ */
98
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
99
+ function formatLogMessage(...args: any[]) {
100
+ if (args.length === 0) {
101
+ return "";
102
+ }
103
+ let message = formatValue(args.shift());
104
+ message = message.replace(/%s/g, () => formatValue(args.shift()));
105
+ return [message, ...args.map(formatValue)].join(" ");
106
+ }
107
+
108
+ /**
109
+ * 格式化值。
110
+ * @param value
111
+ * @returns
112
+ */
113
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
114
+ function formatValue(value: any) {
115
+ if (typeof value === "string") {
116
+ return value;
117
+ }
118
+ const formatted = inspect(value);
119
+ if (typeof formatted !== "string") {
120
+ // This can happen in rare cases e.g. toString() not actually returning a string.
121
+ try {
122
+ return JSON.stringify(value);
123
+ } catch {
124
+ return "[Unable to represent value as string]";
125
+ }
126
+ }
127
+ if (formatted.at(0) === "[" && formatted.at(-1) === "]") {
128
+ return formatted.substring(1, formatted.length - 1);
129
+ }
130
+ return formatted;
131
+ }