📘

webpack / vite で public path の設定はもういらなくなっていた

2021/08/06に公開

※ただし IE は除く

問題

今までのフロントエンドの悩みの一つに、 publicPath の設定がありました。これはchunkを含むビルドした際に、chunk を解決するホストを明示的に指定する必要がある、というものです。

例えば、 https://cdn.example.test/assets/module.js に静的ファイルを配置する際、 publicPath として https://cdn.example.test/assets/ を指定する、という必要がありました。これはレガシーブラウザ環境だと module.js が自分自身がどのようなパスとして実行されているからわからず、相対パスのファイルのパスを知る方法がなかったのでビルド時に指定する必要があったわけです。

解決方法

現在、ESM なら import.meta.url<script src="..."></script> として呼ばれた場合、 document.currentScript.src で自身の実行パスがわかります。

これを使った解決方法があるはずで、今見たら webpack / vite 双方に解決策がありました。

vite: library mode

index.html を持つ standalon なビルドなら自身のアセットパスをそのまま配布すればいいとして、 今回は library mode でビルドを作った際の解決方法です。

import { defineConfig } from "vite";
import path from "path";

export default defineConfig({
  base: "./",
  build: {
    target: "esnext",
    lib: {
      fileName: "mod",
      formats: ["es"],
      entry: path.join(__dirname, "src/mod.ts"),
    },
  },
});

これは dist/mod.es.js を生成します。任意の環境から esm として読み込みます。

この設定で見るべきは base "./" で、これは自分自身からの相対パスとしてモジュールを解決しようとします。

これを任意の場所にデプロイすると、これで分割された chunk 含めて正しく機能します。

<script type="module" src="<deploy path>/mod.es.js"></script>

(正確には、 ./ の相対パスをリクエストされた際に、静的アセットサーバーがリクエスト元からの相対パスを解決する必要があります。最近のCDNなら大丈夫ですが、自前のサーバーだとディレクトリトラバーサル対策等でだめなケースがあるかも)

webpack

// https://webpack.js.org/guides/public-path/#automatic-publicpath
module.exports = {
  output: {
    publicPath: 'auto',
  },
};

webpack は自分の手元で試してませんが、 仕組み上同じになります。 IE で動かす場合、 currentScript の polyfill が必要です。

amiller-gh/currentScript-polyfill: An exceptionally slim polyfill for document.currentScript in IE9+

この polyfill は昔からちょっと問題があるので、自分はあんまり信用してないです…。

おわり

複雑なフロントエンドを気軽に配れる方法がやっと確立された気がする

Discussion