【IE 11 対応版】PDF.js × Webpack

## やりたかったこと

- PDF.js を導入して PDF Viewer を作成する
- IE 11 に対応する
- IDE で PDF.js のプロパティやメソッドに補完を効かせて開発する

## 結論

- PDF.js を node_module としてインストールする
- 開発時は node_module の PDF.js をインポートすることで IDE の補完を有効にする
- 本番時は Prebuilt (ES5-compatible) を HTML で読み込む
- Webpack の externals を用いて HTML で読み込む JavaScript ファイルと連携する

## ディレクトリ構成

```sh
.
├── dist
│   └── js
│       └── main.js
├── src
│   └── js
│       └── main.js
├── vendor
│   └── pdfjs-2.4.456-es5-dist
│       └── 省略
├── README.md
├── babel.config.js
├── index.html
├── package-lock.json
├── package.json
└── webpack.config.js
```

[PDF.js のダウンロードページ](https://mozilla.github.io/pdf.js/getting_started/#download) から Prebuilt (ES5-compatible) の Stable 版をダウンロードして vendor ディレクトリ配下に解凍します。バージョンは上記サンプルと異なる場合があります。

## 依存モジュール

```sh
npm i -D webpack webpack-cli terser-webpack-plugin babel-loader @babel/core @babel/preset-env glob pdfjs-dist
```

## 各ファイル

### index.html

```html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Title</title>
</head>

<body>
    <canvas id="the-canvas"></canvas>

    <script src="vendor/pdfjs-2.4.456-es5-dist/build/pdf.js" defer></script>
    <script src="dist/js/app.js" defer></script>
</body>

</html>
```

### src/js/main.js

```js
import "core-js/stable";
import "regenerator-runtime/runtime";

const pdfjsLib = require("pdfjs-dist");

pdfjsLib.GlobalWorkerOptions.workerSrc = "vendor/pdfjs-2.4.456-es5-dist/build/pdf.worker.js";

pdfjsLib.getDocument("/path/to/xxxx.pdf").promise.then(
  (pdf) => {
    console.log("pdf loaded");

    pdf.getPage(1).then((page) => {
      const viewport = page.getViewport({ scale: 1.0 });

      const canvas = document.querySelector("#the-canvas");
      canvas.width = viewport.width;
      canvas.hieght = viewport.hieght;

      const ctx = canvas.getContext("2d");
      const renderCtx = {
        canvasContext: ctx,
        viewport: viewport,
      };

      page.render(renderCtx).promise.then(
        () => {
          console.log("page rendered");
        },
        (e) => {
          console.error(e);
        }
      );
    });
  },
  (e) => {
    console.error(e);
  }
);

console.log(pdfjsLib);
```

### webpack.config.js

```js
const path = require("path");
const glob = require("glob");
const TerserPlugin = require("terser-webpack-plugin");

module.exports = (env, argv) => {
  const IS_PROD = argv.mode == "production";

  console.log("NODE_ENV: ", env.NODE_ENV);
  console.log("IS_PROD: ", IS_PROD);
  console.log("--mode: ", argv.mode);

  const srcDir = path.resolve(__dirname, "src", "js");

  const entries = {};
  glob.sync("*.js", { cwd: srcDir, ignore: [] }).forEach((filename) => {
    const entry = filename.replace(".js", "");
    entries[entry] = path.resolve(srcDir, filename);
  });
  console.log(entries);

  return {
    mode: argv.mode,
    devtool: IS_PROD ? "none" : "source-map",
    entry: entries,
    output: {
      path: path.resolve(__dirname, "dist"),
      filename: "js/[name].js",
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          loader: "babel-loader",
        },
      ],
    },
    externals: [
      {
        "pdfjs-dist": "pdfjsLib",
      },
    ],
    optimization: IS_PROD
      ? {
          minimizer: [
            new TerserPlugin({
              terserOptions: {
                compress: { drop_console: true },
              },
            }),
          ],
        }
      : {},
  };
};
```

### babel.config.js

```js
module.exports = (api) => {
  api.cache(true);

  return {
    presets: [
      [
        "@babel/preset-env",
        {
          useBuiltIns: "usage",
          corejs: {
            version: "3",
          },
          debug: true,
        },
      ],
    ],
  };
};
```

### package.json

```json
{
  "scripts": {
    "dev": "webpack --env.NODE_ENV=development --mode development --progress",
    "prod": "webpack --env.NODE_ENV=production --mode production --progress"
  },
  "devDependencies": {
    "@babel/core": "^7.10.2",
    "@babel/preset-env": "^7.10.2",
    "babel-loader": "^8.1.0",
    "core-js": "^3.6.5",
    "pdfjs-dist": "^2.4.456",
    "regenerator-runtime": "^0.13.5",
    "terser-webpack-plugin": "^3.0.3",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11",
  },
  "dependencies": {}
}
```

## 試してみたこと

`gulp × browserify × babelify` や `webpack × babel` などで HTML で直接 PDF.js を読み込むのではなく、node_module の PDF.js をバンドルして es5 にトランスパイルする手法も試してみましたが、何かしらのエラーが出てしまいことごとく失敗に終わりました。

webpack の externals の設定を使うことで今回やりたかったことが実現できましたが、もっといい方法があれば知りたいです。