@lark-apaas/fullstack-rspack-preset 0.1.1 → 0.1.2-alpha.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/lib/overlay/components.d.ts +15 -0
- package/lib/overlay/components.js +169 -0
- package/lib/overlay/index.d.ts +44 -0
- package/lib/overlay/index.js +339 -0
- package/lib/preset.js +9 -2
- package/lib/react-refresh-plugin.d.ts +3 -0
- package/lib/react-refresh-plugin.js +11 -0
- package/package.json +6 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 错误容器组件
|
|
3
|
+
* @param {*} document
|
|
4
|
+
* @param {*} root
|
|
5
|
+
* @param {object} props
|
|
6
|
+
* @param {string} props.errorMessage - 错误信息
|
|
7
|
+
* @param {function} props.onRepairClick - 修复按钮点击事件
|
|
8
|
+
* @param {function} props.onCopyClick - 复制按钮点击事件
|
|
9
|
+
*/
|
|
10
|
+
export function ErrorContainer(document: any, root: any, props: {
|
|
11
|
+
errorMessage: string;
|
|
12
|
+
onRepairClick: Function;
|
|
13
|
+
onCopyClick: Function;
|
|
14
|
+
}): void;
|
|
15
|
+
export function RootStyle(document: any, root: any): void;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 错误容器组件
|
|
4
|
+
* @param {*} document
|
|
5
|
+
* @param {*} root
|
|
6
|
+
* @param {object} props
|
|
7
|
+
* @param {string} props.errorMessage - 错误信息
|
|
8
|
+
* @param {function} props.onRepairClick - 修复按钮点击事件
|
|
9
|
+
* @param {function} props.onCopyClick - 复制按钮点击事件
|
|
10
|
+
*/
|
|
11
|
+
function ErrorContainer(document, root, props) {
|
|
12
|
+
// 创建容器元素
|
|
13
|
+
const container = document.createElement('div');
|
|
14
|
+
container.className = 'container';
|
|
15
|
+
// 创建内容元素
|
|
16
|
+
const content = document.createElement('div');
|
|
17
|
+
content.className = 'content';
|
|
18
|
+
container.appendChild(content);
|
|
19
|
+
// 创建图片元素
|
|
20
|
+
const img = document.createElement('img');
|
|
21
|
+
img.src =
|
|
22
|
+
'https://lf3-static.bytednsdoc.com/obj/eden-cn/ylcylz_fsph_ryhs/ljhwZthlaukjlkulzlp/feisuda/template/render_error.svg';
|
|
23
|
+
img.alt = 'render error';
|
|
24
|
+
// 图片占位
|
|
25
|
+
img.width = 120;
|
|
26
|
+
img.height = 120;
|
|
27
|
+
img.className = 'error-image';
|
|
28
|
+
content.appendChild(img);
|
|
29
|
+
// 创建标题元素
|
|
30
|
+
const title = document.createElement('p');
|
|
31
|
+
title.className = 'title';
|
|
32
|
+
title.textContent = '哎呀,写错代码了';
|
|
33
|
+
content.appendChild(title);
|
|
34
|
+
// 创建描述元素
|
|
35
|
+
const description = document.createElement('p');
|
|
36
|
+
description.className = 'description';
|
|
37
|
+
description.textContent = '可复制错误信息,或告诉妙搭进行修复。';
|
|
38
|
+
content.appendChild(description);
|
|
39
|
+
// 创建按钮组元素
|
|
40
|
+
const buttonGroup = document.createElement('div');
|
|
41
|
+
buttonGroup.className = 'button-group';
|
|
42
|
+
content.appendChild(buttonGroup);
|
|
43
|
+
// 创建复制按钮
|
|
44
|
+
const copyBtn = document.createElement('button');
|
|
45
|
+
copyBtn.className = 'button button-copy';
|
|
46
|
+
copyBtn.id = 'copyBtn';
|
|
47
|
+
copyBtn.textContent = '复制错误信息';
|
|
48
|
+
copyBtn.addEventListener('click', props.onCopyClick);
|
|
49
|
+
buttonGroup.appendChild(copyBtn);
|
|
50
|
+
// 创建修复按钮
|
|
51
|
+
const repairBtn = document.createElement('button');
|
|
52
|
+
repairBtn.className = 'button button-repair';
|
|
53
|
+
repairBtn.id = 'repairBtn';
|
|
54
|
+
repairBtn.textContent = '告诉妙搭修复';
|
|
55
|
+
repairBtn.addEventListener('click', props.onRepairClick);
|
|
56
|
+
buttonGroup.appendChild(repairBtn);
|
|
57
|
+
// 添加到根元素
|
|
58
|
+
root.appendChild(container);
|
|
59
|
+
}
|
|
60
|
+
function RootStyle(document, root) {
|
|
61
|
+
const style = document.createElement('style');
|
|
62
|
+
style.id = 'react-refresh-overlay-style';
|
|
63
|
+
style.textContent = `
|
|
64
|
+
* {
|
|
65
|
+
margin: 0;
|
|
66
|
+
padding: 0;
|
|
67
|
+
box-sizing: border-box;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
body {
|
|
71
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
72
|
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
73
|
+
sans-serif;
|
|
74
|
+
-webkit-font-smoothing: antialiased;
|
|
75
|
+
-moz-osx-font-smoothing: grayscale;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.container {
|
|
79
|
+
min-height: 100vh;
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
background-color: #F3F4F6; /* bg-gray-100 */
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.content {
|
|
87
|
+
display: flex;
|
|
88
|
+
flex-direction: column;
|
|
89
|
+
justify-content: center;
|
|
90
|
+
align-items: center;
|
|
91
|
+
text-align: center;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.error-image {
|
|
95
|
+
margin-bottom: 16px; /* mb-4 */
|
|
96
|
+
width: 120px; /* w-[120px] */
|
|
97
|
+
height: auto;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.title {
|
|
101
|
+
font-size: 18px; /* text-l (推测为 text-lg) */
|
|
102
|
+
line-height: 22px;
|
|
103
|
+
color: #1F2329;
|
|
104
|
+
font-weight: 500; /* font-medium */
|
|
105
|
+
margin-bottom: 8px; /* mb-2 */
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.description {
|
|
109
|
+
font-size: 14px; /* text-sm */
|
|
110
|
+
line-height: 22px;
|
|
111
|
+
color: #646A73;
|
|
112
|
+
font-weight: 400; /* font-normal */
|
|
113
|
+
margin-bottom: 8px; /* mb-2 */
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.button-group {
|
|
117
|
+
display: flex;
|
|
118
|
+
gap: 16px; /* space-x-4 */
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.button {
|
|
122
|
+
height: 32px; /* h-[32px] */
|
|
123
|
+
padding: 0 16px;
|
|
124
|
+
border-radius: 6px; /* rounded-[6px] */
|
|
125
|
+
font-size: 14px; /* text-sm */
|
|
126
|
+
font-weight: 400; /* font-[400] */
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
transition: all 0.2s;
|
|
129
|
+
outline: none; /* focus:outline-hidden */
|
|
130
|
+
border: 1px solid;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.button-copy {
|
|
134
|
+
background-color: white; /* bg-white */
|
|
135
|
+
color: #4B5563; /* text-gray-600 */
|
|
136
|
+
border-color: #D0D3D6; /* border-[#D0D3D6] */
|
|
137
|
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); /* shadow-xs */
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.button-copy:hover {
|
|
141
|
+
background-color: #F3F4F6; /* hover:bg-gray-100 */
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.button-copy:active {
|
|
145
|
+
background-color: #E5E7EB; /* active:bg-gray-200 */
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.button-repair {
|
|
149
|
+
background-color: #2563EB; /* bg-blue-600 */
|
|
150
|
+
color: white; /* text-white */
|
|
151
|
+
border-color: transparent; /* border-transparent */
|
|
152
|
+
font-weight: 500; /* font-medium */
|
|
153
|
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); /* shadow-xs */
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.button-repair:hover {
|
|
157
|
+
background-color: #2563EB; /* hover:bg-blue-600 (保持不变) */
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.button-repair:active {
|
|
161
|
+
background-color: #1E40AF; /* active:bg-blue-700 */
|
|
162
|
+
}
|
|
163
|
+
`;
|
|
164
|
+
root.appendChild(style);
|
|
165
|
+
}
|
|
166
|
+
module.exports = {
|
|
167
|
+
ErrorContainer,
|
|
168
|
+
RootStyle,
|
|
169
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
declare namespace _exports {
|
|
2
|
+
export { IframeProps };
|
|
3
|
+
}
|
|
4
|
+
declare namespace _exports {
|
|
5
|
+
export { clearCompileError };
|
|
6
|
+
export { clearRuntimeErrors };
|
|
7
|
+
export { handleRuntimeError };
|
|
8
|
+
export { showCompileError };
|
|
9
|
+
export { showRuntimeErrors };
|
|
10
|
+
}
|
|
11
|
+
export = _exports;
|
|
12
|
+
type IframeProps = {
|
|
13
|
+
onIframeLoad: () => void;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Clears Webpack compilation errors and dismisses the compile error overlay.
|
|
17
|
+
* @returns {void}
|
|
18
|
+
*/
|
|
19
|
+
declare function clearCompileError(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Clears runtime error records and dismisses the runtime error overlay.
|
|
22
|
+
* @param {boolean} [dismissOverlay] Whether to dismiss the overlay or not.
|
|
23
|
+
* @returns {void}
|
|
24
|
+
*/
|
|
25
|
+
declare function clearRuntimeErrors(dismissOverlay?: boolean): void;
|
|
26
|
+
/**
|
|
27
|
+
* Handles runtime error contexts captured with EventListeners.
|
|
28
|
+
* Integrates with a runtime error overlay.
|
|
29
|
+
* @param {Error} error A valid error object.
|
|
30
|
+
* @returns {void}
|
|
31
|
+
*/
|
|
32
|
+
declare function handleRuntimeError(error: Error): void;
|
|
33
|
+
/**
|
|
34
|
+
* Shows the compile error overlay with the specific Webpack error message.
|
|
35
|
+
* @param {string} message
|
|
36
|
+
* @returns {void}
|
|
37
|
+
*/
|
|
38
|
+
declare function showCompileError(message: string): void;
|
|
39
|
+
/**
|
|
40
|
+
* Shows the runtime error overlay with the specific error records.
|
|
41
|
+
* @param {Error[]} errors
|
|
42
|
+
* @returns {void}
|
|
43
|
+
*/
|
|
44
|
+
declare function showRuntimeErrors(errors: Error[]): void;
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable no-undef */
|
|
3
|
+
const { ErrorContainer, RootStyle } = require('./components');
|
|
4
|
+
/* ===== Cached elements for DOM manipulations ===== */
|
|
5
|
+
/**
|
|
6
|
+
* The iframe that contains the overlay.
|
|
7
|
+
* @type {HTMLIFrameElement}
|
|
8
|
+
*/
|
|
9
|
+
let iframeRoot = null;
|
|
10
|
+
/**
|
|
11
|
+
* The document object from the iframe root, used to create and render elements.
|
|
12
|
+
* @type {Document}
|
|
13
|
+
*/
|
|
14
|
+
let rootDocument = null;
|
|
15
|
+
/**
|
|
16
|
+
* The root div elements will attach to.
|
|
17
|
+
* @type {HTMLDivElement}
|
|
18
|
+
*/
|
|
19
|
+
let root = null;
|
|
20
|
+
/**
|
|
21
|
+
* A Cached function to allow deferred render.
|
|
22
|
+
* @type {RenderFn | null}
|
|
23
|
+
*/
|
|
24
|
+
let scheduledRenderFn = null;
|
|
25
|
+
/* ===== Overlay State ===== */
|
|
26
|
+
/**
|
|
27
|
+
* The latest error message from Webpack compilation.
|
|
28
|
+
* @type {string}
|
|
29
|
+
*/
|
|
30
|
+
let currentCompileErrorMessage = '';
|
|
31
|
+
/**
|
|
32
|
+
* The latest runtime error objects.
|
|
33
|
+
* @type {Error[]}
|
|
34
|
+
*/
|
|
35
|
+
let currentRuntimeErrors = [];
|
|
36
|
+
/**
|
|
37
|
+
* The render mode the overlay is currently in.
|
|
38
|
+
* @type {'compileError' | 'runtimeError' | null}
|
|
39
|
+
*/
|
|
40
|
+
let currentMode = null;
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {Object} IframeProps
|
|
43
|
+
* @property {function(): void} onIframeLoad
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Creates the main `iframe` the overlay will attach to.
|
|
47
|
+
* Accepts a callback to be ran after iframe is initialized.
|
|
48
|
+
* @param {Document} document
|
|
49
|
+
* @param {HTMLElement} root
|
|
50
|
+
* @param {IframeProps} props
|
|
51
|
+
* @returns {HTMLIFrameElement}
|
|
52
|
+
*/
|
|
53
|
+
function IframeRoot(document, root, props) {
|
|
54
|
+
const iframe = document.createElement('iframe');
|
|
55
|
+
iframe.id = 'react-refresh-overlay';
|
|
56
|
+
iframe.src = 'about:blank';
|
|
57
|
+
iframe.style.border = 'none';
|
|
58
|
+
iframe.style.height = '100%';
|
|
59
|
+
iframe.style.left = '0';
|
|
60
|
+
iframe.style.minHeight = '100vh';
|
|
61
|
+
iframe.style.minHeight = '-webkit-fill-available';
|
|
62
|
+
iframe.style.position = 'fixed';
|
|
63
|
+
iframe.style.top = '0';
|
|
64
|
+
iframe.style.width = '100vw';
|
|
65
|
+
iframe.style.zIndex = '2147483647';
|
|
66
|
+
iframe.addEventListener('load', function onLoad() {
|
|
67
|
+
// Reset margin of iframe body
|
|
68
|
+
iframe.contentDocument.body.style.margin = '0';
|
|
69
|
+
props.onIframeLoad();
|
|
70
|
+
});
|
|
71
|
+
// We skip mounting and returns as we need to ensure
|
|
72
|
+
// the load event is fired after we setup the global variable
|
|
73
|
+
return iframe;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Creates the main `div` element for the overlay to render.
|
|
77
|
+
* @param {Document} document
|
|
78
|
+
* @param {HTMLElement} root
|
|
79
|
+
* @returns {HTMLDivElement}
|
|
80
|
+
*/
|
|
81
|
+
function OverlayRoot(document, root) {
|
|
82
|
+
const div = document.createElement('div');
|
|
83
|
+
div.id = 'container';
|
|
84
|
+
root.appendChild(div);
|
|
85
|
+
return div;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Ensures the iframe root and the overlay root are both initialized before render.
|
|
89
|
+
* If check fails, render will be deferred until both roots are initialized.
|
|
90
|
+
* @param {RenderFn} renderFn A function that triggers a DOM render.
|
|
91
|
+
* @returns {void}
|
|
92
|
+
*/
|
|
93
|
+
function ensureRootExists(renderFn) {
|
|
94
|
+
if (root) {
|
|
95
|
+
// Overlay root is ready, we can render right away.
|
|
96
|
+
renderFn();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Creating an iframe may be asynchronous so we'll defer render.
|
|
100
|
+
// In case of multiple calls, function from the last call will be used.
|
|
101
|
+
scheduledRenderFn = renderFn;
|
|
102
|
+
if (iframeRoot) {
|
|
103
|
+
// Iframe is already ready, it will fire the load event.
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Create the iframe root, and, the overlay root inside it when it is ready.
|
|
107
|
+
iframeRoot = IframeRoot(document, document.body, {
|
|
108
|
+
onIframeLoad: function onIframeLoad() {
|
|
109
|
+
rootDocument = iframeRoot.contentDocument;
|
|
110
|
+
RootStyle(rootDocument, rootDocument.head);
|
|
111
|
+
root = OverlayRoot(rootDocument, rootDocument.body);
|
|
112
|
+
scheduledRenderFn();
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
// We have to mount here to ensure `iframeRoot` is set when `onIframeLoad` fires.
|
|
116
|
+
// This is because onIframeLoad() will be called synchronously
|
|
117
|
+
// or asynchronously depending on the browser.
|
|
118
|
+
document.body.appendChild(iframeRoot);
|
|
119
|
+
}
|
|
120
|
+
function removeAllChildren(element, skip) {
|
|
121
|
+
/** @type {Node[]} */
|
|
122
|
+
const childList = Array.prototype.slice.call(element.childNodes, typeof skip !== 'undefined' ? skip : 0);
|
|
123
|
+
for (let i = 0; i < childList.length; i += 1) {
|
|
124
|
+
element.removeChild(childList[i]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function copyToClipboard(text) {
|
|
128
|
+
// 优先使用现代的 Clipboard API
|
|
129
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
130
|
+
try {
|
|
131
|
+
await navigator.clipboard.writeText(text);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
// 权限被拒绝或其他错误,降级到 execCommand
|
|
136
|
+
console.warn('Clipboard API 失败,降级到 execCommand:', error);
|
|
137
|
+
return fallbackCopyToClipboard(text);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// 降级方案:使用传统的 execCommand 方法
|
|
141
|
+
return fallbackCopyToClipboard(text);
|
|
142
|
+
}
|
|
143
|
+
// 获取父窗口 origin
|
|
144
|
+
function getPreviewParentOrigin() {
|
|
145
|
+
const { origin } = window.location;
|
|
146
|
+
// 线上环境
|
|
147
|
+
if (origin.includes('feishuapp.cn') ||
|
|
148
|
+
origin.includes('miaoda.feishuapp.net')) {
|
|
149
|
+
return 'https://miaoda.feishu.cn';
|
|
150
|
+
}
|
|
151
|
+
// PRE 环境
|
|
152
|
+
if (origin.includes('fsapp.kundou.cn') ||
|
|
153
|
+
origin.includes('miaoda-pre.feishuapp.net')) {
|
|
154
|
+
return 'https://miaoda.feishu-pre.cn';
|
|
155
|
+
}
|
|
156
|
+
// BOE 环境
|
|
157
|
+
return 'https://miaoda.feishu-boe.cn';
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Creates the main `div` element for the overlay to render.
|
|
161
|
+
* @returns {void}
|
|
162
|
+
*/
|
|
163
|
+
function render() {
|
|
164
|
+
ensureRootExists(function () {
|
|
165
|
+
removeAllChildren(root);
|
|
166
|
+
// 发送 postMessage
|
|
167
|
+
const sendPostMessage = (message, targetOrigin) => {
|
|
168
|
+
const origin = targetOrigin || getPreviewParentOrigin();
|
|
169
|
+
window.parent.postMessage(message, origin);
|
|
170
|
+
};
|
|
171
|
+
// 通知主应用存在自定义 overlay
|
|
172
|
+
sendPostMessage({
|
|
173
|
+
type: 'app-features',
|
|
174
|
+
payload: [{
|
|
175
|
+
feature: 'error-overlay',
|
|
176
|
+
enable: true,
|
|
177
|
+
}],
|
|
178
|
+
});
|
|
179
|
+
if (currentCompileErrorMessage) {
|
|
180
|
+
currentMode = 'compileError';
|
|
181
|
+
ErrorContainer(rootDocument, root, {
|
|
182
|
+
errorMessage: currentCompileErrorMessage,
|
|
183
|
+
onRepairClick: function onRepairClick() {
|
|
184
|
+
sendPostMessage({
|
|
185
|
+
type: 'RenderErrorRepair',
|
|
186
|
+
data: currentCompileErrorMessage,
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
onCopyClick: function onCopyClick() {
|
|
190
|
+
copyToClipboard(currentCompileErrorMessage);
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
else if (currentRuntimeErrors.length) {
|
|
195
|
+
currentMode = 'runtimeError';
|
|
196
|
+
let errors = '';
|
|
197
|
+
currentRuntimeErrors.forEach(error => {
|
|
198
|
+
errors += `${error.message}\n${error.stack}\n`;
|
|
199
|
+
});
|
|
200
|
+
// 叠加全部报错,将报错信息发送到妙搭
|
|
201
|
+
ErrorContainer(rootDocument, root, {
|
|
202
|
+
errorMessage: errors,
|
|
203
|
+
onRepairClick: function onRepairClick() {
|
|
204
|
+
sendPostMessage({
|
|
205
|
+
type: 'RenderErrorRepair',
|
|
206
|
+
data: errors,
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
onCopyClick: function onCopyClick() {
|
|
210
|
+
copyToClipboard(errors);
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Destroys the state of the overlay.
|
|
218
|
+
* @returns {void}
|
|
219
|
+
*/
|
|
220
|
+
function cleanup() {
|
|
221
|
+
// Clean up and reset all internal state.
|
|
222
|
+
try {
|
|
223
|
+
document.body.removeChild(iframeRoot);
|
|
224
|
+
}
|
|
225
|
+
catch (e) {
|
|
226
|
+
// In case user render react app directly to body, will trigger `NotFoundError` when recovery from an Error
|
|
227
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild#exceptions
|
|
228
|
+
}
|
|
229
|
+
scheduledRenderFn = null;
|
|
230
|
+
root = null;
|
|
231
|
+
iframeRoot = null;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Clears Webpack compilation errors and dismisses the compile error overlay.
|
|
235
|
+
* @returns {void}
|
|
236
|
+
*/
|
|
237
|
+
function clearCompileError() {
|
|
238
|
+
if (!root || currentMode !== 'compileError') {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
currentCompileErrorMessage = '';
|
|
242
|
+
currentMode = null;
|
|
243
|
+
cleanup();
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Clears runtime error records and dismisses the runtime error overlay.
|
|
247
|
+
* @param {boolean} [dismissOverlay] Whether to dismiss the overlay or not.
|
|
248
|
+
* @returns {void}
|
|
249
|
+
*/
|
|
250
|
+
function clearRuntimeErrors(dismissOverlay) {
|
|
251
|
+
if (!root || currentMode !== 'runtimeError') {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
currentRuntimeErrors = [];
|
|
255
|
+
if (typeof dismissOverlay === 'undefined' || dismissOverlay) {
|
|
256
|
+
currentMode = null;
|
|
257
|
+
cleanup();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Shows the compile error overlay with the specific Webpack error message.
|
|
262
|
+
* @param {string} message
|
|
263
|
+
* @returns {void}
|
|
264
|
+
*/
|
|
265
|
+
function showCompileError(message) {
|
|
266
|
+
if (!message) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
currentCompileErrorMessage = message;
|
|
270
|
+
render();
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Shows the runtime error overlay with the specific error records.
|
|
274
|
+
* @param {Error[]} errors
|
|
275
|
+
* @returns {void}
|
|
276
|
+
*/
|
|
277
|
+
function showRuntimeErrors(errors) {
|
|
278
|
+
if (!errors || !errors.length) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
currentRuntimeErrors = errors;
|
|
282
|
+
render();
|
|
283
|
+
}
|
|
284
|
+
function debounce(fn, wait) {
|
|
285
|
+
/**
|
|
286
|
+
* A cached setTimeout handler.
|
|
287
|
+
* @type {number | undefined}
|
|
288
|
+
*/
|
|
289
|
+
let timer;
|
|
290
|
+
/**
|
|
291
|
+
* @returns {void}
|
|
292
|
+
*/
|
|
293
|
+
function debounced() {
|
|
294
|
+
const context = this;
|
|
295
|
+
const args = arguments;
|
|
296
|
+
clearTimeout(timer);
|
|
297
|
+
timer = setTimeout(function () {
|
|
298
|
+
return fn.apply(context, args);
|
|
299
|
+
}, wait);
|
|
300
|
+
}
|
|
301
|
+
return debounced;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* The debounced version of `showRuntimeErrors` to prevent frequent renders
|
|
305
|
+
* due to rapid firing listeners.
|
|
306
|
+
* @param {Error[]} errors
|
|
307
|
+
* @returns {void}
|
|
308
|
+
*/
|
|
309
|
+
const debouncedShowRuntimeErrors = debounce(showRuntimeErrors, 30);
|
|
310
|
+
/**
|
|
311
|
+
* Detects if an error is a Webpack compilation error.
|
|
312
|
+
* @param {Error} error The error of interest.
|
|
313
|
+
* @returns {boolean} If the error is a Webpack compilation error.
|
|
314
|
+
*/
|
|
315
|
+
function isWebpackCompileError(error) {
|
|
316
|
+
return (/Module [A-z ]+\(from/.test(error.message) ||
|
|
317
|
+
/Cannot find module/.test(error.message));
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Handles runtime error contexts captured with EventListeners.
|
|
321
|
+
* Integrates with a runtime error overlay.
|
|
322
|
+
* @param {Error} error A valid error object.
|
|
323
|
+
* @returns {void}
|
|
324
|
+
*/
|
|
325
|
+
function handleRuntimeError(error) {
|
|
326
|
+
if (error &&
|
|
327
|
+
!isWebpackCompileError(error) &&
|
|
328
|
+
currentRuntimeErrors.indexOf(error) === -1) {
|
|
329
|
+
currentRuntimeErrors = currentRuntimeErrors.concat(error);
|
|
330
|
+
}
|
|
331
|
+
debouncedShowRuntimeErrors(currentRuntimeErrors);
|
|
332
|
+
}
|
|
333
|
+
module.exports = Object.freeze({
|
|
334
|
+
clearCompileError: clearCompileError,
|
|
335
|
+
clearRuntimeErrors: clearRuntimeErrors,
|
|
336
|
+
handleRuntimeError: handleRuntimeError,
|
|
337
|
+
showCompileError: showCompileError,
|
|
338
|
+
showRuntimeErrors: showRuntimeErrors,
|
|
339
|
+
});
|
package/lib/preset.js
CHANGED
|
@@ -7,11 +7,12 @@ exports.createRecommendRspackConfig = createRecommendRspackConfig;
|
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const core_1 = __importDefault(require("@rspack/core"));
|
|
9
9
|
const devtool_kits_1 = require("@lark-apaas/devtool-kits");
|
|
10
|
+
const plugin_react_refresh_1 = __importDefault(require("@rspack/plugin-react-refresh"));
|
|
10
11
|
const dev_server_listener_1 = require("./utils/dev-server-listener");
|
|
11
12
|
const route_parser_plugin_1 = __importDefault(require("./rspack-plugins/route-parser-plugin"));
|
|
12
13
|
const slardar_performance_monitor_plugin_1 = __importDefault(require("./rspack-plugins/slardar-performance-monitor-plugin"));
|
|
13
14
|
function createRecommendRspackConfig(options) {
|
|
14
|
-
const { isDev = true, enableReactRefresh =
|
|
15
|
+
const { isDev = true, enableReactRefresh = isDev, needRoutes = true, clientBasePath = '', publicPath = '', // 静态资源路径
|
|
15
16
|
} = options;
|
|
16
17
|
const rootDir = process.cwd();
|
|
17
18
|
const serverPort = process.env.SERVER_PORT || '3000';
|
|
@@ -124,7 +125,7 @@ function createRecommendRspackConfig(options) {
|
|
|
124
125
|
}),
|
|
125
126
|
new core_1.default.DefinePlugin({
|
|
126
127
|
'process.env.NODE_ENV': JSON.stringify(isDev ? 'development' : 'production'),
|
|
127
|
-
'process.env.runtimeMode': 'fullstack',
|
|
128
|
+
'process.env.runtimeMode': JSON.stringify('fullstack'),
|
|
128
129
|
'process.env.CLIENT_BASE_PATH': JSON.stringify(clientBasePath),
|
|
129
130
|
// FIXME:安全漏洞,会读取到服务路径,不应该在客户端代码中使用
|
|
130
131
|
'process.env.CWD': JSON.stringify(process.cwd()),
|
|
@@ -157,6 +158,12 @@ function createRecommendRspackConfig(options) {
|
|
|
157
158
|
appPath: './client/src/app.tsx',
|
|
158
159
|
outputPath: path_1.default.resolve(rootDir, 'dist/client/routes.json'),
|
|
159
160
|
}),
|
|
161
|
+
// 热更新
|
|
162
|
+
enableReactRefresh && new plugin_react_refresh_1.default({
|
|
163
|
+
overlay: {
|
|
164
|
+
module: path_1.default.resolve(__dirname, 'overlay/index.js'),
|
|
165
|
+
},
|
|
166
|
+
}),
|
|
160
167
|
],
|
|
161
168
|
optimization: isDev
|
|
162
169
|
? {}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const ReactRefreshPlugin = require('@rspack/plugin-react-refresh');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
function createReactRefreshPlugin() {
|
|
5
|
+
return new ReactRefreshPlugin({
|
|
6
|
+
overlay: {
|
|
7
|
+
module: path.resolve(__dirname, 'overlay/index.js'),
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
module.exports = createReactRefreshPlugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/fullstack-rspack-preset",
|
|
3
|
-
"version": "0.1.1",
|
|
3
|
+
"version": "0.1.2-alpha.1",
|
|
4
4
|
"files": [
|
|
5
5
|
"lib"
|
|
6
6
|
],
|
|
@@ -23,11 +23,16 @@
|
|
|
23
23
|
"prepublishOnly": "npm run build"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
+
"@babel/parser": "^7.28.0",
|
|
27
|
+
"@babel/traverse": "^7.28.0",
|
|
28
|
+
"@babel/types": "^7.28.2",
|
|
26
29
|
"@lark-apaas/miaoda-inspector-babel-plugin": "^1.0.0",
|
|
27
30
|
"@lark-apaas/miaoda-inspector-jsx-runtime": "^1.0.0",
|
|
31
|
+
"@rspack/plugin-react-refresh": "^1.5.1",
|
|
28
32
|
"clsx": "^2.1.1",
|
|
29
33
|
"echarts": "^6.0.0",
|
|
30
34
|
"postcss-import": "^16.1.1",
|
|
35
|
+
"react-refresh": "^0.17.0",
|
|
31
36
|
"tailwind-merge": "^2.5.5"
|
|
32
37
|
},
|
|
33
38
|
"devDependencies": {
|