在Node.js环境中把Fela定义的样式规则转换成图片,本质是解决“无浏览器渲染上下文”与“原子化CSS还原”两个问题。Fela本身只在运行时组合样式对象并产出类名,并不负责视觉呈现,因此Fela2Image需要自行补全渲染逻辑。

一、Fela样式结构回顾
Fela通过createRenderer创建渲染器,调用renderRule传入样式函数得到原子类名。样式以对象书写,例如文字颜色与字号被拆成独立原子类,由Fela插件补全前缀与单位。理解这一点对后续转换非常关键,因为图片绘制必须还原这些被拆分后的最终声明。
下面是一段典型的Fela用法,我们在Node端复用同一份规则,但目的不是插到DOM,而是读取计算结果:
import { createRenderer } from 'fela';
import prefixer from 'fela-plugin-prefixer';
const renderer = createRenderer({
plugins: [prefixer()]
});
const rule = (props) => ({
color: props.color,
fontSize: '14px',
padding: 8
});
const className = renderer.renderRule(rule, { color: 'red' });
// 此时renderer.cache中已存有原子类对应的声明
console.log(className);
上述代码在Node执行并不会报错,但renderer仅维护了类名与声明的映射。我们要做的Fela2Image,就是遍历这些映射,把每个类名对应的CSS属性抽取出来,作为绘制图片时的样式来源。
二、基于Puppeteer的截图方案
最直观的办法是用无头浏览器加载Fela生成的HTML,再截图存为图片。这种方案还原度最高,伪类、媒体查询都能自然支持,适合对视觉一致性要求严格的场景。代价是每次转换都要启动浏览器实例,资源消耗较大。
实现时先把Fela的renderToString结果拼成完整页面,交给puppeteer打开并截元素图:
import puppeteer from 'puppeteer';
import { createRenderer } from 'fela';
import { renderToString } from 'fela-dom';
async function felaToImage(rule, props) {
const renderer = createRenderer();
const cls = renderer.renderRule(rule, props);
const sheet = renderToString(renderer);
const html = `<style>${sheet}</style><div class="${cls}">示例文本</div>`;
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(html);
const el = await page.$('div');
const buf = await el.screenshot({ type: 'png' });
await browser.close();
return buf;
}
这种写法把Fela的样式字符串直接注入页面,由浏览器完成布局与绘制。它的优势是无需自己解析CSS,劣势是依赖Chromium,在Serverless等受限环境可能难以部署。如果批量处理,应当复用browser实例而非频繁启停。
三、纯Node计算绘制方案
若环境不允许启动浏览器,可采用canvas库在Node端手动绘制。思路是读取Fela缓存里的声明,将color、fontSize等映射为canvas的fillStyle与font属性,最后导出PNG。该方式轻量,但仅支持有限CSS特性,复杂布局需自行实现。
下面用canvas包演示如何把单个原子类声明画成图片:
import { createCanvas } from 'canvas';
import { createRenderer } from 'fela';
function drawFelaRule(rule, props) {
const renderer = createRenderer();
const cls = renderer.renderRule(rule, props);
const decl = renderer.cache[cls].declaration;
const canvas = createCanvas(200, 60);
const ctx = canvas.getContext('2d');
if (decl.color) ctx.fillStyle = decl.color;
if (decl.fontSize) ctx.font = `${decl.fontSize} sans-serif`;
ctx.fillText('示例文本', 10, 30);
return canvas.toBuffer('image/png');
}
注意Fela缓存里的declaration是已处理好的属性对象,但单位可能保留字符串,canvas的font只认标准写法,因此实际项目要写一个小函数把14px拆成数值与单位再组装。相比截图法,这种方案在容器化Node服务里更稳,只是丢失了盒模型自动排版能力。
四、原子类缓存与批量优化
当需要处理大量Fela规则时,重复创建渲染器会拖累性能。推荐维护一个全局renderer,并利用Fela同规则同类的特性,让相同样式直接命中缓存,只做一次绘制逻辑。同时可把生成过的图片按类名哈希缓存到磁盘,下次遇到同样规则直接读图。
简易缓存实现如下:
const fs = require('fs');
const path = require('path');
const renderer = createRenderer();
function cachedFelaImage(rule, props) {
const cls = renderer.renderRule(rule, props);
const file = path.join('/tmp', cls + '.png');
if (fs.existsSync(file)) return fs.readFileSync(file);
const buf = drawFelaRuleWith(renderer, cls);
fs.writeFileSync(file, buf);
return buf;
}
这样在批量导出组件预览图时,Fela2Image既能保持Node端独立运行,又能把重复样式的计算成本降到最低。综合来看,团队若已用Fela管理样式,配合上述任一方案都能较快搭建出图片导出能力,区别只在还原度与部署成本之间权衡。
Node.jsFela2ImageFela修改时间:2026-08-11 17:45:35