1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PuppeteerをDockerで動かしPDFを生成させる

Posted at

目的

puppeteerをdockerで動かして、PDFを生成させたくなった。

環境

  • Apple M3(Apple Silicon)
  • Node.js 20

デフォルトのchromeを使いたかったが…

$ pnpm add puppeteer

上記のコマンドではデフォルトで、chrome for testingchrome-headless-shellのバイナリーが$HOME/.cache/puppeteerへインストールされる。

しかし、このまま使うと以下のエラーがでてしまった。

Error: Failed to launch the browser process!
rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2

これはchromeがarm64のlinux用のバイナリを提供していないことが原因な模様。

arm64 linuxに対応しているchromiumを使うことにする。

dockerfile

次のようなdockerfileで動作した。

FROM node:20-slim

RUN apt-get update && \
  apt-get upgrade -y && \
  apt-get install -y \
  chromium \
  fonts-ipafont-gothic \
  fonts-wqy-zenhei \
  fonts-thai-tlwg \
  fonts-kacst \
  fonts-freefont-ttf \
  libxss1 \
  --no-install-recommends && \
  rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY package.json pnpm-lock.yaml ./

RUN npm install pnpm -g

# Puppeteer は同梱ブラウザをダウンロードしない(システム Chromium を使用)
ENV PUPPETEER_SKIP_DOWNLOAD="true"
RUN pnpm install --frozen-lockfile

COPY . .

RUN pnpm build

ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
CMD ["pnpm", "start"]

chromiumのインストールの他に、以下を参考にして主要なフォントに対応する。

PUPPETEER_SKIP_DOWNLOAD="true"を設定することで、不要なchrome for testingのインストールをスキップさせる。
PUPPETEER_EXECUTABLE_PATHにchromiumのパスを設定しておく。

ソースコード

launchOptions.executablePathにchromiumのパスを指定し、sandboxなしで動作させる。

#!/usr/bin/env node

import puppeteer from 'puppeteer';

async function generatePdf(): Promise<void> {
  const outputPath = process.argv[2] ?? 'output/output.pdf';

  const html = `
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Sample PDF</title>
    <style>
      body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; margin: 24px; }
      h1 { font-size: 24px; margin-bottom: 8px; }
      p { line-height: 1.6; }
      .box { margin-top: 16px; padding: 12px 16px; border: 1px solid #ddd; border-radius: 8px; }
    </style>
  </head>
  <body>
    <h1>Hello, PDF</h1>
    <p>これは Puppeteer で HTML 文字列から生成した PDF です。</p>
    <p>こんにちは(日本語)</p>
    <p>Hello(英語)</p>
    <p>你好(中国語・簡体字)</p>
    <p>您好(中国語・繁体字)</p>
    <p>Hola(スペイン語)</p>
    <p>Bonjour(フランス語)</p>
    <p>Guten Tag(ドイツ語)</p>
    <p>Здравствуйте(ロシア語)</p>
    <p>Olá(ポルトガル語)</p>
    <p>Ciao(イタリア語)</p>
    <p>안녕하세요(韓国語)</p>
    <p>مرحباً(アラビア語)</p>
    <p>नमस्ते(ヒンディー語)</p>
    <p>สวัสดี(タイ語)</p>
    <p>Xin chào(ベトナム語)</p>
    <p>Halo(インドネシア語)</p>
    <p>Merhaba(トルコ語)</p>
    <p>Hallo(オランダ語)</p>
    <p>Γειά σας(ギリシャ語)</p>
    <p>Hej(スウェーデン語)</p>
    <div class="box">
      出力先: <code>${outputPath}</code>
    </div>
  </body>
</html>`;

  const launchOptions: Parameters<typeof puppeteer.launch>[0] = {};
  if (process.env.PUPPETEER_EXECUTABLE_PATH) {
    launchOptions.executablePath = process.env.PUPPETEER_EXECUTABLE_PATH;
    launchOptions.args = ['--no-sandbox', '--disable-setuid-sandbox'];
  }

  const browser = await puppeteer.launch(launchOptions);
  try {
    const page = await browser.newPage();
    await page.setContent(html, { waitUntil: 'networkidle0' });
    await page.emulateMediaType('screen');
    await page.pdf({
      path: outputPath,
      format: 'A4',
      printBackground: true,
      margin: { top: '12mm', right: '12mm', bottom: '12mm', left: '12mm' }
    });
    // eslint-disable-next-line no-console
    console.log(`PDF を出力しました: ${outputPath}`);
  } finally {
    await browser.close();
  }
}

generatePdf().catch((error) => {
  // eslint-disable-next-line no-console
  console.error(error);
  process.exitCode = 1;
});


1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?