@love-sqjm/magic 2026.4.15
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 +674 -0
- package/README.md +93 -0
- package/app.js +51 -0
- package/doc/api/examples.md +563 -0
- package/doc/api/index.md +563 -0
- package/doc/architecture.md +322 -0
- package/doc/config-reference.md +646 -0
- package/doc/data-model.md +622 -0
- package/doc/development-guide.md +582 -0
- package/doc/magic-file/index.md +610 -0
- package/doc/user-guide/index.md +485 -0
- package/doc/workflow.md +548 -0
- package/index.js +157 -0
- package/magic.bat +2 -0
- package/magic.ps1 +5 -0
- package/package.json +44 -0
- package/script/build-project.js +16 -0
- package/script/config.js +23 -0
- package/script/create-project.js +73 -0
- package/script/global/printf.js +13 -0
- package/script/global/project-build-config.js +161 -0
- package/script/global/support-platform.js +5 -0
- package/script/module/compiler/global.js +43 -0
- package/script/module/compiler/id-generate.js +18 -0
- package/script/module/compiler/index-dom.js +78 -0
- package/script/module/compiler/macro-replace.js +22 -0
- package/script/module/compiler/macro.js +6 -0
- package/script/module/compiler/start.js +10 -0
- package/script/module/compiler/step/1.js +253 -0
- package/script/module/compiler/step/2.js +79 -0
- package/script/module/compiler/step/3.js +37 -0
- package/script/module/compiler/step/4.js +20 -0
- package/script/module/compiler/step/5.js +634 -0
- package/script/module/compiler/step/6.js +304 -0
- package/script/module/compiler/step/end.js +124 -0
- package/script/run-project.js +249 -0
- package/script/util/bun-fs.js +40 -0
- package/script/util/copy-dir.js +21 -0
- package/script/util/create-simple-dom-element.js +23 -0
- package/script/util/file-util.js +95 -0
- package/script/util/filtration-file.js +20 -0
- package/script/util/get-dir-all-file.js +28 -0
- package/script/util/get-first-object-key.js +9 -0
- package/script/util/is-empty-object.js +8 -0
- package/script/util/is-string-over-size.js +4 -0
- package/script/util/is.js +18 -0
- package/script/util/logging.js +142 -0
- package/script/util/task.js +16 -0
- package/script/util/traversal.js +28 -0
- package/template/platform-config/node-webkit +23 -0
- package/template/platform-config/web +1 -0
- package/template/project-base/app.xml +5 -0
- package/template/project-base/build.module.toml +37 -0
- package/template/project-base/build.toml +43 -0
- package/template/runtime/runtime.css +3 -0
- package/template/runtime/runtime.js +895 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Magic 框架
|
|
2
|
+
|
|
3
|
+
[](https://github.com/SQJM/magic)
|
|
4
|
+
[](https://github.com/SQJM/magic)
|
|
5
|
+
|
|
6
|
+
## 概述
|
|
7
|
+
|
|
8
|
+
**Magic** 是一个基于 Bun.js 运行的全栈 Web 开发框架,旨在提供高效、简洁的全栈开发体验。通过自定义的 `.m` 文件格式,开发者可以将模板、样式和脚本整合到单一文件中,极大简化了组件化开发的复杂度。
|
|
9
|
+
|
|
10
|
+
## 核心特性
|
|
11
|
+
|
|
12
|
+
| 特性 | 描述 |
|
|
13
|
+
|------|------|
|
|
14
|
+
| **一体化文件格式** | `.m` 文件整合 HTML 模板、CSS 样式、JavaScript 脚本 |
|
|
15
|
+
| **多平台支持** | Web 浏览器、Node-Webkit 桌面应用、可复用模块 |
|
|
16
|
+
| **编译时优化** | Babel AST 转换、CSS 作用域、代码压缩 |
|
|
17
|
+
| **热更新支持** | WebSocket 实时刷新开发 |
|
|
18
|
+
| **宏系统** | 支持文件包含、数据绑定等编译时处理 |
|
|
19
|
+
| **模块化架构** | 清晰的编译器流水线设计 |
|
|
20
|
+
|
|
21
|
+
## 技术栈
|
|
22
|
+
|
|
23
|
+
| 类别 | 技术选型 |
|
|
24
|
+
|------|----------|
|
|
25
|
+
| 运行时 | Bun.js |
|
|
26
|
+
| CLI | Commander.js |
|
|
27
|
+
| HTML 解析 | node-html-parser |
|
|
28
|
+
| CSS 处理 | PostCSS + Autoprefixer |
|
|
29
|
+
| JS 压缩 | Terser |
|
|
30
|
+
| HTML 压缩 | html-minifier-terser |
|
|
31
|
+
| AST 处理 | @babel/* |
|
|
32
|
+
| 文件匹配 | fast-glob |
|
|
33
|
+
| WebSocket | ws |
|
|
34
|
+
|
|
35
|
+
## 快速开始
|
|
36
|
+
|
|
37
|
+
### 安装
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
bun install -g @love-sqjm/magic
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 创建项目
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
magic init new-project --web
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 构建项目
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd new-project
|
|
53
|
+
magic build
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 运行项目
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
magic run
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 支持的平台
|
|
63
|
+
|
|
64
|
+
| 平台 | 说明 |
|
|
65
|
+
|------|------|
|
|
66
|
+
| `web` | 浏览器环境,支持内置 HTTP 服务器和热更新 |
|
|
67
|
+
| `node-webkit` | Node-Webkit 桌面应用 |
|
|
68
|
+
| `module` | 可复用的模块库 |
|
|
69
|
+
|
|
70
|
+
## 项目结构
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
magic-project/
|
|
74
|
+
├── app/ # 源码目录
|
|
75
|
+
│ ├── app.xml # 应用配置文件
|
|
76
|
+
│ ├── index.m # 入口模块
|
|
77
|
+
│ └── ... # 其他 .m 模块文件
|
|
78
|
+
├── build/ # 构建输出目录
|
|
79
|
+
├── build.toml # 构建配置文件
|
|
80
|
+
└── package.json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 学习资源
|
|
84
|
+
|
|
85
|
+
- [架构文档](./architecture.md) - 系统架构和技术细节
|
|
86
|
+
- [使用指南](./user-guide/index.md) - 环境配置和部署步骤
|
|
87
|
+
- [.m 文件规范](./magic-file/index.md) - 组件开发文档
|
|
88
|
+
- [API 参考](./api/index.md) - Runtime API 详细说明
|
|
89
|
+
- [配置手册](./config-reference.md) - build.toml 配置详解
|
|
90
|
+
|
|
91
|
+
## 许可证
|
|
92
|
+
|
|
93
|
+
MIT License - [SQJM](https://github.com/SQJM)
|
package/app.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
|
|
3
|
+
const runDir = import.meta.dir + '/';
|
|
4
|
+
|
|
5
|
+
const App = {
|
|
6
|
+
version : JSON.parse( fs.readFileSync( runDir + "package.json" ).toString() ).version,
|
|
7
|
+
runDir : runDir,
|
|
8
|
+
templateDir : {
|
|
9
|
+
runtime : {
|
|
10
|
+
path : runDir + "template/runtime/"
|
|
11
|
+
},
|
|
12
|
+
platformConfig : {
|
|
13
|
+
path : runDir + "template/platform-config/"
|
|
14
|
+
},
|
|
15
|
+
projectBase : {
|
|
16
|
+
path : runDir + "template/project-base/"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
project : {
|
|
20
|
+
dir : process.cwd() + '/'
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function read( path ) {
|
|
25
|
+
if ( !fs.existsSync( path ) ) throw `${ path } doesn't exist`;
|
|
26
|
+
return fs.readFileSync( path ).toString();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const app = {
|
|
30
|
+
...App,
|
|
31
|
+
templateDir : {
|
|
32
|
+
runtime : {
|
|
33
|
+
path : App.templateDir.runtime.path,
|
|
34
|
+
get( fileName ) {
|
|
35
|
+
return read( App.templateDir.runtime.path + fileName );
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
platformConfig : {
|
|
39
|
+
path : App.templateDir.platformConfig.path,
|
|
40
|
+
get( fileName ) {
|
|
41
|
+
return read( App.templateDir.platformConfig.path + fileName );
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
projectBase : {
|
|
45
|
+
path : App.templateDir.projectBase.path,
|
|
46
|
+
get( fileName ) {
|
|
47
|
+
return read( App.templateDir.projectBase.path + fileName );
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
# Magic Runtime 函数使用示例
|
|
2
|
+
|
|
3
|
+
## 目录
|
|
4
|
+
|
|
5
|
+
1. [模块导入 - importM](#1-模块导入---importm)
|
|
6
|
+
2. [元素获取 - $id / $](#2-元素获取---id--)
|
|
7
|
+
3. [UiData 响应式数据 - createUiData](#3-uodata-响应式数据---createuodata)
|
|
8
|
+
4. [动态值绑定 - DynamicValueBind](#4-动态值绑定---dynamicvaluebind)
|
|
9
|
+
5. [事件系统 - emit / on_event](#5-事件系统---emit--on_event)
|
|
10
|
+
6. [参数处理 - parserArgs / createArgs](#6-参数处理---parserargs--createargs)
|
|
11
|
+
7. [路由系统 - router](#7-路由系统---router)
|
|
12
|
+
8. [其他工具函数](#8-其他工具函数)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. 模块导入 - importM
|
|
17
|
+
|
|
18
|
+
### 基本用法
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
// 导入并实例化一个组件
|
|
22
|
+
const dialog = magic.importM("dialog");
|
|
23
|
+
|
|
24
|
+
// 传入参数
|
|
25
|
+
const form = magic.importM("form", {
|
|
26
|
+
title: "用户注册",
|
|
27
|
+
width: 400
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// 传入事件监听
|
|
31
|
+
const button = magic.importM("button", {}, {
|
|
32
|
+
click: (data) => {
|
|
33
|
+
console.log("按钮被点击:", data);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 完整示例
|
|
39
|
+
|
|
40
|
+
```xml
|
|
41
|
+
<!-- button.m -->
|
|
42
|
+
<template>
|
|
43
|
+
<button #id="btn">点击</button>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<script code="event">
|
|
47
|
+
click = (event) => {
|
|
48
|
+
emit_event("click", { time: Date.now() });
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<script code="interface">
|
|
53
|
+
setText = (text) => {
|
|
54
|
+
$btn.textContent = text;
|
|
55
|
+
};
|
|
56
|
+
</script>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
// 使用按钮组件
|
|
61
|
+
const btn = magic.importM("button");
|
|
62
|
+
btn.interface.setText("新文本");
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 2. 元素获取 - $id / $
|
|
68
|
+
|
|
69
|
+
### $id() - 获取模块内元素
|
|
70
|
+
|
|
71
|
+
```xml
|
|
72
|
+
<script code="global">
|
|
73
|
+
const {
|
|
74
|
+
$container, // 获取 #id="container" 的元素
|
|
75
|
+
$title, // 获取 #id="title" 的元素
|
|
76
|
+
$submitBtn // 获取 #id="submitBtn" 的元素
|
|
77
|
+
} = $id();
|
|
78
|
+
</script>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### $() - 全局查询
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
// 在整个文档中查找元素
|
|
85
|
+
const element = magic.$("my-element-id");
|
|
86
|
+
|
|
87
|
+
// 在指定根元素内查找
|
|
88
|
+
const container = magic.$("container-id");
|
|
89
|
+
const child = magic.$("child-id", container);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 查找命名元素
|
|
93
|
+
|
|
94
|
+
```xml
|
|
95
|
+
<template>
|
|
96
|
+
<div #id="form">
|
|
97
|
+
<input #name="username" placeholder="用户名"/>
|
|
98
|
+
<button #name="submit">提交</button>
|
|
99
|
+
</div>
|
|
100
|
+
</template>
|
|
101
|
+
|
|
102
|
+
<script code="global">
|
|
103
|
+
const { $form } = $id();
|
|
104
|
+
|
|
105
|
+
// 使用 $name 查找命名元素
|
|
106
|
+
const usernameInput = $form.$name("username");
|
|
107
|
+
const submitBtn = $form.$name("submit");
|
|
108
|
+
</script>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 3. UiData 响应式数据 - createUiData
|
|
114
|
+
|
|
115
|
+
### 基本用法
|
|
116
|
+
|
|
117
|
+
```xml
|
|
118
|
+
<script code="global">
|
|
119
|
+
const UiData = magic_define_ui_data({
|
|
120
|
+
username: "Guest",
|
|
121
|
+
age: 18,
|
|
122
|
+
isActive: true
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// 修改值会自动触发更新
|
|
126
|
+
UiData.username = "Alice";
|
|
127
|
+
UiData.age = 25;
|
|
128
|
+
UiData.isActive = false;
|
|
129
|
+
</script>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 嵌套对象
|
|
133
|
+
|
|
134
|
+
```xml
|
|
135
|
+
<script code="global">
|
|
136
|
+
const UiData = magic_define_ui_data({
|
|
137
|
+
user: {
|
|
138
|
+
name: "Guest",
|
|
139
|
+
profile: {
|
|
140
|
+
avatar: "",
|
|
141
|
+
bio: ""
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// 深度修改
|
|
147
|
+
UiData.user.name = "Alice";
|
|
148
|
+
UiData.user.profile.avatar = "/avatar.png";
|
|
149
|
+
</script>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 数组支持
|
|
153
|
+
|
|
154
|
+
```xml
|
|
155
|
+
<script code="global">
|
|
156
|
+
const UiData = magic_define_ui_data({
|
|
157
|
+
items: ["苹果", "香蕉", "橙子"],
|
|
158
|
+
selectedIndex: 0
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// 修改数组
|
|
162
|
+
UiData.items.push("葡萄");
|
|
163
|
+
UiData.selectedIndex = 2;
|
|
164
|
+
</script>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 类型自动转换
|
|
168
|
+
|
|
169
|
+
```xml
|
|
170
|
+
<script code="global">
|
|
171
|
+
const UiData = magic_define_ui_data({
|
|
172
|
+
count: 0, // Number
|
|
173
|
+
isValid: false, // Boolean
|
|
174
|
+
pattern: /^abc$/, // RegExp
|
|
175
|
+
data: [1, 2, 3] // Array
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// 传入字符串会自动转换类型
|
|
179
|
+
const UserInput = magic.createUiData(UiData, {
|
|
180
|
+
count: "42", // "42" → 42
|
|
181
|
+
isValid: "true", // "true" → true
|
|
182
|
+
pattern: "/^xyz$/", // 字符串 → RegExp
|
|
183
|
+
data: "[1,2,3,4]" // 字符串 → Array
|
|
184
|
+
});
|
|
185
|
+
</script>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 4. 动态值绑定 - DynamicValueBind
|
|
191
|
+
|
|
192
|
+
### 文本节点绑定
|
|
193
|
+
|
|
194
|
+
```xml
|
|
195
|
+
<template>
|
|
196
|
+
<div #id="container">
|
|
197
|
+
<p #id="name-text">Hello ${username}</p>
|
|
198
|
+
<p #id="count-text">Count: ${count}</p>
|
|
199
|
+
</div>
|
|
200
|
+
</template>
|
|
201
|
+
|
|
202
|
+
<script code="global">
|
|
203
|
+
const UiData = magic_define_ui_data({
|
|
204
|
+
username: "Guest",
|
|
205
|
+
count: 0
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const {
|
|
209
|
+
$nameText,
|
|
210
|
+
$countText
|
|
211
|
+
} = $id();
|
|
212
|
+
</script>
|
|
213
|
+
|
|
214
|
+
<script code="interface">
|
|
215
|
+
startBinding = () => {
|
|
216
|
+
magic_dynamic_value_bind($nameText, $countText);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
increment = () => {
|
|
220
|
+
UiData.count++;
|
|
221
|
+
};
|
|
222
|
+
</script>
|
|
223
|
+
|
|
224
|
+
<script>
|
|
225
|
+
startBinding();
|
|
226
|
+
</script>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 工作原理
|
|
230
|
+
|
|
231
|
+
1. `${username}` 在编译时被转换为注释节点 `<!--MAGIC:DV[username]-->`
|
|
232
|
+
2. `magic_dynamic_value_bind()` 找到所有绑定点
|
|
233
|
+
3. 当 `UiData.username` 改变时,文本节点自动更新
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## 5. 事件系统 - emit / on_event
|
|
238
|
+
|
|
239
|
+
### 定义事件
|
|
240
|
+
|
|
241
|
+
```xml
|
|
242
|
+
<template>
|
|
243
|
+
<button #id="btn">点击</button>
|
|
244
|
+
</template>
|
|
245
|
+
|
|
246
|
+
<expose-event>
|
|
247
|
+
<click/>
|
|
248
|
+
<change/>
|
|
249
|
+
</expose-event>
|
|
250
|
+
|
|
251
|
+
<script code="event">
|
|
252
|
+
click = (eventData) => {
|
|
253
|
+
emit_event("click", { timestamp: Date.now() });
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
change = (newValue) => {
|
|
257
|
+
emit_event("change", { value: newValue });
|
|
258
|
+
};
|
|
259
|
+
</script>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 监听事件
|
|
263
|
+
|
|
264
|
+
```javascript
|
|
265
|
+
const component = magic.importM("my-component", {}, {
|
|
266
|
+
click: (data) => {
|
|
267
|
+
console.log("点击事件:", data);
|
|
268
|
+
},
|
|
269
|
+
change: (data) => {
|
|
270
|
+
console.log("变更事件:", data);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 主动触发事件
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
// 通过 call 接口触发
|
|
279
|
+
const scope = magic.call(component);
|
|
280
|
+
scope.event("click", { custom: true });
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## 6. 参数处理 - parserArgs / createArgs
|
|
286
|
+
|
|
287
|
+
### parserArgs - 解析参数
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
// 解析 JSON 字符串
|
|
291
|
+
const args1 = magic.parserArgs('{"name":"Alice","age":25}');
|
|
292
|
+
// 结果: { name: "Alice", age: 25 }
|
|
293
|
+
|
|
294
|
+
// 解析嵌套键
|
|
295
|
+
const args2 = magic.parserArgs({
|
|
296
|
+
"user:profile:name": "Alice",
|
|
297
|
+
"user:profile:age": "25"
|
|
298
|
+
});
|
|
299
|
+
// 结果: { user: { profile: { name: "Alice", age: "25" } } }
|
|
300
|
+
|
|
301
|
+
// 使用冒号分隔的键
|
|
302
|
+
const args3 = magic.parserArgs({
|
|
303
|
+
"a:b:c": "deep value"
|
|
304
|
+
});
|
|
305
|
+
// 结果: { a: { b: { c: "deep value" } } }
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### createArgs - 创建参数字符串
|
|
309
|
+
|
|
310
|
+
```javascript
|
|
311
|
+
const obj = { name: "Alice", age: 25 };
|
|
312
|
+
const jsonStr = magic.createArgs(obj);
|
|
313
|
+
// 结果: '{"name":"Alice","age":25}'
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 7. 路由系统 - router
|
|
319
|
+
|
|
320
|
+
### 基本使用
|
|
321
|
+
|
|
322
|
+
```javascript
|
|
323
|
+
// 添加路由
|
|
324
|
+
magic.router.addRoute('/', 'home');
|
|
325
|
+
magic.router.addRoute('/about', 'about');
|
|
326
|
+
magic.router.addRoute('/user/:id', 'user-profile');
|
|
327
|
+
|
|
328
|
+
// 初始化路由
|
|
329
|
+
magic.router.init('router-view');
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### 批量添加路由
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
magic.router.addRoutes([
|
|
336
|
+
{ path: '/', component: 'home' },
|
|
337
|
+
{ path: '/about', component: 'about' },
|
|
338
|
+
{ path: '/user/:id', component: 'user-profile' },
|
|
339
|
+
{ path: '/post/:postId/comment/:commentId', component: 'comment' }
|
|
340
|
+
]);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### 导航
|
|
344
|
+
|
|
345
|
+
```javascript
|
|
346
|
+
// 跳转到新路径(添加历史记录)
|
|
347
|
+
magic.router.push('/about');
|
|
348
|
+
|
|
349
|
+
// 替换当前路径(不添加历史)
|
|
350
|
+
magic.router.replace('/user/123');
|
|
351
|
+
|
|
352
|
+
// 获取当前路由
|
|
353
|
+
const route = magic.router.getCurrentRoute();
|
|
354
|
+
console.log(route.path); // "/user/123"
|
|
355
|
+
console.log(route.params.id); // "123"
|
|
356
|
+
console.log(route.query); // { search: "abc" }
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### 动态路由参数
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
// /user/:id → { path: '/user/123', params: { id: '123' } }
|
|
363
|
+
// /post/:postId/comment/:commentId → { path: '/post/1/comment/5', params: { postId: '1', commentId: '5' } }
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### 导航守卫
|
|
367
|
+
|
|
368
|
+
```javascript
|
|
369
|
+
magic.router.beforeEach((to, from) => {
|
|
370
|
+
// to: 目标路由信息
|
|
371
|
+
// from: 来源路由信息
|
|
372
|
+
|
|
373
|
+
if (to.path === '/admin' && !isLoggedIn) {
|
|
374
|
+
return '/login'; // 重定向到登录页
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return true; // 允许导航
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// 支持异步守卫
|
|
381
|
+
magic.router.beforeEach(async (to, from) => {
|
|
382
|
+
const canAccess = await checkPermission(to.path);
|
|
383
|
+
if (!canAccess) {
|
|
384
|
+
return '/unauthorized';
|
|
385
|
+
}
|
|
386
|
+
return true;
|
|
387
|
+
});
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### 完整示例
|
|
391
|
+
|
|
392
|
+
```xml
|
|
393
|
+
<!-- index.m -->
|
|
394
|
+
<template>
|
|
395
|
+
<div #id="app">
|
|
396
|
+
<nav #id="nav">
|
|
397
|
+
<a @click="goHome">首页</a>
|
|
398
|
+
<a @click="goAbout">关于</a>
|
|
399
|
+
<a @click="goUser">用户:123</a>
|
|
400
|
+
</nav>
|
|
401
|
+
<div #id="router-view"></div>
|
|
402
|
+
</div>
|
|
403
|
+
</template>
|
|
404
|
+
|
|
405
|
+
<script code="event">
|
|
406
|
+
goHome = () => magic.router.push('/');
|
|
407
|
+
goAbout = () => magic.router.push('/about');
|
|
408
|
+
goUser = () => magic.router.push('/user/123');
|
|
409
|
+
</script>
|
|
410
|
+
|
|
411
|
+
<script>
|
|
412
|
+
magic.router.addRoutes([
|
|
413
|
+
{ path: '/', component: 'home' },
|
|
414
|
+
{ path: '/about', component: 'about' },
|
|
415
|
+
{ path: '/user/:id', component: 'user-profile' }
|
|
416
|
+
]);
|
|
417
|
+
|
|
418
|
+
magic.router.init('router-view');
|
|
419
|
+
</script>
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## 8. 其他工具函数
|
|
425
|
+
|
|
426
|
+
### importElementInit - 初始化导入组件
|
|
427
|
+
|
|
428
|
+
```javascript
|
|
429
|
+
// 导入多个组件并批量初始化
|
|
430
|
+
magic.importElementInit(comp1, comp2, comp3);
|
|
431
|
+
// 会依次调用每个组件的 _render() 接口方法
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### exportInterface - 导出接口
|
|
435
|
+
|
|
436
|
+
```javascript
|
|
437
|
+
const target = {};
|
|
438
|
+
magic.exportInterface(scope, target, {
|
|
439
|
+
_render: nop, // 直接映射
|
|
440
|
+
onClick: (args) => { // 函数转换
|
|
441
|
+
console.log("Clicked with:", args);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### GetInterface - 获取组件接口
|
|
447
|
+
|
|
448
|
+
```javascript
|
|
449
|
+
const element = magic.$("my-component");
|
|
450
|
+
const interface = magic.GetInterface(element);
|
|
451
|
+
if (interface) {
|
|
452
|
+
interface.someMethod();
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### BindScope - 绑定作用域
|
|
457
|
+
|
|
458
|
+
```javascript
|
|
459
|
+
const elements = [elem1, elem2, elem3];
|
|
460
|
+
const scoped = magic.BindScope(elements, scope);
|
|
461
|
+
// 每个元素会添加 __SCOPE__ 属性指向 scope
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### mapIdElement - ID 映射
|
|
465
|
+
|
|
466
|
+
```javascript
|
|
467
|
+
const map = magic.mapIdElement();
|
|
468
|
+
map.s("element-id", element, "optional-class");
|
|
469
|
+
const el = map.get("element-id");
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### idGenerate - 生成唯一 ID
|
|
473
|
+
|
|
474
|
+
```javascript
|
|
475
|
+
const id1 = magic.idGenerate(); // 生成带时间戳的唯一 ID
|
|
476
|
+
const id2 = magic.idGenerate(6); // 指定长度
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### parserListen - 解析监听器
|
|
480
|
+
|
|
481
|
+
```javascript
|
|
482
|
+
const listen = magic.parserListen({
|
|
483
|
+
click: handleClick,
|
|
484
|
+
change: handleChange
|
|
485
|
+
});
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## 综合示例:完整组件
|
|
491
|
+
|
|
492
|
+
```xml
|
|
493
|
+
<!-- user-card.m -->
|
|
494
|
+
<import root=".">
|
|
495
|
+
<avatar/>
|
|
496
|
+
</import>
|
|
497
|
+
|
|
498
|
+
<template>
|
|
499
|
+
<div #id="card">
|
|
500
|
+
<avatar #id="avatar-img" :src="avatar"/>
|
|
501
|
+
<h3 #id="name">${name}</h3>
|
|
502
|
+
<p #id="bio">${bio}</p>
|
|
503
|
+
<button #id="edit-btn" @click="edit">编辑</button>
|
|
504
|
+
</div>
|
|
505
|
+
</template>
|
|
506
|
+
|
|
507
|
+
<script code="global">
|
|
508
|
+
const UiData = magic_define_ui_data({
|
|
509
|
+
name: "未命名",
|
|
510
|
+
bio: "暂无简介",
|
|
511
|
+
avatar: "/default-avatar.png"
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
const { $name, $bio } = $id();
|
|
515
|
+
</script>
|
|
516
|
+
|
|
517
|
+
<expose-event>
|
|
518
|
+
<edit/>
|
|
519
|
+
</expose-event>
|
|
520
|
+
|
|
521
|
+
<script code="event">
|
|
522
|
+
edit = () => {
|
|
523
|
+
emit_event("edit", { name: UiData.name });
|
|
524
|
+
};
|
|
525
|
+
</script>
|
|
526
|
+
|
|
527
|
+
<script code="interface" once>
|
|
528
|
+
init = () => {
|
|
529
|
+
magic_dynamic_value_bind($name, $bio);
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
setUser = (user) => {
|
|
533
|
+
UiData.name = user.name;
|
|
534
|
+
UiData.bio = user.bio;
|
|
535
|
+
if (user.avatar) UiData.avatar = user.avatar;
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
getUser = () => {
|
|
539
|
+
return {
|
|
540
|
+
name: UiData.name,
|
|
541
|
+
bio: UiData.bio
|
|
542
|
+
};
|
|
543
|
+
};
|
|
544
|
+
</script>
|
|
545
|
+
|
|
546
|
+
<script code="interface">
|
|
547
|
+
startBinding();
|
|
548
|
+
</script>
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**使用:**
|
|
552
|
+
|
|
553
|
+
```javascript
|
|
554
|
+
const card = magic.importM("user-card");
|
|
555
|
+
|
|
556
|
+
card.interface.setUser({
|
|
557
|
+
name: "Alice",
|
|
558
|
+
bio: "一名前端开发者",
|
|
559
|
+
avatar: "/avatars/alice.png"
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
card.interface.getUser(); // { name: "Alice", bio: "一名前端开发者" }
|
|
563
|
+
```
|