はじめに
maplibre-gl を v5 から v6 に上げたところ、背景地図(MapLibre のベクタータイル)だけが表示されなくなりました。
原因を追っていくと import.meta.url が関係していたのですが、正直なところ私はこの import.meta.url が何なのかをよくわかっていませんでした。
この記事では、今回の不具合をきっかけに整理した import.meta.url の正体と、バンドラ環境でハマるポイントをまとめます。
問題
起きたこと
- OpenLayers で描画しているラスタータイルの地図や、ポイント・ポリゴンなどのベクターデータは描画される
- MapLibre で描画している背景地図だけが真っ白
- DevTools の Network を見ると、背景地図のタイルへのリクエストがそもそも発生していない
原因
maplibre-gl v6 は ESM-only(JavaScript の標準モジュール形式 ES Modules だけで配布する形)になり、タイルの取得・解析を担う Web Worker(ブラウザで別スレッドを動かす仕組み)が maplibre-gl-worker.mjs という別ファイルに分離されました。
本体は、だいたい次のようなコードで Worker を起動します。
new Worker(new URL('./maplibre-gl-worker.mjs', import.meta.url), { type: 'module' });
「自分のファイルの隣にある maplibre-gl-worker.mjs を起動する」という意味ですが、Vite を通すとこの「自分のファイルの場所」がずれて Worker ファイルを見つけられず、Worker が起動しない → タイルのリクエストが発生しない、という状態になっていました。
ここで出てくる import.meta.url が、今回わかっていなかったものです。
解決方法
import.meta.url とは
Vite の機能ではなく、JavaScript の標準機能
import.meta.url は Vite 独自の機能ではなく、ES Modules の言語仕様に含まれる標準機能です。ブラウザでも Node.js でも、ESM として実行されるファイルならそのまま使えます。
-
import.meta:ESM の中だけで使える「このモジュールに関するメタ情報」を持つオブジェクト -
import.meta.url:そのうちの「このモジュールファイル自身の URL」
CommonJS の __filename / __dirname に相当するものが、ESM では import.meta.url だと考えるとイメージしやすいです。
// Node.js(ESM)で __dirname 相当を得る例
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
「自分」とは “そのコードが書かれているファイル自身”
「今このファイルが置かれている場所」と聞くと、カレントディレクトリやページの URL と混同しがちですが、指しているのは そのコードが書かれているモジュールファイル自身がロードされた URL です。
| 混同しやすいもの | import.meta.url の値か |
|---|---|
| プロセスのカレントディレクトリ(cwd) | ❌ 違う |
| ブラウザで表示しているページの URL | ❌ 違う |
| そのモジュールを import した側のファイル | ❌ 違う |
| そのコードが書かれているファイル自体の URL | ✅ これ |
例えば maplibre-gl.mjs の中に import.meta.url と書かれていれば、素の状態では次のような値になります。
https://example.com/node_modules/maplibre-gl/dist/maplibre-gl.mjs
だから new URL('./maplibre-gl-worker.mjs', import.meta.url) と書くと、同じディレクトリにある Worker ファイルを相対パスで指せるわけです。
なぜバンドラを通すとずれるのか
import.meta.url は「実際にロードされた場所」を返します。
ところが Vite は依存ライブラリを事前バンドル(開発時は node_modules/.vite/deps/ に、本番ビルドでは assets/index-xxxx.js のようなチャンクに)してしまうため、maplibre のコードは元の node_modules/maplibre-gl/dist/ から別の場所へ移動します。
すると実行時の import.meta.url は「移動後のファイルの場所」を返すようになり、その隣に maplibre-gl-worker.mjs は存在しません。結果として Worker の読み込みに失敗します。
「私は今どこ?」と書かれた紙は、元の本棚にある限り正しく本棚を指します。
でもその紙を別の本にコピーすると、答えはコピー先の場所に変わり、元の本棚の隣にあった付録(Worker ファイル)には辿り着けなくなります。
対処:setWorkerUrl で場所を明示する
maplibre-gl には、Worker ファイルの URL を自動検出に任せず手動で指定する setWorkerUrl が用意されています。
Vite の ?worker&url を使うと、Vite が Worker を正しくバンドルした上でその URL を文字列で返してくれるので、それを渡します。
import { setWorkerUrl } from 'maplibre-gl';
import maplibreWorkerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';
setWorkerUrl(maplibreWorkerUrl);
-
?urlだけだと Worker が依存するmaplibre-gl-shared.mjsが含まれず、Worker 側の import で失敗します -
?worker&urlにすると、依存込みの自己完結した Worker チャンクとしてバンドルされます
これを MapLibre のマップを生成する前に一度実行するようにしたところ、背景地図のタイルがリクエストされ、正常に描画されるようになりました。
おわりに
-
import.meta.urlは Vite の機能ではなく、ES Modules 標準の「このモジュールファイル自身の URL」 - カレントディレクトリやページの URL ではなく、「そのコードが書かれているファイル」の場所を指す
- バンドラがファイルを移動すると値が変わるため、ライブラリが
import.meta.url基準で別ファイルを探している場合は壊れることがある - そういうときは、ライブラリが用意している URL 指定の API(今回は
setWorkerUrl)でバンドラが把握している正しい URL を渡す
「動いていたものがライブラリ更新で急に動かなくなった」ときは、ファイル構成の変化とバンドラの相性を疑ってみると原因に近づけるかもしれません。
参考
- import.meta - JavaScript | MDN
- 静的アセットの取り扱い(new URL(url, import.meta.url))| Vite
- 機能(Web Workers)| Vite
- MapLibre GL JS 6.0: ESM-Only, WebGL2-Required, and Smaller Bundles
JISOUのメンバー募集中!
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてください!
▼▼▼
https://projisou.jp
