无忧SEO博客
AI 技术与流量运营

用 Electron + Express 周末做了个本地 Markdown 桌面笔记应用

用 Electron + Express 一周末做了个本地 Markdown 桌面笔记应用

记录从零到打包发布的完整过程,以及一些踩过的坑。

最近整理笔记的需求变得越来越强烈——Notion 网速偶尔抽风、Obsidian 功能强大但插件生态需要学习曲线、Typora 又不能管理目录树。于是决定自己动手做一个刚刚好的工具:简单笔记 SimpleNote

简单笔记 SimpleNote-2

目标

几条核心原则,从一开始就确定:

  1. 纯本地,不上云:文件完全在自己硬盘,永远不依赖服务器
  2. 文件夹即工作区:任意本地目录即可成为知识库,原始文件格式(.md)不锁定
  3. 轻量,不过度设计:能用标准 Node.js 完成的,绝不引入复杂框架

技术选型

层次 技术
桌面容器 Electron 28
后端服务 Express 4(Node.js)
前端渲染 原生 HTML / CSS / JS
Markdown 解析 marked.js
代码高亮 highlight.js
打包工具 electron-builder

为什么选 Electron + Express?

这个组合看起来”重”,但对于这个场景其实是最简单的:

  • 所有文件读写操作(fs 模块)放在 Express 里,通过 REST API 暴露给前端
  • Electron 只做一件事:把 Express 服务在后台跑起来,再打开一个 BrowserWindow 加载它
  • 前端无需任何框架,纯 JS 调用 fetch 即可

这意味着整个架构对任何写过 Node.js 的人来说都非常透明,没有黑盒。

简单笔记 SimpleNote

架构设计

main.js(Electron 主进程)
    └─ startServer()  ← 来自 server.js
    └─ BrowserWindow.loadURL("http://localhost:{port}")

server.js(Express API 服务)
    ├─ GET  /api/tree       — 获取目录树
    ├─ GET  /api/file       — 读取文件内容
    ├─ POST /api/file       — 新建文件
    ├─ PUT  /api/file       — 保存文件
    ├─ DELETE /api/file     — 删除文件
    ├─ POST /api/folder     — 新建目录
    ├─ DELETE /api/folder   — 删除目录
    ├─ POST /api/rename     — 重命名
    ├─ GET  /api/search     — 全文搜索
    ├─ GET  /api/browse     — 浏览本地文件系统(工作区切换器)
    └─ POST /api/config/root — 切换工作区目录

public/
    ├─ index.html
    └─ js/
       ├─ app.js      — 全局状态、事件绑定、Modal、Toast
       ├─ sidebar.js  — 文件树渲染、右键菜单
       ├─ editor.js   — Markdown 编辑器、格式工具栏
       └─ search.js   — 全文搜索 Spotlight 面板

关键实现:动态端口

Electron 应用和普通 Web 不同,端口 3000 完全可能被系统其他服务占用。解决方案是让 Express 监听 0(由操作系统分配空闲端口),再通过 server.address().port 拿到实际端口:

// server.js
function startServer(port = 3000) {
  return new Promise((resolve) => {
    const server = app.listen(port, () => {
      resolve(server.address().port);
    });
  });
}

// Electron 通过 require.main === module 判断是否直接运行,
// 避免 Electron 加载时也执行 listen(3000)
if (require.main === module) startServer(3000);
module.exports = { startServer };
// main.js
const { startServer } = require('./server');

async function createWindow() {
  const port = await startServer(0); // 0 = 随机空闲端口
  mainWindow.loadURL(`http://localhost:${port}`);
}

功能亮点

格式化工具栏

在编辑器的 textarea 上方放了一排格式按钮,点击后通过 selectionStart / selectionEnd 在光标位置精确插入 Markdown 语法,并自动选中占位文字:

insertFormat(type) {
  const start = textarea.selectionStart;
  const end = textarea.selectionEnd;
  const selectedText = textarea.value.substring(start, end);

  const prefix = '**', suffix = '**';
  const innerText = selectedText || '加粗文本';
  const replacement = prefix + innerText + suffix;

  textarea.value = text.substring(0, start) + replacement + text.substring(end);
  textarea.setSelectionRange(start + prefix.length, start + prefix.length + innerText.length);
}

本地文件系统浏览器

工作区切换器需要让用户在客户端内浏览本地磁盘目录,不能直接用浏览器的 <input type="file"> webkitdirectory(限制太多)。所以在后端提供了 /api/browse 接口:

  • 无参数→返回所有可用盘符(Windows 枚举 A-Z)
  • ?dir=<path> 参数→返回该目录下的子目录列表

前端做成双击进入、单击选中的交互,体验接近系统原生的文件选择对话框。

侧边栏拖拽的一个坑

拖拽调整侧边栏宽度时,CSS 的 transition 会产生明显的延迟感(拖动不跟手)。解决方法很简单:拖拽开始时临时禁用 transition,松开后恢复:

handle.addEventListener('mousedown', () => {
  sidebar.style.transition = 'none'; // 拖拽时禁动画
});
document.addEventListener('mouseup', () => {
  sidebar.style.transition = ''; // 松开后恢复
});

打包

使用 electron-builder,先用 electron-icon-maker 将设计好的 1024×1024 PNG 图标转换为 ICO / ICNS 格式:

npx electron-icon-maker --input=build/icon.png --output=build

然后在 package.json 配置图标路径:

"build": {
  "appId": "com.simplenote.app",
  "productName": "简单笔记 SimpleNote",
  "win": {
    "icon": "build/icons/win/icon.ico",
    "target": ["nsis", "portable"]
  }
}

运行 npm run dist 后,输出目录将包含安装包(.exe)和绿色便携版,可直接分发。

总结

整个项目代码量不大(后端约 420 行、前端共约 1000 行),但日常使用完全够用。设计上坚持「能不引入的依赖就不引入」,所有文件操作直接使用 Node.js 内置 fs 模块,无编译步骤,修改即生效。

免费下载地址:https://wuyouge.lanzout.com/ibZjw3lidpne

如果你也对”刚刚好”的工具感兴趣,不妨自己动手试一试——从 Express 到 Electron 的距离,比你想象的要近很多。

更多AI编程开发技巧,请关注本站,想与作者交流学习,可➕微信:wuyouseo

 

赞(0)
未经允许不得转载:无忧SEO » 用 Electron + Express 周末做了个本地 Markdown 桌面笔记应用