开发一个基于 Node.js 和 mermaid.js 的 Web 服务器程序,实现在线编辑 Markdown 文档并支持 mermaid.js 流程图渲染的功能。我会为你搭建一个完整、可直接运行的服务端程序,包含核心的 Web 服务、Markdown 解析和 Mermaid 渲染能力。

实现思路

  1. 使用 Express 作为基础 Web 框架搭建服务器
  2. 使用 marked 解析 Markdown 内容,并配置 Mermaid 扩展
  3. 使用 mermaid 实现流程图的渲染
  4. 提供一个简洁的前端编辑界面,支持实时预览
  5. 实现基本的文件保存/加载功能(本地文件存储)

完整实现代码

1. 项目初始化与依赖安装

首先创建项目并安装所需依赖:

创建项目目录

mkdir md-edit-app
cd md-edit-app

初始化 npm 项目

npm init -y

安装核心依赖

npm install express@4 marked@12
npm install cors body-parser fs-extra ejs

我用的 node.js 版本: 18.20 , 搭配的版本是

npm install express@4.18.3 marked@12.0.2
npm install cors@2.8.5 body-parser@1.20.2 fs-extra@11.2.0 ejs@3.1.9

2. 服务器主程序 (app.js)
const express = require('express');
const marked = require('marked');
// 移除 const mermaid = require('mermaid');
const cors = require('cors');
const bodyParser = require('body-parser');
const fs = require('fs-extra');
const path = require('path');

// 初始化 Express 应用
const app = express();

// 移除 mermaid 初始化代码 ↓
// mermaid.initialize({
//   startOnLoad: false,
//   theme: 'default',
//   securityLevel: 'loose' // 允许更多的渲染选项
// });

// 配置 Marked 解析器,添加 Mermaid 支持
const renderer = new marked.Renderer();

// 重写代码块渲染逻辑,识别 mermaid 代码块
renderer.code = (code, language) => {
  if (language === 'mermaid') {
    // 为 Mermaid 代码块生成容器(仅保留代码,渲染交给前端)
    return `<div class="mermaid">${code}</div>`;
  }
  // 其他代码块使用默认渲染
  return `<pre><code class="language-${language}">${code}</code></pre>`;
};

// 配置 marked
marked.setOptions({
  renderer: renderer,
  highlight: true,
  breaks: true,
  gfm: true // 启用 GitHub 风格的 Markdown
});

// 中间件配置
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

// 设置模板引擎
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

// 确保保存文件的目录存在
const DOCS_DIR = path.join(__dirname, 'docs');
fs.ensureDirSync(DOCS_DIR);

// 路由配置
// 首页 - 编辑器界面
app.get('/', (req, res) => {
  res.render('editor', { title: 'Markdown 在线编辑器 (支持Mermaid)' });
});

// 解析 Markdown 为 HTML (API)
app.post('/api/parse', (req, res) => {
  try {
    const { markdown } = req.body;
    if (!markdown) {
      return res.status(400).json({ error: 'Markdown 内容不能为空' });
    }
    // 解析 Markdown 为 HTML
    const html = marked.parse(markdown);
    res.json({ html });
  } catch (error) {
    res.status(500).json({ error: '解析 Markdown 失败: ' + error.message });
  }
});

// 保存文档 (API)
app.post('/api/save', (req, res) => {
  try {
    const { filename, content } = req.body;
    if (!filename || !content) {
      return res.status(400).json({ error: '文件名和内容不能为空' });
    }
    // 拼接文件路径
    const filePath = path.join(DOCS_DIR, `${filename}.md`);
    // 写入文件
    fs.writeFileSync(filePath, content, 'utf8');
    res.json({ success: true, message: '文件保存成功', filePath });
  } catch (error) {
    res.status(500).json({ error: '保存文件失败: ' + error.message });
  }
});

// 加载文档 (API)
app.get('/api/load/:filename', (req, res) => {
  try {
    const { filename } = req.params;
    const filePath = path.join(DOCS_DIR, `${filename}.md`);
    // 检查文件是否存在
    if (!fs.existsSync(filePath)) {
      return res.status(404).json({ error: '文件不存在' });
    }
    // 读取文件内容
    const content = fs.readFileSync(filePath, 'utf8');
    res.json({ success: true, content });
  } catch (error) {
    res.status(500).json({ error: '加载文件失败: ' + error.message });
  }
});

