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 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();
@@ -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;