nodeppt 2.0 Markdown 语法全解:从演示模板到解析器实现原理

原创2026-10-13 18:41:271,910 阅读
文章标签:前端开发工具CLI模板引擎文档

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

![](https://source.unsplash.com/HoevDVvxInw/960x720)

---

## 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

![](https://source.unsplash.com/uPGOEbjbVGA/800x600)

## 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

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 上。

6.4 image 增强

!![](url 属性) 或 !figure 语法(实现见 packages/nodeppt-parser/lib/markdown/img.js)可以为图片指定外部包装标签与属性:

!![div](https://webslides.tv/static/images/logos/google.svg .aligncenter.graylogo)

输出 <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):

  1. 拆分配置与内容:content.split(/<slide.*>/i)<a href="https://link.gitcode.com/i/3a28c5adf942a45fa61629ff08e417c3" target="_blank">0] 取 YAML 配置,js-yaml 解析后与 [defaults.js 深合并。
  2. 按页拆分:get-parser.js 用 \n<slide\s*(.*)> 正则匹配所有 slide 标签,把正文拆成每页内容。
  3. 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 八个容器插件。
  4. HTML 结构生成:每页包成 <slide ...><div class="wrap" wrap="true">渲染结果</div></slide>。
  5. posthtml 后处理:内置四个插件依次执行——tags/slide.js(section 化、背景图/视频注入、:class 分流)、tags/note.js(note → speaker-note)、tags/header-footer.js(header/footer 归位)、tags/attrs.js(css-module 上溯合并到父节点)。
  6. 模板渲染:最终结果连同全局配置注入 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- 开头),复用同样的机制产出全新的组件。

附:如何查看与运行这份演示

登录后查看全文
nodeppt