viteでpdf.jsを使おうとしたら意外に手間取った

2024/03/28に公開

経緯

私は普段LINEのliffを使ったシステムを構築しているのですが、liffでpdfを表示させるとiPhoneでは綺麗に表示されるがAndroidだとダウンロードされてしまう事態が発生。
しぶしぶ、代わりにブラウザ上でpdfをレンダリングする方法がないか調べているとpdf.jsというライブラリを発見!
日本語記事ではstableなファイルをそのままダウンロードしてサーバー内に配置している方法がよく紹介されているが、gitで管理しているので、ディレクトリを汚したくない!ということでnpmでinstallすることに。
Laravel + vite を使っているので、 @viteでjsを読み込ませ、

import * as pdfjsLib from "pdfjs-dist";

をすると、

エラーが発生

Top-level await is not available in the configured target environment

というエラーがvite上で発生。
そのままGoogleで検索してみると下記issueがHit
https://github.com/mozilla/pdf.js/issues/17245

解決方法

とりあえずvite.config.jsに下記コードを埋め込めば大丈夫みたい

vite.config
    optimizeDeps:{
      esbuildOptions: {
        target: "es2022",
      }
    }

おまけ

pdf全ページ表示するコード全文載せておきます。
地味に全ページ表示させる方法がfor文で回す必要があり、面倒くさかったです。

preview-pdf.js
import * as pdfjsLib from "pdfjs-dist";
import * as pdfWorker from "pdfjs-dist/build/pdf.worker.mjs";

const pdfPath = "/assets/hoge.pdf";


// 非同期でPDFファイルを読み込み
const loadingTask = pdfjsLib.getDocument(pdfPath);
(async () => {
    const pdf = await loadingTask.promise;

    // 全てのページを取得
    for (let i = 1; i <= pdf.numPages; i++) {
        const page = await pdf.getPage(i);
        const scale = 1.0;
        const viewport = page.getViewport({ scale });

        // 高DPIをサポート
        const outputScale = window.devicePixelRatio || 1;

        // PDFのページ寸法を使用してキャンバスを準備
        const canvas = document.createElement("canvas");
        const context = canvas.getContext("2d");

        canvas.width = Math.floor(viewport.width * outputScale);
        canvas.height = Math.floor(viewport.height * outputScale);
        canvas.style.width = Math.floor(viewport.width) + "px";
        canvas.style.height = Math.floor(viewport.height) + "px";

        // 縦並びにするためにblock要素を追加
        canvas.style.display = "block";

        const transform = outputScale !== 1
            ? [outputScale, 0, 0, outputScale, 0, 0]
            : null;

        // PDFのページをキャンバスにレンダリング
        const renderContext = {
            canvasContext: context,
            transform,
            viewport,
        };
        page.render(renderContext);

        // キャンバスをDOMに追加
        pdfElement.appendChild(canvas);
    }
})();

追記

上記の方法で npm run dev しているときは良かったのですが、いざbuildしようとするとまた同じエラーが出ました。そのためもう一つの方法である、vite-plugin-top-level-awaitのプラグインを使うことにしました。

terminal
npm i vite-plugin-top-level-await

vite configに追記、また、謎にpath2dのエラーもで始めたので、追記してます。

vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import topLevelAwait from "vite-plugin-top-level-await";


export default defineConfig({
    plugins: [
        topLevelAwait({
          // The export name of top-level await promise for each chunk module
          promiseExportName: "__tla",
          // The function to generate import names of top-level await promise in each chunk module
          promiseImportName: i => `__tla_${i}`
        }),
        laravel({
            input: [
                'resources/css/app.css',
            ],
            refresh: true,
        }),
    ],
    optimizeDeps: {
        esbuildOptions: {
            target: "es2022",
        }
    },
    build: {
        rollupOptions: {
            // ライブラリーにバンドルされるべきではない依存関係を
            // 外部化するようにします
            external: ['path2d-polyfill']
        },
    },
});

これで解決したが、buildした際に以下の残念な忠告が出るようになってしまいました。
ただ、ここは原因探している暇もないので、無視することに。

terminal
(!) Some chunks are larger than 500 kBs after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.

Discussion