yida-jsx 1.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/README.md +230 -0
- package/cli/index.js +125 -0
- package/cli/templates.js +485 -0
- package/package.json +44 -0
- package/rollup.config.mjs +166 -0
package/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# 宜搭 JSX 脚手架工具
|
|
2
|
+
|
|
3
|
+
一个用于快速创建宜搭 JSX 组件项目的脚手架工具。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- 快速初始化宜搭 JSX 项目
|
|
8
|
+
- 自动生成符合规范的页面结构
|
|
9
|
+
- 支持创建子组件
|
|
10
|
+
- 自动配置 Rollup 打包配置
|
|
11
|
+
- 符合宜搭开发规范
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
### 全局安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @liuxianhua1996/yida-jsx
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 本地安装
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @liuxianhua1996/yida-jsx
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 使用方法
|
|
28
|
+
|
|
29
|
+
### 1. 初始化新项目
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
yida-cli init <project-name>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
示例:
|
|
36
|
+
```bash
|
|
37
|
+
yida-cli init my-yida-project
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
这会创建以下目录结构:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
project/
|
|
44
|
+
├── src/
|
|
45
|
+
│ ├── pages/ # 页面目录
|
|
46
|
+
│ │ └── visit/ # 示例页面
|
|
47
|
+
│ │ ├── index.js
|
|
48
|
+
│ │ ├── index.jsx
|
|
49
|
+
│ │ ├── index.css
|
|
50
|
+
│ │ └── components/
|
|
51
|
+
│ └── utils/ # 工具函数
|
|
52
|
+
│ └── date.js
|
|
53
|
+
├── rollup.config.mjs # Rollup 配置
|
|
54
|
+
├── package.json # 项目配置
|
|
55
|
+
└── .gitignore # Git 忽略文件
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. 创建新页面
|
|
59
|
+
|
|
60
|
+
在项目根目录下运行:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
yida-cli page <page-name>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
示例:
|
|
67
|
+
```bash
|
|
68
|
+
yida-cli page visit
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
这会在 `src/pages/` 目录下创建一个新的页面文件夹,包含:
|
|
72
|
+
- `index.js` - 页面逻辑(didMount, onSubmit 等)
|
|
73
|
+
- `index.jsx` - 页面主视图(包含 function render)
|
|
74
|
+
- `index.css` - 页面主样式
|
|
75
|
+
- `components/` - 存放该页面专用的子组件
|
|
76
|
+
|
|
77
|
+
### 3. 创建新组件
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
yida-cli component <component-name> <page-name>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
示例:
|
|
84
|
+
```bash
|
|
85
|
+
yida-cli component Header visit
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
这会在指定页面的 `components/` 目录下创建组件文件夹,包含:
|
|
89
|
+
- `<ComponentName>.jsx` - 组件源码
|
|
90
|
+
- `<ComponentName>.css` - 组件专用样式
|
|
91
|
+
|
|
92
|
+
### 4. 打包项目
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm run build
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 4. 打包项目
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm run build
|
|
102
|
+
```
|
|
103
|
+
### 5. 打包指定页面
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm run build:page <page-name>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 6. 开发模式(监听文件变化)
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm run dev
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 命令选项
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
yida-cli --help # 显示帮助信息
|
|
119
|
+
yida-cli --version # 显示版本号
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## 项目结构说明
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
project/
|
|
126
|
+
├── src/
|
|
127
|
+
│ ├── pages/ # 页面根目录
|
|
128
|
+
│ │ └── visit/ # 访客页面根目录
|
|
129
|
+
│ │ ├── index.js # 页面逻辑(didMount, onSubmit 等)
|
|
130
|
+
│ │ ├── index.jsx # 页面主视图(包含 function render)
|
|
131
|
+
│ │ ├── index.css # 页面主样式
|
|
132
|
+
│ │ │
|
|
133
|
+
│ │ ├── components/ # 存放该页面专用的子组件
|
|
134
|
+
│ │ │ └── Header/ # Header 组件文件夹
|
|
135
|
+
│ │ │ ├── Header.jsx # Header 源码
|
|
136
|
+
│ │ │ └── Header.css # Header 专用样式
|
|
137
|
+
│ │ │
|
|
138
|
+
│ │ └── out/ # 打包产物目录(由 Rollup 自动生成)
|
|
139
|
+
│ │ ├── index.js # 每一行都是 export function ...
|
|
140
|
+
│ │ ├── index.jsx # 已嵌套 Header 源码的 render 函数
|
|
141
|
+
│ │ └── index.css # 已合并 Header.css 的全量样式
|
|
142
|
+
│ │
|
|
143
|
+
│ └── utils/ # 全局共享工具
|
|
144
|
+
│ └── date.js # formatDate 等
|
|
145
|
+
│
|
|
146
|
+
├── rollup.config.mjs # 核心打包逻辑
|
|
147
|
+
└── package.json # 项目依赖配置文件
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## 开发规范
|
|
151
|
+
|
|
152
|
+
### JSX 组件规范
|
|
153
|
+
|
|
154
|
+
- 禁止使用原生 React Hooks(useState, useRef, useEffect 等)
|
|
155
|
+
- 交互逻辑必须写在 JS 面板,JSX 仅负责渲染 UI
|
|
156
|
+
- 禁止使用可选链(?.),必须使用 && 进行逻辑判断
|
|
157
|
+
- 必须同时实现 PC 和 Mobile 端组件
|
|
158
|
+
- 所有组件逻辑必须包裹在 `render` 方法中
|
|
159
|
+
|
|
160
|
+
### JS 面板规范
|
|
161
|
+
|
|
162
|
+
- JS 面板一定要有 `export function didMount()` 方法
|
|
163
|
+
- 所有变量必须加模块前缀(如 `sales_`)
|
|
164
|
+
- 在 JSX 中绑定事件,在 JS 面板定义逻辑
|
|
165
|
+
|
|
166
|
+
### CSS 样式规范
|
|
167
|
+
|
|
168
|
+
- 普通组件隔离:类名前必须带 `:root`
|
|
169
|
+
- 模态框样式禁止带 `:root`,但需确保类名唯一
|
|
170
|
+
- 严禁使用 Tailwind CSS
|
|
171
|
+
|
|
172
|
+
## 示例代码
|
|
173
|
+
|
|
174
|
+
### 页面逻辑 (index.js)
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
import { formatDate } from '../../utils/date';
|
|
178
|
+
|
|
179
|
+
export function didMount() {
|
|
180
|
+
console.log('页面加载完成');
|
|
181
|
+
this.initData();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function onNameChange(val) {
|
|
185
|
+
this.setState({ name: val });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function onSubmit() {
|
|
189
|
+
const date = formatDate(Date.now());
|
|
190
|
+
console.log('提交数据', this.state, date);
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 页面视图 (index.jsx)
|
|
195
|
+
|
|
196
|
+
```jsx
|
|
197
|
+
import React from 'react';
|
|
198
|
+
import Header from './components/Header';
|
|
199
|
+
|
|
200
|
+
export default function render() {
|
|
201
|
+
const { title } = this.state;
|
|
202
|
+
return (
|
|
203
|
+
<div className="page">
|
|
204
|
+
<Header title={title} />
|
|
205
|
+
<div className="content">内容区</div>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## 常见问题
|
|
212
|
+
|
|
213
|
+
### Q: 如何在项目中使用 CLI 命令?
|
|
214
|
+
|
|
215
|
+
A: 确保在项目根目录(包含 package.json 和 rollup.config.mjs 的目录)中运行命令。
|
|
216
|
+
|
|
217
|
+
### Q: 创建的组件如何使用?
|
|
218
|
+
|
|
219
|
+
A: 在页面的 JSX 文件中导入组件:
|
|
220
|
+
```jsx
|
|
221
|
+
import ComponentName from './components/ComponentName/ComponentName';
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Q: 如何自定义模板?
|
|
225
|
+
|
|
226
|
+
A: 修改 `cli/templates.js` 文件中的模板内容。
|
|
227
|
+
|
|
228
|
+
## 许可证
|
|
229
|
+
|
|
230
|
+
ISC
|
package/cli/index.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createProject, createPage, createComponent } from './templates.js';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
function printHelp() {
|
|
12
|
+
console.log(`
|
|
13
|
+
宜搭 JSX 脚手架 CLI
|
|
14
|
+
|
|
15
|
+
用法:
|
|
16
|
+
yida-cli <command> [options]
|
|
17
|
+
|
|
18
|
+
命令:
|
|
19
|
+
init <project-name> 初始化新项目
|
|
20
|
+
page <page-name> 创建新页面
|
|
21
|
+
component <name> [page] 创建新组件
|
|
22
|
+
|
|
23
|
+
选项:
|
|
24
|
+
-h, --help 显示帮助信息
|
|
25
|
+
-v, --version 显示版本号
|
|
26
|
+
|
|
27
|
+
示例:
|
|
28
|
+
yida-cli init my-project
|
|
29
|
+
yida-cli page visit
|
|
30
|
+
yida-cli component Header visit
|
|
31
|
+
`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function printVersion() {
|
|
35
|
+
console.log('yida-cli v1.0.0');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getProjectDir() {
|
|
39
|
+
let currentDir = process.cwd();
|
|
40
|
+
|
|
41
|
+
while (currentDir !== path.parse(currentDir).root) {
|
|
42
|
+
if (fs.existsSync(path.join(currentDir, 'package.json')) &&
|
|
43
|
+
fs.existsSync(path.join(currentDir, 'rollup.config.mjs'))) {
|
|
44
|
+
return currentDir;
|
|
45
|
+
}
|
|
46
|
+
currentDir = path.dirname(currentDir);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
throw new Error('未找到项目根目录,请在项目目录中运行此命令');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function main() {
|
|
53
|
+
const args = process.argv.slice(2);
|
|
54
|
+
const command = args[0];
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
switch (command) {
|
|
58
|
+
case 'init': {
|
|
59
|
+
const projectName = args[1];
|
|
60
|
+
if (!projectName) {
|
|
61
|
+
console.error('错误: 请指定项目名称');
|
|
62
|
+
console.log('用法: yida-cli init <project-name>');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const targetDir = args[2] || process.cwd();
|
|
66
|
+
createProject(projectName, targetDir);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
case 'page': {
|
|
71
|
+
const pageName = args[1];
|
|
72
|
+
if (!pageName) {
|
|
73
|
+
console.error('错误: 请指定页面名称');
|
|
74
|
+
console.log('用法: yida-cli page <page-name>');
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
const projectDir = getProjectDir();
|
|
78
|
+
createPage(pageName, projectDir);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case 'component': {
|
|
83
|
+
const componentName = args[1];
|
|
84
|
+
if (!componentName) {
|
|
85
|
+
console.error('错误: 请指定组件名称');
|
|
86
|
+
console.log('用法: yida-cli component <component-name> [page-name]');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
const pageName = args[2];
|
|
90
|
+
if (!pageName) {
|
|
91
|
+
console.error('错误: 请指定页面名称');
|
|
92
|
+
console.log('用法: yida-cli component <component-name> <page-name>');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
const projectDir = getProjectDir();
|
|
96
|
+
createComponent(componentName, pageName, projectDir);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
case '-h':
|
|
101
|
+
case '--help':
|
|
102
|
+
printHelp();
|
|
103
|
+
break;
|
|
104
|
+
|
|
105
|
+
case '-v':
|
|
106
|
+
case '--version':
|
|
107
|
+
printVersion();
|
|
108
|
+
break;
|
|
109
|
+
|
|
110
|
+
default:
|
|
111
|
+
if (!command) {
|
|
112
|
+
printHelp();
|
|
113
|
+
} else {
|
|
114
|
+
console.error(`未知命令: ${command}`);
|
|
115
|
+
console.log('运行 "yida-cli --help" 查看帮助信息');
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error('错误:', error.message);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
main();
|
package/cli/templates.js
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const templates = {
|
|
9
|
+
indexJs: (pageName) => `import { formatDate } from '../../utils/date';
|
|
10
|
+
|
|
11
|
+
// 必须导出的生命周期
|
|
12
|
+
export function didMount() {
|
|
13
|
+
console.log('${pageName} 页面加载完成');
|
|
14
|
+
this.initData();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 自定义方法
|
|
18
|
+
export function onNameChange(val) {
|
|
19
|
+
this.setState({ name: val });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function onSubmit() {
|
|
23
|
+
const date = formatDate(Date.now());
|
|
24
|
+
console.log('提交数据', this.state, date);
|
|
25
|
+
}
|
|
26
|
+
`,
|
|
27
|
+
|
|
28
|
+
indexJsx: (pageName) => `// 主入口
|
|
29
|
+
import React from 'react';
|
|
30
|
+
|
|
31
|
+
export default function render() {
|
|
32
|
+
const { title } = this.state;
|
|
33
|
+
return (
|
|
34
|
+
<div className="${pageName}-page">
|
|
35
|
+
<div className="${pageName}-header">
|
|
36
|
+
<h1>{title || '${pageName} 页面'}</h1>
|
|
37
|
+
</div>
|
|
38
|
+
<div className="${pageName}-content">内容区</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
`,
|
|
43
|
+
|
|
44
|
+
indexCss: (pageName) => `:root .${pageName}-page {
|
|
45
|
+
width: 100%;
|
|
46
|
+
min-height: 100vh;
|
|
47
|
+
background: #f5f5f5;
|
|
48
|
+
padding: 20px;
|
|
49
|
+
box-sizing: border-box;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
:root .${pageName}-header {
|
|
53
|
+
background: #fff;
|
|
54
|
+
padding: 20px;
|
|
55
|
+
border-radius: 8px;
|
|
56
|
+
margin-bottom: 20px;
|
|
57
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
:root .${pageName}-content {
|
|
61
|
+
background: #fff;
|
|
62
|
+
padding: 20px;
|
|
63
|
+
border-radius: 8px;
|
|
64
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
65
|
+
}
|
|
66
|
+
`,
|
|
67
|
+
|
|
68
|
+
componentJsx: (componentName) => `import React from 'react';
|
|
69
|
+
|
|
70
|
+
export default function ${componentName}({ title }) {
|
|
71
|
+
return (
|
|
72
|
+
<div className="${componentName.toLowerCase()}-container">
|
|
73
|
+
<h2>{title || '${componentName}'}</h2>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
`,
|
|
78
|
+
|
|
79
|
+
componentCss: (componentName) => `:root .${componentName.toLowerCase()}-container {
|
|
80
|
+
padding: 16px;
|
|
81
|
+
background: #fff;
|
|
82
|
+
border-radius: 4px;
|
|
83
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
|
84
|
+
}
|
|
85
|
+
`,
|
|
86
|
+
|
|
87
|
+
utilsDate: () => `export function formatDate(timestamp) {
|
|
88
|
+
const date = new Date(timestamp);
|
|
89
|
+
const year = date.getFullYear();
|
|
90
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
91
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
92
|
+
return \`\${year}-\${month}-\${day}\`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function formatDateTime(timestamp) {
|
|
96
|
+
const date = new Date(timestamp);
|
|
97
|
+
const year = date.getFullYear();
|
|
98
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
99
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
100
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
101
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
102
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
103
|
+
return \`\${year}-\${month}-\${day} \${hours}:\${minutes}:\${seconds}\`;
|
|
104
|
+
}
|
|
105
|
+
`,
|
|
106
|
+
|
|
107
|
+
packageJson: (projectName) => `{
|
|
108
|
+
"name": "${projectName}",
|
|
109
|
+
"version": "1.0.0",
|
|
110
|
+
"description": "宜搭 JSX 项目",
|
|
111
|
+
"main": "index.js",
|
|
112
|
+
"type": "module",
|
|
113
|
+
"scripts": {
|
|
114
|
+
"build": "rollup -c",
|
|
115
|
+
"build:page": "node scripts/build-page.js",
|
|
116
|
+
"dev": "rollup -c -w",
|
|
117
|
+
"dev:page": "node scripts/dev-page.js"
|
|
118
|
+
},
|
|
119
|
+
"keywords": ["yida", "jsx"],
|
|
120
|
+
"author": "",
|
|
121
|
+
"license": "ISC",
|
|
122
|
+
"devDependencies": {
|
|
123
|
+
"@babel/core": "^7.29.0",
|
|
124
|
+
"@babel/preset-env": "^7.29.0",
|
|
125
|
+
"@babel/preset-react": "^7.28.5",
|
|
126
|
+
"@rollup/plugin-babel": "^6.1.0",
|
|
127
|
+
"@rollup/plugin-commonjs": "^29.0.0",
|
|
128
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
129
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
130
|
+
"rollup": "^4.57.1"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
`,
|
|
134
|
+
|
|
135
|
+
rollupConfig: () => `import resolve from '@rollup/plugin-node-resolve';
|
|
136
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
137
|
+
import fs from 'fs';
|
|
138
|
+
import path from 'path';
|
|
139
|
+
import { fileURLToPath } from 'url';
|
|
140
|
+
|
|
141
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
142
|
+
const __dirname = path.dirname(__filename);
|
|
143
|
+
const pagesDir = path.resolve(__dirname, 'src/pages');
|
|
144
|
+
|
|
145
|
+
const targetPage = process.env.PAGE_NAME;
|
|
146
|
+
|
|
147
|
+
function recursiveParser(filePath, context) {
|
|
148
|
+
if (context.files.has(filePath)) return "";
|
|
149
|
+
context.files.add(filePath);
|
|
150
|
+
|
|
151
|
+
let code = fs.readFileSync(filePath, "utf-8");
|
|
152
|
+
const dir = path.dirname(filePath);
|
|
153
|
+
|
|
154
|
+
code = code.replace(/import\\s+['"](..+?\\.css)['"];?\\r?\\n?/g, (m, p) => {
|
|
155
|
+
const fullPath = path.resolve(dir, p);
|
|
156
|
+
if (fs.existsSync(fullPath)) {
|
|
157
|
+
context.css.push(
|
|
158
|
+
\`/* From: \${path.basename(fullPath)} */\\n\${fs.readFileSync(fullPath, "utf-8")}\`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return "";
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
code = code.replace(/import\\s+['"](..+?\\.js)['"];?\\r?\\n?/g, (m, p) => {
|
|
165
|
+
const fullPath = path.resolve(dir, p);
|
|
166
|
+
if (fs.existsSync(fullPath) && !fullPath.endsWith("index.js")) {
|
|
167
|
+
context.logic.push(
|
|
168
|
+
\`/* Logic From: \${path.basename(fullPath)} */\\n\${fs.readFileSync(fullPath, "utf-8")}\`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return "";
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
let childJSXDefinitions = "";
|
|
175
|
+
const jsxImportRegex =
|
|
176
|
+
/import\\s+[\\w\\s{},]+\\s+from\\s+['"](..+?)['"];?\\r?\\n?/g;
|
|
177
|
+
code = code.replace(jsxImportRegex, (match, importPath) => {
|
|
178
|
+
let childPath = path.resolve(dir, importPath);
|
|
179
|
+
if (!fs.existsSync(childPath)) {
|
|
180
|
+
if (fs.existsSync(childPath + ".jsx")) childPath += ".jsx";
|
|
181
|
+
else if (fs.existsSync(childPath + ".js")) childPath += ".js";
|
|
182
|
+
else return "";
|
|
183
|
+
}
|
|
184
|
+
childJSXDefinitions += recursiveParser(childPath, context) + "\\n";
|
|
185
|
+
return "";
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
code = code.replace(/import\\s+[\\s\\S]*?from\\s+['"].*?['"];?\\s*/g, "");
|
|
189
|
+
code = code.replace(/['"]?react['"]?;?\\s*/g, "");
|
|
190
|
+
|
|
191
|
+
const isMainEntry = filePath.endsWith("index.jsx");
|
|
192
|
+
if (isMainEntry) {
|
|
193
|
+
const renderFuncRegex = /(function\\s+render\\s*\\([^)]*\\)\\s*\\{)/;
|
|
194
|
+
|
|
195
|
+
code = code
|
|
196
|
+
.replace(/export\\s+default\\s+function\\s+render/, "function render")
|
|
197
|
+
.replace(/export\\s+default\\s+render/, "")
|
|
198
|
+
.replace(/export\\s+\\{\\s*render\\s+as\\s+default\\s*\\}\\s*;?/g, "");
|
|
199
|
+
|
|
200
|
+
if (renderFuncRegex.test(code)) {
|
|
201
|
+
code = code.replace(renderFuncRegex, \`$1\\n\${childJSXDefinitions}\`);
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
code = code
|
|
205
|
+
.replace(/export\\s+default\\s+/g, "")
|
|
206
|
+
.replace(/export\\s+function\\s+/g, "function ")
|
|
207
|
+
.replace(/export\\s+/g, "");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return code.trim();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function logicCleanerPlugin(extraLogic = []) {
|
|
214
|
+
return {
|
|
215
|
+
name: "logic-cleaner-plugin",
|
|
216
|
+
renderChunk(code) {
|
|
217
|
+
let combinedCode = extraLogic.join("\\n\\n") + "\\n\\n" + code;
|
|
218
|
+
|
|
219
|
+
let cleanCode = combinedCode.replace(/export\\s+\\{\\s*[\\s\\S]*?\\};?/g, "");
|
|
220
|
+
|
|
221
|
+
cleanCode = cleanCode.replace(/^export\\s+/gm, "");
|
|
222
|
+
cleanCode = cleanCode.replace(
|
|
223
|
+
/^(async\\s+)?function\\s+(\\w+)/gm,
|
|
224
|
+
"export $1function $2",
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
return cleanCode.trim();
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function buildPage(pageName) {
|
|
233
|
+
const pagePath = path.join(pagesDir, pageName);
|
|
234
|
+
const outDir = path.join(pagePath, "out");
|
|
235
|
+
|
|
236
|
+
if (!fs.existsSync(pagePath)) {
|
|
237
|
+
console.error(\`错误: 页面目录不存在: \${pagePath}\`);
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
|
242
|
+
|
|
243
|
+
const context = { css: [], logic: [], files: Set.prototype };
|
|
244
|
+
context.files = new Set();
|
|
245
|
+
|
|
246
|
+
if (fs.existsSync(path.join(pagePath, "index.jsx"))) {
|
|
247
|
+
const finalViewCode = recursiveParser(
|
|
248
|
+
path.join(pagePath, "index.jsx"),
|
|
249
|
+
context,
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
fs.writeFileSync(path.join(outDir, "index.jsx"), finalViewCode);
|
|
253
|
+
|
|
254
|
+
const mainCssPath = path.join(pagePath, "index.css");
|
|
255
|
+
if (fs.existsSync(mainCssPath))
|
|
256
|
+
context.css.push(fs.readFileSync(mainCssPath, "utf-8"));
|
|
257
|
+
fs.writeFileSync(
|
|
258
|
+
path.join(outDir, "index.css"),
|
|
259
|
+
context.css.join("\\n\\n"),
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
input: path.join(pagePath, "index.js"),
|
|
264
|
+
output: { file: path.join(outDir, "index.js"), format: "es" },
|
|
265
|
+
plugins: [
|
|
266
|
+
resolve({ browser: true }),
|
|
267
|
+
commonjs(),
|
|
268
|
+
logicCleanerPlugin(context.logic),
|
|
269
|
+
],
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const buildTasks = [];
|
|
277
|
+
|
|
278
|
+
if (fs.existsSync(pagesDir)) {
|
|
279
|
+
const pages = fs
|
|
280
|
+
.readdirSync(pagesDir)
|
|
281
|
+
.filter((dir) => fs.statSync(path.join(pagesDir, dir)).isDirectory());
|
|
282
|
+
|
|
283
|
+
if (targetPage) {
|
|
284
|
+
console.log(\`📦 单独打包页面: \${targetPage}\`);
|
|
285
|
+
const task = buildPage(targetPage);
|
|
286
|
+
if (task) {
|
|
287
|
+
buildTasks.push(task);
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
console.log(\`📦 全量打包所有页面 (\${pages.length} 个)\`);
|
|
291
|
+
pages.forEach((pageName) => {
|
|
292
|
+
const task = buildPage(pageName);
|
|
293
|
+
if (task) {
|
|
294
|
+
buildTasks.push(task);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export default buildTasks;
|
|
301
|
+
`,
|
|
302
|
+
|
|
303
|
+
gitignore: () => `# 依赖
|
|
304
|
+
node_modules/
|
|
305
|
+
package-lock.json
|
|
306
|
+
yarn.lock
|
|
307
|
+
pnpm-lock.yaml
|
|
308
|
+
|
|
309
|
+
# 构建输出
|
|
310
|
+
dist/
|
|
311
|
+
build/
|
|
312
|
+
src/pages/*/out/
|
|
313
|
+
*.log
|
|
314
|
+
*.tsbuildinfo
|
|
315
|
+
|
|
316
|
+
# 编辑器和 IDE
|
|
317
|
+
.vscode/
|
|
318
|
+
.idea/
|
|
319
|
+
*.swp
|
|
320
|
+
*.swo
|
|
321
|
+
*~
|
|
322
|
+
.DS_Store
|
|
323
|
+
|
|
324
|
+
# 临时文件
|
|
325
|
+
*.tmp
|
|
326
|
+
*.temp
|
|
327
|
+
.cache/
|
|
328
|
+
.temp/
|
|
329
|
+
|
|
330
|
+
# 测试覆盖率
|
|
331
|
+
coverage/
|
|
332
|
+
.nyc_output/
|
|
333
|
+
|
|
334
|
+
# 环境变量
|
|
335
|
+
.env
|
|
336
|
+
.env.local
|
|
337
|
+
.env.*.local
|
|
338
|
+
|
|
339
|
+
# 系统文件
|
|
340
|
+
Thumbs.db
|
|
341
|
+
Desktop.ini
|
|
342
|
+
|
|
343
|
+
# 调试日志
|
|
344
|
+
npm-debug.log*
|
|
345
|
+
yarn-debug.log*
|
|
346
|
+
yarn-error.log*
|
|
347
|
+
pnpm-debug.log*
|
|
348
|
+
|
|
349
|
+
# Rollup 缓存
|
|
350
|
+
.rollup.cache/
|
|
351
|
+
|
|
352
|
+
# 其他
|
|
353
|
+
*.tgz
|
|
354
|
+
`,
|
|
355
|
+
|
|
356
|
+
buildPageScript: () => `import { execSync } from 'child_process';
|
|
357
|
+
import path from 'path';
|
|
358
|
+
import { fileURLToPath } from 'url';
|
|
359
|
+
|
|
360
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
361
|
+
const __dirname = path.dirname(__filename);
|
|
362
|
+
|
|
363
|
+
const args = process.argv.slice(2);
|
|
364
|
+
const pageName = args[0];
|
|
365
|
+
|
|
366
|
+
if (!pageName) {
|
|
367
|
+
console.error('❌ 请指定页面名称');
|
|
368
|
+
console.log('用法: node scripts/build-page.js <页面名称>');
|
|
369
|
+
console.log('示例: node scripts/build-page.js visit');
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
console.log(\`📦 开始打包页面: \${pageName}\`);
|
|
375
|
+
|
|
376
|
+
const command = \`set PAGE_NAME=\${pageName}&& rollup -c\`;
|
|
377
|
+
|
|
378
|
+
execSync(command, {
|
|
379
|
+
stdio: 'inherit',
|
|
380
|
+
shell: true,
|
|
381
|
+
cwd: path.join(__dirname, '..')
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
console.log(\`✅ 页面 \${pageName} 打包完成\`);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
console.error('❌ 打包失败:', error.message);
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
`,
|
|
390
|
+
|
|
391
|
+
devPageScript: () => `import { execSync } from 'child_process';
|
|
392
|
+
import path from 'path';
|
|
393
|
+
import { fileURLToPath } from 'url';
|
|
394
|
+
|
|
395
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
396
|
+
const __dirname = path.dirname(__filename);
|
|
397
|
+
|
|
398
|
+
const args = process.argv.slice(2);
|
|
399
|
+
const pageName = args[0];
|
|
400
|
+
|
|
401
|
+
if (!pageName) {
|
|
402
|
+
console.error('❌ 请指定页面名称');
|
|
403
|
+
console.log('用法: node scripts/dev-page.js <页面名称>');
|
|
404
|
+
console.log('示例: node scripts/dev-page.js visit');
|
|
405
|
+
process.exit(1);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
console.log(\`📦 开始监听页面: \${pageName}\`);
|
|
410
|
+
|
|
411
|
+
const command = \`set PAGE_NAME=\${pageName}&& rollup -c -w\`;
|
|
412
|
+
|
|
413
|
+
execSync(command, {
|
|
414
|
+
stdio: 'inherit',
|
|
415
|
+
shell: true,
|
|
416
|
+
cwd: path.join(__dirname, '..')
|
|
417
|
+
});
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.error('❌ 监听失败:', error.message);
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
`
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
export function createProject(projectName, targetDir) {
|
|
426
|
+
const projectPath = path.join(targetDir, projectName);
|
|
427
|
+
|
|
428
|
+
if (fs.existsSync(projectPath)) {
|
|
429
|
+
throw new Error(`目录 ${projectName} 已存在`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
433
|
+
fs.mkdirSync(path.join(projectPath, 'src'));
|
|
434
|
+
fs.mkdirSync(path.join(projectPath, 'src/pages'));
|
|
435
|
+
fs.mkdirSync(path.join(projectPath, 'src/utils'));
|
|
436
|
+
fs.mkdirSync(path.join(projectPath, 'scripts'));
|
|
437
|
+
|
|
438
|
+
fs.writeFileSync(path.join(projectPath, 'package.json'), templates.packageJson(projectName));
|
|
439
|
+
fs.writeFileSync(path.join(projectPath, 'rollup.config.mjs'), templates.rollupConfig());
|
|
440
|
+
fs.writeFileSync(path.join(projectPath, '.gitignore'), templates.gitignore());
|
|
441
|
+
fs.writeFileSync(path.join(projectPath, 'src/utils/date.js'), templates.utilsDate());
|
|
442
|
+
fs.writeFileSync(path.join(projectPath, 'scripts/build-page.js'), templates.buildPageScript());
|
|
443
|
+
fs.writeFileSync(path.join(projectPath, 'scripts/dev-page.js'), templates.devPageScript());
|
|
444
|
+
|
|
445
|
+
console.log(`✓ 项目 ${projectName} 创建成功`);
|
|
446
|
+
console.log(`✓ 请运行以下命令安装依赖:`);
|
|
447
|
+
console.log(` cd ${projectName}`);
|
|
448
|
+
console.log(` npm install`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export function createPage(pageName, projectDir) {
|
|
452
|
+
const pagesDir = path.join(projectDir, 'src/pages');
|
|
453
|
+
const pagePath = path.join(pagesDir, pageName);
|
|
454
|
+
|
|
455
|
+
if (fs.existsSync(pagePath)) {
|
|
456
|
+
throw new Error(`页面 ${pageName} 已存在`);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
fs.mkdirSync(pagePath, { recursive: true });
|
|
460
|
+
fs.mkdirSync(path.join(pagePath, 'components'));
|
|
461
|
+
|
|
462
|
+
fs.writeFileSync(path.join(pagePath, 'index.js'), templates.indexJs(pageName));
|
|
463
|
+
fs.writeFileSync(path.join(pagePath, 'index.jsx'), templates.indexJsx(pageName));
|
|
464
|
+
fs.writeFileSync(path.join(pagePath, 'index.css'), templates.indexCss(pageName));
|
|
465
|
+
|
|
466
|
+
console.log(`✓ 页面 ${pageName} 创建成功`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export function createComponent(componentName, pageName, projectDir) {
|
|
470
|
+
const componentsDir = path.join(projectDir, 'src/pages', pageName, 'components');
|
|
471
|
+
const componentPath = path.join(componentsDir, componentName);
|
|
472
|
+
|
|
473
|
+
if (fs.existsSync(componentPath)) {
|
|
474
|
+
throw new Error(`组件 ${componentName} 已存在`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
fs.mkdirSync(componentPath, { recursive: true });
|
|
478
|
+
|
|
479
|
+
fs.writeFileSync(path.join(componentPath, `${componentName}.jsx`), templates.componentJsx(componentName));
|
|
480
|
+
fs.writeFileSync(path.join(componentPath, `${componentName}.css`), templates.componentCss(componentName));
|
|
481
|
+
|
|
482
|
+
console.log(`✓ 组件 ${componentName} 创建成功`);
|
|
483
|
+
console.log(`✓ 请在页面中引入组件:`);
|
|
484
|
+
console.log(` import ${componentName} from './components/${componentName}/${componentName}';`);
|
|
485
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yida-jsx",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "宜搭 JSX 组件开发脚手架工具",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"yida-cli": "./cli/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"cli",
|
|
12
|
+
"rollup.config.mjs",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "rollup -c",
|
|
17
|
+
"build:page": "node scripts/build-page.js",
|
|
18
|
+
"dev": "rollup -c -w",
|
|
19
|
+
"dev:page": "node scripts/dev-page.js",
|
|
20
|
+
"init": "node cli/index.js init",
|
|
21
|
+
"page": "node cli/index.js page",
|
|
22
|
+
"component": "node cli/index.js component"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"yida",
|
|
26
|
+
"jsx",
|
|
27
|
+
"scaffold",
|
|
28
|
+
"cli",
|
|
29
|
+
"宜搭"
|
|
30
|
+
],
|
|
31
|
+
"author": "",
|
|
32
|
+
"license": "ISC",
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@babel/core": "^7.29.0",
|
|
35
|
+
"@babel/preset-env": "^7.29.0",
|
|
36
|
+
"@babel/preset-react": "^7.28.5",
|
|
37
|
+
"@rollup/plugin-babel": "^6.1.0",
|
|
38
|
+
"@rollup/plugin-commonjs": "^29.0.0",
|
|
39
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
40
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
41
|
+
"cross-env": "^7.0.3",
|
|
42
|
+
"rollup": "^4.57.1"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import resolve from "@rollup/plugin-node-resolve";
|
|
2
|
+
import commonjs from "@rollup/plugin-commonjs";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const pagesDir = path.resolve(__dirname, "src/pages");
|
|
10
|
+
|
|
11
|
+
const targetPage = process.env.PAGE_NAME;
|
|
12
|
+
|
|
13
|
+
function recursiveParser(filePath, context) {
|
|
14
|
+
if (context.files.has(filePath)) return "";
|
|
15
|
+
context.files.add(filePath);
|
|
16
|
+
|
|
17
|
+
let code = fs.readFileSync(filePath, "utf-8");
|
|
18
|
+
const dir = path.dirname(filePath);
|
|
19
|
+
|
|
20
|
+
code = code.replace(/import\s+['"](\..+?\.css)['"];?\r?\n?/g, (m, p) => {
|
|
21
|
+
const fullPath = path.resolve(dir, p);
|
|
22
|
+
if (fs.existsSync(fullPath)) {
|
|
23
|
+
context.css.push(
|
|
24
|
+
`/* From: ${path.basename(fullPath)} */\n${fs.readFileSync(fullPath, "utf-8")}`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return "";
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
code = code.replace(/import\s+['"](\..+?\.js)['"];?\r?\n?/g, (m, p) => {
|
|
31
|
+
const fullPath = path.resolve(dir, p);
|
|
32
|
+
if (fs.existsSync(fullPath) && !fullPath.endsWith("index.js")) {
|
|
33
|
+
context.logic.push(
|
|
34
|
+
`/* Logic From: ${path.basename(fullPath)} */\n${fs.readFileSync(fullPath, "utf-8")}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return "";
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
let childJSXDefinitions = "";
|
|
41
|
+
const jsxImportRegex =
|
|
42
|
+
/import\s+[\w\s{},]+\s+from\s+['"](\..+?)['"];?\r?\n?/g;
|
|
43
|
+
code = code.replace(jsxImportRegex, (match, importPath) => {
|
|
44
|
+
let childPath = path.resolve(dir, importPath);
|
|
45
|
+
if (!fs.existsSync(childPath)) {
|
|
46
|
+
if (fs.existsSync(childPath + ".jsx")) childPath += ".jsx";
|
|
47
|
+
else if (fs.existsSync(childPath + ".js")) childPath += ".js";
|
|
48
|
+
else return "";
|
|
49
|
+
}
|
|
50
|
+
childJSXDefinitions += recursiveParser(childPath, context) + "\n";
|
|
51
|
+
return "";
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
code = code.replace(/import\s+[\s\S]*?from\s+['"].*?['"];?\s*/g, "");
|
|
55
|
+
code = code.replace(/['"]?react['"]?;?\s*/g, "");
|
|
56
|
+
|
|
57
|
+
const isMainEntry = filePath.endsWith("index.jsx");
|
|
58
|
+
if (isMainEntry) {
|
|
59
|
+
const renderFuncRegex = /(function\s+render\s*\([^)]*\)\s*\{)/;
|
|
60
|
+
|
|
61
|
+
code = code
|
|
62
|
+
.replace(/export\s+default\s+function\s+render/, "function render")
|
|
63
|
+
.replace(/export\s+default\s+render/, "")
|
|
64
|
+
.replace(/export\s+\{\s*render\s+as\s+default\s*\}\s*;?/g, "");
|
|
65
|
+
|
|
66
|
+
if (renderFuncRegex.test(code)) {
|
|
67
|
+
code = code.replace(renderFuncRegex, `$1\n${childJSXDefinitions}`);
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
code = code
|
|
71
|
+
.replace(/export\s+default\s+/g, "")
|
|
72
|
+
.replace(/export\s+function\s+/g, "function ")
|
|
73
|
+
.replace(/export\s+/g, "");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return code.trim();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function logicCleanerPlugin(extraLogic = []) {
|
|
80
|
+
return {
|
|
81
|
+
name: "logic-cleaner-plugin",
|
|
82
|
+
renderChunk(code) {
|
|
83
|
+
let combinedCode = extraLogic.join("\n\n") + "\n\n" + code;
|
|
84
|
+
|
|
85
|
+
let cleanCode = combinedCode.replace(/export\s+\{\s*[\s\S]*?\};?/g, "");
|
|
86
|
+
|
|
87
|
+
cleanCode = cleanCode.replace(/^export\s+/gm, "");
|
|
88
|
+
cleanCode = cleanCode.replace(
|
|
89
|
+
/^(async\s+)?function\s+(\w+)/gm,
|
|
90
|
+
"export $1function $2",
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return cleanCode.trim();
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function buildPage(pageName) {
|
|
99
|
+
const pagePath = path.join(pagesDir, pageName);
|
|
100
|
+
const outDir = path.join(pagePath, "out");
|
|
101
|
+
|
|
102
|
+
if (!fs.existsSync(pagePath)) {
|
|
103
|
+
console.error(`错误: 页面目录不存在: ${pagePath}`);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
|
108
|
+
|
|
109
|
+
const context = { css: [], logic: [], files: Set.prototype };
|
|
110
|
+
context.files = new Set();
|
|
111
|
+
|
|
112
|
+
if (fs.existsSync(path.join(pagePath, "index.jsx"))) {
|
|
113
|
+
const finalViewCode = recursiveParser(
|
|
114
|
+
path.join(pagePath, "index.jsx"),
|
|
115
|
+
context,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
fs.writeFileSync(path.join(outDir, "index.jsx"), finalViewCode);
|
|
119
|
+
|
|
120
|
+
const mainCssPath = path.join(pagePath, "index.css");
|
|
121
|
+
if (fs.existsSync(mainCssPath))
|
|
122
|
+
context.css.push(fs.readFileSync(mainCssPath, "utf-8"));
|
|
123
|
+
fs.writeFileSync(
|
|
124
|
+
path.join(outDir, "index.css"),
|
|
125
|
+
context.css.join("\n\n"),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
input: path.join(pagePath, "index.js"),
|
|
130
|
+
output: { file: path.join(outDir, "index.js"), format: "es" },
|
|
131
|
+
plugins: [
|
|
132
|
+
resolve({ browser: true }),
|
|
133
|
+
commonjs(),
|
|
134
|
+
logicCleanerPlugin(context.logic),
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const buildTasks = [];
|
|
143
|
+
|
|
144
|
+
if (fs.existsSync(pagesDir)) {
|
|
145
|
+
const pages = fs
|
|
146
|
+
.readdirSync(pagesDir)
|
|
147
|
+
.filter((dir) => fs.statSync(path.join(pagesDir, dir)).isDirectory());
|
|
148
|
+
|
|
149
|
+
if (targetPage) {
|
|
150
|
+
console.log(`📦 单独打包页面: ${targetPage}`);
|
|
151
|
+
const task = buildPage(targetPage);
|
|
152
|
+
if (task) {
|
|
153
|
+
buildTasks.push(task);
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
console.log(`📦 全量打包所有页面 (${pages.length} 个)`);
|
|
157
|
+
pages.forEach((pageName) => {
|
|
158
|
+
const task = buildPage(pageName);
|
|
159
|
+
if (task) {
|
|
160
|
+
buildTasks.push(task);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export default buildTasks;
|