@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.
Files changed (56) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +93 -0
  3. package/app.js +51 -0
  4. package/doc/api/examples.md +563 -0
  5. package/doc/api/index.md +563 -0
  6. package/doc/architecture.md +322 -0
  7. package/doc/config-reference.md +646 -0
  8. package/doc/data-model.md +622 -0
  9. package/doc/development-guide.md +582 -0
  10. package/doc/magic-file/index.md +610 -0
  11. package/doc/user-guide/index.md +485 -0
  12. package/doc/workflow.md +548 -0
  13. package/index.js +157 -0
  14. package/magic.bat +2 -0
  15. package/magic.ps1 +5 -0
  16. package/package.json +44 -0
  17. package/script/build-project.js +16 -0
  18. package/script/config.js +23 -0
  19. package/script/create-project.js +73 -0
  20. package/script/global/printf.js +13 -0
  21. package/script/global/project-build-config.js +161 -0
  22. package/script/global/support-platform.js +5 -0
  23. package/script/module/compiler/global.js +43 -0
  24. package/script/module/compiler/id-generate.js +18 -0
  25. package/script/module/compiler/index-dom.js +78 -0
  26. package/script/module/compiler/macro-replace.js +22 -0
  27. package/script/module/compiler/macro.js +6 -0
  28. package/script/module/compiler/start.js +10 -0
  29. package/script/module/compiler/step/1.js +253 -0
  30. package/script/module/compiler/step/2.js +79 -0
  31. package/script/module/compiler/step/3.js +37 -0
  32. package/script/module/compiler/step/4.js +20 -0
  33. package/script/module/compiler/step/5.js +634 -0
  34. package/script/module/compiler/step/6.js +304 -0
  35. package/script/module/compiler/step/end.js +124 -0
  36. package/script/run-project.js +249 -0
  37. package/script/util/bun-fs.js +40 -0
  38. package/script/util/copy-dir.js +21 -0
  39. package/script/util/create-simple-dom-element.js +23 -0
  40. package/script/util/file-util.js +95 -0
  41. package/script/util/filtration-file.js +20 -0
  42. package/script/util/get-dir-all-file.js +28 -0
  43. package/script/util/get-first-object-key.js +9 -0
  44. package/script/util/is-empty-object.js +8 -0
  45. package/script/util/is-string-over-size.js +4 -0
  46. package/script/util/is.js +18 -0
  47. package/script/util/logging.js +142 -0
  48. package/script/util/task.js +16 -0
  49. package/script/util/traversal.js +28 -0
  50. package/template/platform-config/node-webkit +23 -0
  51. package/template/platform-config/web +1 -0
  52. package/template/project-base/app.xml +5 -0
  53. package/template/project-base/build.module.toml +37 -0
  54. package/template/project-base/build.toml +43 -0
  55. package/template/runtime/runtime.css +3 -0
  56. package/template/runtime/runtime.js +895 -0
