nodejs v18.20 如何使用 express markdown-it 和 mermaid.min.js 10.9

推荐先阅读:MarkDown(mermaid)绘制流程图详细教程

在 Node.js 18.20 环境下,结合 Express 框架、markdown-it 解析器和 mermaid 10.9 版本来实现 Markdown 中 Mermaid 图表的渲染,提供一个完整可运行的示例方案。

实现思路

  1. 搭建基础的 Express 服务
  2. 配置 markdown-it,并集成 mermaid 10.9 插件解析 Mermaid 语法
  3. 提供一个示例接口/页面,展示 Markdown 转 HTML(包含 Mermaid 图表)的效果
  4. 确保前端能加载 Mermaid 渲染脚本,完成图表的最终渲染

完整代码实现

1. 初始化项目并安装依赖
bash 复制代码
mkdir my-app
cd my-app
# 初始化 package.json(按需填写)
npm init -y

# 安装所需依赖
npm install express@4 markdown-it@13 
2. 核心代码文件(app.js)
javascript 复制代码
// app.js
const express = require('express');
const markdownIt = require('markdown-it');
const fs = require('fs');
const path = require('path');

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

// 配置静态文件目录
app.use(express.static(path.join(__dirname, 'public')));

// 配置 markdown-it(不受 mermaid 模块格式影响)
const md = markdownIt({
  html: true,
  linkify: true,
  typographer: true
});

// 注册 mermaid 解析插件(仅处理代码块,不依赖 mermaid 实例)
md.use(function(md) {
  const fence = md.renderer.rules.fence;
  md.renderer.rules.fence = function(tokens, idx, options, env, self) {
    const token = tokens[idx];
    if (token.info.trim() === 'mermaid') {
      const code = token.content.trim();
      // 仅生成容器标签,前端会负责渲染,后端无需调用 mermaid 核心方法
      return `<div class="mermaid">${code}</div>`;
    }
    return fence(tokens, idx, options, env, self);
  };
});

// 读取静态 HTML 文件(缓存)
const indexHtml = fs.readFileSync(path.join(__dirname, 'public', 'index.htm'), 'utf8');

// 处理渲染请求(异步函数)
app.get('/render-md', async (req, res) => {
  try {
    // 1. 读取 example.md 文件
    const mdPath = path.join(__dirname, 'public', 'example.md');
    const testMd = fs.readFileSync(mdPath, 'utf8');

    // 2. 解析 Markdown 为 HTML(核心逻辑,无需后端 mermaid 实例)
    const renderedHtml = md.render(testMd);

    // 3. 注入到 index.html 并返回
    const finalHtml = indexHtml.replace('<div id="md-content"></div>', renderedHtml);
    res.send(finalHtml);

  } catch (error) {
    console.error('处理 Markdown 时出错:', error);
    res.status(500).send(`
      <h1>出错了</h1>
      <p>无法读取或解析 example.md 文件:${error.message}</p>
    `);
  }
});

