NipoPlusには日報をPDFに変換する機能があります。PDFファイルを生成するにはいくつかのライブラリがありますが、今回はpdfmakeを使用しました。
pdfmakeはフロント(ブラウザ側)でもバックエンド(サーバ)でも利用できますが、それぞれで使い方が微妙に異なる点に注意が必要です。
NipoPlusではPDFの生成をサーバサイドで行うように設計しました。これはひとえにファイルサイズの問題があるためです。
pdfmake単体で2MB!加えて日本語フォント情報も別に持つ必要があり、subset化しても総重量は5MBを超えます
1つのPDFファイルをNode.jsで作成する#
pdfmakeをサーバサイドで利用するには、次のようなコードで実行できます。
たったこれだけで、「これは1つめのPDFファイルです」と書かれたPDFファイルが生成できます。思ったより簡単ですね。
Typescriptなので環境の構築も必要になります。構築はこちらの記事が参考になります。
日本語フォントについては有償、無償様々なものがありますので、お好みで用意してください。商用可で無償なフォントだとIPAフォントが有名ですね。
補足としてこのプロジェクトのpackage.jsonは以下の通りです。
フロントばかりコーディングしているとstreamとかpipeといった概念がほとんどでてこないので、結構新鮮でした。
そしてこのstreamはとっつきにくく、これらのせいで丸一日時間を浪費することに・・・
PDFファイルを生成せずに直接アーカイブする(複数ファイル)#
nodejsでアーカイブを作成するために今回はachiverを使用しました。
といった形でappendすることでZipファイルにまとめていきます。
achive.appendにpdfmakeで作成したpdfファイルを追加していくことで複数のPDFファイルを1つのZipにアーカイブできます。
pdfmakeでは pdfDoc.pipe(writeStream)と書くことでpdfファイルを出力できますが、一旦ファイルに書き出さなくてもpdfmakeから直接archiveに送ることができます。
pdfmakeは公式ガイドが少し不親切ですが、githubのサンプルページに記載が有りました。
Cloud Functionsとかを抜きにして、単純なnodejsで2つのpdfファイルを作成し、1つのZipにまとめるコードは次のとおりです
上記プログラムを実行するとoutput.zipという圧縮ファイルが1つ作成されます。解答すると中には2つのpdfファイルが保存されています。
もしこれをFirebaseのCloud Functions上で実行させる場合は保存先がstorageになると思いますので、ファイルの書き出し先を次のように書き換えてあげればOKです
非同期処理とCloud Functionsのライフサイクルに注意する#
returnのが実行されると、streamの処理が途中でも関数を終えてしまうことを正しく理解していなかったため、原因究明に思った以上の時間を浪費してしまいました。
のような形で処理が終わることを検知できますが、それより先にreturnが走らないように注意する必要があります。
そのための解決として処理をまるごと関数に切り出してPromiseを返す関数として実行するのがシンプルで見やすいですね。
async / awaitや promiseは普段何気なく使用していますが、一度立ち止まって改めて学び直すことでより理解を深めたいと思います。
また今回、この機能の解決に思いの外時間を要したのはひとえにstreamの特性を正しく理解していなかったことにあります。
本当はもっと効率の良いやり方があるのだと思いますが、思いの外pdfmakeに関する情報が見当たりませんでした。
サーバサイドでPDFを作成する場合はpdfmakeではなくpdfKitのほうが人気がありそうですね。機会があれば今度pdfkitも触れてみたいと思います。
もしこの記事が私と同じような悩みを抱えている方のヒントに慣れば幸いです。