@@ -0,0 +1,610 @@
1
+ # Magic .m 文件格式规范
2
+
3
+ ## 概述
4
+
5
+ `.m` 文件是 Magic 框架的核心组件格式,它将 HTML 模板、CSS 样式和 JavaScript 脚本整合到单一声明式文件中。
6
+
7
+ ## 文件结构
8
+
9
+ ```xml
10
+ <import> <!-- 1. 模块导入 (可选) -->
11
+ <template> <!-- 2. DOM 模板 (必需) -->
12
+ <script code=""> <!-- 3. 脚本 (可选, 可多个) -->
13
+ <css> <!-- 4. 样式 (可选, 可多个) -->
14
+ <expose-event> <!-- 5. 事件暴露 (可选) -->
15
+ ```
16
+
17
+ ## 1. import - 模块导入
18
+
19
+ ### 基本语法
20
+
21
+ ```xml
22
+ <import>
23
+ <!-- 导入组件 -->
24
+ <button/>
25
+ <edit/>
26
+ <list-view/>
27
+ </import>
28
+ ```
29
+
30
+ ### 带命名空间
31
+
32
+ ```xml
33
+ <import namespace="magic-ui/ui">
34
+ <!-- 从指定命名空间导入 -->
35
+ <module:control>
36
+ <list-view/>
37
+ </module:control>
38
+ <module:input>
39
+ <button/>
40
+ <search/>
41
+ </module:input>
42
+ </import>
43
+ ```
44
+
45
+ ### 带 root 属性
46
+
47
+ ```xml
48
+ <import root="magic-ui/ui">
49
+ <!-- root 指定基础路径 -->
50
+ </import>
51
+ ```
52
+
53
+ ### 组合使用
54
+
55
+ ```xml
56
+ <import root="magic-ui/ui">
57
+ <module:control>
58
+ <list-view/>
59
+ </module:control>
60
+ <module:input>
61
+ <button/>
62
+ </module:input>
63
+ </import>
64
+ ```
65
+
66
+ ### import 属性说明
67
+
68
+ | 属性 | 说明 |
69
+ |------|------|
70
+ | `namespace` | 命名空间前缀,区分不同模块组 |
71
+ | `root` | 基础路径,解析模块的相对路径 |
72
+
73
+ ### 模块路径解析规则
74
+
75
+ 1. 无命名空间: `<组件名>` → `{root}/{组件名}/{组件名}.m`
76
+ 2. 有命名空间: `<命名空间:组件名>` → `{root}/{命名空间}/{组件名}.m`
77
+
78
+ ## 2. template - DOM 模板
79
+
80
+ ### 基本语法
81
+
82
+ ```xml
83
+ <template>
84
+ <div>Hello World</div>
85
+ </template>
86
+ ```
87
+
88
+ ### 元素属性
89
+
90
+ | 属性前缀 | 说明 | 示例 |
91
+ |----------|------|------|
92
+ | `#id` | 元素 ID,用于 `$id()` 获取 | `<div #id="myDiv">` |
93
+ | `#name` | 元素名称,用于 `$name()` 查找 | `<div #name="title">` |
94
+ | `@事件名` | 绑定事件监听 | `<button @click="handler">` |
95
+ | `:属性` | 传递参数给组件 | `<comp :value="123">` |
96
+ | `#id:监听名` | 监听子模块事件 | `<child #listen:select="handler">` |
97
+
98
+ ### 属性详解
99
+
100
+ #### #id - 元素标识
101
+
102
+ ```xml
103
+ <template>
104
+ <div #id="container">
105
+ <span #id="title">Title</span>
106
+ </div>
107
+ </template>
108
+ ```
109
+
110
+ #### @ - 事件绑定
111
+
112
+ ```xml
113
+ <!-- 基本用法 -->
114
+ <button @click="click">Click</button>
115
+
116
+ <!-- 带参数 -->
117
+ <button @click="click:arg1,arg2">Click</button>
118
+ ```
119
+
120
+ 事件格式: `@事件名="处理函数"` 或 `@事件名="处理函数:参数"`
121
+
122
+ #### : - 参数传递
123
+
124
+ ```xml
125
+ <!-- 传递静态值 -->
126
+ <edit :text="Hello" />
127
+
128
+ <!-- 传递动态值 -->
129
+ <edit :value="someVariable" />
130
+ ```
131
+
132
+ #### #listen - 监听子模块事件
133
+
134
+ ```xml
135
+ <!-- 监听子模块暴露的事件 -->
136
+ <child-component #listen:select="onSelect"/>
137
+ ```
138
+
139
+ #### #import - 动态导入
140
+
141
+ ```xml
142
+ <!-- 动态导入模块 -->
143
+ <dynamic-component #import="path/to/module"/>
144
+ ```
145
+
146
+ ### 文本插值
147
+
148
+ 使用 `${}` 语法进行文本插值:
149
+
150
+ ```xml
151
+ <template>
152
+ <p>Hello ${userName}</p>
153
+ <p>Count: ${count + 1}</p>
154
+ </template>
155
+ ```
156
+
157
+ ### 完整示例
158
+
159
+ ```xml
160
+ <template>
161
+ <div #id="main-view">
162
+ <h1 #id="title">${projectName}</h1>
163
+ <button
164
+ #id="submit-btn"
165
+ @click="submit:arg1"
166
+ :text="提交"
167
+ >提交</button>
168
+ <child-component #listen:select="onSelect"/>
169
+ </div>
170
+ </template>
171
+ ```
172
+
173
+ ## 3. script - 脚本
174
+
175
+ ### 脚本类型
176
+
177
+ 通过 `code` 属性区分不同类型的脚本:
178
+
179
+ | code 值 | 加载顺序 | 说明 |
180
+ |---------|----------|------|
181
+ | 无 / `default` | 4 | 普通脚本,组件初始化后执行 |
182
+ | `before` | -1 | 元素生成前执行,无法调用元素 |
183
+ | `global` | 0 | 全局脚本,元素生成后执行 |
184
+ | `event` | 2 | 事件处理函数定义 |
185
+ | `listen` | 1 | 监听功能定义 |
186
+ | `interface` | 3 | 接口方法定义 |
187
+
188
+ ### 加载顺序图
189
+
190
+ ```
191
+ before(-1) → global(0) → listen(1) → event(2) → interface(3) → default(4)
192
+ ```
193
+
194
+ ### 脚本作用域
195
+
196
+ 在 `<script>` 中可以使用以下内置对象:
197
+
198
+ | 对象 | 说明 |
199
+ |------|------|
200
+ | `$id()` | 获取模板中定义的元素 |
201
+ | `emit_event` | 触发事件 |
202
+ | `UiData` | UI 数据对象 (通过 `magic_define_ui_data` 创建) |
203
+
204
+ ### $id() 用法
205
+
206
+ ```xml
207
+ <script code="global">
208
+ const {
209
+ $mainView, // 获取 #id="mainView" 的元素
210
+ $title, // 获取 #id="title" 的元素
211
+ $submitBtn // 获取 #id="submitBtn" 的元素
212
+ } = $id();
213
+ </script>
214
+ ```
215
+
216
+ ### emit_event 用法
217
+
218
+ ```xml
219
+ <script code="event">
220
+ click = (event) => {
221
+ emit_event("select", { value: "test" });
222
+ }
223
+ </script>
224
+ ```
225
+
226
+ ### 完整示例
227
+
228
+ ```xml
229
+ <script code="before">
230
+ // 可以定义函数和变量
231
+ function createItems(...textArr) {
232
+ const arr = [];
233
+ textArr.forEach(text => {
234
+ arr.push(magic.importM("item", { text }));
235
+ });
236
+ return arr;
237
+ }
238
+
239
+ const templateItemList = {
240
+ "type-a": createItems("A", "B"),
241
+ "type-b": createItems("X", "Y")
242
+ };
243
+ </script>
244
+
245
+ <script code="global">
246
+ // 获取元素
247
+ const {
248
+ $container,
249
+ $title
250
+ } = $id();
251
+ </script>
252
+
253
+ <script code="listen">
254
+ // 监听处理 - 接收子模块事件
255
+ typeSelect = (data) => {
256
+ console.log("Selected:", data);
257
+ }
258
+ </script>
259
+
260
+ <script code="event">
261
+ // 事件处理函数
262
+ click = (event) => {
263
+ console.log("Clicked!");
264
+ }
265
+ </script>
266
+
267
+ <script code="interface" once>
268
+ setTitle = (title) => {
269
+ $title.textContent = title;
270
+ };
271
+ </script>
272
+
273
+ <script>
274
+ // 普通脚本 - 组件初始化逻辑
275
+ function init() {
276
+ $container.appendChild(something);
277
+ }
278
+ init();
279
+ </script>
280
+ ```
281
+
282
+ ## 4. css - 样式
283
+
284
+ ### 基本语法
285
+
286
+ ```xml
287
+ <css>
288
+ div {
289
+ color: red;
290
+ }
291
+ </css>
292
+ ```
293
+
294
+ ### scope 属性 - 作用域
295
+
296
+ | scope 值 | 效果 |
297
+ |----------|------|
298
+ | 无 | 全局样式 |
299
+ | `#id:xx` | 作用于 `#id="xx"` 的元素 |
300
+ | `.class` | 作用于指定 class 的元素 |
301
+ | `#id` | 作用于指定 id 的元素 |
302
+
303
+ #### #id:xx 作用域
304
+
305
+ ```xml
306
+ <!-- Magic 特有的选择器语法 -->
307
+ <css scope="#id:myComponent">
308
+ /* 只影响 #id="myComponent" 内部的元素 */
309
+ & {
310
+ color: red;
311
+ }
312
+ & > .child {
313
+ padding: 10px;
314
+ }
315
+ </css>
316
+ ```
317
+
318
+ 编译后自动转换为带唯一类名的选择器:
319
+
320
+ ```css
321
+ .m-css-scope-abc123 {
322
+ color: red;
323
+ }
324
+ .m-css-scope-abc123 > .child {
325
+ padding: 10px;
326
+ }
327
+ ```
328
+
329
+ ### default-theme 属性
330
+
331
+ 添加 `default-theme` 属性会将所有 CSS 值转换为 CSS 变量,用于主题化样式。
332
+
333
+ **使用场景:**
334
+ - 用于定义元素的基础样式(颜色、边框、背景等)
335
+ - 这些样式可以被主题系统覆盖
336
+ - 适合需要随主题变化的样式
337
+
338
+ ```xml
339
+ <css scope="#id:button" default-theme>
340
+ & {
341
+ background-color: #c6c6c6;
342
+ border-radius: 5px;
343
+ color: #333333;
344
+ }
345
+ </css>
346
+ ```
347
+
348
+ 生成默认主题变量文件 `default-theme-var.css`:
349
+
350
+ ```css
351
+ :root {
352
+ --button-m-css-scope-xxx-background-color: #c6c6c6;
353
+ --button-m-css-scope-xxx-border-radius: 5px;
354
+ --button-m-css-scope-xxx-color: #333333;
355
+ }
356
+ .m-css-scope-xxx {
357
+ background-color: var(--button-m-css-scope-xxx-background-color);
358
+ border-radius: var(--button-m-css-scope-xxx-border-radius);
359
+ color: var(--button-m-css-scope-xxx-color);
360
+ }
361
+ ```
362
+
363
+ ### 无 default-theme 的 CSS
364
+
365
+ 不使用 `default-theme` 的 CSS 会直接输出,不转换为变量。
366
+
367
+ **使用场景:**
368
+ - 用于定义元素的布局样式(display、position、text-wrap 等)
369
+ - 这些样式定义了元素本身的行为,不应被主题覆盖
370
+ - 适合结构性样式
371
+
372
+ ```xml
373
+ <css scope="#id:app">
374
+ & {
375
+ display: flex;
376
+ text-wrap: wrap;
377
+ position: relative;
378
+ }
379
+ </css>
380
+ ```
381
+
382
+ ### default-theme 的最佳实践
383
+
384
+ **推荐用法:**
385
+ - 有 `default-theme`:定义可主题化的样式(颜色、字体、边框等)
386
+ - 无 `default-theme`:定义布局和行为样式(flex、grid、position 等)
387
+
388
+ ```xml
389
+ <!-- 主题样式:颜色、背景等 -->
390
+ <css scope="#id:app" default-theme>
391
+ & {
392
+ background-color: pink;
393
+ color: #333;
394
+ }
395
+ </css>
396
+
397
+ <!-- 布局样式:flex、position 等 -->
398
+ <css scope="#id:app">
399
+ & {
400
+ display: flex;
401
+ text-wrap: wrap;
402
+ }
403
+ </css>
404
+ ```
405
+
406
+ ### & 引用符
407
+
408
+ `&` 代表当前作用域的根元素:
409
+
410
+ ```xml
411
+ <css scope="#id:container">
412
+ & {
413
+ padding: 10px; /* #id="container" 本身 */
414
+ }
415
+ & > .child {
416
+ margin: 5px; /* #id="container" 的直接子元素 .child */
417
+ }
418
+ </css>
419
+ ```
420
+
421
+ ### 完整示例
422
+
423
+ ```xml
424
+ <css scope="#id:message" default-theme>
425
+ & {
426
+ background-color: #ff0000;
427
+ color: #ffffff;
428
+ }
429
+ </css>
430
+
431
+ <css scope="#id:message">
432
+ & {
433
+ display: flex;
434
+ align-items: center;
435
+ padding: 5px;
436
+ }
437
+ & > .icon {
438
+ font-size: 20px;
439
+ }
440
+ </css>
441
+ ```
442
+
443
+ ## 5. expose-event - 事件暴露
444
+
445
+ 定义子模块可以触发的事件,供父模块通过 `#listen` 监听。
446
+
447
+ ### 基本语法
448
+
449
+ ```xml
450
+ <expose-event>
451
+ <click/>
452
+ <change/>
453
+ </expose-event>
454
+ ```
455
+
456
+ ### 带参数
457
+
458
+ ```xml
459
+ <expose-event>
460
+ <select :value/>
461
+ <change :oldValue :newValue/>
462
+ </expose-event>
463
+ ```
464
+
465
+ ### 完整示例
466
+
467
+ ```xml
468
+ <expose-event>
469
+ <click/>
470
+ <select :value/>
471
+ </expose-event>
472
+ ```
473
+
474
+ 在 `<script code="event">` 中通过 `emit_event()` 触发:
475
+
476
+ ```xml
477
+ <script code="event">
478
+ click = (event) => {
479
+ emit_event("click", event);
480
+ }
481
+ select = (data) => {
482
+ emit_event("select", { value: data });
483
+ }
484
+ </script>
485
+ ```
486
+
487
+ 父模块监听:
488
+
489
+ ```xml
490
+ <template>
491
+ <my-component #listen:click="onClick" #listen:select="onSelect"/>
492
+ </template>
493
+ ```
494
+
495
+ ## 6. 宏 - 编译时处理
496
+
497
+ ### magic_define_include
498
+
499
+ 在编译时包含文件内容:
500
+
501
+ ```xml
502
+ <script code="before">
503
+ const template = magic_define_include("./template.html");
504
+ </script>
505
+ ```
506
+
507
+ ### magic_define_ui_data
508
+
509
+ 创建带类型转换和响应式绑定的 UI 数据对象:
510
+
511
+ ```xml
512
+ <script code="global">
513
+ const UiData = magic_define_ui_data({
514
+ text: "hello",
515
+ count: 0,
516
+ options: ["a", "b"]
517
+ });
518
+ </script>
519
+ ```
520
+
521
+ ### magic_dynamic_value_bind
522
+
523
+ 动态绑定值到文本节点:
524
+
525
+ ```xml
526
+ <script code="interface">
527
+ show = (text) => {
528
+ magic_dynamic_value_bind($textElement, UiData);
529
+ }
530
+ </script>
531
+ ```
532
+
533
+ ## 7. 完整示例
534
+
535
+ ```xml
536
+ <import root="magic-ui/ui">
537
+ <module:input>
538
+ <button/>
539
+ <edit/>
540
+ </module:input>
541
+ </import>
542
+
543
+ <template>
544
+ <div #id="container">
545
+ <h2 #id="title">新建项目</h2>
546
+ <edit #id="name-input" :placeholder="项目名称"/>
547
+ <button #id="submit-btn" @click="submit">创建</button>
548
+ </div>
549
+ </template>
550
+
551
+ <script code="before">
552
+ function validateName(name) {
553
+ return name.length > 0;
554
+ }
555
+ </script>
556
+
557
+ <script code="global">
558
+ const {
559
+ $container,
560
+ $title,
561
+ $nameInput,
562
+ $submitBtn
563
+ } = $id();
564
+ </script>
565
+
566
+ <expose-event>
567
+ <confirm/>
568
+ <cancel/>
569
+ </expose-event>
570
+
571
+ <script code="event">
572
+ submit = (event) => {
573
+ const name = $nameInput.value;
574
+ if (validateName(name)) {
575
+ emit_event("confirm", { name });
576
+ }
577
+ }
578
+ </script>
579
+
580
+ <script code="interface">
581
+ setTitle = (title) => {
582
+ $title.textContent = title;
583
+ };
584
+ </script>
585
+
586
+ <css scope="#id:container" default-theme>
587
+ & {
588
+ background-color: #f5f5f5;
589
+ padding: 20px;
590
+ }
591
+ </css>
592
+
593
+ <css scope="#id:container">
594
+ & {
595
+ display: flex;
596
+ flex-direction: column;
597
+ gap: 15px;
598
+ width: 350px;
599
+ }
600
+ </css>
601
+ ```
602
+
603
+ ## 8. 注意事项
604
+
605
+ 1. **模板根元素**: `<template>` 内必须有根元素
606
+ 2. **ID 唯一性**: `#id` 值在同一文件中必须唯一
607
+ 3. **脚本执行顺序**: 按照 `before` → `global` → `listen` → `event` → `interface` → `default` 的顺序执行
608
+ 4. **宏使用限制**: `magic_define_include` 的参数必须是静态字符串
609
+ 5. **CSS 作用域**: 使用 `#id:xx` 选择器避免样式冲突
610
+ 6. **事件命名**: 推荐使用小驼峰命名,如 `click`, `selectChange`