在React中生成PDF

原文信息: 查看原文查看原文

Generate a PDF in React

- Colby Fayock

PDF为世界提供了一种高度兼容的共享文档和媒体的通用格式,但通常以程序化方式生成它们可能会有些棘手。

我们将探讨一些使用JavaScript在不同环境中生成PDF的选项。

生成PDF的难题…

当使用PDF时,你通常像查看图像一样阅读或审查它们,但如果你尝试复制一些文本、搜索PDF或点击链接,你可能已经注意到PDF不仅仅是一个静态图像。

许多生成PDF的解决方案都依赖于能够将它们生成为图像,这缺乏嵌入文本所提供的可访问性和可用性。

但根据你的限制和环境,也许值得权衡。

html2pdf.js

html2pdf.js 是一个客户端库,允许你使用Canvas从HTML渲染PDF,特别是html2canvasjsPDF

要创建你的PDF,你需要指定你想要从页面渲染哪个元素,将其传递给html2pdf实例,然后库将生成PDF并提示你的用户下载它。

如何开始?

你可以通过几种不同的方式包括html2pdf.js,包括指向第三方CDN的脚本标签或通过npm导入。

如果使用npm,首先安装html2pdf.js:

npm install html2pdf.js

将其作为依赖项导入:

import html2pdf from 'html2pdf.js';

选择一个HTML元素并将其传递给html2pdf:

html2pdf(document.getElementById('my-id'));

这将提示你的用户开始下载文件。

你也可以在支持的地方动态导入依赖项,以避免它直接与你的应用程序捆绑在一起,只有在需要时才加载它。

const html2pdf = await require('html2pdf.js');
html2pdf(document.getElementById('my-id'));

文档中有许多可用选项

例如,在我显示发票的页面上,html2pdf.js将渲染:

使用html2pdf.js从HTML生成PDF

它的优点是什么?

html2pdf允许你在浏览器中使用JavaScript生成PDF。这意味着你不需要处理向外部服务器发出请求,所以你处理的基础设施更少,网络请求也更少。

你还可以使用任何工具以任何方式管理你的HTML,因为你最终将传递一个DOM节点来渲染。

这也促进了你已经构建的页面的可重用性,所以你不必为UI和PDF版本分别维护多个页面。

可以改进的地方?

它通常工作得很好,但渲染在生成目标HTML时可能会有些不一致。CSS可能不会完美呈现,有时页面的部分可能会显得偏移或被切断。

虽然API为你提供了一些选项和灵活性,但你仍然受限于它如何渲染页面。你可以隐藏东西(data-html2canvas-ignore),但你不能例如像打印预览那样提供特定的样式。

页面偏移导致项目被切断的问题似乎可以通过html2pdf.js GitHub问题中找到的一些基础样式来修复。

@layer base { img { display: initial; } }

PDF Kit & React PDF

PDF Kit 是另一种JavaScript HTML到PDF渲染工具,它的工作方式略有不同。

PDF Kit本身实际上并不渲染HTML,而是允许你使用它描述的类似于HTML5 Canvas的API来创建和定位元素。

但在React环境中,你会得到更接近HTML或JSX的东西,其中React PDF 使用组件API和底层的PDF Kit,以更自然的方式表达内容(就像你在React中所做的那样)。

我们将重点介绍React PDF,但如果你想看纯JavaScript版本,请查看文档。

如何开始?

首先使用npm安装React PDF:

npm install @react-pdf/renderer

导入一些组件作为依赖项:

import { renderToStream, Page, Text, Document, StyleSheet } from '@react-pdf/renderer';

设置一些样式:

const styles = StyleSheet.create({
  page: { 
    padding: 50
  },
  title: {
    fontSize: 22,
  },
});

创建一个文档组件:

const MyDocument = () => (
  <Document>
    <Page style={styles.page}>
      <Text style={styles.title}>My Text</Text>
    </Page>
  </Document>
);

如果要渲染到流,使用renderToStream方法:

await renderToStream(<MyDocument />)

此时它就像一个React组件一样工作,所以你可以使用动态值来使它更容易处理动态数据。

创建动态路由处理器