// 启动服务
const port = 8000;
app.listen(port, () => {
  console.log(`server start:http://localhost:${port}/render-md`);
});
mkdir public; cd public
3. public/index.htm
html 复制代码
<!-- public/index.htm -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Mermaid 测试</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        .mermaid { margin: 20px 0; }
        pre { background: #f5f5f5; padding: 10px; border-radius: 5px; }
    </style>
    <!-- 引入 mermaid 10.9 前端脚本 -->
    <!-- https://cdn.jsdelivr.net/npm/mermaid@10.9/dist/mermaid.min.js -->
    <script src="/mermaid.min.js"></script>
</head>
<body>
    <!-- 预留占位符,用于注入解析后的 HTML 内容 -->
    <div id="md-content"></div>

    <script>
        // 前端初始化 mermaid 并渲染图表
        mermaid.initialize({ startOnLoad: true });
    </script>
</body>
</html>
public/example.md
markdown 复制代码
### Mermaid 测试示例

这是一个使用 mermaid 10.9 渲染的流程图:

```mermaid
flowchart TD
    A[Node.js 18.20] --> B[Express]
    B --> C[markdown-it]
    C --> D[mermaid 10.9]
    D --> E[渲染图表]

饼图示例

40% 30% 30% 技术栈占比 Express markdown-it mermaid

时序图示例

mermaid markdown-it Express 前端 mermaid markdown-it Express 前端 请求渲染 Markdown 解析 Markdown 文本 识别 Mermaid 代码块 返回容器标签 返回解析后的 HTML 发送带容器的 HTML 渲染图表

代码关键部分解释

  1. Mermaid 配置

    • startOnLoad: false 关闭自动加载,避免前端脚本重复初始化
    • securityLevel: 'loose' 放宽安全限制,适合开发测试环境(生产环境可调整为 strict
  2. Markdown-It 插件

    • 重写 fence 渲染规则,识别 ```````mermaid```` 代码块
    • 将 Mermaid 代码包裹在 <div class="mermaid"> 容器中,供前端脚本识别渲染
  3. 前端渲染

    • 通过 CDN 引入与后端版本一致的 mermaid 10.9 前端脚本
    • 前端执行 mermaid.initialize({ startOnLoad: true }) 触发图表渲染

运行与验证

cd public

curl -O https://cdn.jsdelivr.net/npm/mermaid@10.9/dist/mermaid.min.js

  1. 执行代码: node app.js
  2. 访问地址:http://localhost:8000/render-md
  3. 效果:页面会显示 Markdown 解析后的内容,且包含两个可正常渲染的 Mermaid 图表(流程图和饼图)

进阶使用建议

  1. 读取本地 Markdown 文件

    可替换测试用的 testMd 变量,改为读取本地 .md 文件:

    javascript 复制代码
    const testMd = fs.readFileSync(path.join(__dirname, 'test.md'), 'utf8');
  2. 生产环境优化

    • 将 mermaid 前端脚本下载到本地,避免依赖 CDN
    • 调整 securityLevelstrict,限制不安全的代码执行
    • 增加错误处理,捕获 Markdown 解析或 Mermaid 渲染异常
  3. 自定义 Mermaid 主题

    可在初始化时指定自定义主题:

    javascript 复制代码
    mermaid.initialize({
      theme: 'forest', // 可选:default、forest、dark、neutral
      themeVariables: {
        primaryColor: '#61a955',
        primaryTextColor: '#fff'
      }
    });

总结

  1. Node.js 18.20 下使用 mermaid 10.9 需后端解析 Markdown 并生成 Mermaid 容器前端引入对应版本脚本完成渲染
  2. 核心步骤是通过 markdown-it 插件识别 mermaid 代码块,包裹专属容器,再由前端 mermaid 脚本渲染。
  3. 确保前后端 mermaid 版本一致(均为 10.9),避免兼容性问题。

如果 example.md 不在后端,是由客户端通过网页上传的,如何编程?

为了实现让客户端通过网页上传 example.md 文件,而非将文件固定在后端的 public 目录下,我会帮你完成这个完整的上传+解析+渲染流程,核心是实现文件上传接口、解析上传的 Markdown 文件、再渲染包含 Mermaid 图表的内容。

实现思路

  1. 扩展 Express 服务,添加文件上传功能(使用 multer 处理文件上传)。
  2. 前端页面新增文件上传表单,允许用户选择本地 Markdown 文件并提交。
  3. 后端接收上传的 Markdown 文件,读取内容后解析为 HTML。
  4. 将解析后的内容注入到 index.html 中返回给前端,完成 Mermaid 图表渲染。

完整实现步骤

1. 安装新增依赖

需要安装 multer 处理文件上传:

bash 复制代码
npm install multer@1.4 --save
2. 改造前端页面(public/index.html)

新增文件上传表单,让用户可以选择并上传 Markdown 文件:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Mermaid 测试-上传.md</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        .mermaid { margin: 20px 0; }
        pre { background: #f5f5f5; padding: 10px; border-radius: 5px; }
        .upload-form { margin-bottom: 40px; padding: 20px; border: 1px solid #eee; border-radius: 8px; }
        input[type="file"] { margin: 10px 0; }
        button { padding: 8px 16px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
        button:hover { background: #0056b3; }
    </style>
    <!-- 引入 mermaid 10.9 前端脚本 -->
    <!-- https://cdn.jsdelivr.net/npm/mermaid@10.9/dist/mermaid.min.js -->
    <script src="/mermaid.min.js"></script>
</head>
<body>
    <!-- 文件上传表单 -->
    <div class="upload-form">
        <h3>上传 Markdown 文件(支持 Mermaid 图表)</h3>
        <form id="mdUploadForm" enctype="multipart/form-data" method="POST" action="/upload-md">
            <input type="file" name="mdFile" accept=".md,.MD" required>
            <br>
            <button type="submit">上传并渲染</button>
        </form>
    </div>

    <!-- 渲染后的 Markdown 内容占位符 -->
    <div id="markdown-content"></div>

    <script>
        // 前端初始化 mermaid 并渲染图表
        mermaid.initialize({ startOnLoad: true });
    </script>
</body>
</html>
3. 改造后端代码(server.js)

添加文件上传处理逻辑,接收客户端上传的 Markdown 文件并解析:

javascript 复制代码
// server.js
const express = require('express');
const markdownIt = require('markdown-it');
const fs = require('fs');
const path = require('path');
const multer = require('multer');

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

// 配置静态文件目录
app.use(express.static(path.join(__dirname, 'public')));

// ====================== 配置文件上传 ======================
// 改造 fileFilter:不直接抛错,而是通过 req 传递错误信息
const fileFilter = (req, file, cb) => {
  const allowedTypes = ['.md', '.MD'];
  const ext = path.extname(file.originalname).toLowerCase();
  
  if (allowedTypes.includes(ext)) {
    cb(null, true);
  } else {
    // 不抛错,而是标记错误类型,后续在路由中处理
    req.fileValidationError = '仅支持上传 .md 或 .MD 格式的文件!';
    cb(null, false); // 第二个参数为 false 表示拒绝该文件
  }
};

// 配置 multer
const upload = multer({
  storage: multer.memoryStorage(),
  limits: {
    fileSize: 1024 * 1024 * 5 // 5MB
  },
  fileFilter: fileFilter // 使用改造后的 fileFilter
});

// ====================== 配置 markdown-it ======================
const md = markdownIt({
  html: true,
  linkify: true,
  typographer: true
});

// 注册 mermaid 解析插件
md.use(function(md) {
  const fence = md.renderer.rules.fence;
  md.renderer.rules.fence = function(tokens, idx, options, env, self) {
    const token = tokens[idx];
    if (token.info.trim() === 'mermaid') {
      const code = token.content.trim();
      return `<div class="mermaid">${code}</div>`;
    }
    return fence(tokens, idx, options, env, self);
  };
});

// 读取静态 HTML 文件(缓存)
const indexHtml = fs.readFileSync(path.join(__dirname, 'public', 'index.html'), 'utf8');

// ====================== 接口定义 ======================
// 1. 首页:返回上传页面
app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// 2. 处理 Markdown 文件上传 + 解析 + 渲染
app.post('/upload-md', upload.single('mdFile'), async (req, res) => {
  try {
    // 1. 检查文件类型错误(来自 fileFilter)
    if (req.fileValidationError) {
      return res.status(400).send(`
        <h1>上传失败</h1>
        <p>${req.fileValidationError}</p>
        <a href="/">返回上传页面</a>
      `);
    }

    // 2. 检查是否有文件上传
    if (!req.file) {
      return res.status(400).send(`
        <h1>上传失败</h1>
        <p>请选择要上传的 Markdown 文件!</p>
        <a href="/">返回上传页面</a>
      `);
    }

    // 3. 读取并解析 Markdown 内容
    const mdContent = req.file.buffer.toString('utf8');
    const renderedHtml = md.render(mdContent);
    const finalHtml = indexHtml.replace('<div id="markdown-content"></div>', renderedHtml);
    
    res.send(finalHtml);

  } catch (error) {
    // 4. 捕获其他未知错误:只返回通用提示,不暴露服务器细节
    console.error('文件处理异常:', error); // 控制台保留详细日志(供开发者排查)
    res.status(500).send(`
      <h1>处理失败</h1>
      <p>文件处理出错,请稍后重试!</p>
      <a href="/">返回上传页面</a>
    `);
  }
});

// ====================== 全局错误处理中间件(兜底) ======================
// 捕获所有未处理的错误,统一返回友好提示
app.use((err, req, res, next) => {
  console.error('全局异常:', err); // 控制台记录详细错误
  res.status(500).send(`
    <h1>服务器出错</h1>
    <p>抱歉,服务器暂时无法处理你的请求,请稍后重试!</p>
    <a href="/">返回上传页面</a>
  `);
});

// 启动服务 
const port = 8000;
app.listen(port, () => {
  console.log(`服务已启动:http://localhost:${port}`);
  console.log(`访问 http://localhost:${port} 即可上传 Markdown 文件`);
});

关键代码解释

  1. 文件上传配置(multer)

    • memoryStorage():将上传的文件临时存储在内存中,避免写入磁盘(适合小体积 Markdown 文件),如果需要持久化,可改用 diskStorage 配置存储路径。
    • fileFilter:过滤文件类型,仅允许 .md/.markdown 文件,防止上传恶意文件。
    • limits.fileSize:限制文件大小为 5MB,避免超大文件占用服务器资源。
  2. 文件内容读取

    • 上传的文件通过 req.file.buffer 获取(Buffer 格式),需用 toString('utf8') 转为字符串才能被 markdown-it 解析。
  3. 接口逻辑

    • GET /:返回带上传表单的首页。
    • POST /upload-md:接收上传的文件,解析后渲染内容,核心流程是「接收文件 → 读取内容 → 解析 Markdown → 注入 HTML → 返回结果」。

运行验证

  1. 确保目录结构:

    你的项目/
    ├── server.js
    ├── public/
    │ └── index.html 和 mermaid.min.js
    └── node_modules/

  2. 安装依赖:npm install express markdown-it multer

  3. 启动服务:node server.js

  4. 访问 http://localhost:8000,会看到文件上传表单:

    • 选择本地的 Markdown 文件(包含 Mermaid 代码块)。
    • 点击「上传并渲染」,页面会显示解析后的内容,Mermaid 图表正常渲染。

进阶优化建议

  1. 持久化上传文件 (可选):

    如果需要保存用户上传的 Markdown 文件,可修改 multer 存储配置:

    javascript 复制代码
    // 配置磁盘存储
    const storage = multer.diskStorage({
      destination: (req, file, cb) => {
        const uploadDir = path.join(__dirname, 'uploads');
        // 确保目录存在
        if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir);
        cb(null, uploadDir);
      },
      filename: (req, file, cb) => {
        // 自定义文件名:时间戳 + 原文件名
        const filename = `${Date.now()}-${file.originalname}`;
        cb(null, filename);
      }
    });
    const upload = multer({ storage: storage, /* 其他配置 */ });
  2. 支持多文件上传

    upload.single('mdFile') 改为 upload.array('mdFiles', 5)(最多上传 5 个文件),并遍历处理每个文件。

  3. 前端异步上传 (无刷新):

    用 AJAX/Fetch 实现异步上传,避免页面刷新:

    javascript 复制代码
    // 在 index.html 的 <script> 中添加
    document.getElementById('mdUploadForm').addEventListener('submit', async (e) => {
      e.preventDefault(); // 阻止默认表单提交
      const formData = new FormData(e.target);
      const response = await fetch('/upload-markdown', {
        method: 'POST',
        body: formData
      });
      const html = await response.text();
      // 替换页面内容(仅保留渲染结果)
      document.body.innerHTML = html;
    });

总结

  1. 客户端上传 Markdown 文件的核心是用 multer 处理文件上传,优先选择内存存储(小文件)或磁盘存储(需持久化)。
  2. 后端流程:接收文件 → 读取内容(Buffer 转字符串)→ markdown-it 解析 → 注入 HTML 返回。
  3. 关键限制:过滤文件类型、限制文件大小,避免服务器安全风险。
相关推荐
Ama_tor2 小时前
obsidian插件|图片预览工具のimage toolkit
markdown·plugins
cuber膜拜3 小时前
Marp CLI快速入门
pdf·npm·markdown·ppt·marp
放飞自我的Coder1 天前
【Mermaid本地实时渲染 单html本地直接运行】
html·mermaid
你想考研啊2 天前
win11卸载sql server express版本
express
克里斯蒂亚诺更新4 天前
vue展示node express调用python解析tdms
服务器·python·express
打小就很皮...4 天前
基于 React 实现 Vditor 的可复用 Markdown 渲染组件
前端·react.js·markdown·vditor
打小就很皮...5 天前
React 合同审查组件:按合同原文定位
前端·react.js·markdown
打小就很皮...5 天前
React 合同审查组件:按合同标题定位
前端·react.js·markdown
winfredzhang7 天前
使用 wxPython 构建文件编辑器与预览器:从零到一的完整实现
编辑器·html·markdown·预览·config