nodeppt 2.0 Markdown 语法全解:从演示模板到解析器实现原理
nodeppt 2.0 Markdown 语法全解:从演示模板到解析器实现原理
导读
本文以 nodeppt 官方演示文件 packages/nodeppt-parser/tests/demo.md 为骨架,系统讲解 nodeppt 2.0 基于 GFM 的幻灯片 Markdown 语法体系——包括 YAML 头部配置、<slide> 页分割、{.class} 属性注入、::: 容器布局、FontAwesome 图标、代码高亮、echarts/mermaid/KaTeX 图表公式,以及动效与备注等全部能力。与此同时,深入 nodeppt-parser 源码,揭示这些语法如何经过 markdown-it 与 posthtml 两级管道被解析、改写并最终渲染为 <section class="slide"> 网页结构。读完本文,你将能读懂并复刻 nodeppt 全套语法,理解每个语法糖背后的实现机制,直接上手编写属于自己的演示稿。
一、demo.md 的角色:一份可运行的语法全量演示
packages/nodeppt-parser/__tests__/demo.md 是 nodeppt-parser 的测试演示文档,同时也是 nodeppt 官方站点的演示稿之一。全文 995 行,几乎覆盖了 nodeppt 2.0 的全部扩展语法:
- 文件头部是 YAML 配置段;
- 正文由
<slide>标签分割成数十张独立的幻灯片; - 每张幻灯片内部又混合使用属性注入、容器组件、行内组件、代码块插件与 HTML 混排。
在解析器视角下,这份文件被 packages/nodeppt-parser/lib/get-parser.js 按 \n<slide.*> 正则拆分为"配置 + 多页内容"两部分,然后逐页走一遍 markdown → HTML 的完整流水线。因此,理解 demo.md 就等于理解了 nodeppt 的语法全貌;而对照源码,则可以进一步理解每个语法是如何被实现的。
二、YAML 头部:全局配置
demo.md 文件最前面是配置段,采用 YAML 语法:
title: nodeppt markdown 演示
speaker: 三水清
url: https://github.com/ksky521/nodeppt
js:
- https://www.echartsjs.com/asset/theme/shine.js
prismTheme: solarizedlight
plugins:
- echarts
- katex
这份配置由 packages/nodeppt-parser/lib/yaml-parser.js 用 js-yaml 解析,再与 packages/nodeppt-parser/defaults.js 中的默认值通过 lodash.defaultsdeep 深合并后注入 EJS 模板。
各配置项的含义如下:
| 配置项 | 作用 | 默认值(defaults.js) |
|---|---|---|
title |
演讲主题 | nodeppt markdown |
speaker |
演讲者 | 空 |
url |
地址 | 空 |
js |
页面 JS 文件数组,渲染到 body 之前 | 空 |
css |
页面 CSS 文件数组,放到头部 | 空 |
theme |
整体主题 | moon |
transition |
页面切换动效 | move |
highlightStyle |
代码高亮样式 | monokai_sublime |
prismTheme |
prism 配色,取值范围 ['dark', 'coy', 'funky', 'okaidia', 'tomorrow', 'solarizedlight', 'twilight'] |
— |
plugins |
启用的插件,目前支持 echarts、katex、mermaid 等 |
— |
pluginsOptions |
插件配置 | — |
webslidesOptions |
WebSlides 运行时配置(如 autoslide: 5000 开启自动播放) |
— |
baseTemplate |
自定义 EJS 模板路径,解析器会按相对于 md 文件的位置解析 | — |
需要注意:plugins 数组中的字符串元素会被保留,而对象元素 {key: options} 会被拆出 key 作为插件名、值存入 pluginsOptions(见 yaml-parser.js)。demo.md 中声明了 echarts 与 katex 两个插件,并在 js 里引入了 echarts 的 shine 主题脚本,为后面的图表演示做准备。
三、<slide> 标签:分页与背景体系
nodeppt 用 <slide> 标签分割每一页幻灯片。解析器先将内容按 <slide 拆分,并为每一页套上固定骨架:
<section class="slide" attrs...>
<div class="wrap" wrap="true">
<!-- 具体 markdown 渲染的内容 -->
</div>
</section>
这一步由 packages/nodeppt-parser/lib/tags/slide.js 中的 posthtml 插件完成:<slide> 标签被替换为 <section>,并合并 class: 'slide' 与 slide: true 属性。
3.1 class 与 :class 的分流
<slide> 标签上普通的 class、image 等属性会挂到 <section> 上;而以冒号开头的属性(如 :class="size-40")会被单独提取,挂到内层 div.wrap 上:
<slide :class="size-50">
### Let's check out some examples.
demo.md 中大量使用了这一机制,例如 <slide class="frame moveIn" :class="size-60 bg-white tobuild"> 表示:section 拥有 frame moveIn 类,而内部 wrap 拥有 size-60 bg-white tobuild 类。这让我们可以在同一页上分别控制整页与内容容器的样式。
3.2 背景图片 image
<slide image="url 附加属性"> 会被解析为背景大图。解析逻辑见 slide.js:图片 URL 与附加属性用空白分隔,附加属性中除了 dark、light、anim 之外都会映射为 background-* 类,最终在 section 内容最前面插入一个带 background-image:url(...) 内联样式的 span。demo.md 中的用法:
<slide class="bg-purple aligncenter" image="https://source.unsplash.com/C1HhAQrbykQ/ .dark">
# nodeppt
## 这可能是迄今为止最好的网页版演示库
image="...url...":普通背景图;image="...url... .dark":背景图上叠加偏黑色透明蒙层;image="...url... .light":叠加偏白色蒙层;image="...url... .anim":背景图缓慢运动;- 还支持
top/bottom/right/center/left等位置修饰(源码中对应类直接保留,不生成background-前缀)。
3.3 背景视频 video
<slide video="url1,url2 附加属性"> 会生成 <video> 背景。源码 slide.js 中,视频 URL 以逗号分隔(支持多源),自动带上 loop、muted、autoplay、preload="true",附加的 dark/light 类原样保留,其余变为 background-video-* 类,poster 等属性会透传给 video 标签。demo.md 的用法:
<slide class="bg-blue aligncenter" video='https://webslides.tv/static/videos/peggy.mp4 .dark poster="https://webslides.tv/static/images/peggy.jpg"'>
3.4 渐变与品牌背景类
demo.md 还演示了直接在 <slide class="..."> 上使用预设背景类:
bg-gradient-h/bg-gradient-v/bg-gradient-r:水平、垂直、径向渐变背景;bg-apple:Apple 品牌渐变背景;bg-primary/bg-secondary/bg-light:WebSlides 主色背景;bg-black、bg-black-blue、bg-white、bg-red、bg-green、bg-blue、bg-purple、bg-brown、bg-dark:通用与品牌色背景;bg-trans-dark(rgba(0,0,0,0.5))与bg-trans-light(rgba(255,255,255,0.2)):透明背景。
对应 CSS 位于 packages/nodeppt-js/assets/less/modules/_slides-bg.less 等样式模块中。demo.md 用 :::flexbox 容器把这些色块排成色板,既演示了背景类,也演示了 flexbox 网格布局。
四、属性语法:{.class} 与 {..css-module}
nodeppt 的 Markdown 解析基于 markdown-it,并深度定制了 attribute 语法(源自 markdown-it-attrs,增加多 class 与 css-module 支持),核心实现在 packages/nodeppt-parser/lib/markdown/attrs 目录,包含 index.js、utils.js 与 patterns.js。
基本规则:
# header {.style-me.class2}
paragraph {data-toggle=modal}
输出:
<h1 class="style-me class2">header</h1>
<p data-toggle="modal">paragraph</p>
关键扩展点:
- 多 class:
{.class1.class2}支持多个类(utils.js 中addAttrs会把.拆分为空格)。 - css-module:
{..css-module}双点语法会把类上溯到上一层节点,demo.md 中大量使用,例如## .bg-primary {..bg-primary}表示把bg-primary类加到上级节点(本页 section/wrap)上。解析为css-module键,随后由 posthtml 的 tags/attrs.js 在 AST 层把 css-module 合并到父节点。 - 键值对与引号:支持
{key=value}、{key="value with space"}。 - id:
{#id}生成 id 属性。
属性语法适用的位置非常广,从 patterns.js 可以看出它覆盖了:围栏代码块( ```python {.cls})、行内链接(text{.button})、图片与行内代码、表格、列表项、水平线、行尾块属性等十几类场景。demo.md 中 [.button](){.button} 按钮、{.build.fadeIn} 动效、{.text-intro} 排版类、表格的 {.text-cols} 多列等,全部依赖这套属性注入机制。
五、容器语法:强大的 :::
nodeppt 扩展了 markdown-it-container 语法(核心实现见 packages/nodeppt-parser/lib/markdown/container.js),用至少三个冒号 ::: 开启/闭合容器,容器名后的 {.attrs} 会被解析为容器的 class 等属性,容器内部用 ---(hr)分隔多个区块。注册清单见 packages/nodeppt-parser/lib/get-markdown-parser.js:column、shadowbox、steps、card、flexblock、cta、gallery、div。
一个重要的实现事实是:demo.md 中出现的 :::flexbox、:::blink、:::features、:::specs 并没有单独注册容器插件,而是统一走 flexblock 容器的 handler(containers/flexblock.js),根据 opts 里解析出的类名(blink、specs、clients、features 等)决定为每个 <li> 项内部追加 <a>、<div>、<figure>/<figcaption> 等不同的嵌套结构。下面逐一讲解。
5.1 div:通用容器
:::div {.attrs} 是最通用的包装容器,demo.md 中简写为 :::(不带名字也可用),例如:
::: {.content-left}
## button
[.button](){.button} [.button.radius](){.button.radius}
:::
输出 <div class="content-left">...</div>。实现见 containers/div.js,同时支持 footer、note、header 三种特殊 tag 名。
5.2 flexbox / flexblock:多栏网格
demo.md 中把配色色板放进 :::flexbox,每项之间用 --- 分隔,自动生成 <ul class="flexblock"> 与 <li> 项:
:::flexbox {.border.metrics}
Founded
::1972::
----
::fa-users::
24M Subscribers
:::
容器名后的 {.border.metrics} 等属性会被追加到 ul 上。渲染时每个项是一个 li,解析器会自动"扒掉"仅由单个 <p> 包裹的外层(见 flexblock.js),并针对不同子类型追加标签:
:::flexbox {.border.metrics}:指标网格;:::blink:追加<a>链接包装(demo.md 用它做"About/Services/Clients"列表);:::features:追加<div>包装,可与 blink 组合;:::specs:追加<div>包装(demo.md 的 "SIMPLE NAVIGATION / Permalinks / SVG ICONS" 列表);:::flexblock的clients子类型:追加figure/figcaption/a(demo.md 中!div加 logo 的部分)。
5.3 column:分栏布局
:::column {.vertical-align} 生成 .grid 网格布局,hr 处被替换为新的 column 项,支持 sm、ms、sms 等比例(见 containers/column.js)。demo.md 用它实现"为什么选择 nodeppt"的三列特性列表、FAQ 两列布局:
:::column {.vertical-align}
- 基于 GFM 的 markdown 语法编写
- ...
---
```html {..fadeInUp..slow}
<article id="webslides">...</article>
:::
### 5.4 card:图文卡片
`:::card`、`:::card-50`、`:::card-60` 等生成图文卡片。实现见 <a href="https://link.gitcode.com/i/9bc9784fc9fc90dc35ff4b907b8c65b5" target="_blank">containers/card.js</a>:解析器会把 `---` 分隔的两部分识别为"图片侧"与"内容侧",把图片包进 `<figure>`,另一侧包进 `flex-content` div,并默认追加 `bg-white` 背景。demo.md 中:
```markdown
:::card-50

---
## Bonsai
...
:::
配合 <slide class="fullscreen"> 可实现全屏卡片页。
5.5 cta:行动号召
:::cta 会把 --- 前的内容包进 .number div、之后的内容包进 .benefit div,生成 <div class="cta">(见 containers/cta.js)。demo.md 用 ::^$^40:: 生成大号数字图标配合 cta:
:::cta
::^$^40::
---
## Watch TV shows anytime, anywhere
:::
5.6 gallery:团队画廊
:::gallery 与 :::gallery overlay 生成 <ul class="flexblock gallery">,每项包含 figure + figcaption(overlay 模式用 .overlay div)。demo.md 中展示了两种团队介绍卡片:
:::gallery overlay

## Alicia Jiménez
Founder & CEO
:::
5.7 steps:步骤组件
:::steps 生成 <ul class="flexblock steps">,每项自动编号 process step-N(见 containers/steps.js):
:::steps
:~fa-file~:
## Interfaces
...
:::
5.8 shadowbox:阴影卡片
:::shadowbox 生成 <div class="bg-white shadow"><ul class="flexblock reasons"> 结构(见 containers/shadow.js),demo.md 用它做 "Why WebSlides" 的双栏要点:
:::shadowbox
## We're web people.
...
:::
5.9 header / footer / note:页眉页脚与演讲备注
在 slide 内部使用 :::header、:::footer、:::note 容器(div.js 把这三个名字映射为对应 tag),随后由 posthtml 插件 tags/header-footer.js 和 tags/note.js 处理:header 被移动到 section 开头、footer 追加到末尾(内容包进 .wrap);note 则被改写为 <div class="speaker-note"><div class="wrap">...</div></div>,作为演讲者备注,演示时按 N 键显示。demo.md 中:
:::header {.abeee}
### Header
:::
:::footer
### Footer
:::
:::note
### Note
:::
六、行内组件:图标、span、引用与按钮
6.1 FontAwesome 图标
packages/nodeppt-parser/lib/markdown/fa.js 注册了三档图标语法:
| 语法 | 输出 |
|---|---|
:fa-xxx: |
<i class="fa fa-xxx"></i>(行内) |
:~fa-xxx~: |
<span><i class="fa fa-xxx"></i></span>(带 span 包装) |
::fa-xxx:: |
块级 <i class="fa fa-xxx"></i>(自动剥离外层 p) |
demo.md 中使用 :fa-info-circle large:、::fa-heart large::、:fa-long-arrow-right:、:fa-link:、:fa-text-height:、::~fa-users~:、:~fa-line-chart~:、:~fa-building-o~:、:~fa-smile-o~:、:~fa-usd~:、:~fa-university~:、:~fa-file~:、:fa-github:、:fa-clock-o: 等,覆盖行内、span、块级三种形态。此外 ::→::、::40+::、::500+::、::1972::、::64%::、::^$^40:: 这类 ::文本:: 块级文本同样由 fa 插件的 block 规则剥离段落包裹后输出。
6.2 span 与 cite
- span:
:span:或:span: {.text-span}生成<span>,支持接属性,实现来自 packages/nodeppt-parser/lib/markdown/span.js(代码会排除:fa-前缀以免冲突)。 - cite:
==文字==生成<cite>,demo.md 中==Zaha Hadid==即用此语法标注引用出处,配合{.text-quote}排版,实现见 packages/nodeppt-parser/lib/markdown/cite.js。
6.3 按钮与链接增强
- 按钮:nodeppt 的按钮就是普通链接 + 属性注入的产物:
[.button](){.button} [.button.radius](){.button.radius}
[.button.ghost](){.button.ghost} [:fa-github: svg-icon](){.button}
链接属性由 attrs 的 inline nesting link 模式(patterns.js)注入到 link_open token 上。
- 链接增强:packages/nodeppt-parser/lib/markdown/link.js 会为
http(s)外链自动追加target="_blank";对href为空或为#的链接,则把 href 替换为javascript:void(0),避免页面跳转。
6.4 image 增强
! 或 !figure 语法(实现见 packages/nodeppt-parser/lib/markdown/img.js)可以为图片指定外部包装标签与属性:
!
输出 <div ...><img src="..." class="aligncenter graylogo"/></div>。demo.md 中大量用它包装 logo 图(!div、.blacklogo 等)。
6.5 HTML 混排与 JSX
nodeppt 允许在 Markdown 中直接混排 HTML。demo.md 中 <nav>、<article id="webslides"> 等原始 HTML 会原样输出。markdown-it-jsx 的定制版(packages/nodeppt-parser/lib/markdown/jsx.js)把 <Component> 一类的标签按 JSX 内联解析,并在 blockify 阶段剥离多余的外层 <p>,保证复杂 demo 的 HTML 结构不被破坏。
七、代码块、图表与公式
7.1 代码高亮(Prism)
代码高亮使用 packages/nodeppt-parser/lib/markdown/prism.js,基于 markdown-it-prism 增加 attrs 支持: ```html {..fadeInUp..slow} 可以把属性注入到 <pre> 上(css-module 双点语法上溯)。主题由 YAML 的 prismTheme 控制(demo.md 使用 solarizedlight)。未知语言默认按 textile 处理。
7.2 echarts 图表
```echarts 围栏内的内容被当作 JS 对象字面量求值,序列化后写入隐藏的 .echarts-data div,运行时由 echarts 插件渲染。实现见 packages/nodeppt-parser/lib/markdown/echarts.js。demo.md 中有一个完整的环形饼图配置(含 tooltip、legend、两个 series 的嵌套饼图与 rich label 样式),并支持 {style="height:400px;"} 属性注入。
7.3 mermaid 流程图
```mermaid 围栏内容被包进 .embed div,运行时由 mermaid 插件渲染(实现见 packages/nodeppt-parser/lib/markdown/mermaid.js),在 YAML plugins 中启用。
7.4 KaTeX 数学公式
demo.md 用表格演示了 $...$ 数学公式(Maxwell 方程组),由 @ksky521/markdown-it-katex 提供,在 YAML plugins: <a href="https://link.gitcode.com/i/80a77bda1a3999c9b281b6c95b98a70f" target="_blank">katex] 中启用(见 [get-markdown-parser.js)。
7.5 表格
标准 GFM 表格可用,demo.md 演示了对齐语法(:---、:---:、---:)并组合 {.text-cols} 让列表分列:
| Left-aligned | Center-aligned | Right-aligned |
| :----------- | :------------: | ------------: |
| git status | git status | git status |
八、动效体系
nodeppt 的页面内动效来自 WebSlides 与 animate.css 的结合。demo.md 展示了多种用法:
- build 动效:在列表父节点加
{.build.fadeIn},让子项逐条出现:
- 基于 GFM 的 markdown 语法编写
- 支持 html 混排...
{.build.fadeIn}
- tobuild + 动效类:
<slide class="frame moveIn" :class="size-60 bg-white tobuild">表示整页以moveIn动画进入,内部元素带tobuild逐步构建。 - 元素级动效类:
{.fadeIn.slow}(慢速淡入)、{.fadeInUp}、{.zoomIn}、{.fadeInUp..slow}(css-module 上溯 + slow 修饰)。 - 支持的动效包括
fadeIn、zoomIn、rollIn、moveIn、fadeInUp及slow等修饰类。
九、引用与排版辅助类
- 块引用:标准
>引用,可配合{.text-quote}排版。 - 排版类:demo.md 使用
{.text-intro}(引言)、{.text-landing}(落地页标题)、{.text-serif}(衬线字体)、{.text-shadow}(文字阴影)、{.text-context}(上下文标签)、{.text-subtitle}(副标题)、{.text-data}(数据大字)、{.text-symbols}(符号分隔)、{.text-cols}(多列文本)、{.aligncenter}等。 - 页内定位类:
slide-bottom(内容贴底)、alignright、content-center等。
十、解析器流水线:从 Markdown 到幻灯片 HTML
最后从源码层面串起整条调用链(入口见 packages/nodeppt-parser/index.js):
- 拆分配置与内容:
content.split(/<slide.*>/i)<a href="https://link.gitcode.com/i/3a28c5adf942a45fa61629ff08e417c3" target="_blank">0]取 YAML 配置,js-yaml解析后与 [defaults.js 深合并。 - 按页拆分:get-parser.js 用
\n<slide\s*(.*)>正则匹配所有 slide 标签,把正文拆成每页内容。 - Markdown 渲染:每页内容交由 get-markdown-parser.js 返回的 markdown-it 实例渲染——按序挂载 prism 高亮、sup/br/katex、jsx、echarts、mermaid、fa、link、attrs、img、cite、span 以及
column/shadowbox/steps/card/flexblock/cta/gallery/div八个容器插件。 - HTML 结构生成:每页包成
<slide ...><div class="wrap" wrap="true">渲染结果</div></slide>。 - posthtml 后处理:内置四个插件依次执行——tags/slide.js(section 化、背景图/视频注入、
:class分流)、tags/note.js(note → speaker-note)、tags/header-footer.js(header/footer 归位)、tags/attrs.js(css-module 上溯合并到父节点)。 - 模板渲染:最终结果连同全局配置注入 template/index.ejs,得到完整 HTML 页面。
至此,从 demo.md 里每一行语法到最终 <section class="slide"> 页面结构的映射关系都已打通:<slide> 与 image/video/:class 由 posthtml 插件负责,{.attr} 由 attrs 双插件(markdown-it 层注入 + posthtml 层上溯)负责,::: 容器由 markdown-it-container 扩展负责,图表公式与动效则由对应插件与 WebSlides 运行时协作完成。理解了这条流水线,你不仅能照着 demo.md 写稿,还能自行扩展语法——例如在 nodeppt.config.js 的 plugins 中注册自己的 markdown-it 或 posthtml 插件(id 分别以 markdown-、posthtml- 开头),复用同样的机制产出全新的组件。
附:如何查看与运行这份演示
- 阅读源码:直接查看 packages/nodeppt-parser/tests/demo.md 以及同目录的测试用例。
- 本地运行:在仓库根目录按 package.json 安装依赖后,用
nodeppt serve启动 dev server 预览效果,或nodeppt build构建出静态 HTML(命令细节见 packages/nodeppt/README.md)。 - 配套文档:仓库
site/目录下还有 classes.md、layout.md、component.md、background.md、animation.md、echarts.md、mermaid.md 等专题文档,可与本文互为印证。