@rexnow/rslib-plugin 3.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/LICENSE +21 -0
- package/README.md +308 -0
- package/dist/dev-server/directory.d.ts +13 -0
- package/dist/dev-server/index.d.ts +6 -0
- package/dist/dev-server/types.d.ts +6 -0
- package/dist/dev-server.js +234 -0
- package/dist/generators/danmu.d.ts +5 -0
- package/dist/generators/stream.d.ts +2 -0
- package/dist/generators/subtitle.d.ts +2 -0
- package/dist/generators/video.d.ts +5 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +6513 -0
- package/dist/utils/index.d.ts +14 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Baran
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# @rexnow/rslib-plugin
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@rexnow/rslib-plugin)
|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
> Rex Widget 专用的 Rslib 构建插件
|
|
7
|
+
|
|
8
|
+
## 🚀 简介
|
|
9
|
+
|
|
10
|
+
`@rexnow/rslib-plugin` 是一个专为 Rex Widget 开发优化的 Rslib 插件。它提供了以下核心功能:
|
|
11
|
+
|
|
12
|
+
- 🔧 **自动类型生成**:根据 `WidgetMetadata` 自动生成 TypeScript 类型定义
|
|
13
|
+
- 📦 **构建优化**:清除 Rex Widget 不支持的导出声明
|
|
14
|
+
- 🛠️ **开发体验**:提供完整的类型支持和智能提示
|
|
15
|
+
- 🔄 **热更新**:开发模式下自动重新生成类型定义
|
|
16
|
+
|
|
17
|
+
## 📦 安装
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -D @rexnow/rslib-plugin
|
|
21
|
+
# 或
|
|
22
|
+
yarn add -D @rexnow/rslib-plugin
|
|
23
|
+
# 或
|
|
24
|
+
pnpm add -D @rexnow/rslib-plugin
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 🛠️ 使用方法
|
|
28
|
+
|
|
29
|
+
### 基础配置
|
|
30
|
+
|
|
31
|
+
在你的 `rslib.config.ts` 文件中添加插件:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { pluginRexWidget } from '@rexnow/rslib-plugin';
|
|
35
|
+
import { defineConfig } from '@rslib/core';
|
|
36
|
+
|
|
37
|
+
export default defineConfig({
|
|
38
|
+
plugins: [pluginRexWidget()],
|
|
39
|
+
lib: [
|
|
40
|
+
{
|
|
41
|
+
format: 'esm',
|
|
42
|
+
syntax: ['node 18'],
|
|
43
|
+
dts: true,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 自定义配置
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { pluginRexWidget } from '@rexnow/rslib-plugin';
|
|
53
|
+
import { defineConfig } from '@rslib/core';
|
|
54
|
+
|
|
55
|
+
export default defineConfig({
|
|
56
|
+
plugins: [
|
|
57
|
+
pluginRexWidget({
|
|
58
|
+
typesFilePath: 'src/custom-types.d.ts', // 自定义类型文件路径
|
|
59
|
+
}),
|
|
60
|
+
],
|
|
61
|
+
lib: [
|
|
62
|
+
{
|
|
63
|
+
format: 'esm',
|
|
64
|
+
syntax: ['node 18'],
|
|
65
|
+
dts: true,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 📋 配置选项
|
|
72
|
+
|
|
73
|
+
| 选项 | 类型 | 默认值 | 描述 |
|
|
74
|
+
|------|------|--------|------|
|
|
75
|
+
| `typesFilePath` | `string` | `'src/rex-widget-env.d.ts'` | 生成的类型定义文件路径 |
|
|
76
|
+
|
|
77
|
+
## 💡 工作原理
|
|
78
|
+
|
|
79
|
+
### 1. 解析 WidgetMetadata
|
|
80
|
+
|
|
81
|
+
插件会解析你的代码中的 `WidgetMetadata` 对象:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// src/index.ts
|
|
85
|
+
WidgetMetadata = {
|
|
86
|
+
id: 'my-widget',
|
|
87
|
+
title: 'My Widget',
|
|
88
|
+
modules: [
|
|
89
|
+
{
|
|
90
|
+
id: 'search-module',
|
|
91
|
+
title: 'Search Module',
|
|
92
|
+
functionName: 'searchContent',
|
|
93
|
+
description: 'Search for content',
|
|
94
|
+
params: [
|
|
95
|
+
{
|
|
96
|
+
name: 'query',
|
|
97
|
+
title: 'Search Query',
|
|
98
|
+
type: 'input',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'category',
|
|
102
|
+
title: 'Category',
|
|
103
|
+
type: 'enumeration',
|
|
104
|
+
enumOptions: [
|
|
105
|
+
{ title: 'Movies', value: 'movies' },
|
|
106
|
+
{ title: 'TV Shows', value: 'tvshows' },
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 2. 生成类型定义
|
|
116
|
+
|
|
117
|
+
插件会根据 `WidgetMetadata` 自动生成相应的类型定义:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
// src/rex-widget-env.d.ts
|
|
121
|
+
/// <reference types='@rexnow/libs/env' />
|
|
122
|
+
|
|
123
|
+
//#region search-module
|
|
124
|
+
/**
|
|
125
|
+
* Params of Search Module
|
|
126
|
+
*/
|
|
127
|
+
interface SearchContentParams {
|
|
128
|
+
/**
|
|
129
|
+
* Search Query
|
|
130
|
+
*/
|
|
131
|
+
query: string;
|
|
132
|
+
/**
|
|
133
|
+
* Category
|
|
134
|
+
*/
|
|
135
|
+
category: 'movies' | 'tvshows';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Search Module
|
|
140
|
+
* @description Search for content
|
|
141
|
+
* @param {SearchContentParams} params
|
|
142
|
+
* @returns {Promise<VideoItem[]>}
|
|
143
|
+
*/
|
|
144
|
+
function searchContent(params: SearchContentParams): Promise<VideoItem[]>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Search Module
|
|
148
|
+
*/
|
|
149
|
+
type SearchContentType = typeof searchContent;
|
|
150
|
+
//#endregion search-module
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 3. 清除导出声明
|
|
154
|
+
|
|
155
|
+
插件会自动清除构建输出中的导出声明,因为 Rex Widget 不支持脚本有导出声明。
|
|
156
|
+
|
|
157
|
+
## 📚 完整示例
|
|
158
|
+
|
|
159
|
+
### 项目结构
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
my-widget/
|
|
163
|
+
├── src/
|
|
164
|
+
│ ├── index.ts # 主要逻辑
|
|
165
|
+
│ └── rex-widget-env.d.ts # 自动生成的类型定义
|
|
166
|
+
├── rslib.config.ts # 构建配置
|
|
167
|
+
├── package.json
|
|
168
|
+
└── tsconfig.json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 源代码示例
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
// src/index.ts
|
|
175
|
+
WidgetMetadata = {
|
|
176
|
+
id: 'movie-search',
|
|
177
|
+
title: '电影搜索',
|
|
178
|
+
modules: [
|
|
179
|
+
{
|
|
180
|
+
id: 'search',
|
|
181
|
+
title: '搜索电影',
|
|
182
|
+
functionName: 'searchMovies',
|
|
183
|
+
description: '根据关键词搜索电影',
|
|
184
|
+
params: [
|
|
185
|
+
{
|
|
186
|
+
name: 'keyword',
|
|
187
|
+
title: '关键词',
|
|
188
|
+
type: 'input',
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: 'year',
|
|
192
|
+
title: '年份',
|
|
193
|
+
type: 'input',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: 'genre',
|
|
197
|
+
title: '类型',
|
|
198
|
+
type: 'enumeration',
|
|
199
|
+
enumOptions: [
|
|
200
|
+
{ title: '动作', value: 'action' },
|
|
201
|
+
{ title: '喜剧', value: 'comedy' },
|
|
202
|
+
{ title: '剧情', value: 'drama' },
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// 实现搜索函数
|
|
211
|
+
async function searchMovies(params: SearchMoviesParams): Promise<VideoItem[]> {
|
|
212
|
+
const { keyword, year, genre } = params;
|
|
213
|
+
|
|
214
|
+
// 使用 Widget API 进行搜索
|
|
215
|
+
const response = await Widget.http.get('https://api.example.com/search', {
|
|
216
|
+
params: { q: keyword, year, genre },
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return response.data.map(item => ({
|
|
220
|
+
id: item.id,
|
|
221
|
+
title: item.title,
|
|
222
|
+
year: item.year,
|
|
223
|
+
// ... 其他字段
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 生成的类型定义
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
// src/rex-widget-env.d.ts (自动生成)
|
|
232
|
+
/// <reference types='@rexnow/libs/env' />
|
|
233
|
+
|
|
234
|
+
//#region search
|
|
235
|
+
/**
|
|
236
|
+
* Params of 搜索电影
|
|
237
|
+
*/
|
|
238
|
+
interface SearchMoviesParams {
|
|
239
|
+
/**
|
|
240
|
+
* 关键词
|
|
241
|
+
*/
|
|
242
|
+
keyword: string;
|
|
243
|
+
/**
|
|
244
|
+
* 年份
|
|
245
|
+
*/
|
|
246
|
+
year: string;
|
|
247
|
+
/**
|
|
248
|
+
* 类型
|
|
249
|
+
*/
|
|
250
|
+
genre: 'action' | 'comedy' | 'drama';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 搜索电影
|
|
255
|
+
* @description 根据关键词搜索电影
|
|
256
|
+
* @param {SearchMoviesParams} params
|
|
257
|
+
* @returns {Promise<VideoItem[]>}
|
|
258
|
+
*/
|
|
259
|
+
function searchMovies(params: SearchMoviesParams): Promise<VideoItem[]>;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 搜索电影
|
|
263
|
+
*/
|
|
264
|
+
type SearchMoviesType = typeof searchMovies;
|
|
265
|
+
//#endregion search
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## 🎯 支持的参数类型
|
|
269
|
+
|
|
270
|
+
插件支持以下参数类型的自动类型生成:
|
|
271
|
+
|
|
272
|
+
| 参数类型 | 描述 | 生成的 TypeScript 类型 |
|
|
273
|
+
|----------|------|----------------------|
|
|
274
|
+
| `input` | 输入框 | `string` |
|
|
275
|
+
| `enumeration` | 枚举选择 | `'option1' \| 'option2' \| ...` |
|
|
276
|
+
| `constant` | 常量值 | `'constantValue'` |
|
|
277
|
+
|
|
278
|
+
## 🔧 开发模式
|
|
279
|
+
|
|
280
|
+
在开发模式下,插件会监听文件变化并自动重新生成类型定义:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
# 启动开发模式
|
|
284
|
+
npm run dev
|
|
285
|
+
# 或
|
|
286
|
+
pnpm dev
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## 🤝 最佳实践
|
|
290
|
+
|
|
291
|
+
1. **保持 WidgetMetadata 结构清晰**:确保每个模块都有明确的 ID 和功能定义
|
|
292
|
+
2. **使用描述性的函数名**:函数名应该清晰地表达其功能
|
|
293
|
+
3. **提供完整的参数描述**:为每个参数提供有意义的标题和描述
|
|
294
|
+
4. **合理组织模块**:将相关功能组织在一起,避免模块过于复杂
|
|
295
|
+
|
|
296
|
+
## 📚 相关文档
|
|
297
|
+
|
|
298
|
+
- [@rexnow/libs](../libs/README.md) - 核心工具库
|
|
299
|
+
- [create-rex-widget](../create-rex-widget/README.md) - 脚手架工具
|
|
300
|
+
- [Rex Widget 开发指南](https://docs.forward-widget.com)
|
|
301
|
+
|
|
302
|
+
## 🤝 贡献
|
|
303
|
+
|
|
304
|
+
欢迎提交 Issue 和 Pull Request 来改善这个项目。
|
|
305
|
+
|
|
306
|
+
## 📄 许可证
|
|
307
|
+
|
|
308
|
+
MIT License
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** @jsxImportSource hono/jsx */
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import type { HonoEnv } from './types.js';
|
|
4
|
+
interface FileInfo {
|
|
5
|
+
name: string;
|
|
6
|
+
size: number | null;
|
|
7
|
+
}
|
|
8
|
+
interface DirectoryProps {
|
|
9
|
+
fileInfos: FileInfo[];
|
|
10
|
+
}
|
|
11
|
+
export declare const Directory: ({ fileInfos }: DirectoryProps) => import("hono/jsx/jsx-dev-runtime").JSX.Element;
|
|
12
|
+
export declare const directoryRouter: Hono<HonoEnv, import("hono/types").BlankSchema, "/">;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { networkInterfaces } from "node:os";
|
|
2
|
+
import { serve } from "@hono/node-server";
|
|
3
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
4
|
+
import { Hono } from "hono";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
6
|
+
import { readdir, stat } from "node:fs/promises";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
const formatFileSize = (size)=>{
|
|
9
|
+
if (null === size) return '';
|
|
10
|
+
if (size < 1024) return `${size} B`;
|
|
11
|
+
if (size < 1048576) return `${(size / 1024).toFixed(1)} KB`;
|
|
12
|
+
if (size < 1073741824) return `${(size / 1048576).toFixed(1)} MB`;
|
|
13
|
+
return `${(size / 1073741824).toFixed(1)} GB`;
|
|
14
|
+
};
|
|
15
|
+
const FileItem = ({ fileInfo })=>{
|
|
16
|
+
const sizeText = formatFileSize(fileInfo.size);
|
|
17
|
+
return /*#__PURE__*/ jsx("li", {
|
|
18
|
+
class: "file-item",
|
|
19
|
+
children: /*#__PURE__*/ jsxs("a", {
|
|
20
|
+
href: `/${fileInfo.name}`,
|
|
21
|
+
class: "file-link",
|
|
22
|
+
children: [
|
|
23
|
+
/*#__PURE__*/ jsx("span", {
|
|
24
|
+
class: "file-name",
|
|
25
|
+
children: fileInfo.name
|
|
26
|
+
}),
|
|
27
|
+
/*#__PURE__*/ jsx("span", {
|
|
28
|
+
class: "file-size",
|
|
29
|
+
children: sizeText
|
|
30
|
+
})
|
|
31
|
+
]
|
|
32
|
+
})
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
const Directory = ({ fileInfos })=>/*#__PURE__*/ jsxs("html", {
|
|
36
|
+
lang: "zh-CN",
|
|
37
|
+
children: [
|
|
38
|
+
/*#__PURE__*/ jsxs("head", {
|
|
39
|
+
children: [
|
|
40
|
+
/*#__PURE__*/ jsx("meta", {
|
|
41
|
+
charset: "utf-8"
|
|
42
|
+
}),
|
|
43
|
+
/*#__PURE__*/ jsx("title", {
|
|
44
|
+
children: "Rex Widget - 文件列表"
|
|
45
|
+
}),
|
|
46
|
+
/*#__PURE__*/ jsx("style", {
|
|
47
|
+
children: `
|
|
48
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
49
|
+
:root {
|
|
50
|
+
--bg-1: #000e1a;
|
|
51
|
+
--bg-2: #071221;
|
|
52
|
+
--bg-3: #00383d;
|
|
53
|
+
--card-bg: rgba(20, 20, 22, 0.55);
|
|
54
|
+
--card-border: rgba(255, 255, 255, 0.08);
|
|
55
|
+
--divider: rgba(255, 255, 255, 0.08);
|
|
56
|
+
--text-primary: #ffffff;
|
|
57
|
+
--text-secondary: rgba(255, 255, 255, 0.6);
|
|
58
|
+
--text-tertiary: rgba(255, 255, 255, 0.5);
|
|
59
|
+
--hover-bg: rgba(255, 255, 255, 0.06);
|
|
60
|
+
}
|
|
61
|
+
body {
|
|
62
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
63
|
+
background: linear-gradient(135deg, var(--bg-1) 0%, var(--bg-2) 50%, var(--bg-3) 100%);
|
|
64
|
+
min-height: 100vh;
|
|
65
|
+
color: var(--text-primary);
|
|
66
|
+
padding: calc(env(safe-area-inset-top, 0px) + 12px) 16px calc(env(safe-area-inset-bottom, 0px) + 16px);
|
|
67
|
+
}
|
|
68
|
+
.container {
|
|
69
|
+
max-width: 800px;
|
|
70
|
+
margin: 0 auto;
|
|
71
|
+
}
|
|
72
|
+
.header {
|
|
73
|
+
padding: 24px 0 12px;
|
|
74
|
+
}
|
|
75
|
+
h1 {
|
|
76
|
+
color: var(--text-primary);
|
|
77
|
+
font-size: 32px;
|
|
78
|
+
font-weight: 700;
|
|
79
|
+
margin-bottom: 6px;
|
|
80
|
+
text-align: left;
|
|
81
|
+
}
|
|
82
|
+
.content {
|
|
83
|
+
padding: 0;
|
|
84
|
+
}
|
|
85
|
+
.file-list {
|
|
86
|
+
list-style: none;
|
|
87
|
+
background: var(--card-bg);
|
|
88
|
+
border-radius: 18px;
|
|
89
|
+
overflow: hidden;
|
|
90
|
+
border: 1px solid var(--card-border);
|
|
91
|
+
backdrop-filter: blur(8px);
|
|
92
|
+
}
|
|
93
|
+
.file-item {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
padding: 18px 16px;
|
|
97
|
+
border-bottom: 1px solid var(--divider);
|
|
98
|
+
transition: background-color 0.2s ease;
|
|
99
|
+
}
|
|
100
|
+
.file-item:last-child {
|
|
101
|
+
border-bottom: none;
|
|
102
|
+
}
|
|
103
|
+
.file-item:hover {
|
|
104
|
+
background-color: var(--hover-bg);
|
|
105
|
+
}
|
|
106
|
+
.file-link {
|
|
107
|
+
text-decoration: none;
|
|
108
|
+
color: var(--text-primary);
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
flex: 1;
|
|
112
|
+
font-size: 16px;
|
|
113
|
+
font-weight: 400;
|
|
114
|
+
}
|
|
115
|
+
.file-name {
|
|
116
|
+
flex: 1;
|
|
117
|
+
margin-right: 8px;
|
|
118
|
+
overflow: hidden;
|
|
119
|
+
text-overflow: ellipsis;
|
|
120
|
+
white-space: nowrap;
|
|
121
|
+
}
|
|
122
|
+
.file-size {
|
|
123
|
+
color: var(--text-tertiary);
|
|
124
|
+
font-size: 14px;
|
|
125
|
+
font-weight: 400;
|
|
126
|
+
min-width: 72px;
|
|
127
|
+
text-align: right;
|
|
128
|
+
margin-right: 8px;
|
|
129
|
+
}
|
|
130
|
+
.empty-message {
|
|
131
|
+
text-align: center;
|
|
132
|
+
color: var(--text-tertiary);
|
|
133
|
+
padding: 60px 20px;
|
|
134
|
+
font-size: 16px;
|
|
135
|
+
}
|
|
136
|
+
.section-title {
|
|
137
|
+
color: var(--text-secondary);
|
|
138
|
+
font-size: 13px;
|
|
139
|
+
font-weight: 400;
|
|
140
|
+
text-transform: uppercase;
|
|
141
|
+
letter-spacing: 0.5px;
|
|
142
|
+
margin: 30px 0 8px 0;
|
|
143
|
+
text-align: left;
|
|
144
|
+
}
|
|
145
|
+
`
|
|
146
|
+
})
|
|
147
|
+
]
|
|
148
|
+
}),
|
|
149
|
+
/*#__PURE__*/ jsx("body", {
|
|
150
|
+
children: /*#__PURE__*/ jsxs("div", {
|
|
151
|
+
class: "container",
|
|
152
|
+
children: [
|
|
153
|
+
/*#__PURE__*/ jsx("div", {
|
|
154
|
+
class: "header",
|
|
155
|
+
children: /*#__PURE__*/ jsx("h1", {
|
|
156
|
+
children: "文件列表"
|
|
157
|
+
})
|
|
158
|
+
}),
|
|
159
|
+
/*#__PURE__*/ jsx("div", {
|
|
160
|
+
class: "content",
|
|
161
|
+
children: 0 === fileInfos.length ? /*#__PURE__*/ jsx("div", {
|
|
162
|
+
class: "empty-message",
|
|
163
|
+
children: "\uD83D\uDCED 此目录为空"
|
|
164
|
+
}) : /*#__PURE__*/ jsxs(Fragment, {
|
|
165
|
+
children: [
|
|
166
|
+
/*#__PURE__*/ jsx("div", {
|
|
167
|
+
class: "section-title",
|
|
168
|
+
children: "文件"
|
|
169
|
+
}),
|
|
170
|
+
/*#__PURE__*/ jsx("ul", {
|
|
171
|
+
class: "file-list",
|
|
172
|
+
children: fileInfos.map((fileInfo)=>/*#__PURE__*/ jsx(FileItem, {
|
|
173
|
+
fileInfo: fileInfo
|
|
174
|
+
}, fileInfo.name))
|
|
175
|
+
})
|
|
176
|
+
]
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
]
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
]
|
|
183
|
+
});
|
|
184
|
+
const directoryRouter = new Hono();
|
|
185
|
+
directoryRouter.get('/', async (c)=>{
|
|
186
|
+
const distPath = c.get('distPath');
|
|
187
|
+
const files = await readdir(distPath);
|
|
188
|
+
const fileInfos = await Promise.all(files.map(async (file)=>{
|
|
189
|
+
const filePath = join(distPath, file);
|
|
190
|
+
const stats = await stat(filePath);
|
|
191
|
+
return {
|
|
192
|
+
name: file,
|
|
193
|
+
size: stats.isFile() ? stats.size : null
|
|
194
|
+
};
|
|
195
|
+
}));
|
|
196
|
+
return c.html(/*#__PURE__*/ jsx(Directory, {
|
|
197
|
+
fileInfos: fileInfos
|
|
198
|
+
}));
|
|
199
|
+
});
|
|
200
|
+
const getLocalIPs = ()=>{
|
|
201
|
+
const interfaces = networkInterfaces();
|
|
202
|
+
const ips = [];
|
|
203
|
+
for (const name of Object.keys(interfaces)){
|
|
204
|
+
const nets = interfaces[name];
|
|
205
|
+
if (nets) {
|
|
206
|
+
for (const net of nets)if ('IPv4' === net.family && !net.internal) ips.push(net.address);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return ips;
|
|
210
|
+
};
|
|
211
|
+
const createDevServer = async (options)=>{
|
|
212
|
+
const { api, port } = options;
|
|
213
|
+
const app = new Hono();
|
|
214
|
+
app.use('*', serveStatic({
|
|
215
|
+
root: api.context.distPath
|
|
216
|
+
}));
|
|
217
|
+
app.use('*', (c, next)=>{
|
|
218
|
+
c.set('distPath', api.context.distPath);
|
|
219
|
+
return next();
|
|
220
|
+
});
|
|
221
|
+
app.route('/', directoryRouter);
|
|
222
|
+
const server = serve({
|
|
223
|
+
fetch: app.fetch,
|
|
224
|
+
port
|
|
225
|
+
});
|
|
226
|
+
const localIPs = getLocalIPs();
|
|
227
|
+
api.logger.ready("Rex Widget 插件已启动,监听地址");
|
|
228
|
+
api.logger.info(` http://localhost:${port}`);
|
|
229
|
+
if (localIPs.length > 0) localIPs.forEach((ip)=>{
|
|
230
|
+
api.logger.info(` http://${ip}:${port}`);
|
|
231
|
+
});
|
|
232
|
+
return server;
|
|
233
|
+
};
|
|
234
|
+
export { createDevServer };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RsbuildPlugin } from "@rsbuild/core";
|
|
2
|
+
interface RexWidgetPluginOptions {
|
|
3
|
+
/**
|
|
4
|
+
* 生成的 dts 文件路径
|
|
5
|
+
* @default `src/rex-widget-env.d.ts`
|
|
6
|
+
*/
|
|
7
|
+
typesFilePath?: string;
|
|
8
|
+
/**
|
|
9
|
+
* 监听端口
|
|
10
|
+
* @default 8000
|
|
11
|
+
*/
|
|
12
|
+
devPort?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare const pluginRexWidget: ({ typesFilePath, devPort, }?: RexWidgetPluginOptions) => RsbuildPlugin;
|
|
15
|
+
export {};
|