はじめに
前回のエントリーでは、Azure Functions で Puppeteer を動かして、特定のページのスクリーンショットを取得する Web API を作ってみました。 uncaughtexception.hatenablog.com
Web API で動作がするのは確認できましたが、Web ブラウザーのアドレスランを編集して URL クエリーに対象の URL を入れるのがやや面倒なので、Web API を使用しつつ Web ページで UI を作ってみたいと思い、 静的ページとバックエンド API を一緒にデプロイできる Azure Static Web Apps (以下 SWA、ちな Preview) で試してみました。
Azure Static Web Apps を作る
クイック スタートがあるので、そのまま使います。
ざっくりいうと、
です。
すると、テンプレート状態のリポジトリに、GitHub Action が追加されます。
あとはこのリポジトリに git push していけば GitHub Action が良しなにビルド&デプロイしてくれるはずです。
API を追加する
出来上がったレポジトリーはフロントエンド用のコードのみなので、バックエンド API を追加します。
# ここからは VSCode + Azure Functions 用拡張機能を使います。
ここまでやるとこういう構成が出来上がります。
スクリーンショット API 実装
Puppeteer でスクリーンショットを作成する API を実装します。
まずは api
ディレクトリーで Puppeteer をインストール。
npm i puppeteer
次に api/screenshot/index.js
を実装。
どこにでもありそうなサンプルです。
const puppeteer = require("puppeteer"); module.exports = async function (context, req) { const url = (req.body && req.body.url) || req.query.url || "https://microsoft.com/ja-jp/"; const browser = await puppeteer.launch({ timeout: 0 }); const page = await browser.newPage(); await page.goto(url); const screenshotBuffer = await page.screenshot({ fullPage: true }); await browser.close(); context.res = { body: screenshotBuffer, headers: { "content-type": "image/png" } }; };
SWA にパブリッシュしてみる。
ローカルで動くことを確認したら、git push。
Git push が無事成功し、GitHub Action でビルドが実行され、無事 SWA 上にデプロイが完了、、、しません。
Zipping Api Artifacts
Done Zipping Api Artifacts
The content server has rejected the request with: BadRequest
Reason: The size of the function content was too large. The limit for this static site is 30000000 bytes.
30MB、、、?ドキュメントには 100MB ってあるけど??
似た issue があったので、便乗して聞いてみみるとすぐ返信が。
so currently we have 2 size limits: one for you app (that is the well documented 100 MB limit), the other limit is for the total size of your function. It seems you are hitting the function size limit (30 MB).
要は、プレビュー期間中のサイズ制限は 2 つあって、
-
app
(恐らくフロントエンドの vue とか React とかのアプリ) は 100MB まで - Function のコードは 30MB まで
とのこと。
今回は Puppeteer に含まれる Chromium がデカかったため、30MB の制限を超えていました。
たとえ 100MB だとしても、Chromium がデカすぎて入りきらない気がします。
※ 念のためもう1回いますが、プレビュー期間中の制限です。
そういえば、前回 Azure Functions へデプロイした時は、リモート ビルドを使っていました。
リモート ビルドにより Puppeteer や Chromium のバイナリーはデプロイ後にインストールしていたので、デプロイ パッケージ自体は大したサイズではありませんでした (node_modules が丸ごと含まれないので)。
回避策 (非推奨。どうしても今すぐ試した人向け)
GitHub Action からのデプロイが、リモート ビルドに対応してくれればいいんですが、どうも GitHub Action で実行されるビルド用コンテナー次第なので、今すぐどうこうすることは難しそうです。
というわけで、関数コードの実装で無理やり回避してみます。
CAUTION!
以下、とてもお勧めできない実装が続きますので、用法用量を守って正しくお使いください。
(2021/05/17 追記)
Azure Static Web Apps は 2021/05/12 に一般提供を開始し、Bring Your Own Functions 機能 (BYOF) を提供しています。
BYOF により手持ちの Azure Functions リソースを Static Web Apps のバックエンドに使うことが可能になったので、こちらのエントリーにまとめた Azure Functions での Puppeteer 使用方法を使って、Puppeteer が使える Azure Functions を作成し、それを Static Web Apps とリンクする方法がが適切です。
uncaughtexception.hatenablog.com
(追記終わり)
まず GitHub Action でのビルドで Puppeteer がインストールされないよう、 api/package.json
の dependencies
から puppeteer
を消しておきます。
# この対応の時点でもうね。。。
次にユーティリティー関数として、実行時に npm install
でモジュールをインストールする requireAsync
という関数を定義します。
api/common/preinvocation.js
const util = require("util"); const exec = util.promisify(require("child_process").exec); const moduleMap = {}; const requireAsync = async function (module) { if (moduleMap[module]) return moduleMap[module]; try { moduleMap[module] = require(module); } catch { await exec(`cd /home && npm i ${module}`); moduleMap[module] = require(module); } return moduleMap[module] }; exports.requireAsync = requireAsync;
指定されたモジュールがロードできれば、それをキャッシュしつつそのまま返し、
ロードできなければ、/home
配下に npm install
して、それをロードして返します。
/home
に移動しているのは、関数コードがある /home/site/wwwroot
は読み取り専用になっている (はず) だからです。
この requireAsync
関数を使うように、api/screenshot/index.js
の冒頭も変えます。
api/screenshot/index.js
(変更部分のみ)
const { requireAsync } = require("../common/preinvocation"); module.exports = async function (context, req) { const puppeteer = await requireAsync("puppeteer");
結果、関数実行時に /home/node_modules
に Puppeteer がインストールされていなければ、そのタイミングでインストールします。
あればロードされるはずです。
想像つくと思いますが、初回の実行は極めて遅いです。
そりゃそうですよね、Puppetter をインストールしているですから。
完成
と言っていいのかわかりませんが、一通り動くものがこちら。
※フロント エンドのコードは、Vue を全く知らない人が想像で書いたレベルです。
結論
早く GA して、サイズ制限を取っ払ってほしい。