📦

Webpack で publicPath を実行時に設定する

2021/07/17に公開

TL;DR

  • Webpack では実行時に __webpack_public_path__ をセットすることで publicPath を変更することができる
  • CDN の有無など、環境によって publicPath が変わる場合に便利
  • Rails では ASSET_HOST 環境変数を meta タグ経由などで渡すとよい

Webpack では publicPath を output.publicPath で設定するのが通常である。
ただ、 webpack build のタイミングでは適切な publicPath を決定できないこともある。たとえば、 webpack build の結果を焼き込んだ Docker イメージを production (CDN使用) と staging (CDNなし) に共通で使用するケースがあてはまる。

この状況対応するため、Webpack には publicPath を自動的に推定してくれる機能 がある。
しかし、 publicPath: '/' では困る場合 (たとえば Rails では /packs/ にするのが通例)にはこの機能は使えない。

そこで、Webpack の実行時に __webpack_public_path__ を設定することで publicPath を設定する機能 を使うとよい。

これが必要になるのは Rails のようなアプリケーションサーバーと組み合わせて Webpack を使う(アセットのディレクトリを / にしにくい)ケースが多いだろうから、以下は Rails を例にして説明する。

Rails の場合の設定例

ASSET_HOST 環境変数 (Rails.configuration.action_controller.asset_host) を meta タグに書き込み、Webpack のエントリーポイントの頭で読み込むとよい。

app/views/layouts/application.html.erb
<head>
  <meta name="asset-host" content="<%= Rails.configuration.action_controller.asset_host %>" />
  <%= javascript_pack_tag 'application' %>
</head>
application.js
// import はファイル内で最初に処理される。
// __webpack_public_path__ の設定をエントリーポイント直下で行うと
// 他の import の後になってしまうので、専用のファイルに切り出し、最初の import にする
import './setPublicPath';
// import { foo } from './foo';
setPublicPath.js
function getRailsAssetHost() {
  const assetHostElm = document.querySelector("meta[name='asset-host']");
  if (assetHostElm !== null) {
    return assetHostElm.getAttribute("content");
  } else {
    return undefined;
  }
}

const railsAssetHost = getRailsAssetHost();
if (typeof railsAssetHost !== "undefined") {
  // もともと webpack.config.js で設定されていた publicPath と ASSET_HOST を結合する
  const originalPublicPath = __webpack_public_path__;
  __webpack_public_path__ = railsAssetHost + originalPublicPath;
}

余談

__webpack_public_path__ は Webpack のコンパイル時に __webpack_require__.p に置き換えられるので、コンパイルされた JS 内を前者で検索しても見つからない。これは Webpack の実装詳細なので変更される可能性はある。

Discussion