mdMagic MDfull context preserved
home/syntax
SYNTAX
[ executable markdown ]

别把 8 个语法一起背。
先记 3 个,再补 5 个。

这页按学习顺序重排成三层:先跑通执行闭环,再处理复用与展开,最后进入模块化和跨文件组织。

01

先跑通执行闭环

先记住节点怎么定义、怎么传值、怎么展示。大多数文档先靠这 3 个语法起步。

::define$ref{}::render
02

再处理复用与展开

内容要复用就用 content / use,数据要批量展开就补上 for。

::content::use::for
03

最后补模块边界

文件之间开始拆分时,再引入 meta 和 import 管理导出与命名空间。

::meta::import
[ 起步顺序 ]

先 define,再 ref,最后 render。

这是最小心智模型。你可以先把 Magic MD 理解成“文档里声明一组节点,节点之间靠 $ref 传值,最后把结果渲染回 Markdown”。

::define
声明节点
→ engine →
解析 / 执行
$ref{}
接入上游数据
渲染
::render
放回文档
ID 规则
myId / ns/id

本地节点写 [id],跨文件引用统一写 ns/id。

闭合规则
:: | ::endfor | ::render[id]::

define/content/meta/use 用独立一行 ::;for 用 ::endfor;render 同行闭合。

围栏语言
config / output

config 写输入配置,output 写持久化结果缓存。

$ref 范围
output body input $item

