JavaScript >> Javascript 文檔 >  >> Node.js

如何使用 Puppeteer 和 JavaScript 在 Node.js 中生成 PDF

如何使用 Puppeteer 和 Express 生成 PDF 文件並在瀏覽器中呈現。

開始使用

對於本教程,我們將使用 CheatCode Node.js Boilerplate 為我們的工作提供一個起點。首先,讓我們將它的副本克隆到我們的​​計算機:

終端

git clone https://github.com/cheatcode/nodejs-server-boilerplate.git server

接下來,安裝樣板的依賴項:

終端

cd server && npm install

之後,我們需要安裝puppeteer 來自 NPM 的包,它將幫助我們生成 PDF:

終端

npm i puppeteer

最後,啟動開發服務器:

終端

npm run dev

之後,我們就擁有了工作所需的一切。

創建 PDF 生成器函數

我們的首要任務是編寫用於實際生成 PDF 的函數。這個函數會為我們的 PDF 內容獲取一些 HTML 和 CSS,然後將其輸出為實際的 PDF:

/lib/generatePDF.js

import puppeteer from "puppeteer";

export default (html = "") => {};

在這裡,我們首先導入 puppeteer 我們之前安裝的依賴項。這就是我們將用來生成 PDF 的內容。在該導入之下,我們為 generatePDF() 創建了一個骨架 函數,接受單個參數 html 作為一個字符串。

/lib/generatePDF.js

import puppeteer from "puppeteer";

export default async (html = "") => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.setContent(html);
};

接下來,使用 puppeteer 我們從頂部導入的包,我們使用 puppeteer.launch() 創建一個 Web 瀏覽器的實例 .請注意,在這裡,我們希望該函數返回一個 JavaScript Promise,因此我們添加了 await 前面的關鍵字表示“等待此函數返回的 Promise 解決,然後再繼續我們的其餘代碼。”

為了使它也能正常工作,我們添加了一個 async 關鍵字就在我們上面的函數定義之前。如果我們不這樣做,JavaScript 會拋出運行時錯誤,提示“await 是保留關鍵字”。

一旦我們有了 Puppeteer browser 例如,接下來,我們用 browser.newPage() 創建一個新頁面 .雖然它可能看起來不像,但這就像在您的網絡瀏覽器中打開一個標籤(Puppeteer 是所謂的“無頭”瀏覽器,或者,沒有 GUI 或圖形用戶界面的網絡瀏覽器)。

同樣,我們使用 await 關鍵字在這裡。這是因為所有 我們將從 Puppeteer 中使用的函數返回一個 JavaScript 承諾。我們要await 這些 Promise 是因為我們正在做的是 同步 流程(意思是,我們要確保代碼中的每一步都完成,然後再進行下一步)。

最後,使用我們的 page 可用時,我們設置頁面的內容——HTML 標記組成了我們在瀏覽器中看到的內容(如果它不是無頭的)。

此時,如果我們要使用帶有 GUI 的瀏覽器,我們會看到我們傳入的任何 HTML/CSS 都呈現在屏幕上。

/lib/generatePDF.js

import puppeteer from "puppeteer";

export default async (html = "") => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.setContent(html);

  const pdfBuffer = await page.pdf();

  await page.close();
  await browser.close();

  return pdfBuffer;
};

構建我們函數的其餘部分,現在,我們看看我們如何從在瀏覽器中呈現頁面到獲取 PDF。在這裡,我們調用 Puppeteer page.pdf() 功能。它負責將我們的 HTML 頁面轉換為 PDF 格式。

請注意,我們在 page 上調用此方法 我們在上面創建的變量並設置內容。本質上,這就是說“將此頁面轉換為 PDF”。到 page.pdf() ,您可以選擇傳遞選項來自定義 PDF 的外觀。

雖然看起來可能不多,但這就是我們取回 PDF 文件所需要做的一切。您會注意到我們將響應存儲到 page.pdf() 在名為 pdfBuffer 的變量中 .這是因為我們得到的響應是一個文件緩衝區 這是我們的 PDF 在內存中的表示(意思是文件在變成我們計算機上的實際文件之前的內容)。

在我們從底部的函數返回這個文件緩衝區之前,我們確保調用 page.close()browser.close() 清除內存中的 Puppeteer 實例。這非常重要 因為如果你不這樣做,在我們的 PDF 生成之後,Puppeteer 會繼續佔用內存。這意味著,每次有人調用此函數時,都會在內存中創建一個新的 Puppeteer 實例。 這樣做的次數足夠多,您的服務器就會耗盡內存 導致崩潰。

這樣,我們的 generatePDF() 功能齊全。為了完成本教程,讓我們在我們的服務器上創建一個 HTTP 路由,我們可以使用它來調用我們的 generatePDF() 功能。

連接一條路線來測試我們的 PDF 生成器

