12557 个 Star 的仓库里,一行幻灯片代码都没有

发布于 · 2,615 字 · 约 7 分钟#Github 解读#CLI原文链接
12557 个 Star 的仓库里,一行幻灯片代码都没有 封面图
  • 主仓库 12557 Star 却不含幻灯片代码,仅承担官网、生态导览和论坛职责,实际功能分布在四个活跃子仓库
  • 作者 2019 年埋掉约 8000 Star 的旧 Electron 应用,重写为 Marpit、Core、CLI、VS Code 四层解耦架构并沿用至今
  • Marpit 将每页幻灯片包进 inline SVG 与 foreignObject,实现零 JS 矢量缩放与样式隔离,且语法严格兼容 CommonMark
  • 主题系统简化为纯 CSS 加 @theme 声明,marp-cli 导出 PPTX 提供截图与 LibreOffice 转换两条路径,分别牺牲可编辑性与还原度
  • 生态依赖 yhatt 一人维护,bus factor 为 1,当年规划的 Marp Web 主界面未兑现,VS Code 扩展成为实际主力

昨天想找个 Markdown 写幻灯片的工具,顺手点开了 Marp。12557 个 Star,MIT 协议,TypeScript,描述写着「Markdown 演示生态的入口仓库」。我心想这体量,源码得有多少东西。

clone 下来一看,愣住了。

116 个文件,没有一行幻灯片代码。仓库主体是一个 Next.js 官网(marp.app)加一份 README,README 的核心内容是一张表,告诉你真正的代码在另外四个仓库里。Star 挂在门口,货在别处,这个仓库本身就是它工程哲学的展品。

八千星的桌面应用,说埋就埋

要看懂这个空仓库,得把时间拨回 2016 年。日本开发者 Yuki Hattori 给自己写了个叫 mdSlide 的小工具,后来改名 Marp,一个基于 Electron 的 Markdown 幻灯片编辑器。到 2019 年,这个项目攒了约 8000 Star,教育场景一堆用户,看着是标准的开源成功故事。

但作者在 2019 年 6 月的官方博文里说了实话,头痛来自缺失的可维护性。用户的请求源源不断,旧 Marp 的架构扛不住演进。他的解法不是重构,是推倒。旧应用停止维护、归档,今天你去翻 yhatt/marp 那个坟场仓库,7850 Star,最后一次提交停在 2019 年 9 月。博文里的原话翻译过来大意是,我绝不建议继续使用旧 Marp,它的维护两年前就停了,有安全方面的顾虑。

把自己的明星产品亲手埋掉,换成一套全新的生态,这个决定当时并不轻松。博文里还记了一笔旧账,2017 年有人提议把 Marp 做成网页应用,离线用户强烈反对,那时候 PWA 还没深入人心。两年后作者还是走了这条路,只不过换了姿势,不再做「一个应用」,改做「一套零件」。

重写后的 Marp Next 分成四层。Marpit 是最底层的瘦身框架,只干一件事,把 Markdown 变成最小化的 HTML 和 CSS。Marp Core 在上面加电池,内置主题、Emoji、KaTeX 数学公式、自动缩放。Marp CLI 是瑞士军刀,负责导出 HTML、PDF、PPTX 和图片。Marp for VS Code 让你在编辑器里直接预览。这套分法到今天都没变过,四个仓库现在全部活跃,最近一次提交都在两个月内。

星数挂在门口,代码住在别处

回到那个空仓库。它其实有明确的职责,官网源码、生态导览、Discussions 论坛,三件事。GitHub 上的 issue 区是空的,因为讨论都汇聚到主仓库的 Discussions 里,四个子仓库各自保持安静。

仓库职责Star最近提交
marp入口 + 官网 + 论坛125572026-07
marp-cli转换器,导出四件套38292026-09
marp-vscodeVS Code 扩展20922026-08
marpit底层框架13812026-09
marp-core核心转换器11542026-09

这张表有个耐人寻味的细节。星数最高的仓库贡献最小,真正干活的 marpit 只有约十分之一的星。社区把掌声给了门面,作者也乐意让门面当流量入口,反正生态里随便哪个仓库都是 MIT。

四层关系摞起来是这样。

Marp 生态分层
Marp 生态分层

顺着图看,入口仓库不带逻辑,应用层三个宿主各自挑场景,底下的 Marpit 才是契约本体。右角那张虚线卡我们后面再聊,它是一个没兑现的承诺。

一张幻灯片,一个 foreignObject

Marpit 最独特的设计,是把每一页幻灯片包进一个 inline SVG。我翻了 src/markdown/inline_svg.js,实现比想象中朴素,markdown-it 解析出 token 后,按幻灯片边界切开,每一页包进一个 svg 元素,带上 data-marpit-svg 标记和 viewBox,尺寸从主题的 widthPixel 和 heightPixel 读出来,页内内容再包一层 foreignObject。

你想想看这个结构换来了什么。SVG 天生矢量缩放,viewBox 定死就是像素级精确,PowerPoint 和 Keynote 那种所见即所得的尺寸感回来了,而整个幻灯片不需要一行 JavaScript 就能在浏览器里看。foreignObject 还是个隔离罩,Markdown 内容注入的 DOM 关在盒子里面,不会撑破主题 CSS 定义的设计。官方管这叫零 JS 幻灯片,静态 HTML 文件双击打开就是一场演示。

还有一条纪律值得单说。Marpit 加的任何语法都不许破坏 CommonMark,一份 Marp Markdown 拿到普通编辑器里打开,还是一篇正常的文档。这是当年旧 Marp 被用户反复教育出来的,有人要花哨语法,也有人要求严格遵守 Markdown 规范,两头都得伺候。

