14
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

aptpodAdvent Calendar 2018

Day 2

JSで描画したViewをHeadless ChromeでPDF化するまで

Last updated at Posted at 2018-12-01

aptpodが贈る、aptpod Advent Calendar 2018の2日目担当蔵下です。普段はJSでごにょごにょ開発しています。

本記事の経緯

普段の開発ではIoTの高粒度なデータを扱うことが多く、Viewを実装する上でもかなり工夫しないと動作が激重で使い物にならない...ことになりかねません:fearful: 今回、複数の高粒度のデータ(数十件分)をJSの描画ロジックで描画しブラウザで印刷するという要件があったのですが、ある程度のスペックがないと印刷まで数分、ネットワーク上のプリンターで出力するまでまた数分(場合によってはプリンターが不安定に...)という状況に出くわしました。

この状況を解決するためにクライアント側で実装している描画ロジックを切り出し、Node.js(サーバー)側でHeadless Chromeに描画 → PDF化 → クライアント側でPDFをダウンロードして印刷するまでを試してみることにしました。

必要なもの

  • Node.js
  • Puppeteer
    • Node.jsからHeadless Chromeを簡単に扱えるフレームワーク
    • 何気に初めて触ったけど意外と簡単だった
  • 印刷用HTML(切り出した描画ロジックを実行)

インストール

任意の場所に作業用のフォルダーを用意して、Puppeteerをインストールします。

npm i puppeteer

描画からPDF化するまで

ソースコードを書く前に、今回の Node.js(サーバー)側でHeadless Chromeに描画 → PDF化 を実現するためには次のような処理が必要になります。

  1. Headless Chromeから描画ロジックを実行するHTMLを読み込む
    → HTML側でデータの読み込み処理は実行させたくなかったためNode.js側からデータを渡す
  2. HTML側で描画ロジック(JS)を実行
  3. 描画ロジックが完了したことをHeadless Chromeへ伝える
  4. Headless Chrome側でHTMLをキャプチャしPDF化

このフローでは 3. 描画ロジックが完了したことをHeadless Chromeへ伝える が肝です。それがないと描画ロジックが実行される前にキャプチャの処理が走るため、真っ白なPDFが出来上がるという残念な感じになります:fearful:

ソースコード

Node.js側のタスク

build-pdf.js
const fs = require('fs')
const puppeteer = require('puppeteer')

(async () => {
  // PDFの出力先
  const OUTPUT_PATH = './pdf/'
  // PDFのファイル名
  const FILE_NAME = 'capture-pdf'
  // PDFの用紙フォーマット
  const FORMAT = 'A4';
  const URL = '描画ロジックを実行するHTMLのURL'

  // Headless Chromeを起動
  await puppeteer.launch()
    .then(async browser => {
      // ページ
      const page = await browser.newPage()

      // 描画ロジックに必要なデータ
      const data = {
        text: 'お酒大好き!'
      }
      // HTMLの描画ロジック側からwindow.puppeteerGetDataでdataを取得できるように
      await page.exposeFunction('puppeteerGetData', () => data)

      // HTMLの描画ロジック側からwindow.puppeteerPdfでPDF生成
      await page.exposeFunction('puppeteerPdf',
        async () => {
          // 出力先のディレクトリが無ければ生成
          if (!fs.existsSync(OUTPUT_PATH)) {
            fs.mkdirSync(OUTPUT_PATH);
          }

          // PDF作成処理
          await page.pdf({
            path: `${OUTPUT_PATH}${FILE_NAME}.pdf`,
            format: FORMAT
          });

          // Headless Chromeを閉じる
          browser.close();
        }
    })
})();

Puppeteerと連携するために描画用ロジックに追加するプログラム

描画ロジックで使うデータをPuppeteer側から受け取る処理。

if (window.puppeteerGetData) {
  // Puppeteer側からデータを受け取る
  const data = window.puppeteerGetData()
  console.log(data.text) // 'お酒大好き!'
}

描画ロジック完了後、Puppeteer側でのHTMLキャプチャ処理を実行する。

if (window.puppeteerPdf) {
  // Puppeteer側のキャプチャ処理を実行
  window.puppeteerPdf()
}

タスクの実行

上のソースコードが書けたらタスク実行!!!!

node build-pdf

出力先にPDFが作成さえれば無事成功:tada:

いろいろ試行錯誤したけど思い返せば簡単にできた

Headless Chromeを使ったHTMLのPDF化は、GitHubなどでライブラリとして公開されていたものもありましたが、ページのload完了は見てくれるけどJSの実行完了はみてくれなかったので、最終的には自分でごにょごにょしました(Puppeteer使ったけど:sweat_smile:)。

体感では、描画ロジック実行 → 印刷できる状態(ブラウザでは印刷ダイアログ表示)に比べると数倍速くなったかなと思います(ちゃんとベンチ取れよ〜 お仕事の兼ね合いであれがあれなもんで)。

aptpod Advent Calendar 2018の別日にまた他のネタで書く予定なので、引き続きお付き合いいただけると嬉しいです!

14
6
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
14
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?