Express/Puppeteer从EJS模板生成PDF异常:未渲染数据与格式
问题根源
你遇到的核心问题是:Puppeteer加载的是未经过Express渲染的原始EJS模板文件,EJS的变量标签(比如<%= name %>)只有通过Express的res.render或EJS引擎手动编译,才能转换成填充了用户数据的HTML。直接读取EJS文件内容,Puppeteer只会把它当成纯文本解析,自然显示原始模板代码。
两种解决方案
方案一:手动编译EJS模板,传递渲染后的HTML给Puppeteer
适合模板不依赖外部静态资源(如CSS、图片)的场景,直接把编译好的HTML字符串传给Puppeteer:
const express = require('express'); const router = express.Router(); const puppeteer = require('puppeteer'); const fs = require('fs'); const ejs = require('ejs'); router.get('/generate_invoice', async (req, res) => { // 示例用户数据 const userData = { name: '张三', email: 'zhangsan@example.com', total: 300, items: [{desc: '基础服务', price: 200}, {desc: '增值服务', price: 100}] }; try { // 读取EJS模板文件 const templatePath = './views/invoice.ejs'; const templateContent = fs.readFileSync(templatePath, 'utf8'); // 用EJS引擎编译模板,注入用户数据得到完整HTML const renderedHtml = ejs.render(templateContent, userData); // 启动Puppeteer生成PDF const browser = await puppeteer.launch({ headless: 'new' }); const page = await browser.newPage(); // 加载编译后的HTML,等待页面完全就绪 await page.setContent(renderedHtml, { waitUntil: 'networkidle0' }); const pdfBuffer = await page.pdf({ format: 'A4', printBackground: true, // 确保背景样式生效 margin: { top: '20px', right: '20px', bottom: '20px', left: '20px' } }); await browser.close(); // 返回PDF给客户端 res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', 'attachment; filename="invoice.pdf"'); res.send(pdfBuffer); } catch (err) { console.error(err); res.status(500).send('PDF生成失败'); } }); module.exports = router;
方案二:让Puppeteer访问已渲染的Express路由
如果你的EJS模板依赖外部静态资源(如自定义CSS、logo图片),直接传HTML字符串会丢失资源引用,这时候可以让Puppeteer访问同一个服务器上的渲染路由:
const express = require('express'); const router = express.Router(); const puppeteer = require('puppeteer'); // 新增专门渲染发票HTML的路由 router.get('/invoice_html', (req, res) => { const userData = { name: '张三', email: 'zhangsan@example.com', total: 300, items: [{desc: '基础服务', price: 200}, {desc: '增值服务', price: 100}] }; res.render('invoice', userData); }); // 生成PDF的路由 router.get('/generate_invoice', async (req, res) => { try { const browser = await puppeteer.launch({ headless: 'new' }); const page = await browser.newPage(); // 访问已渲染的HTML路由,替换成你的服务器实际地址和端口 const invoiceUrl = `http://localhost:3000/invoice_html`; await page.goto(invoiceUrl, { waitUntil: 'networkidle0' }); const pdfBuffer = await page.pdf({ format: 'A4', printBackground: true, margin: { top: '20px', right: '20px', bottom: '20px', left: '20px' } }); await browser.close(); res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', 'attachment; filename="invoice.pdf"'); res.send(pdfBuffer); } catch (err) { console.error(err); res.status(500).send('PDF生成失败'); } }); module.exports = router;
注意事项
- 生产环境中,Puppeteer需要配置
headless: 'new'(新版无界面模式),避免兼容性问题 - 使用
waitUntil: 'networkidle0'确保页面所有资源加载完成后再生成PDF,避免内容缺失 - 模板中的静态资源请使用绝对路径(如
/css/invoice.css),确保Puppeteer能正确加载
内容的提问来源于stack exchange,提问作者dmx
相关产品推荐
相关产品推荐

