指南 · 进阶
版本基线 3.x。把 docxtemplater 用进真实项目:表格行循环、异步数据
renderAsync、浏览器下载、Node 服务端把文档作为响应返回、错误聚合处理、自定义分隔符。
一、表格里按数据增删行(免费)
最常用的需求之一:表格行数随数据变化。做法是把循环区块标签放进表格行的单元格——docxtemplater 会按数组长度复制整行。
在 Word 表格里这样写(开/闭标签放在行首尾单元格):
| {#rows}姓名 | 金额 |
| {name} | {amount}{/rows} |doc.render({
rows: [
{ name: '张三', amount: 100 },
{ name: '李四', amount: 200 },
],
});这是免费核心能力,不需要付费的 table 模块(table 模块面向更复杂的跨行合并、嵌套表格等)。配合
paragraphLoop: true可避免多余空行。
二、异步数据:renderAsync
数据字段是 Promise(要先发 HTTP 请求、查数据库)时,不能用同步 render(它不会等 Promise)。改用 renderAsync:
const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true });
await doc.renderAsync({
user: new Promise((resolve) => {
// 可以是 HTTP 请求 / 数据库查询 / 外部 API
setTimeout(() => resolve('John'), 1000);
}),
});
const buf = doc.toBuffer();
fs.writeFileSync('out.docx', buf);
renderAsync(data)返回 Promise,内部会先 resolve 数据里所有 Promise 再渲染。旧版等价写法是await doc.resolveData(data)然后doc.render()。付费 image 模块常因「图片需异步加载」而要求走这条路。
三、浏览器:拉模板 + 触发下载
import Docxtemplater from 'docxtemplater';
import PizZip from 'pizzip';
import PizZipUtils from 'pizzip/utils/index.js';
import { saveAs } from 'file-saver';
function loadFile(url, cb) {
PizZipUtils.getBinaryContent(url, cb); // 必须二进制
}
loadFile('/tpl.docx', (err, content) => {
if (err) throw err;
const doc = new Docxtemplater(new PizZip(content), {
paragraphLoop: true,
linebreaks: true,
});
doc.render({ first_name: 'John' });
saveAs(doc.toBlob(), 'out.docx'); // toBlob 自 3.62.0
});也可用 fetch 拉模板,但务必取 arrayBuffer()(不能 text()):
const content = await (await fetch('/tpl.docx')).arrayBuffer();
const doc = new Docxtemplater(new PizZip(content), { paragraphLoop: true });四、Node 服务端:把文档作为响应返回
服务端通常不落地磁盘,用 toBuffer() 拿字节直接作为响应体:
// Express 示例
app.get('/contract', (req, res) => {
const content = fs.readFileSync('tpl.docx', 'binary');
const doc = new Docxtemplater(new PizZip(content), {
paragraphLoop: true,
linebreaks: true,
});
doc.render({ name: req.query.name });
const buf = doc.toBuffer();
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
);
res.setHeader('Content-Disposition', 'attachment; filename="contract.docx"');
res.send(buf);
});docx 的 MIME 是
...wordprocessingml.document。不要res.json(buf)——JSON 序列化会破坏二进制。
五、错误处理:聚合的可读信息
模板有多处问题时,docxtemplater 抛出 MultiError,把全部子错误放在 error.properties.errors:
try {
doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true });
doc.render(data);
} catch (error) {
if (error.properties && Array.isArray(error.properties.errors)) {
const messages = error.properties.errors
.map((e) => e.properties.explanation)
.join('\n');
console.log(messages);
// 如:The tag beginning with "foobar" is unopened
}
throw error;
}注意两个抛错时机:构造/编译时抛模板结构错误(标签未闭合等,TemplateError);render 时抛数据/解析错误。
error.properties.id可用于程序化区分错误类型。
六、自定义分隔符
文档里本就有花括号、或与默认 {} 冲突时,换分隔符:
const doc = new Docxtemplater(zip, {
delimiters: { start: '[[', end: ']]' },
});
// 模板里标签写成 [[name]]docxtemplater 没有「反斜杠转义单个花括号」的机制;要在文档里大量保留字面花括号,换分隔符是最干净的办法。也可用行内指令
{=[[ ]]=}临时切换。
七、把多个模块一起挂(付费模块)
新版用构造选项的 modules 数组传入模块实例(多为付费):
import expressionParser from 'docxtemplater/expressions.js';
// import ImageModule from 'docxtemplater-image-module'; // 付费
const doc = new Docxtemplater(zip, {
parser: expressionParser,
modules: [/* new ImageModule(opts) */],
paragraphLoop: true,
linebreaks: true,
});旧链式
.attachModule(mod)已不推荐;统一在构造函数一次传入。
进入 指南 · 专家:免费/付费边界全表、Word 拆标签经典坑、安全(GPLv3 含义)、与 docx/SheetJS 的选型。