本文向您展示如何使用Node.js、Puppeteer、headless Chrome 和Docker 从复杂风格的React 页面生成PDF 文档。
背景:几个月前,一位客户要求我开发一个功能,允许他以PDF 格式检索React 页面的内容。该页面本质上是患者病例报告和数据可视化,其中包含大量SVG。还有操作布局和执行HTML 元素重新定位的特殊要求。因此,与原始React 页面相比,PDF 中应该有不同的样式和附加内容。
这个任务比简单的CSS 规则复杂得多,所以让我们首先考虑可能的方法。我们找到了三个主要解决方案。这篇博文描述了它的潜力和最终实现。
目录:
是在客户端生成还是在服务器端生成? 选项1:从DOM 创建屏幕截图选项2:使用PDF 库最终选项3:Node.js、Puppeteer、Head 将响应Chrome 样式控制文件发送到客户端并使用Puppeteer 将其保存到Docker 选项3 +1:CSS 打印规则摘要是在客户端还是服务器端生成的?
PDF 文件可以在客户端和服务器端生成。但是,您不想用完用户浏览器可以提供的所有资源,因此让后端处理它可能更有意义。
不过,我将向您展示这两种方法的解决方案。
选项1:从DOM 创建屏幕截图
乍一看,这个解决方案似乎是最简单的,事实证明也是如此,但它有其自身的局限性。如果您没有特殊需求,例如在PDF 中选择文本或执行文本搜索,这是一种简单易用的方法。
该方法简单易行。从页面创建屏幕截图并将其保存为PDF 文件。这很容易。这可以使用两个包来完成。
Html2canvas,一个基于DOM生成截图jsPdf并生成PDF的库,开始编码。
npm 安装html2canvas jspdf
及以上!
注意html2canvas的onclone方法。如果您需要在截屏之前操作DOM(例如隐藏打印按钮),则非常有用。我见过很多项目都使用这个包。但不幸的是,这不是我们想要的,因为PDF 创建必须在后端完成。
选项2:使用PDF 库
NPM 有几个库,包括jsPDF(上面提到的)和PDFKit。问题是,如果我想使用这些库,我必须重建页面。这显然会对可维护性产生负面影响,因为所有后续更改都必须应用于PDF 模板和React 页面。
请参阅下面的代码。您必须自己手动创建PDF 文档。您必须遍历DOM 来查找每个元素并将其转换为PDF 格式,这是一项繁琐的任务。我必须找到一种更简单的方法。
此代码片段来自PDFKit 文档。但是,如果您的目标是直接生成PDF 文件而不是转换现有(且不断变化的)HTML 页面,那么它仍然很有用。
最终选项3:基于Node.js 的Puppeteer 和Headless Chrome
什么是傀儡师?该文件说:
Puppeteer 是一个节点库,它提供了用于通过DevTools 协议控制Chrome 或Chromium 的高级API。 Puppeteer 默认以无头模式运行Chrome 或Chromium,但也可以配置为以完整(非无头)模式运行。
这本质上是一个可以从Node.js 运行的浏览器。如果您阅读该文档,它首先会说您可以使用Puppeteer 生成页面的屏幕截图和PDF。精彩的!这正是我们想要的。
首先,使用npmi i puppeteer 安装Puppeteer 并实现其功能。
这是一个简单的功能,可以导航到URL 并为您的站点生成PDF 文件。
首先,启动浏览器(仅在无头模式下支持PDF 生成),然后打开一个新页面,设置视口并导航到指定的URL。
当您设置waitUntil:'networkidle0' 选项时,如果至少500 毫秒没有网络连接,Puppeteer 就会认为导航完成。 (更多信息可从API 文档中获取。)
然后将PDF 保存为变量,关闭浏览器,然后返回到PDF。
注意:page.pdf 方法接受一个选项对象。您可以使用“路径”选项将文件保存到磁盘。如果未指定路径,PDF 将不会保存到磁盘,而是会进行缓冲。 (稍后我会解释如何处理这个问题。)
如果您在从受保护页面生成PDF 之前需要登录,则必须首先进入登录页面,验证并输入表单元素ID 或名称,然后提交表单。
始终将登录凭据存储在环境变量中,而不是对其进行硬编码。
风格控制
Puppeteer 还为这种交互方式提供了解决方案。如果您在生成PDF 之前插入样式标签,Puppeteer 会生成一个包含修改后样式的文件。
wait page.addStyleTag({ content: '.nav { display: none} .navbar { border: 0px} #print-button {display: none}' }) 将文件发送到客户端并保存
现在后台已经生成了一个PDF文件。下一步是什么?
如上所述,如果您不将文件保存到磁盘,您最终会得到一个缓冲区。您需要做的就是将具有适当内容类型的缓冲区发送到前端。
现在,要获取生成的PDF,您只需在浏览器中向服务器发出请求即可。
提交请求后,开始下载缓冲区内容。最后一步是将缓冲区数据转换为PDF 文件。
就是这样!单击“保存”按钮,您的浏览器将保存PDF。
将Puppeteer 与Docker 结合使用
我认为这是实现——最困难的部分,所以让我节省百度几个小时的时间。
官方文档指出*“使用Docker 运行无头Chrome 可能很困难”*。官方文档有一个故障排除部分,其中包含有关使用Docker 安装puppeteer 所需的所有信息。
如果您要在Alpine 映像上安装Puppeteer,请务必在看到页面的这一部分时向下滚动一点。否则,您可能会错过这样一个事实:您将无法运行最新版本的Puppeteer,并且需要使用标志禁用shm。
const browser=wait puppeteer.launch({ headless: true, args: ['--disable-dev-shm-usage']}); 否则,Puppeteer 子进程可能会在正常启动之前耗尽内存。
场景3+1:CSS打印规则
从开发人员的角度来看,人们可能会认为简单地使用CSS 打印规则会更容易。没有NPM 模块,只有纯CSS。但是跨浏览器兼容性怎么样?
选择CSS 打印规则时,您应该在所有浏览器中测试结果,以确保提供的布局相同,但这在100% 的情况下是不可能的。
例如,在特定元素后插入中断符并不是什么复杂的事情,但您可能会惊讶地发现在Firefox 中使用中断符需要一种解决方法。
除非您是一位经验丰富的CSS 高手,在创建可打印页面方面拥有丰富的经验,否则这可能会非常耗时。
当您可以保持打印样式表简单时,打印规则非常有用。
让我们看一个例子。
@media print { .print-button { display: none; } .content div { Break-after: always }} 上面的CSS 隐藏了打印按钮,并在每个包含内容类的div 之后插入分页符。这是一篇很棒的文章,概述了打印规则的功能以及打印规则的问题,包括浏览器兼容性。
考虑到所有因素,CSS 打印规则在从不太复杂的页面生成PDF 时效果非常好。
总结
让我们简要回顾一下前面介绍的从HTML 页面生成PDF 文件的场景。
从DOM 生成屏幕截图:当您需要从页面获取快照(例如创建缩略图)时,这很有用,但当您需要处理大量数据时,这可能会很困难。只需使用PDF 库即可。如果您计划从头开始以编程方式创建PDF 文件,那么这是完美的解决方案。否则,您将需要同时维护HTML 和PDF 模板,这是一个很大的禁忌。 Puppeteer:使用Docker 相对困难,但实现给了我们最好的结果,并且最容易编码。 CSS 打印规则:如果您的用户受过良好教育,了解如何将页面内容打印到文件,并且您的页面相对简单,那么这可能是最简单的解决方案。从我们的案例中可以看出,事实并非如此。今天是愚人节,以上所有内容均适用。
作者:疯狂科技极客
链接:https://juejin.im/post/5ca1dc0251882543d569e075
版权声明:本文由今日头条转载,如有侵犯您的版权,请联系本站编辑删除。