行動すれば次の現実

テック中心の個人ブログ

Railsで簡単にPDFが出力できるGroverを導入してみた(Herokuへのデプロイも)

この記事では、Groverを使ってPDFを出力する際の導入方法や注意点を記載しております。また、Herokuへのデプロイについても説明します。

Groverとは?

GroverとはGoogle PuppeteerとChromiumを使ってHTMLをPDFや画像ファイル変換するGemです。

RailsでGroverを使うと、HTML(ERB)を実装するのと同じ感覚で、簡単にPDF出力ロジックを実装することが出来ます。

github.com

Google Puppeteerとは?

スクリプトを介してヘッドレストブラウザ(GUIを持たないブラウザ)や通常のブラウザを操作することができるフレームワークです。主にブラウザの自動テストなどに使用されます。類似の代表的なフレームワークにSeleniumなどがあります。

developer.chrome.com

Chromiumとは?

Chromeのベースとなるブラウザエンジンです。Chromeとの違いは自動アップデートやクラッシュレポートなどの機能が搭載されていない点です。

Grover、Puppeteer、Chromiumの関係

Grover、Puppeteer、Chromiumの関係について整理します。

  • ① GroverはPuppeteerとChromiumを元にHTMLを描画します。
    • 実際にはPuppeteerがChromiumを使用してブラウザを操作してHTMLを描画します。
  • ② Groverは描画されたHTMLを元に画像ファイル(PDF、PNG、JPEG)を生成します。

Grover、Puppeteer、Chromiumの関係

実装イメージ

インストール

Gemfileにgroverを追加します。

Gemfile

gem 'grover'

設定ファイル

initializersにGroverの共通定義を設定します。 実際の出力処理で個別に上書きすることも可能です。

config/initializers/grover.rb

Grover.configure do |config|
  config.options = {
    format: 'A4',
    margin: {
      top: '30px',
      right: '20px',
      left: '20px'
    },
    cache: false,
    display_url: <%= ENV.fetch("GROVER_DISPLAY_URL") {'localhost:3000' } %>
  }
end

display_urlには自ホストのアドレスを入力してください。これは出力対象のHTMLでCSSファイルを読み込んでいる場合に必要になります。デプロイを想定して環境変数などする必要があります。

HTMLファイル内に直接スタイルを定義している場合、display_urlは不要です。

PDF出力コントローラーの実装例

  1. ActionController::Baseに定義されているrender_to_stringを使用して、指定したtemplateからHTML構文を生成します。
  2. HTML構文をGroverに渡して、to_pdfメソッドでPDFを生成します。
  3. send_dataでPDFファイルをクライアントに返します。
def show
  html = self.class.new.render_to_string(template: 'sample/index')
  pdf = Grover.new(html).to_pdf
  send_data(pdf, filename: 'sample.pdf', type: 'application/pdf', disposition: 'inline')
end

Herokuで使う場合の注意点

Herokuにデプロイする場合はいくつか注意点があります。

buildpackの追加

Herokuで使用するためにはNode.jsとpuppeteerのビルドパックを追加する必要があります。

Node.jsビルドパックの追加

heroku buildpacks:add heroku/nodejs --index=1

puppeteer-heroku-buildpackの追加

heroku buildpacks:add jontewks/puppeteer --index=2

puppeteerのビルドパックの実態はpuppeteer-heroku-buildpackです。このビルドパックにはHerokuサーバー上でpuppeteerを使用するために必要なライブラリ群が含まれています。

しかし、このビルドパックを導入するとSlug Size(Herokuの容量)をかなり消費することになります。HerokuのSlug Sizeは最大500MBまでなのですが、puppeteerを導入すると余裕で400MBは超えてしまうでしょう。

その原因はpuppeteer-heroku-buildpackに含まれているxdg-utilsというライブラリの影響が大きいと思います。このライブラリだけで344MB消費しています。

そのため、puppeteer-heroku-buildpackのGithubではxdg-utilsを除いたタグ(22.0.0-no-xdg-utils)も作成されているようです。xdg-utilsを除くことによる影響は100%ないとは保証できないとのことですので、導入は自己責任でお願いします。

ちなみに私の環境では特に問題なかったので22.0.0-no-xdg-utilsをビルドしています。

heroku buildpacks:add https://github.com/jontewks/puppeteer-heroku-buildpack#22.0.0-no-xdg-utils --index=2

GROVER_NO_SANDBOXの設定

GROVER_NO_SANDBOX=trueという環境変数を設定する必要があります。

no_sandboxはChromiumに対して適用されるようです。

そもそもChromiumのサンドボックスとはタブ毎に独立したプロセスでアプリケーションを稼働する仕組みをさします。デフォルトではこれが有効になっていますのが、Herokuで利用するためには無効にする必要があります。

heroku config:set GROVER_NO_SANDBOX=true

日本語フォントの追加

Herokuは日本語フォントに対応しておらず、そのまま出力しようとすると文字化けしてしまいます。

そのため、日本語フォントをHerokuに読み込ませる必要があります。

文字情報技術促進協議会のサイトからIPAフォントをダウンロードして、任意のttfファイルをプロジェクトのルートに配置します。

IPAexゴシックを使用する例

.fonts/
  - ipaexg.ttf

終わりに

PDF出力用のGemはいくつかありますが、 Groverを使用するとHTMLを実装する感覚でPDFレイアウトを組むことができるので保守性の観点でもおすすめです。

Groverのデメリットは処理が重くなりやすい点だと思います。シンプルなレイアウトであれば数秒で作成されますが、複数ページなどを出力する場合は処理時間の問題が発生します。

Herokuには30秒でタイムアウトしてしまうという仕様がありますので、要件によってはバックグラウンド(非同期処理)でPDFを作成して、完了したらクライアントにダウンロードさせるなどの考慮が必要になるかと思います。