在 Strapi 项目的协作流程中,mock 数据通常以 JSON 文件形式提交到仓库。产品、设计或后端同事想快速了解内容类型结构时,要么依赖运行中的 Strapi 后台,要么手动打开编辑器查看原始 JSON。前者需要环境,后者可读性太差。StrapiMock2Image 就是为解决这个痛点而出现的 Node.js 小工具,它把 Strapi mock 数据渲染成一张静态图片,方便在文档、issue 或聊天群里直接贴图。下面详细拆解它的实现方式。

一、StrapiMock2Image 的核心思路
StrapiMock2Image 并不是一个官方库,而是一套组合 Node.js 基础能力的实现模式。它的核心链路分为三步:读取并解析 Strapi 导出的 mock JSON 数据,将数据注入一个可预览的 HTML 模板,最后用无头浏览器把渲染后的页面截成图片。选择无头浏览器而不是 canvas 或 SVG 直接绘制,是因为 HTML 和 CSS 的排版能力更强,可以轻松还原 Strapi 后台的内容结构感,也方便后续扩展复杂布局。
如果只是临时给同事看一眼数据,完全可以用一个简短的脚本完成,不必引入任何框架。Node.js 的 fs 模块负责文件读写,模板引擎推荐 Handlebars 或 EJS,截图则交给 Puppeteer。这三个依赖组合在一起,代码量可以控制在 100 行以内。对于需要反复使用的场景,再考虑封装成 CLI 工具,加入参数解析和错误处理。
在开始编码之前,需要确认 Strapi mock 数据的具体形态。Strapi 的 mock 通常是一个数组,每个元素代表一条内容记录,包含 id、attributes 等字段。比如一篇文章的 mock 可能长这样:{"id":1,"attributes":{"title":"示例文章","content":"正文内容"}}。解析时要把 attributes 展平,方便模板直接引用。
const fs = require('fs');
const Handlebars = require('handlebars');
// 读取并解析 Strapi mock JSON
const mockData = JSON.parse(fs.readFileSync('./mock.json', 'utf8'));
// 展平 attributes 字段,便于模板使用
const items = mockData.map(entry => ({
id: entry.id,
...entry.attributes
}));
// 读取 Handlebars 模板并编译
const templateSource = fs.readFileSync('./template.hbs', 'utf8');
const template = Handlebars.compile(templateSource);
// 渲染出完整的 HTML 字符串
const html = template({ items });
fs.writeFileSync('./output.html', html, 'utf8');
console.log('HTML generated successfully');
二、模板设计与数据渲染细节
HTML 模板是整个工具中最灵活的部分。一个基础模板通常包含头部标题、数据卡片列表和简单的样式。使用 Handlebars 的 each 循环遍历 items,每个条目渲染成一个 <div> 卡片。注意在模板源码中,<div>、<h2> 这些标签就是普通的 HTML,不需要额外转义,因为模板引擎最终会输出给浏览器解析。
为了让截图后的图片看起来更像一张可读的文档,而不是原始数据堆砌,可以在模板中加入内联 CSS。内联样式能避免外部样式表加载失败或路径问题,确保 Puppeteer 截图时样式一定生效。字体方面建议使用系统中常见的 sans-serif 字体栈,避免在 Linux 服务器上出现中文方块字。如果必须使用自定义字体,需要通过 page.addStyleTag 或 page.evaluateOnNewDocument 提前注入 @font-face,并等待字体加载完成再截图。
下面是一个极简的 Handlebars 模板示例,它遍历 items 并输出标题和内容摘要。模板文件命名为 template.hbs,放在脚本同目录下。渲染出来的 HTML 会作为 Puppeteer 的输入页面。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
body { font-family: sans-serif; margin: 40px; }
.card { border: 1px solid #ddd; border-radius: 8px; padding: 16px; margin-bottom: 16px; }
.card h2 { margin: 0 0 8px; font-size: 20px; }
.card p { margin: 0; color: #555; }
</style>
</head>
<body>
<h1>Strapi Mock 数据预览</h1>
{{#each items}}
<div class="card">
<h2>{{title}}</h2>
<p>{{content}}</p>
</div>
{{/each}}
</body>
</html>
三、用 Puppeteer 完成截图输出
Puppeteer 负责把渲染好的 HTML 文件加载进无头 Chromium,并执行截图。截图前需要设置合适的视口大小,例如宽度 1200 像素、高度根据内容自动扩展。通过 fullPage: true 选项,Puppeteer 会截取整个页面的完整高度,不用手动计算内容长度。如果希望图片更清晰,可以设置 deviceScaleFactor: 2,让输出图片的物理像素翻倍。
加载本地 HTML 文件时,使用 file:// 协议加上绝对路径即可。等待页面完全渲染的关键是 waitUntil: 'networkidle0',它会等待网络空闲 500 毫秒后再继续。如果模板里有外部图片或异步请求,这个等待条件能有效避免截图出现空白区域。不过对于纯内联样式的静态模板,networkidle0 可能会因为浏览器内部请求而略微延迟,实际使用中也可以用 domcontentloaded 加一个手动延时来替代。
截图完成后记得调用 browser.close() 释放 Chromium 进程,否则脚本执行结束后会有僵尸进程占用资源。完整的截图脚本如下,它读取上一步生成的 output.html,输出 mock-image.png。
const puppeteer = require('puppeteer');
const path = require('path');
(async () => {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
// 设置视口和缩放,保证截图清晰
await page.setViewport({
width: 1200,
height: 800,
deviceScaleFactor: 2
});
const filePath = 'file://' + path.join(__dirname, 'output.html');
await page.goto(filePath, { waitUntil: 'networkidle0' });
await page.screenshot({
path: 'mock-image.png',
fullPage: true
});
await browser.close();
console.log('Image saved as mock-image.png');
})();
四、封装成命令行工具与参数化
如果每次都要手动修改脚本里的文件路径,效率并不高。把上面的逻辑封装成一个 CLI 工具,通过命令行参数指定输入 JSON 路径、输出图片路径以及视口尺寸,会更符合日常使用习惯。Node.js 的 process.argv 可以拿到所有命令行参数,手动解析 --input、--output、--width、--height 等选项。对于更复杂的参数组合,可以引入 commander 或 yargs 库来简化解析。
CLI 封装还有一个好处是可以挂载到 npm scripts 中。比如在 package.json 里配置 "mock:image": "node strapi-mock2image.js --input ./mock.json --output ./preview.png",之后运行 npm run mock:image 就能一键生成图片。如果团队内部有多个 Strapi 内容类型,可以写一个循环脚本批量处理每个 mock 文件,输出到对应的预览图目录。
下面是一个简单的参数解析示例,重点展示如何从命令行获取输入输出路径和视口宽高。注意条件判断中的逻辑与运算符 && 在代码块中要转义为 &&,这是 HTML 文本环境下的必要处理。
#!/usr/bin/env node
const args = process.argv.slice(2);
let inputPath = './mock.json';
let outputPath = './mock-image.png';
let width = 1200;
let height = 800;
args.forEach((arg, index) => {
if (arg === '--input' && args[index + 1]) {
inputPath = args[index + 1];
}
if (arg === '--output' && args[index + 1]) {
outputPath = args[index + 1];
}
if (arg === '--width' && args[index + 1]) {
width = parseInt(args[index + 1], 10);
}
if (arg === '--height' && args[index + 1]) {
height = parseInt(args[index + 1], 10);
}
});
console.log(`Input: ${inputPath}, Output: ${outputPath}, Viewport: ${width}x${height}`);
五、常见坑点与替代方案
第一个常见问题是中文字符显示为方块。这通常是因为运行环境缺少中文字体,尤其是使用精简版 Linux 容器时。解决办法是安装 fonts-noto-cjk 或 fonts-wqy-zenhei 字体包,或者在模板中通过 @font-face 从本地字体文件加载。第二个坑是截图时图片还没加载完就生成了,导致页面出现灰色占位。除了使用 networkidle0,还可以在 page.screenshot 之前手动等待图片的 onload 事件,或者轮询某个 DOM 元素是否出现。
如果不想依赖 Puppeteer 这么大的依赖体积,也可以考虑使用 playwright 或者更轻量的 puppeteer-core 配合系统已有 Chrome。另一个替代方向是用 sharp 结合 SVG 渲染,但对于复杂排版来说开发成本较高。StrapiMock2Image 的价值在于它把通用的数据可视化需求用一条最短路径解决了,而且整个实现完全透明,方便按需修改模板和截图逻辑。
实际使用中,如果 Strapi mock 数据里包含 Markdown 格式的富文本字段,需要先用 marked 或 markdown-it 把 Markdown 转成 HTML 再注入模板。另外,如果内容条目数量很多,生成的长图可能超过数千像素,此时建议增加 page.pdf 导出 PDF 作为备选,或者限制每页条目数量,分页截图后再拼接。这些扩展都能在现有核心代码上快速实现。
Node.jsStrapi Mock图片生成修改时间:2026-10-07 03:43:30