使用此的一个示例是通过创建一个Next.js路由处理器来查询数据,就像页面一样,并渲染PDF,将其作为流返回。

为此,你可以在app/pdf/route.tsx中创建你的路由,在里面,你将创建PDF组件,渲染它,并在Next.js响应中返回它。

例如:

import { NextResponse } from 'next/server';

const Invoice = (props: InvoiceProps) => (
  <Document>{/** PDF组件 */}</Document>
);

export async function GET(request: Request, { params }: { params: { invoiceId: string; }}) {
  const invoice = await getMyInvoice(params.invoiceId);
  const stream = await renderToStream(<Invoice {...invoice} />)
  return new NextResponse(stream as unknown as ReadableStream)
}

这可能会渲染:

使用React渲染的PDF

它的优点是什么?

渲染工作得很好。

你可以选择如何渲染文档,包括在视图中渲染它和将其渲染到ReadableStream。

这在你想要创建一个路由处理器来动态生成和交付PDF的情况下很有用,或者如果你想要在一个服务器上生成PDF。

它还允许你嵌入实际的字体。一旦打开PDF,文本节点实际上是可选取和可复制的,这对于很多原因来说都是很好的。

可以改进的地方?

目前React PDF似乎在即将到来的React 19中不起作用。关于如何支持的问题,GitHub问题中有一些讨论,甚至是一个分支。

虽然你可以在HTML/JSX类似的结构中创建PDF,但你仍然需要用它们的组件单独维护它,尽管如果不使用像html2pdf这样直接从DOM中获取的解决方案,那种语法可能比其他一些API更好。

Puppeteer

Puppeteer是一个流行的选项,用于处理HTML,截屏,并尝试渲染通常涉及浏览器的动态内容。

这是另一种我们可以用来根据现有内容创建PDF的工具。

它的工作原理是Puppeteer帮助自动化Chromium浏览器,一旦连接,我们可以导航到不同的页面,与这些页面交互,并且你可以想象,截屏甚至生成PDF。

如何开始?

使用Puppeteer非常依赖于你所处的环境。例如,在无服务器函数中运行Puppeteer是棘手的,但幸运的是,我有一个教程:使用Puppeteer和Next.js API路由构建网络爬虫

对于这个例子,我假设你能够运行标准的Puppeteer库。

首先,安装Puppeteer:

npm install puppeteer

将模块导入到你的项目中:

import puppeteer from 'puppeteer';

然后我们可以自动化运行Puppeteer,例如,如果我们想要生成一个PDF,我们可以运行:

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://spacejelly.dev'); 
const pdf = await page.pdf();
await browser.close();

此时,PDF是一个Uint8Array,我们可以将其上传到我们选择的位置。

例如:

使用Puppeteer生成的PDF

提示:查看我的YouTube视频,我展示了如何使用Clerk创建一个经过身份验证的Puppeteer会话来生成PDF!

或者,而不是生成PDF,你可以截屏:

const screenshot = await page.screenshot();

区别在于.screenshot生成的是一个图像,而不是PDF文件,这可能是一个重要的区分。

Alt: 使用Puppeteer生成的PDF

它的优点是什么?

Puppeteer非常多功能。你有很多选项可以控制和与页面交互。

一旦你设置了页面,你可以很容易地使用.pdf方法捕获它,其中包含了嵌入的文本,这对于高质量的PDF至关重要。

可以改进的地方?

Puppeteer在这个用例中可能会慢,其他方法更快,这可以带来更好的用户体验。

它也可能很难在一个环境中设置,假设你可以在首先设置Puppeteer的环境中。

我们的最佳选择是什么?

它们都有各自的优势和劣势,但我认为React PDF是我们这里更好的解决方案。

当然,我们必须创建和维护一个单独的模板,但那个模板不需要与UI完全相同,交互性的标准和期望水平是不同的。

与html2pdf相比,React PDF为我们提供了嵌入的文本。

与Puppeteer相比,React PDF更快,更容易设置。

但最终你应该权衡这些选项,看看在你的场景中什么最有效。

分享于 2024-08-24

访问量 139

预览图片