MarkText 开发者指南:Electron + Vue 3 所见即所得 Markdown 编辑器的工程架构与开发工作流 📅 发布时间:2026/9/18 10:26:02 👁 浏览次数: MarkText 开发者指南Electron Vue 3 所见即所得 Markdown 编辑器的工程架构与开发工作流【免费下载链接】marktextA simple and elegant markdown editor, available for Linux, macOS and Windows.项目地址: https://gitcode.com/gh_mirrors/ma/marktext本文是 MarkText 仓库根目录CLAUDE.md的深度开发者指南它面向打算参与该开源项目、或希望理解其内部工程的开发者完整讲解 pnpm monorepo 的布局、三进程 Electron 架构、IPC 类型化约定、开发/构建/测试命令与代码规范。读完本文你将掌握从pnpm install到build:linux的完整工程链路理解 renderer 沙箱化后所有 Node 能力如何经 preload 桥接并能独立完成单元测试、E2E 测试与类型检查的本地复现。项目概览MarkText 是什么MarkText 是一款WYSIWYG所见即所得Markdown 编辑器构建在 Electron Vue 3 之上支持 Linux、macOS 与 Windows 三大桌面平台。它的核心能力包括CommonMark 与 GitHub Flavored MarkdownGFM语法支持数学公式渲染KaTeX、Mermaid 图表与PlantUML 图多种编辑模式聚焦模式focus mode、打字机模式typewriter mode与源码模式source-code mode。当前仓库版本为0.20.0-dev见 package.json以 MIT 协议开源。仓库采用 pnpm workspace 组织为 monorepo根目录只承载共享工具链与面向 CI 的脚本三个真实包desktop、muya、website全部位于packages/之下。技术栈总览下表来自CLAUDE.md的 Tech Stack 章节并经仓库package.json交叉核对分层技术语言TypeScript 5.9严格模式packages/muyajs/作为 JS 保留通过 ambient shim 兼容桌面壳Electron 42构建系统electron-vite 5打包electron-builder 26前端框架Vue 3状态管理Pinia 3路由Vue Router 4UI 组件库Element Plus单元测试Vitest 4E2E 测试Playwright包管理器pnpm ≥ 10 workspacepackageManager: pnpm10.33.4仓库布局pnpm monorepoNode.js 最低版本≥ 20.19.0PR CI 使用 Node 22.21.1release CI 使用 Node 24.14.1根目录 package.json 的engines字段确认了node 20.19.0、pnpm 10的硬性要求pnpm字段还声明了两条关键依赖覆盖overridespostcss固定为8.5.15esbuild0.27.0 0.28.1统一到0.28.1用于规避构建期的不兼容问题。值得注意的历史演进packages/muyajs/marktext/muyajs是早期遗留的 JS 版编辑器内核而packages/muya/muyajs/core是其 TypeScript 重写版基于ot-json1、ot-text-unicode、snabbdom、marked16与rxjs构建。当前 desktop 的 renderer 已切换到muyajs/core作为编辑器引擎遗留的muyajs正在退役详见 packages/muya/CLAUDE.md。目录结构pnpm monorepo 解析仓库采用 pnpm workspacepnpm-workspace.yaml声明packages/*并额外列出packages/muya/examples与packages/muya/e2e两个嵌套 workspace。pnpm-lock.yaml单一锁文件贯穿所有包根目录的 ESLint v9 flat config 同时覆盖 desktop 与 muyajswebsite 使用独立的 ESLint v8 配置并被根配置忽略。repo-root/ package.json Workspace 编排者——每个面向 CI 的脚本通过 pnpm --filter marktext ... 代理到 packages/desktop pnpm-workspace.yaml packages: [packages/*] 加 allowBuilds pnpm-lock.yaml 单一锁文件所有包共享 eslint.config.js 根 ESLint v9 flat config覆盖 desktop muyajs scripts/ workspace 级脚本postinstall.ts、minify-locales.ts、 generateThirdPartyLicense.ts、validateLicenses.ts、 thirdPartyChecker.ts均内部指向 packages/desktop docs/ 长文开发者文档 dist/ electron-builder 输出的安装包git-ignored directories.output: ../../distCI 产物 glob dist/* packages/ desktop/ Electron 应用name: marktext muyajs/ 遗留 Markdown 编辑内核name: marktext/muyajs muya/ muya 的 TypeScript 重写name: muyajs/core website/ marktext-websiteVite React 18独立工具链根目录已经不再拥有自己的src/、test/、static/、build/它们全部迁移到了packages/desktop/。packages/desktop应用的唯一载体packages/desktop/持有全部 Electron / Vue / 构建期依赖以及 dev/build/test/typecheck 脚本它通过workspace:*依赖marktext/muyajs与muyajs/core。其内部结构的关键目录src/common/—— 可从 main、preload、renderer 三端共用的纯 Node.js 工具src/main/—— Electron 主进程IO、原生对话框、窗口管理、自动更新src/preload/—— Electron preload 脚本是 renderer 访问 Node 能力的唯一桥梁src/renderer/—— Vue 3 应用编辑器 UI、Pinia stores其下components/为 Vue 单文件组件store/为 Pinia storeseditor.ts、preferences.ts、layout.ts 等pages/与router/承载顶层页面与路由src/shared/—— 跨进程类型shared/types/与 IPC 契约shared/types/ipc.tssrc/types/—— ambient.d.ts声明test/unit/与test/e2e/—— Vitest 与 Playwright 测试electron.vite.config.ts、electron-builder.yml、vitest.config.ts、tsconfig.json/tsconfig.base.jsonpatches/—— pnpm 补丁由 patch-package 消费如native-keymap3.3.9.patchstatic/—— 打包进应用的静态资源图标、主题、locales/*.json语言包。开发工作流从安装到热重载所有命令从仓库根目录执行。根package.json将每个 desktop 专属脚本通过pnpm --filter marktext代理到packages/desktop因此命令名与 monorepo 化之前完全一致。安装依赖pnpm install安装会自动运行scripts/postinstall.ts依次完成打native-keymap补丁、下载 Electron、重建原生模块、压缩语言包minify locales。注意根目录pnpm-workspace.yaml的allowBuilds白名单ced、electron-winstaller、esbuild、keytar、native-keymap、sharp、unrs-resolver、workerd决定了哪些依赖允许执行安装脚本。开发模式与热重载pnpm run devrenderer 通过 Vite HMR 自动热重载在 dev 窗口按CtrlR可重载 renderer会重新执行 preload 脚本主进程main/目录的改动不会被窗口重载捕获需要重启pnpm run dev才能生效。预览构建产物pnpm run start预览最近一次 electron-vite 构建不重新构建会自动设置PERF_TESTINGtrue。快速构建验证pnpm run build:unpack不打包安装包只做 renderer/main 编译的快速验证路径。该命令内部先运行tsx ../../scripts/minify-locales.ts再执行electron-vite build见 packages/desktop/package.json。格式化与语言包pnpm run format # 用 Prettier 全仓库自动格式化与只检查的 lint 不同 pnpm run minify-locales # 压缩语言包生产构建必需dev 可跳过性能调试pnpm run perf:inspect # 对预览构建暴露 :5858 端口的 Node inspector就绪后附加 pnpm run perf:inspect-brk # 在第一行断点处暂停对应脚本为cross-env PERF_TESTINGtrue electron-vite preview -- --inspect5858详见 packages/desktop/package.json。网站开发暂未接入 CIpnpm --filter marktext-website dev # Vite dev server pnpm --filter marktext-website build # 静态构建 → packages/website/build/如需直接在某个包内调用脚本用pnpm --filter name script或pnpm -C packages/name script。构建与打包三平台产物pnpm run build:win # Windows x64 —— NSIS 安装器 zip pnpm run build:mac # macOS x64 arm64 —— DMG zip pnpm run build:linux # Linux —— AppImage、snap、deb、rpm、tar.gz所有平台构建脚本都会在打包前自动执行minify-locales与electron-rebuild。以 packages/desktop/package.json 中的build:linux为例其完整链路为tsx ../../scripts/minify-locales.ts electron-rebuild electron-vite build electron-builder --linux --publish never打包细节由 packages/desktop/electron-builder.yml 控制值得关注的配置项appIdcom.github.marktext.marktextproductName 为marktext输出目录directories.output: ../../dist安装包落在仓库根dist/CI 产物 glob 即指向此处electron-vite 的out/仍保留在packages/desktop/内文件关联声明了md、markdown、mmd、mdown、mdtxt、mdtext六种 Markdown 扩展名MIME 类型为text/markdownWindows 下使用icons/md.ico图标WindowsNSIS zip 双目标NSIS 配置oneClick: false、allowToChangeInstallationDirectory: true、创建桌面与开始菜单快捷方式并引入windows/installer.nshmacOSDMG zip 双目标notarize: false通过extendInfo声明相机与 Documents/Downloads 文件夹使用说明继承 entitlementsbuild/mac/entitlements.mac.plistLinuxAppImage / snap / deb / rpm / tar.gz 五目标桌面分类Office;TextEditor;UtilityMIME 类型text/markdown语言electronLanguages: [en-US]避免打包全部 Chromium 语言资源应用自带内置语言瘦身通过files/asarUnpack/npmRebuild: false排除源码、map 文件、mermaid/katex 的冗余大文件与 Linux 构建中间产物。测试体系单元测试与 E2Epnpm run test # 全部单元测试Vitest pnpm run test:unit # 仅单元测试 pnpm run test:e2e # 端到端测试Playwright pnpm run lint # ESLint提交前运行CI 强制 pnpm run typecheck # vue-tsc --noEmitCI 强制运行单个测试文件时注意路径相对于packages/desktop用-C让 pnpm 在 desktop 包的 vitest 配置内解析 spec 路径pnpm -C packages/desktop exec vitest run test/unit/specs/markdown-basic.spec.ts pnpm -C packages/desktop exec vitest run -t partial test namePlaywright 配置位于test/e2e/playwright.config.tspnpm -C packages/desktop exec playwright test test/e2e/launch.spec.ts pnpm -C packages/desktop exec playwright test -g partial test name仓库实际测试资产印证了这套体系的分层packages/desktop/test/unit/specs/下覆盖编码encoding.spec.ts、主题theme-background-color.spec.ts、拼写检查spellchecker-switch-language.spec.ts、图片上传upload-image.spec.ts、PDF 导出pdf.spec.ts等 50 余个单元测试packages/desktop/test/e2e/下则包含启动launch.spec.ts、标签页tabs.spec.ts、XSS 安全xss.spec.ts、源码模式source-mode-*.spec.ts等 60 余个 Playwright 场景。此外packages/muya/test/spec/维护 CommonMark 0.31 与 GFM 0.29-gfm 的一致性测试套件通过expected-failures.json锁定通过率基线当前基线见test/spec/conformance.md只允许合规率单向上升。代码风格与工程规范代码风格由 ESLint Prettier 强制提交前需通过pnpm run lint与pnpm run typecheck。核心约定2 空格缩进不写分号使用单引号TypeScript 开启strict: true详见 packages/website/content/docs/dev/TYPESCRIPT.md跨进程类型位于packages/desktop/src/shared/types/ambient 声明位于packages/desktop/src/types/IPC 通道经由packages/desktop/src/shared/types/ipc.ts中的契约做类型化renderer 完全沙箱化——所有 IPC 与 Node 访问都通过window.electron.*/window.fileUtils.*等类型定义于packages/desktop/src/types/global.d.ts。注释规范要求注释必须描述代码中不明显的内容——理由、单位、不变量、归属、调用方需要的抽象绝不复述代码或名字里已有的词优先用自解释的命名替代注释。muya 包内部另有更严格的 lint 约定antfu 配置接口名必须以I[A-Z0-9]开头、私有成员必须加_前缀、圈复杂度 ≤ 20、单函数 ≤ 200 行、禁止value as unknown as X双重强转并通过 madge 在 CI 中强制无循环依赖pnpm -C packages/muya check-circular。架构三进程 Electron 模型MarkText 的所有 Electron 进程都位于packages/desktop/编辑器引擎 muya 作为独立 workspace 包被 renderer 与测试消费。main process (packages/desktop/src/main/) ├── 完整的 Node.js Electron API 访问 ├── IO、文件系统、原生对话框、自动更新、拼写检查 ├── 每次应用启动只有一个实例 └── 通过 IPC 控制编辑器窗口 preload (packages/desktop/src/preload/) ├── main 与 renderer 之间的桥 └── 编译为 CommonJS renderer (packages/desktop/src/renderer/) ├── 每个编辑器窗口一个进程由 main 派生 ├── Vue 3 Pinia —— 全部 UI 状态与编辑器交互 ├── 同时承载 MuyaWYSIWYG与 CodeMirror源码模式 └── 只编译为 ES Modules Muya (packages/muya/即 muyajs/core) ├── 自包含的编辑器内核 ├── 尽量不依赖 Electron API ├── 负责 Markdown 解析、块数据结构、文档导出与渲染 └── 遗留的 packages/muyajs/marktext/muyajs正在被其取代主进程入口 packages/desktop/src/main/index.ts 展示了启动链路先初始化异常处理与 electron-log 日志renderer 日志按窗口 id 写入renderer-id.log、启动本地 crashReporter、响应--disable-gpu参数禁用硬件加速、通过requestSingleInstanceLock保证单实例macOS 与开发环境除外随后注册沙箱安全的 IPC 处理器、设置 Windows AppUserModelID 并实例化应用控制器。窗口层由 packages/desktop/src/main/windows/base.ts 定义窗口类型分为BASE不应直接创建、EDITOR、SETTINGS三类生命周期分为NONE → LOADING → READY → QUITTED_buildUrlWithSettings会把用户偏好代码字体、字号、主题、标题栏样式等编码进 renderer URL 的 query 参数并以当前主题背景色预绘制窗口以获得最快的 ready 时间修复了 #3957 的白色闪烁。沙箱与安全边界CLAUDE.md指出 renderer 运行在沙箱化环境中contextIsolation: true、nodeIntegration: false、sandbox: true自 #4244 起。这一结论被两处源码印证packages/desktop/src/preload/index.ts 开头注释明确Sandboxed preload: onlyelectroncan be requiredpackages/desktop/electron.vite.config.ts renderer 段注明 The renderer runs in a sandboxed Chromium context (contextIsolation: true, nodeIntegration: false, sandbox: true). All Node access must go through the preload → IPC bridge。因此所有 Node 能力都经由 preload 的类型化 contextBridge 面暴露window.electron.*窗口控制、shell、剪贴板、webFrame、window.fileUtils.*文件系统操作的异步封装、window.path.*基于pathe的跨平台路径 API、window.ripgrep.*、window.i18nUtils.*、window.uploader.*、window.fonts.*等。preload 还会在启动时通过一次同步的mt::boot-info握手把平台、环境变量、资源路径等注入bootInfo使 Vue 的 computed 属性无需await即可同步读取同时暴露一个最小化的processshim含nextTick映射到微任务队列避免第三方包在模块加载期读取process.platform时抛错。构建期还会把global替换为globalThis规避部分依赖如 dragula 经 custom-event在沙箱 renderer 中引用 Node 全局导致崩溃的问题。IPC 约定类型化通道契约大部分 main 与 renderer 之间的 IPC 通道使用mt::前缀例如mt::open-new-tab、mt::file-saved少数内部通道不遵循该约定如language-changed。完整通道目录与约定见 packages/website/content/docs/dev/IPC.md。packages/desktop/src/shared/types/ipc.ts 是 IPC 通道契约的单一事实来源把通道划分为四类IpcInvokeChannelsrenderer → main返回PromiseT如mt::fs::read-file、mt::shell::open-external、mt::fonts::listIpcSendChannelsrenderer → mainfire-and-forget如mt::close-window、mt::check-for-update、mt::clipboard::write-textIpcSyncChannelsrenderer → main同步如启动握手的mt::boot-infoIpcMainEventChannelsmain → renderer推送事件renderer 通过.on订阅如 ripgrep 的mt::rg::match/mt::rg::progress/mt::rg::done。preload 中invoke/send/on等封装都带泛型约束通道名、参数元组与返回类型在调用点即被检查新注册通道需要在此契约、main 的 handler 与 preload 桥三处同时落地。从契约可见 renderer 通过mt::fs::*获得完整的文件系统能力读写、移动、删除、stat、路径判断通过mt::spellchecker-*获得拼写检查通过mt::uploader::upload上传图片通过mt::rg::start启动全文搜索。重要构建注意事项CLAUDE.md的 Important Build Notes 章节汇集了最容易踩坑的工程细节CommonJS vs ESMmain与preload编译为 CommonJSrenderer仅 ESM——renderer 中禁止使用require()。由于 preload 沙箱化后只能require(electron)ESM-only 的pathe必须内联进 preload 产物同理主进程中 ESM-only 的plist与electron-store也通过externalizeDeps.exclude内联否则require(plist)会在启动时抛ERR_PACKAGE_PATH_NOT_EXPORTED语言包压缩生产构建前必须执行pnpm run minify-locales它已包含在build:win/mac/linux中但不包含在dev中原生模块更换 Electron 版本后需执行pnpm run rebuild-native即electron-rebuild -felectron-builder 输出directories.output指向../../dist安装包落在仓库根dist/CI 产物 glob 位置electron-vite 的out/留在packages/desktop/内路径别名定义于 packages/desktop/electron.vite.config.ts并在vitest.config.ts与tsconfig.base.json中镜像→packages/desktop/src/renderer/srccommon→packages/desktop/src/commonshared→packages/desktop/src/sharedmuya→../muyajs即packages/muyajsrenderer 侧 import 形如muya/lib/...workspace 依赖marktext/muyajs在packages/desktop/package.json声明保证模块解析留在 workspace 内renderer 额外将path别名到pathe使common/*与 muya 保留import path from path语句的同时不引入 Node 的 path 模块pathe恒用/分隔符且正确处理 Windows 盘符Workspace 依赖muya 自身的 npm 运行时依赖github-markdown-css、katex、dompurify、snabbdom等声明在packages/muyajs/package.json使 Node 模块解析在 workspace 内完成补丁patch-package补丁位于packages/desktop/patches/根 postinstall 以cwdpackages/desktop调用 patch-package 以保证路径正确。贡献指南PR 提交到develop分支而非main在 PR 描述中关联相关 issue提交前运行pnpm run lint所有 PR 合并前必须通过 CI完整贡献指南见.github/CONTRIBUTING.md。延伸阅读packages/website/content/docs/dev/存放本文档引用的更深层开发者文档同样发布为 marktext.me 的开发者文档区ARCHITECTURE.md —— 进程/模块分层细节BUILD.md —— 各平台完整构建前置条件DEBUGGING.md —— 主进程/renderer 调试器附加方法INTERFACE.md —— Muya 与 renderer 的公开接口IPC.md —— 完整 IPC 通道目录与mt::约定LINUX_DEV.md —— Linux 专属开发环境搭建PERFORMANCE.md —— 性能测量工作流与pnpm run perf:inspect配合RELEASE.md 与 RELEASE_HOTFIX.md —— 发布流程若以git clone方式获取仓库克隆后按本文开发工作流一节执行pnpm install与pnpm run dev即可进入开发循环。【免费下载链接】marktextA simple and elegant markdown editor, available for Linux, macOS and Windows.项目地址: https://gitcode.com/gh_mirrors/ma/marktext创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考