為了測試我們的 PDF 生成,我們將使用我們在構建此應用程序的 CheatCode Node.js 樣板中為我們設置的 Express 服務器創建一個 HTTP 路由。為了確保我們的接線合理、快速,讓我們看看我們的 Express 服務器是如何設置的,然後我們的代碼將放在哪裡。

/index.js

import express from "express";
import startup from "./lib/startup";
import api from "./api/index";
import middleware from "./middleware/index";
import logger from "./lib/logger";

startup()
  .then(() => {
    const app = express();
    const port = process.env.PORT || 5001;

    middleware(app);
    api(app);

    app.listen(port, () => {
      if (process.send) {
        process.send(`Server running at http://localhost:${port}\n\n`);
      }
    });

    process.on("message", (message) => {
      console.log(message);
    });
  })
  .catch((error) => {
    logger.error(error);
  });

從項目的根目錄,index.js 文件包含啟動我們的 Express 服務器的所有代碼。裡面的想法是我們有一個 startup() 之前調用的方法 我們設置了我們的 HTTP 服務器(這設置了我們的錯誤事件偵聽器,如果我們願意,還設置了在我們的 HTTP 服務器啟動之前需要加載的任何其他內容)。

.then() startup() 的回調 方法,我們調用熟悉的express() 函數,接收我們的 app 實例作為回報。有了這個,我們監聽 process.env.PORT 上的連接 (通常在部署應用程序時設置)或默認端口 5001 .

就在我們對 app.listen() 的調用之上 我們調用兩個函數middleware()api() 它包含我們的應用程序實例。這些函數用於分隔我們的代碼以進行組織。我們將編寫用於在 api() 內生成 PDF 的測試路線 在這裡發揮作用。

現在讓我們來看看那個函數:

/api/index.js

import generatePDF from "../lib/generatePDF";
import graphql from "./graphql/server";

export default (app) => {
  graphql(app);

  app.use("/pdf", (req, res) => {
    // We'll call to generatePDF() here...
  });
};

app 我們從 /index.js 傳入的實例 ,在這裡,我們為我們的服務器設置了 API。默認情況下,這個樣板使用 GraphQL 作為其主要 API,所以在這裡,我們通過 graphql() 調用來設置該 GraphQL API , 也傳入 app 實例。我們不會在本教程的工作中使用它。

我們關心的部分是我們對 app.use() 的調用 , 傳入 /pdf 我們期望我們的路線生活的路徑。我們的目標是,當我們訪問這條路線時,我們會調用 generatePDF() ——傳入一些 HTML 和 CSS——然後將其返回到我們的路由。重點是在瀏覽器中呈現我們的 PDF 文件(使用瀏覽器的內置 PDF 查看器),這樣我們就可以驗證我們的功能是否正常工作並訪問免費下載按鈕。

/api/index.js

import generatePDF from "../lib/generatePDF";
import graphql from "./graphql/server";

export default (app) => {
  graphql(app);

  app.use("/pdf", async (req, res) => {
    const pdf = await generatePDF(`
      <html>
        <head>
          <title>Test PDF</title>
        </head>
        <body>
           // The contents of our PDF will go here...
        </body>
      </html>
    `);

    res.set("Content-Type", "application/pdf");
    res.send(pdf);
  });
};

為此,使用 generatePDF() 我們之前編寫的函數並已導入頂部,在 Express 路由的回調函數內部,我們添加 async 我們之前了解的關鍵字,然後調用 generatePDF() , 傳入一個 HTML 字符串(我們接下來會添加)。

回想一下,當我們調用 generatePDF() ,我們希望將我們的 PDF 作為 文件緩衝區 (我們瀏覽器的內存表示)。巧妙的是,如果我們告訴入站 HTTP 請求格式——Content-Type ——在我們的響應中,它會以不同的方式處理我們發回給它的數據。

在這裡,我們使用 .set() HTTP res 上的方法 ponse 對象,說“我們要設置 Content-Type application/pdf 的標頭 ." application/pdf 部分是所謂的 MIME 類型。 MIME 類型是瀏覽器普遍識別的文件/數據類型。使用該類型,我們可以告訴我們的瀏覽器“我們為響應您的請求而發回的數據採用以下格式。”

之後,我們需要做的就是調用.send() res 上的方法 ponse,傳入我們的 pdf 文件緩衝區。瀏覽器負責其餘的工作!

在我們對此進行測試之前,讓我們充實我們的測試 HTML:

/api/index.js

import generatePDF from "../lib/generatePDF";
import graphql from "./graphql/server";