普通依赖读 output/body,模板读 input/*,循环体读 $item.*。

quick-start.mdmarkdown
::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]::
note

这个例子已经覆盖了最关键的三步。读完它再去看 content、for、import,会更容易理解每个语法在补什么能力。

[ 01 / 执行闭环 ]

先把数据跑起来。

这是 Magic MD 的最小可用集合。定义节点,引用上游结果,再把结果渲染回文档。

::define$ref{}::render
先写 ::define,再让下游用 $ref{} 读它,最后用 ::render 展示结果。
被 $ref{} 引用的定义必须先出现;render 只展示,不负责执行。
普通依赖路径从 .output 或 .body 开始,不能直接引用 .config。
flow.mdmarkdown
::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]::
note

把这三项记牢后,已经足够写出大部分单文件工作流。

::define声明可执行节点
syntax
::define[myId]{plugin="number-stat"}
指令标记
关键字
实例 ID
插件名称
::闭合行(独立一行,不可缺少)
attributes
属性必填类型说明
pluginreqstring插件名称,与插件目录名对应
config 围栏语言必须是 config,不是 json。

给节点起名,绑定插件,写配置,以单独一行 :: 闭合。

$ref{}引用上游输出
syntax
$ref{myId.output.path.to[0]}
引用开始
来源定义 ID(支持 ns/id)
访问器(output 或 body)
字段路径(支持 . 和 [n])
引用结束
模板参数用 $ref{input/name};循环变量用 $ref{$item.field} 或 $ref{$item_index}。

把任意上游节点的值接入当前配置、模板或循环体。普通路径从 .output 或 .body 开始。

::render渲染节点输出
syntax
::render[myId]::
关键字
目标节点 ID(支持 ns/id)
同行闭合
render 不独立执行,只展示 define、content 或 use 的结果。

把节点或内容块的结果显示到页面。支持块级和行内写法,也支持 ns/id 引用。

[ 02 / 复用与展开 ]

把结构抽出来,再批量生成。

当一段内容要重复出现,或同一套结构要对数组中的每个元素展开时,再引入 content、use、for。

::content::use::for
::content 负责定义一段可渲染内容;template="true" 时它更像一个参数化模板。
::use 从已有内容或定义派生新实例,可在 config 围栏里注入 with、patch、bind。
::for 的 of 必须是解析为数组的 $ref{},并且一定要用 ::endfor 闭合。
reuse.mdmarkdown
::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
note

把 content 和 use 看成“模板 + 实例化”,for 看成“批量展开 body”会更容易理解。

::content可复用内容块
syntax
::content[cardId]{template="true"}
关键字
内容 ID
模板模式(可选)
::闭合行(独立一行)
body 内可嵌套 Markdown、define、render、for 和 $ref{}。

定义可在多处渲染的 Markdown 内容。开启 template="true" 后可作为模板,被 ::use 实例化。

::use派生与实例化
syntax
::use[localConn]{from="db/conn"}
关键字
新绑定 ID
源 symbol(本地或 ns/id)
::闭合行(独立一行)
attributes
属性必填类型说明
fromreqstring源 symbol ID 或 ns/id,不接受文件路径
简单派生可只写头部和闭合;进阶写法在 config 围栏里使用 with、patch、bind。

从现有 define 或 content 派生新实例,可注入模板参数、配置补丁或依赖重绑定。

::for按数组批量展开
syntax
::for[loopId]{each="item" of="$ref{...}" limit="10"}
关键字
循环 ID
循环变量名
数组数据源
最大迭代(可选)
::endfor循环闭合
attributes
属性必填类型说明
eachreqstring循环变量名,在 body 内用 $ref{$变量名} 引用
ofreq$ref{...}数据源,必须解析为数组
limitoptnumber最大迭代次数,防止意外展开大数组
offsetoptnumber从数组的指定位置开始迭代
concurrencyoptnumber控制循环体内定义的并发执行数

遍历数组,批量执行循环体。内部可以写 define、content、use、render 和普通 Markdown。

[ 03 / 模块与边界 ]

文件一多,就开始管导出和命名空间。

文档拆到多个文件后,meta 负责定义外部可见边界,import 负责把外部 symbol 拉进当前作用域。

::meta::import
::meta[module] 的 exports 决定哪些 symbol 对外可见;不写时默认全部公开。
::import 强制带 ns,引用 imported symbol 时统一用 ns/id,避免和本地 ID 冲突。
常用 import 是单行形式;进阶用法还可以带 output 围栏缓存导入结果。
module.mdmarkdown
::meta[module]
```config
{ "exports": ["statusCard", "queryTemplate:query"] }
```
::

::import{from="./shared/report.md" ns="report"}

::render[report/statusCard]::
note

模块化关注的是文件之间如何复用,而不是单个节点如何执行。

::meta模块元数据
syntax
::meta[module]
关键字
域名(module 或 config)
::闭合行(独立一行)
attributes
属性必填类型说明
[domain]req"module" | "config"元数据域名
没有 ::meta[module] 时,symbol 默认公开;写了 exports 后只暴露白名单。

声明文档级元数据。module 域控制导出边界,config 域存放文档配置。

::import跨文件导入
syntax
::import{from="./db.md" ns="db"}
关键字
源文件路径(相对路径)
命名空间(必填)
attributes
属性必填类型说明
fromreqstring源文件相对路径
nsreqstring命名空间前缀,引用时用 ns/id 格式
pickoptstring只导入指定 symbol(逗号分隔),与 exclude 互斥
excludeoptstring排除指定 symbol(逗号分隔),与 pick 互斥
renameoptstring重命名映射(原名:新名,逗号分隔)
完整规范还支持 output 围栏缓存导入结果;这页先展示最常用的单行形态。

从其他 .md 文件导入 symbol,强制命名空间防止 ID 冲突。最常见写法是单行导入。

[ 完整示例 ]

把执行链和循环串起来。

下面这个例子专注展示最常见的工作流:声明数据源、按数组展开、为每一项生成输出,最后再做汇总。

repo-watch.mdmarkdown
::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]::
note

这类文档最适合用来熟悉 define、ref、for、render 的配合。content / use / import 更适合在结构已经稳定后再抽出来。

[ 常见失误 ]

格式边界是硬规则。

语法数量不多,但边界很严格。多数解析问题不是概念错,而是括号、围栏、闭合行写偏了。

correct ✓wrong ✗
::define[myId]{plugin="http-request"}
::define{#myId plugin=http-request}
ID 用 [],不是 {}。插件名必须带引号。
```config
```json
配置围栏语言必须是 config,不是 json。
::
:::
define/content/meta/use 的闭合只能是独立一行的 ::。
$ref{query.output.rows[0].name}
$ref{query.config.sql}
普通引用路径从 .output 或 .body 开始,不能直接读取 .config。
::import{from="./db.md" ns="db"}
::import{from="./db.md"}
ns 是必填属性,不可省略。
::use[conn]{from="db/conn"}
::use[conn]{from="./db.md"}
from 只接受 symbol ID(本地或 ns/id),不接受文件路径。
::render[ns/id]::
::render[ns.id]::
命名空间分隔符是 /,不是点号或其他符号。