十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Typora平替方案:开源编辑器+WebDAV+小程序构建写作阅读链路

Typora平替方案:开源编辑器+WebDAV+小程序构建写作阅读链路 先说一个可能会让很多人意外的判断Typora 的“平替”重点从来不是“找一个差不多的编辑器”而是重新设计一条“从写作到阅读”的完整链路。如果你只是想在电脑上找一个能写 Markdown 的软件那么免费开源方案确实不少VS Code 加插件也完全够用。但如果你遇到过下面几个场景感受就完全不同了在公司电脑写完一篇 Markdown 笔记回家想用手机继续看文件还在办公电脑上。给客户交付一份产品说明文档对方不习惯看 GitHub 风格的代码仓库也不愿意装任何软件只希望“点开就能读”。自己整理的技术笔记越来越长想按目录跳转、想搜索、想在碎片时间里用手机复习但现有的 Markdown 工具链根本支撑不起来这种需求。这篇文章要讲的不是一个具体的商业软件而是一套实践思路如何用 4MB 级别的轻量工具组合把 Markdown 编辑、网盘存储、小程序阅读三件事串起来。这里说的 4MB 不是指某个虚构的“超级软件”安装包大小而是指通过合理的工程取舍编辑端、存储端、阅读端的核心代码体积完全能控制在非常轻量的范围内。读完这篇文章你可以根据自己的技术栈组合出一套完全属于自己的“Typora 替代方案”并且理解这套方案为什么在工程上更合理、在体验上更顺手。先说结论真正适合做 Typora 平替的不是某个大而全的软件而是“开源编辑器 WebDAV/对象存储 小程序渲染层”的三层结构。每一层都可以单独替换、单独维护这也是它比单体软件更值得推荐的根本原因。1. 这篇文章真正要解决的问题很多人搜索“Typora 平替”时心里真实的需求是不想付费或者觉得 89 元买一个 Markdown 编辑器不划算。但如果你只把“平替”理解为“找到一个不用钱的 Typora”那么你大概率会在多个免费编辑器之间反复横跳最后发现都不满意。原因很简单Typora 的价值不只是“能写 Markdown”而是它的沉浸式写作体验、即时渲染、跨平台同步和稳定的文件管理。免费编辑器如果只在“即时渲染”这一个点上做功夫那你依然缺少同步能力、发布能力和多端阅读能力。所以这篇文章真正要解决的痛点不是“找一个免费的 Typora”而是怎么搭建一套从写到读的轻量级个人知识管理链路。它包含三个核心问题Markdown 编辑端怎么选一个轻量、可靠、可扩展的编辑方案存储端怎么把写好的文件变成“多端可用、可备份、可分享”的数据阅读端怎么让手机上的小程序能够优雅地渲染 Markdown并像浏览网页一样阅读如果你是一个经常写技术笔记、做文档输出、或者需要给团队交付资料的人这篇文章非常适合你。你不需要有很深的前端功底但如果你了解一点 JavaScript 或者小程序开发实践起来会更顺利。2. 核心概念与方案选型对比在动手之前我们需要先把概念理清。这个方案里有三个容易被混淆的关键词Markdown 编辑器、网盘存储、小程序阅读。2.1 Markdown 编辑器不只是一个文本框Markdown 编辑器有很多种形态纯文本编辑器比如 VS Code、Vim提供语法高亮但没有即时预览。分屏预览编辑器左边写、右边看比如许多在线编辑器。沉浸式即时渲染编辑器像 Typora 一样输入即渲染所见即所得。如果你追求 Typora 的体验那么编辑端应该优先考虑“即时渲染”或“分屏预览”方案。开源方案里Mark Text是比较接近 Typora 体验的桌面编辑器也有不少开发者选择在 VS Code 中安装 Markdown Preview Enhanced 插件。但要嵌入到自定义工作流里更灵活的方案是使用Vditor或milkdown这类开源编辑器核心它们体积小、支持自定义扩展可以方便地对接后续的存储和发布链路。这里想强调的一个判断是编辑器的选择应该由你的“存储和发布需求”倒推而不是只凭编辑体验做决定。如果你选择的编辑器不支持插件、不支持自定义导出、没有 API那么就算写起来再舒服也很难和网盘、小程序打通。2.2 网盘存储文件要放在哪里才有意义Markdown 文件本质上是纯文本它可以存在本地但存在本地意味着“换设备就没法用”。要让多端可用存储层需要有以下几个能力支持文件各级目录操作。支持按路径读取、写入。支持增量更新或者至少支持按需拉取。最好还有版本管理能力。常见的存储方案对比方案优点缺点适合场景WebDAV坚果云等协议通用可用 HTTP 操作文件无需关心底层实现同步冲突需注意免费流量有限个人笔记同步、跨端写作对象存储阿里云 OSS、七牛云等高可用、容量大、可配合 CDN 加速需要自己管理权限和数据生命周期公开文档发布、图片附件托管Git 仓库天然带版本管理支持协作不适合大型二进制文件需要一定命令行基础技术文档、开源项目文档本地文件系统最简单、完全可控无法多端同步纯本地体验、离线写稿从“平替 Typora 网盘”这个需求来说WebDAV 是最容易落地的一种方案。原因有三个协议轻量任何语言都能发起 HTTP 请求。坚果云等国内服务商直接提供 WebDAV 支持不需要自己搭服务器。写操作和读操作都是文件级别的非常符合 Markdown 这种纯文本文件的存储方式。如果你有更强的工程能力也可以把对象存储作为“发布层”把 Git 作为“版本层”把 WebDAV 作为“同步层”。这三层可以并存各司其职。2.3 小程序阅读Markdown 到页面的最后一步小程序阅读端是整个链路里“最容易被低估”的一环。很多人以为小程序里显示 Markdown 就是把文本原样输出结果真做起来才发现两个问题小程序原生组件不支持直接渲染 Markdown 语法。Markdown 里包含的代码块、表格、数学公式、图片需要分别处理。所以小程序阅读端需要一个转换管线Markdown 文本 - 解析 AST - 渲染成 HTML/JSON 节点 - 小程序富文本组件展示。这里推荐的技术是使用开源的 Markdown 解析库比如marked、markdown-it、towxml等在小程序端将 Markdown 转换为 HTML 字符串然后用小程序的原生rich-text组件渲染。对于代码高亮可以在解析阶段引入 highlight.js 的 token 处理在渲染阶段配合自定义样式。另外一个关键设计是小程序端不应该直接去网盘上拉取原始 Markdown 再解析原因有两个。第一小程序通过 request 访问 WebDAV 或对象存储时会遇到域名白名单、权限校验等问题第二每次打开文章都实时解析原始文本性能损耗不小。更好的做法是在存储层之上增加一层“发布服务”在写入或更新时主动触发一次 Markdown 转 HTML然后把 HTML 字符串和 Markdown 文本一起存储。小程序端优先读取 HTML读取成本低、渲染稳定。3. 环境准备与前置条件下面进入实践部分。文章里的方案以“个人知识管理”为背景我尽量选用通用技术和主流服务避免绑定某个特定厂商。你不需要一次性把所有组件都搭起来可以先跑通一个最小链路再逐步叠加功能。3.1 技术栈选择建议编辑端可选 Vditor浏览器端编辑器或 Mark Text桌面端。本文示例以 Vditor 的轻量接入为例。存储端使用 WebDAV以坚果云为例对象存储选型逻辑类似。小程序端微信小程序原生开发 marked 转换库 rich-text 组件。发布转换脚本Node.js 脚本用于把 Markdown 转换为 HTML并上传到存储端。3.2 需要准备的工具工具用途备注Node.js运行转换脚本、本地服务版本以实际环境为准建议 16 以上微信开发者工具小程序开发、预览需要注册小程序 AppID坚果云账号提供 WebDAV 存储免费版即可满足个人使用VS Code编辑代码和 Markdown可选说明一下这里不需要一台独立的服务器。整个方案里只有“发布转换”这一步需要本地运行 Node.js 脚本如果你想做到“写完自动发布”可以后续再接一个云函数或持续集成任务但这属于进阶内容。4. 核心流程拆解从本地写作到小程序阅读整个链路可以拆成四个步骤每一步解决一个独立问题。4.1 第一步在本地完成 Markdown 写作这一步的用户体验目标是让作者专注于写作。如果你使用桌面编辑器那么写入文件的编码要统一为 UTF-8图片建议使用相对路径或集中放置到assets目录。写作时不考虑“发布格式”只考虑“内容完整准确”。4.2 第二步将 Markdown 文件推送到网盘写作完成后文件需要进入存储层。这里有两个方式手动方式客户端工具直接上传文件到 WebDAV。自动方式本地监听文件变动变动后自动上传。对于个人使用手动方式即可。用一个简单的 Node.js 脚本通过 WebDAV 的PUT方法上传文件。4.3 第三步转换成小程序可读的 HTML这是整个方案最核心的一步。Markdown 源文件是给人读的但不是所有客户端都能直接渲染它。转换这一步要完成读取 Markdown 原文。使用marked解析为 HTML 字符串。对代码块开启代码高亮。将生成的 HTML 和原文件一起存入存储端或者存入小程序的云开发数据库。4.4 第四步小程序端拉取并展示小程序端发起网络请求从存储端拉取 HTML 字符串然后用rich-text节点渲染。这里要注意安全性和性能要做 HTML 清洗避免脚本注入要设置缓存机制避免每次都拉全量文档。5. 完整示例与代码实现下面我们用一个最小化的示例把整条链路跑通。示例不追求完整生产级但核心逻辑完整、可复制。5.1 编辑端示例Vditor 快速接入先说编辑端。假设你在开发一个内部工具型网页页面上有一个 Markdown 编辑区域。使用 Vditor 最简单的方式是通过 CDN 引入。!DOCTYPE html html langzh-CN head meta charsetUTF-8 titleMarkdown 写作工作台/title link relstylesheet hrefhttps://cdn.jsdelivr.net/npm/vditor/dist/index.css / script srchttps://cdn.jsdelivr.net/npm/vditor/dist/index.min.js/script /head body div ideditor/div button idsaveBtn保存到网盘/button script const vditor new Vditor(editor, { height: 600, mode: ir, // ir 即即时渲染模式最接近 Typora 的体验 preview: { markdown: { toc: true, mark: true, math: true, } }, after: function() { console.log(编辑器初始化完成); } }); document.getElementById(saveBtn).addEventListener(click, function() { const markdownContent vditor.getValue(); // 这里把 markdownContent 发送到后端或本地脚本 console.log(markdownContent); }); /script /body /html这段代码的关键点是mode: ir让编辑器进入即时渲染模式。preview.markdown.math和toc让编辑器天然支持数学公式和目录结构。保存按钮触发获取内容后续你可以对接上传脚本。5.2 上传脚本用 Node.js 把文件推到 WebDAV下面这段脚本用来把一个本地 Markdown 文件通过 WebDAV 上传到网盘。以坚果云为例你需要先在坚果云开放应用授权获取应用密码而不是登录密码。// 文件路径scripts/upload-webdav.js const https require(https); const fs require(fs); const path require(path); // 配置建议改为环境变量 const WEBDAV_HOST dav.jianguoyun.com; const WEBDAV_PATH /dav/markdown-notes/; const USERNAME your_emailexample.com; const PASSWORD your_webdav_app_password; function uploadFile(localFilePath, remoteFileName) { const content fs.readFileSync(localFilePath, utf-8); const options { hostname: WEBDAV_HOST, port: 443, method: PUT, path: ${WEBDAV_PATH}${remoteFileName}, headers: { Content-Type: text/markdown, Content-Length: Buffer.byteLength(content), Authorization: Basic Buffer.from(${USERNAME}:${PASSWORD}).toString(base64) } }; const req https.request(options, (res) { console.log(状态码: ${res.statusCode}); if (res.statusCode 200 res.statusCode 300) { console.log(文件上传成功: ${remoteFileName}); } else { console.log(文件上传失败); } res.resume(); }); req.on(error, (e) { console.error(请求错误: ${e.message}); }); req.write(content); req.end(); } const localFile process.argv[2]; const remoteName process.argv[3] || path.basename(localFile); if (!localFile) { console.log(用法: node upload-webdav.js 本地Markdown文件 [远程文件名]); process.exit(1); } uploadFile(localFile, remoteName);运行方式node scripts/upload-webdav.js ./docs/第一章.md 第一章.md如果你的使用场景是在浏览器里直接上传那么与浏览器的跨域限制、鉴权方式有关需要额外处理这里先用 Node.js 脚本演示最基础的流程。5.3 转换脚本Markdown 转带代码高亮的 HTML现在把本地的 Markdown 文件转换成适合在小程序里展示的 HTML。// 文件路径scripts/markdown-to-html.js const fs require(fs); const path require(path); const { marked } require(marked); const hljs require(highlight.js); // 配置 marked 使用 highlight.js 做代码高亮 marked.setOptions({ highlight: function(code, lang) { if (lang hljs.getLanguage(lang)) { return hljs.highlight(code, { language: lang }).value; } return hljs.highlightAuto(code).value; }, breaks: true, gfm: true, }); function convert(markdownPath, outputHtmlPath) { const markdownContent fs.readFileSync(markdownPath, utf-8); const htmlContent marked.parse(markdownContent); // 套一层基础样式方便小程序端直接使用 const fullHtml !DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 link relstylesheet hrefhttps://cdn.jsdelivr.net/npm/highlight.js11/styles/github.css style body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif; max-width: 800px; margin: 0 auto; padding: 16px; line-height: 1.7; color: #333; } pre { background: #f6f8fa; padding: 16px; border-radius: 8px; overflow-x: auto; } code { font-family: SFMono-Regular, Consolas, Liberation Mono, monospace; } img { max-width: 100%; } /style /head body ${htmlContent} /body /html; fs.writeFileSync(outputHtmlPath, fullHtml, utf-8); console.log(转换完成: ${outputHtmlPath}); } const markdownFile process.argv[2]; const outputFile process.argv[3] || output.html; if (!markdownFile) { console.log(用法: node markdown-to-html.js Markdown文件 [输出HTML文件]); process.exit(1); } convert(markdownFile, outputFile);安装依赖并运行npm install marked highlight.js node scripts/markdown-to-html.js ./docs/第一章.md ./docs/第一章.html这里需要说明一个工程上的取舍小程序端并不需要这份完整的 HTML 字符串包含html和body标签在把 HTML 存入数据库时应当把body内部的内容单独存储。上面的脚本把整页 HTML 输出是为了兼顾 PC 端浏览器预览需要。如果你只给小程序用可以只提取body内部片段然后在代码里做截取。5.4 小程序端通过 rich-text 渲染 HTML小程序端用rich-text组件渲染 HTML 字符串。下面是一个最小实现。// 文件路径miniprogram/pages/reader/reader.json { navigationBarTitleText: Markdown 阅读, usingComponents: {} }!-- 文件路径miniprogram/pages/reader/reader.wxml -- view classreader-container rich-text nodes{{htmlContent}} bindtaphandleTap/rich-text /view/* 文件路径miniprogram/pages/reader/reader.wxss */ .reader-container { padding: 32rpx; font-size: 30rpx; line-height: 1.8; word-break: break-word; }// 文件路径miniprogram/pages/reader/reader.js Page({ data: { htmlContent: }, onLoad(options) { const documentId options.id; this.fetchDocument(documentId); }, fetchDocument(documentId) { // 这里以 wx.cloud.callFunction 为例实际请替换为自己的数据源 wx.cloud.callFunction({ name: getDocument, data: { id: documentId }, success: (res) { const htmlContent res.result.htmlContent || ; this.setData({ htmlContent }); }, fail: (err) { console.error(获取文档失败, err); } }); } });这里的小程序端代码非常薄核心渲染逻辑全部交给rich-text。真正的工作量集中在“如何让 HTML 在小程序里显示得漂亮”包括标题的字体大小和间距。表格边框和隔行变色。代码块的横向滚动。图片的自适应宽度。引用块、列表的样式。这些样式可以全部写在rich-text节点的inner属性里或者在小程序页面的 WXSS 中针对rich-text内部的标签写样式。不过rich-text对内部节点的样式支持有一些限制更复杂的场景建议使用towxml这类专门为小程序设计的 Markdown 渲染库。5.5 云函数示例从存储端读取并返回 HTML如果你希望数据链路更完整可以参考下面这个云函数思路。它负责从数据库读取文档记录并返回 HTML 内容。// 文件路径cloudfunctions/getDocument/index.js const cloud require(wx-server-sdk); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); const db cloud.database(); exports.main async (event, context) { const { id } event; if (!id) { return { code: 1, message: 缺少文档ID }; } try { const result await db.collection(documents).doc(id).get(); const document result.data; return { code: 0, htmlContent: document.htmlContent, title: document.title, updatedAt: document.updatedAt }; } catch (e) { return { code: 1, message: e.message }; } };这个云函数的好处是小程序端不需要知道文件存在哪里、是什么格式只需要拿到一个干净的 HTML 字符串。这也正是三层架构的意义所在每一层只关心自己负责的那一部分。6. 运行结果与效果验证跑完上面的流程后我们需要验证整条链路是否真的通了。建议按下面的顺序排查。6.1 验证编辑端打开本地页面在 Vditor 编辑器里写一段包含标题、列表、代码块的 Markdown点击“保存到网盘”按钮在控制台确认输出的markdownContent和预期一致。预期结果控制台输出你刚才写的完整 Markdown 源文本。6.2 验证上传脚本运行上传脚本后登录坚果云网页端进入markdown-notes目录应该能看到刚刚上传的文件。预期结果状态码: 201 文件上传成功: 第一章.md如果返回 401说明 WebDAV 用户名密码错误或者服务商要求 HTTPS 而脚本没有启用。6.3 验证转换脚本运行转换脚本后用浏览器打开生成的 HTML 文件。检查三个点标题层级是否正确。代码块是否有背景色和关键字高亮。表格是否渲染正常。预期结果HTML 页面中可以正常看到完整的 Markdown 渲染内容和样式。6.4 验证小程序端在微信开发者工具中打开小程序项目传入一个已知的文档 ID观察页面是否渲染出 HTML 内容。点击页面里的图片、链接确认事件是否有响应。预期结果页面展示与浏览器预览效果一致代码块可以横向滚动图片不会溢出屏幕。如果小程序端白屏优先打开调试器的 Network 面板看云函数请求是否成功以及返回的htmlContent是否为空。7. 常见问题与排查方法实践这套方案时有几个问题出现频率最高我把它们整理成表格。问题现象可能原因排查方式解决方案上传文件返回 401WebDAV 密码不是登录密码而是应用密码检查服务商后台是否开启 WebDAV 并生成应用密码在坚果云安全选项中生成独立应用密码脚本上传成功但网页端看不到文件上传到了错误的路径打印请求的完整 URL与服务商目录对比修改WEBDAV_PATH为实际路径小程序端 HTML 内容能显示但代码块没有高亮转换时未配置 highlight.js或样式未引入检查生成 HTML 里pre code的 class 是否为hljs开头在 marked 配置中加入 highlight 函数rich-text无法渲染表格小程序基础库版本过低或表格标签不在白名单在开发者工具中查看渲染层日志升级基础库或使用 towxml 渲染方案图片在网盘上有但在小程序端无法显示图片链接是 HTTP或者有防盗链限制用浏览器直接打开图片链接测试将图片托管到对象存储并配置 CDN云函数返回超时数据库读取慢或云函数代码逻辑有误在云函数日志中查看执行时间和错误栈优化查询确保有索引必要时拆分接口这里要特别提醒一个容易被忽视的问题Markdown 转换为 HTML 后包含用户输入的原始 HTML 标签。如果内容来自不可信来源必须做 XSS 过滤。个人使用场景问题不大但如果你的小程序打算给更多人用一定要在转换管线和rich-text展示端加一层安全校验。8. 最佳实践与工程建议8.1 把“内容”和“展示”分离这是整套方案的核心理念。Markdown 源文件是最原始的内容资产它不依赖任何编辑器、任何存储服务、任何渲染方案。哪怕过几年小程序不再流行只要 Markdown 文件还在你的知识资产就没有丢失。所以存储端一定要有“源文件的独立备份”HTML 文件只是派生产物。8.2 使用规范的文件命名和目录结构建议用日期加主题的方式组织文件例如2024-01-15-typora-alternative.md 2024-01-20-wechat-miniprogram-markdown.md目录上可以按年份或专题分类markdown-notes/ ├── 2024/ │ ├── 2024-01-15-typora-alternative.md │ └── 2024-01-20-wechat-miniprogram-markdown.md ├── assets/ │ └── images/ └── output/ ├── 2024-01-15-typora-alternative.html └── 2024-01-20-wechat-miniprogram-markdown.html这样的好处是后续写自动化脚本时可以根据目录结构批量处理文件。8.3 发布流程中的权限控制如果你把 HTML 放到对象存储并通过域名公开访问要注意两点开启 CDN 后HTML 文件可能有缓存更新文档后需要刷新 CDN 缓存。私人内容不要放在公共读的桶里最好加访问鉴权或者使用临时链接。8.4 小程序端注意性能和缓存小程序里频繁拉取 HTML 字符串会消耗流量也会增加打开耗时。建议在本地缓存每个文档的 HTML存储到小程序的 Storage 中当文档版本变化时再重新拉去。可以在云函数返回结果里增加一个version字段小程序端对比版本号决定是否更新缓存。8.5 定时备份源文件WebDAV、对象存储、Git 仓库都不能保证 100% 不丢数据。建议至少有一个异地的冷备。最简单的做法是每个月把整个markdown-notes目录打包下载到本地或者多推一份到另一个存储服务。9. 一个更进阶的架构参考如果你不只是想做“个人笔记”而是想给团队搭建一套“多人写作 - 审核 - 发布”的知识库系统那么上面的最小方案需要做一些扩展编辑端接入 Git 或者相关 API让多人修改有记录可查。存储端把“源文件存储”和“制品存储”分开。源文件进 Git制品进对象存储。转换层从本地脚本升级为服务端函数当 Git 仓库有推送时自动触发构建转换完成后自动上传 CDN。阅读端除了小程序还可以生成普通的 Web 页面通过链接分享给只使用浏览器的读者。这个架构本质上和前端工程里的“开发环境 - 构建 - 部署”非常相似。Markdown 就是源代码HTML 就是构建产物网盘或对象存储就是部署环境小程序就是分发渠道。理解了这层关系你会发现“Typora 平替”这个话题的底层其实是内容生成与分发管线的设计问题。10. 总结与后续学习方向这篇文章讲清楚了几个核心点Typora 平替的核心不是替换一个编辑器而是重写一条“编辑 - 存储 - 阅读”的内容链路。Markdown 编辑器选型的判断标准不只看编辑体验还要看能否和后续的存储、发布打通。WebDAV 是目前个人场景下最轻量、最容易落地的存储同步方案。小程序阅读端的核心是 Markdown 渲染管线和 HTML 的安全展示。三层架构的每一层都可以独立替换不要把自己绑定在单一工具或单一服务上。如果你现在还是用 Typora 或某个本地编辑器写 Markdown建议下一步做这样一件事把最近一个月写的文档整理成规范目录导入到本地脚本里跑通“本地文件 - WebDAV - HTML 转换”这一步。先不用急着做小程序端等存储链路稳定了再花一个周末把小程序阅读端加上。到这一步你已经不再依赖任何一个商业软件的“平替”而是拥有了一套完全可控的知识管理基础设施。这比纠结于某个编辑器是否免费要有意义得多。
返回列表