主题是 CSS,不是配置文件

旧 Marp 的主题系统要懂构建流程、懂 Sass、懂应用内部逻辑才能改。Marpit 重做这套时选了个聪明角度,主题就是一份纯 CSS,在文件开头用一行 @theme 名字 的注释声明身份。

src/theme.js 里是一整条 PostCSS 管线,meta、nesting、root 替换、section 尺寸,各管一段。幻灯片宽高直接从 CSS 的像素值解析,顺手还带了一张绝对单位换算表,cm、mm、in、pt、pc、q 全部换算成 px。会写网页就会做 Marp 主题,这个门槛砍得足够低。

顺带说个集成上的巧宗。Marp for VS Code 能做得很顺,一个公开原因是 VS Code 的 Markdown 预览和 Marpit 用的是同一台 markdown-it 引擎,同一份文档在编辑器原生预览和幻灯片预览之间不会精神分裂。这种「搭别人的船」的判断力,贯穿了整个生态。

导出这条路上站满了别人的轮子

marp-cli 的转换器把「不重造轮子」执行得很彻底,我数了一下它导出 PPTX 的两条路。

默认路径是截图。converter.ts 里的 convertFileToPPTX 先把每页渲染成 PNG(走 puppeteer-core 拉起 Chrome,默认 2 倍缩放),然后用 pptxgenjs 组装,版式按幻灯片像素除以 96 定义成英寸,每页 PNG 转成 base64 贴成满版背景,Marp 注释里写的讲者备注通过 addNotes 塞进每页。所以默认导出的 PPTX 打开是能放、有备注,但一个字都改不了,它本质是一叠图。

想改字,走第二条路,--pptx-editable。这条路先转 PDF,再调用本机的 LibreOffice,用 --infilter=impress_pdf_import 让 Impress 按 PDF 导入再转出 PPTX。代码里印着一行实验性警告,原意是输出依赖 LibreOffice,幻灯片的还原性不完全保证。这条警告说得很诚实,PDF 转出来的可编辑 PPTX,布局能飘到什么程度用过 LibreOffice 的人心里都有数。

前两天我刚拆过 GenOffice,那边走的是原生 OOXML 生成的路线,公式、文本都是真对象。Marp 这两条路一条牺牲可编辑性,一条牺牲还原度,正好是「Markdown 转 PPTX」这个命题的两条经典逃逸路径。

两条路并排看更清楚。

Marp 导出 PPTX 双路径
Marp 导出 PPTX 双路径

左边是默认的截图路线,右边是实验性的 LibreOffice 路线,工具选型的时候,你对 PPTX 的期待是「能放」还是「能改」,直接决定该用哪条路,这事得先想清楚。

一个人的生态,和没兑现的 Web 梦

说完成绩单,该翻翻另一面了。

先说最要紧的一点,这是一个人的生态。主仓库贡献者 10 人,除了 yhatt 本人就是 dependabot 机器人和零星补丁,marpit 也差不多。八年时间一个人扛着五个仓库往前走,质量确实稳(四个仓库全部活跃、CI 严格、文档齐整),但 bus factor 等于 1 这个事实不会因为稳就消失。你在生产链路里引入 Marp,说到底是在押注 yhatt 的个人持续投入和他的赞助者名单。

再翻一份旧账。2019 年那份路线图没有完全兑现,博文的迁移计划写得明白,将来的主界面会是 Marp Web,桌面应用顶多变成 Web 界面的壳。七年过去,你猜怎么着,Marp Web 至今顶着一个 tech demo 的帽子,被折叠在 README 的「过时与不活跃项目」列表里,和 Marp React、Marp Vue 躺在一块。真正的主界面变成了谁?VS Code 扩展加命令行。作者当年押注浏览器优先,用户最终用脚投票选了编辑器优先。计划图和地形图,永远不是同一张图。

装机成本也别忽略。CLI 导 PDF 和截图走 Chrome,可编辑 PPTX 走 LibreOffice,两个大家伙都得你自己装,marp-cli 只带了 puppeteer-core 这种不含浏览器的核心包。想在一台干净的服务器上跑自动化导出,先掂量一下依赖体积。

说到底什么人该用 Marp?我的判断,写技术分享、课程讲义、内部分档,内容以文字和代码为主,源文件想进 git 想要 diff 的,Marp 是这个场景里最成熟的答案,VS Code 里装个扩展五分钟就能开工。要视觉冲击力的大场面发布会 deck,或者要在 PowerPoint 里跟设计团队协作,另请高明,它的主题再漂亮也是 Markdown 的漂亮。

先有契约,再有生态

拆完我一直在想,一个空仓库凭什么值 12557 个 Star。

答案不在仓库里。

它从来不是在给一个产品攒星,是在给一份契约攒星。Marpit 定义了 Markdown 怎么变成幻灯片,主题 CSS 定义了设计怎么表达,这两份契约稳定了,上层的宿主可以随便换,CLI 是宿主,VS Code 是宿主,理论上你今天想给 Vim 写个预览插件也能当宿主。旧 Marp 死就死在应用和逻辑长在了一块,埋掉应用才救出了逻辑。

这个思路对做工具的人很有迁移价值。你在做一个开发者工具吗?可以先问自己一句,我的核心能不能抽成一份不带界面的契约?契约立住了,界面、插件、集成都是别人可以帮你长出来的部分。一个人维护不过来一个产品,但维护得动一份契约加四个小仓库,Marp 用八年证明了这件事。

项目地址在上面,想体验的话不用 clone 它,装 VS Code 扩展或者一行 npx @marp-team/marp-cli 就够了。毕竟这个仓库里,本来也没有代码可 clone。

评论互动

© 2026 王若风的技术博客 · Powered by Astro