// 获取文档列表 (API)
app.get('/api/docs', (req, res) => {
  try {
    // 读取目录下所有 .md 文件
    const files = fs.readdirSync(DOCS_DIR)
      .filter(file => path.extname(file) === '.md')
      .map(file => ({
        name: path.basename(file, '.md'),
        path: file
      }));
    res.json({ success: true, docs: files });
  } catch (error) {
    res.status(500).json({ error: '获取文档列表失败: ' + error.message });
  }
});

// 启动服务器
const PORT = 8000;
app.listen(PORT, () => {
  console.log(`服务器运行在: http://localhost:${PORT}`);
  console.log(`文档保存目录: ${DOCS_DIR}`);
});
3. 前端编辑器页面 (views/editor.ejs)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><%= title %></title>
  <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
  <style>
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      display: flex;
      flex-direction: column;
      height: 100vh;
    }
    .header {
      background: #2c3e50;
      color: white;
      padding: 1rem;
      text-align: center;
    }
    .container {
      display: flex;
      flex: 1;
      overflow: hidden;
    }
    .editor-container, .preview-container {
      flex: 1;
      padding: 1rem;
      overflow: hidden;
      border: 1px solid #ddd;
    }
    textarea {
      width: 100%;
      height: 100%;
      padding: 1rem;
      font-family: 'Consolas', 'Monaco', monospace;
      font-size: 14px;
      border: none;
      outline: none;
      resize: none;
    }
    .preview {
      width: 100%;
      height: 100%;
      overflow-y: auto;
      padding: 1rem;
      background: #f9f9f9;
    }
    .controls {
      padding: 1rem;
      background: #f1f1f1;
      display: flex;
      gap: 1rem;
    }
    button {
      padding: 0.5rem 1rem;
      background: #3498db;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    button:hover {
      background: #2980b9;
    }
    .file-input {
      padding: 0.5rem;
    }
    pre {
      background: #f4f4f4;
      padding: 1rem;
      border-radius: 4px;
      overflow-x: auto;
    }
    code {
      font-family: 'Consolas', 'Monaco', monospace;
    }
  </style>
</head>
<body>
  <div class="header">
    <h1>Markdown 在线编辑器 (支持 Mermaid 流程图)</h1>
  </div>
  
  <div class="controls">
    <input type="text" id="filename" placeholder="输入文件名(无需.md)" value="demo">
    <button id="saveBtn">保存文档</button>
    <button id="loadBtn">加载文档</button>
    <button id="refreshBtn">刷新预览</button>
  </div>
  
  <div class="container">
    <div class="editor-container">
      <textarea id="editor" placeholder="请输入 Markdown 内容...">
## Markdown 编辑器 (支持 Mermaid)

### 功能说明
- 支持标准 Markdown 语法
- 支持 Mermaid 流程图渲染
- 支持文档保存/加载

### Mermaid 示例
```mermaid
graph TD
    A[开始] --> B{选择操作}
    B -->|编辑| C[Markdown 编辑]
    B -->|预览| D[实时预览]
    C --> E[保存文档]
    D --> E
    E --> F[结束]

普通代码示例

  console.log('Hello, Markdown!');
  </textarea>
</div>
<div class="preview-container">
  <div id="preview" class="preview"></div>
</div>
```

运行与使用说明

  1. 创建目录结构
 md-edit-app/
   ├── app.js          // 服务器主程序
   ├── views/          // 模板目录
   │   └── editor.ejs  // 编辑器页面
   ├── public/         // 静态资源目录 (空即可)
   └── docs/      // 文档保存目录 (自动创建)
  1. 启动服务器
 node app.js
  1. 访问编辑器
    打开浏览器访问 http://localhost:8000 即可看到 Markdown 编辑器界面

  2. 功能使用

    • 在左侧编辑框输入 Markdown 内容(包含 Mermaid 代码块)
    • 点击「刷新预览」查看右侧渲染效果
    • 输入文件名后,点击「保存文档」可将内容保存到服务器
    • 输入已保存的文件名,点击「加载文档」可加载保存的内容

总结

  1. 核心功能:该程序基于 Express 搭建 Web 服务器,使用 marked 解析 Markdown,集成 mermaid.js 实现流程图渲染,提供了完整的在线编辑、预览、保存、加载功能。
  2. 技术栈:Node.js + Express + marked (Markdown解析) + mermaid.js (流程图渲染) + EJS (模板引擎)。
  3. 扩展建议:可进一步添加用户认证、多人协作、云存储、更多 Markdown 扩展(如表格、公式)等功能,也可优化前端界面使其更美观易用。

这个程序开箱即用,你可以直接复制代码运行,体验完整的 Markdown 在线编辑和 Mermaid 渲染功能。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