别把 8 个语法一起背。
先记 3 个,再补 5 个。
这页按学习顺序重排成三层:先跑通执行闭环,再处理复用与展开,最后进入模块化和跨文件组织。
先跑通执行闭环
先记住节点怎么定义、怎么传值、怎么展示。大多数文档先靠这 3 个语法起步。
再处理复用与展开
内容要复用就用 content / use,数据要批量展开就补上 for。
最后补模块边界
文件之间开始拆分时,再引入 meta 和 import 管理导出与命名空间。
先 define,再 ref,最后 render。
这是最小心智模型。你可以先把 Magic MD 理解成“文档里声明一组节点,节点之间靠 $ref 传值,最后把结果渲染回 Markdown”。
本地节点写 [id],跨文件引用统一写 ns/id。
define/content/meta/use 用独立一行 ::;for 用 ::endfor;render 同行闭合。
config 写输入配置,output 写持久化结果缓存。
普通依赖读 output/body,模板读 input/*,循环体读 $item.*。
::define[repos]{plugin="http-request"}
```config
{
"url": "https://api.github.com/orgs/vercel/repos?per_page=3"
}
```
::
::define[firstRepo]{plugin="json-viewer"}
```config
{
"data": "$ref{repos.output.body[0]}"
}
```
::
首个仓库:
::render[firstRepo]::这个例子已经覆盖了最关键的三步。读完它再去看 content、for、import,会更容易理解每个语法在补什么能力。
先把数据跑起来。
这是 Magic MD 的最小可用集合。定义节点,引用上游结果,再把结果渲染回文档。
::define[repos]{plugin="http-request"}
```config
{
"url": "https://api.github.com/orgs/vercel/repos?per_page=3"
}
```
::
::define[firstRepo]{plugin="json-viewer"}
```config
{
"data": "$ref{repos.output.body[0]}"
}
```
::
首个仓库:
::render[firstRepo]::把这三项记牢后,已经足够写出大部分单文件工作流。
::define声明可执行节点::define[myId]{plugin="number-stat"}::闭合行(独立一行,不可缺少)| 属性 | 必填 | 类型 | 说明 |
|---|---|---|---|
| plugin | req | string | 插件名称,与插件目录名对应 |
给节点起名,绑定插件,写配置,以单独一行 :: 闭合。
$ref{}引用上游输出$ref{myId.output.path.to[0]}把任意上游节点的值接入当前配置、模板或循环体。普通路径从 .output 或 .body 开始。
::render渲染节点输出::render[myId]::把节点或内容块的结果显示到页面。支持块级和行内写法,也支持 ns/id 引用。
把结构抽出来,再批量生成。
当一段内容要重复出现,或同一套结构要对数组中的每个元素展开时,再引入 content、use、for。
::content[repoCard]{template="true"}
### $ref{input/name}
Stars: $ref{input/stars}
::
::use[topRepo]{from="repoCard"}
```config
{
"with": {
"name": "magic-md",
"stars": 1200
}
}
```
::
::render[topRepo]::
::for[repoLoop]{each="repo" of="$ref{repos.output.body}" limit="3"}
- $ref{$repo.name}
::endfor把 content 和 use 看成“模板 + 实例化”,for 看成“批量展开 body”会更容易理解。
::content可复用内容块::content[cardId]{template="true"}::闭合行(独立一行)定义可在多处渲染的 Markdown 内容。开启 template="true" 后可作为模板,被 ::use 实例化。
::use派生与实例化::use[localConn]{from="db/conn"}::闭合行(独立一行)| 属性 | 必填 | 类型 | 说明 |
|---|---|---|---|
| from | req | string | 源 symbol ID 或 ns/id,不接受文件路径 |
从现有 define 或 content 派生新实例,可注入模板参数、配置补丁或依赖重绑定。
::for按数组批量展开::for[loopId]{each="item" of="$ref{...}" limit="10"}::endfor循环闭合| 属性 | 必填 | 类型 | 说明 |
|---|---|---|---|
| each | req | string | 循环变量名,在 body 内用 $ref{$变量名} 引用 |
| of | req | $ref{...} | 数据源,必须解析为数组 |
| limit | opt | number | 最大迭代次数,防止意外展开大数组 |
| offset | opt | number | 从数组的指定位置开始迭代 |
| concurrency | opt | number | 控制循环体内定义的并发执行数 |
遍历数组,批量执行循环体。内部可以写 define、content、use、render 和普通 Markdown。
文件一多,就开始管导出和命名空间。
文档拆到多个文件后,meta 负责定义外部可见边界,import 负责把外部 symbol 拉进当前作用域。
::meta[module]
```config
{ "exports": ["statusCard", "queryTemplate:query"] }
```
::
::import{from="./shared/report.md" ns="report"}
::render[report/statusCard]::模块化关注的是文件之间如何复用,而不是单个节点如何执行。
::meta模块元数据::meta[module]::闭合行(独立一行)| 属性 | 必填 | 类型 | 说明 |
|---|---|---|---|
| [domain] | req | "module" | "config" | 元数据域名 |
声明文档级元数据。module 域控制导出边界,config 域存放文档配置。
::import跨文件导入::import{from="./db.md" ns="db"}| 属性 | 必填 | 类型 | 说明 |
|---|---|---|---|
| from | req | string | 源文件相对路径 |
| ns | req | string | 命名空间前缀,引用时用 ns/id 格式 |
| pick | opt | string | 只导入指定 symbol(逗号分隔),与 exclude 互斥 |
| exclude | opt | string | 排除指定 symbol(逗号分隔),与 pick 互斥 |
| rename | opt | string | 重命名映射(原名:新名,逗号分隔) |
从其他 .md 文件导入 symbol,强制命名空间防止 ID 冲突。最常见写法是单行导入。
把执行链和循环串起来。
下面这个例子专注展示最常见的工作流:声明数据源、按数组展开、为每一项生成输出,最后再做汇总。
::meta[module]
```config
{ "exports": ["repoCount"] }
```
::
# Repo Watch
::define[repos]{plugin="http-request"}
```config
{
"url": "https://api.github.com/orgs/vercel/repos?per_page=3"
}
```
::
::for[repoLoop]{each="repo" of="$ref{repos.output.body}" limit="3"}
::define[repoPreview]{plugin="json-viewer"}
```config
{
"data": {
"name": "$ref{$repo.name}",
"stars": "$ref{$repo.stargazers_count}"
}
}
```
::
::render[repoPreview]::
::endfor
::define[repoCount]{plugin="number-stat"}
```config
{
"label": "repos scanned",
"value": "$ref{repoLoop.output.meta.succeeded}"
}
```
::
::render[repoCount]::这类文档最适合用来熟悉 define、ref、for、render 的配合。content / use / import 更适合在结构已经稳定后再抽出来。
格式边界是硬规则。
语法数量不多,但边界很严格。多数解析问题不是概念错,而是括号、围栏、闭合行写偏了。
::define[myId]{plugin="http-request"}::define{#myId plugin=http-request}```config```json:::::$ref{query.output.rows[0].name}$ref{query.config.sql}::import{from="./db.md" ns="db"}::import{from="./db.md"}::use[conn]{from="db/conn"}::use[conn]{from="./db.md"}::render[ns/id]::::render[ns.id]::有冲突,以实现为准。
网站这页负责建立心智模型,最终语义仍应以语法正则和参考文档为准。