@lark-apaas/fullstack-rspack-preset 0.1.2-alpha.0 → 0.1.2-alpha.2
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/index.d.ts +37 -2
- package/lib/index.js +104 -3
- 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 +8 -1
- package/lib/react-refresh-plugin.d.ts +3 -0
- package/lib/react-refresh-plugin.js +11 -0
- package/package.json +9 -2
package/lib/index.d.ts
CHANGED
|
@@ -1,2 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { Configuration } from '@rspack/core';
|
|
2
|
+
/**
|
|
3
|
+
* 创建 Fullstack Rspack 配置(简化版)
|
|
4
|
+
*
|
|
5
|
+
* 自动从环境变量读取配置,用户只需提供覆盖配置
|
|
6
|
+
*
|
|
7
|
+
* @param overrides - 用户自定义的 Rspack 配置,会与默认配置深度合并
|
|
8
|
+
* @returns 完整的 Rspack 配置对象
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```javascript
|
|
12
|
+
* const { createFullstackRspackConfig } = require('@lark-apaas/fullstack-rspack-preset');
|
|
13
|
+
*
|
|
14
|
+
* module.exports = createFullstackRspackConfig({
|
|
15
|
+
* entry: {
|
|
16
|
+
* main: './client/src/index.tsx',
|
|
17
|
+
* },
|
|
18
|
+
* resolve: {
|
|
19
|
+
* alias: {
|
|
20
|
+
* '@': path.resolve(__dirname, 'client/src'),
|
|
21
|
+
* },
|
|
22
|
+
* },
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* 支持的环境变量:
|
|
27
|
+
* - NODE_ENV: development | production (默认: development)
|
|
28
|
+
* - ENABLE_REACT_REFRESH: true | false (默认: false)
|
|
29
|
+
* - ENABLE_INSPECTOR: true | false (默认: false)
|
|
30
|
+
* - NEED_ROUTES: true | false (默认: true)
|
|
31
|
+
* - CLIENT_BASE_PATH: 客户端基础路径 (默认: /)
|
|
32
|
+
* - ASSETS_CDN_PATH: 生产环境 CDN 路径 (默认: /)
|
|
33
|
+
* - SERVER_PORT: 服务器端口 (默认: 3000)
|
|
34
|
+
* - CLIENT_DEV_PORT: 客户端开发端口 (默认: 8080)
|
|
35
|
+
*/
|
|
36
|
+
export declare function createFullstackRspackConfig(overrides?: Partial<Configuration>): Configuration;
|
|
37
|
+
export default createFullstackRspackConfig;
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,106 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
|
|
36
|
+
exports.createFullstackRspackConfig = createFullstackRspackConfig;
|
|
37
|
+
const webpack_merge_1 = require("webpack-merge");
|
|
38
|
+
const devtool_kits_1 = require("@lark-apaas/devtool-kits");
|
|
39
|
+
const preset_1 = require("./preset");
|
|
40
|
+
/**
|
|
41
|
+
* 创建 Fullstack Rspack 配置(简化版)
|
|
42
|
+
*
|
|
43
|
+
* 自动从环境变量读取配置,用户只需提供覆盖配置
|
|
44
|
+
*
|
|
45
|
+
* @param overrides - 用户自定义的 Rspack 配置,会与默认配置深度合并
|
|
46
|
+
* @returns 完整的 Rspack 配置对象
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```javascript
|
|
50
|
+
* const { createFullstackRspackConfig } = require('@lark-apaas/fullstack-rspack-preset');
|
|
51
|
+
*
|
|
52
|
+
* module.exports = createFullstackRspackConfig({
|
|
53
|
+
* entry: {
|
|
54
|
+
* main: './client/src/index.tsx',
|
|
55
|
+
* },
|
|
56
|
+
* resolve: {
|
|
57
|
+
* alias: {
|
|
58
|
+
* '@': path.resolve(__dirname, 'client/src'),
|
|
59
|
+
* },
|
|
60
|
+
* },
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* 支持的环境变量:
|
|
65
|
+
* - NODE_ENV: development | production (默认: development)
|
|
66
|
+
* - ENABLE_REACT_REFRESH: true | false (默认: false)
|
|
67
|
+
* - ENABLE_INSPECTOR: true | false (默认: false)
|
|
68
|
+
* - NEED_ROUTES: true | false (默认: true)
|
|
69
|
+
* - CLIENT_BASE_PATH: 客户端基础路径 (默认: /)
|
|
70
|
+
* - ASSETS_CDN_PATH: 生产环境 CDN 路径 (默认: /)
|
|
71
|
+
* - SERVER_PORT: 服务器端口 (默认: 3000)
|
|
72
|
+
* - CLIENT_DEV_PORT: 客户端开发端口 (默认: 8080)
|
|
73
|
+
*/
|
|
74
|
+
function createFullstackRspackConfig(overrides = {}) {
|
|
75
|
+
// 1. 自动加载 .env 文件
|
|
76
|
+
try {
|
|
77
|
+
Promise.resolve().then(() => __importStar(require('dotenv'))).then((dotenv) => dotenv.config());
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
// dotenv 是可选依赖,如果没有安装也不影响使用
|
|
81
|
+
}
|
|
82
|
+
// 2. 从环境变量读取配置
|
|
83
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
84
|
+
const enableReactRefresh = process.env.ENABLE_REACT_REFRESH === 'true';
|
|
85
|
+
// enableInspector 目前在 preset 内部由 isDev 控制,保留环境变量以备后续扩展
|
|
86
|
+
// const enableInspector = process.env.ENABLE_INSPECTOR === 'true';
|
|
87
|
+
const needRoutes = process.env.NEED_ROUTES !== 'false'; // 默认 true
|
|
88
|
+
// 3. 处理路径配置
|
|
89
|
+
const clientBasePath = (0, devtool_kits_1.normalizeBasePath)(process.env.CLIENT_BASE_PATH || '/');
|
|
90
|
+
const publicPath = isDev
|
|
91
|
+
? (process.env.CLIENT_BASE_PATH || '/')
|
|
92
|
+
: (process.env.ASSETS_CDN_PATH || '/');
|
|
93
|
+
// 4. 调用原有的 preset 函数生成基础配置
|
|
94
|
+
// 注意:enableInspector 目前在 preset 中由 isDev 控制,暂不暴露为独立参数
|
|
95
|
+
const baseConfig = (0, preset_1.createRecommendRspackConfig)({
|
|
96
|
+
isDev,
|
|
97
|
+
enableReactRefresh,
|
|
98
|
+
needRoutes,
|
|
99
|
+
clientBasePath,
|
|
100
|
+
publicPath,
|
|
101
|
+
});
|
|
102
|
+
// 5. 深度合并用户配置
|
|
103
|
+
return (0, webpack_merge_1.merge)(baseConfig, overrides);
|
|
104
|
+
}
|
|
105
|
+
// 默认导出简化版 API
|
|
106
|
+
exports.default = createFullstackRspackConfig;
|
|
@@ -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';
|
|
@@ -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.2-alpha.
|
|
3
|
+
"version": "0.1.2-alpha.2",
|
|
4
4
|
"files": [
|
|
5
5
|
"lib"
|
|
6
6
|
],
|
|
@@ -23,12 +23,19 @@
|
|
|
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",
|
|
33
|
+
"dotenv": "^16.4.5",
|
|
29
34
|
"echarts": "^6.0.0",
|
|
30
35
|
"postcss-import": "^16.1.1",
|
|
31
|
-
"
|
|
36
|
+
"react-refresh": "^0.17.0",
|
|
37
|
+
"tailwind-merge": "^2.5.5",
|
|
38
|
+
"webpack-merge": "^6.0.1"
|
|
32
39
|
},
|
|
33
40
|
"devDependencies": {
|
|
34
41
|
"@lark-apaas/devtool-kits": "^1.0.0",
|