export default (app) => {
  graphql(app);

  app.use("/pdf", async (req, res) => {
    const pdf = await generatePDF(`
      <html>
        <head>
          <title>Test PDF</title>
          <style>
            body {
              padding: 60px;
              font-family: "Hevletica Neue", "Helvetica", "Arial", sans-serif;
              font-size: 16px;
              line-height: 24px;
            }

            body > h4 {
              font-size: 24px;
              line-height: 24px;
              text-transform: uppercase;
              margin-bottom: 60px;
            }

            body > header {
              display: flex;
            }

            body > header > .address-block:nth-child(2) {
              margin-left: 100px;
            }

            .address-block address {
              font-style: normal;
            }

            .address-block > h5 {
              font-size: 14px;
              line-height: 14px;
              margin: 0px 0px 15px;
              text-transform: uppercase;
              color: #aaa;
            }

            .table {
              width: 100%;
              margin-top: 60px;
            }

            .table table {
              width: 100%;
              border: 1px solid #eee;
              border-collapse: collapse;
            }

            .table table tr th,
            .table table tr td {
              font-size: 15px;
              padding: 10px;
              border: 1px solid #eee;
              border-collapse: collapse;
            }

            .table table tfoot tr td {
              border-top: 3px solid #eee;
            }
          </style>
        </head>
        <body>
          <h4>Invoice</h4>
          <header>
            <div class="address-block">
              <h5>Recipient</h5>
              <address>
                Doug Funnie<br />
                321 Customer St.<br />
                Happy Place, FL 17641<br />
              </address>
            </div>
            <div class="address-block">
              <h5>Sender</h5>
              <address>
                Skeeter Valentine<br />
                123 Business St.<br />
                Fake Town, TN 37189<br />
              </address>
            </div>
          </header>
          <div class="table">
            <table>
              <thead>
                <tr>
                  <th style="text-align:left;">Item Description</th>
                  <th>Price</th>
                  <th>Quantity</th>
                  <th>Total</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td style="text-align:left;">Swiss Army Cat</td>
                  <td style="text-align:center;">$32.70</td>
                  <td style="text-align:center;">x1</td>
                  <td style="text-align:center;">$32.70</td>
                </tr>
                <tr>
                  <td style="text-align:left;">Holeless Strainer</td>
                  <td style="text-align:center;">$9.00</td>
                  <td style="text-align:center;">x2</td>
                  <td style="text-align:center;">$18.00</td>
                </tr>
                <tr>
                  <td style="text-align:left;">"The Government Lies" T-Shirt</td>
                  <td style="text-align:center;">$20.00</td>
                  <td style="text-align:center;">x1</td>
                  <td style="text-align:center;">$20.00</td>
                </tr>
              </tbody>
              <tfoot>
                <tr>
                  <td colSpan="2" />
                  <td style="text-align:right;"><strong>Total</strong></td>
                  <td style="text-align:center;">$70.70</td>
                </tr>
              </tfoot>
            </table>
          </div>
        </body>
      </html>
    `);

    res.set("Content-Type", "application/pdf");
    res.send(pdf);
  });
};

<head></head> 我們的 HTML 標記,我們添加了一些 CSS 來設置我們在 <body></body> 中添加的標記的樣式 標籤。雖然細節超出了本教程的範圍,但我們得到的是一個簡單的發票設計(PDF 渲染的常見用例):

如果我們訪問 http://localhost:5001/pdf 在我們的網絡瀏覽器中,內置的 PDF 閱讀器應該啟動,我們應該看到我們的 PDF 呈現在屏幕上。從這裡,我們可以使用右上角的下載按鈕將副本保存到我們的計算機。

總結

在本教程中,我們學習瞭如何使用 Puppeteer 將 HTML 轉換為 PDF。我們了解瞭如何創建 Puppeteer 瀏覽器實例、在該實例上打開頁面以及設置該頁面的 HTML 內容。接下來,我們學習瞭如何將該 HTML 頁面轉換為 PDF 文件緩衝區,然後在緩存在變量中後,關閉 Puppeteer 頁面和瀏覽器實例以節省內存。

最後,我們學習瞭如何獲取從 Puppeteer 收到的 PDF 文件緩衝區,並使用 Express 在瀏覽器中進行渲染。


Tutorial JavaScript 教程
  1. 在 JavaScript 中將數組與 0 進行比較

  2. Javascript 圖像 URL 驗證

  3. 異步 JavaScript:歷史、模式和陷阱

  4. 無法在 Blazor 中移動 div

  5. JavaScript正則表達式在字符串後獲取數字

  6. Jamstack 的演變

  7. 消除副作用 - 有些果汁不值得擠

  1. 我寫了一個狀態管理 npm - Any State

  2. Python 和瀏覽器 - 重溫

  3. 7 個由 AI 驅動的開發工具,可提高開發人員的工作效率

  4. 快速反應

  5. 空值合併運算符 ??

  6. Javascript:命名捕獲組

  7. 更多可用的表單——控制滾動位置

  1. Javascript Array.push 比 Array.concat 快 945 倍🤯🤔

  2. 在 React Native 中使用 Expo 在 5 分鐘內實現推送通知

  3. 一次性解決最佳買賣股票問題

  4. JavaScript 函數保護