結論
正確にはmini_racerを使う時に限ったことではないのですが、Node.jsのAPIのURLには、legacy APIとWHATWG準拠のAPIがあることに注意しましょう
(ブラウザのURL APIもWHATWG API準拠)
上記のAPIドキュメントから引用します。
下の図は'https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash'
というURLを両方のAPIでパースしたときの違いを表しています。
上半分がlegacy APIのurl.parse()
を使ったときで、下半分がWHATWGのURLオブジェクトのプロパティを表しています。
originプロパティなど、いろいろ違いがあります。
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ href │
├──────────┬──┬─────────────────────┬────────────────────────┬───────────────────────────┬───────┤
│ protocol │ │ auth │ host │ path │ hash │
│ │ │ ├─────────────────┬──────┼──────────┬────────────────┤ │
│ │ │ │ hostname │ port │ pathname │ search │ │
│ │ │ │ │ │ ├─┬──────────────┤ │
│ │ │ │ │ │ │ │ query │ │
" https: // user : pass @ sub.example.com : 8080 /p/a/t/h ? query=string #hash "
│ │ │ │ │ hostname │ port │ │ │ │
│ │ │ │ ├─────────────────┴──────┤ │ │ │
│ protocol │ │ username │ password │ host │ │ │ │
├──────────┴──┼──────────┴──────────┼────────────────────────┤ │ │ │
│ origin │ │ origin │ pathname │ search │ hash │
├─────────────┴─────────────────────┴────────────────────────┴──────────┴────────────────┴───────┤
│ href │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
また、パースの仕方も、legacy APIではモジュールをimportした上でurl.parse()
をするという違いがあります。
// WHATWG API
const myURL = new URL('https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash');
// legacy API
import url from 'node:url';
const myURL = url.parse('https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash');
はまった点
JavaScriptランタイムとしてmini_racerを使っている環境でURL APIを使おうとしました。
mini_racerはJavaScriptエンジンとしてV8を使っているのですが、ブラウザやNode.jsとは違ってURL APIが実装されていません。
そのため、最初は以下のnode-url
のパッケージを使おうとしていました。
このパッケージはwebpack 4までfallbackとして自動で追加されていたものです。
しかしこのパッケージはNode.jsのURLモジュール (legacy API)を提供しているもので、WHATWG準拠のAPIのように new URL()
のように使えませんでした。
Node.jsのURL APIには2種類あるのを知りませんでした...
対応方法
whatwg-url
を用いて、ブラウザと同様にWHATWG準拠のURL APIを使いました。
import { URL, URLSearchParams } from 'whatwg-url'
const url = new URL('https://qiita.com')
const params = new URLSearchParams('?param=value')
残りの課題
例えばSSRしているコンポーネントでブラウザ上でも実行したい場合、上記の方法でも実行できますが、ブラウザにはURL APIが実装されているため、不要なソースコードをimportしてしまっていることになります。
そのため以下のように、globalThisにURL APIが実装されているかを判定して使うAPIを判定する処理を入れたいと思っています。
import {
URL as whatwgURL,
URLSearchParams as whatwgURLSearchParams,
} from 'whatwg-url'
let hasNative
let outputURL
let outputURLSearchParams
try {
const url = new globalThis.URL('https://qiita.com')
const params = new globalThis.URLSearchParams('?param=value')
hasNative = 'searchParams' in url && params.get('param') === 'value'
} catch (error) {
hasNative = false
}
if (hasNative) {
outputURL = globalThis.URL
outputURLSearchParams = globalThis.URLSearchParams
} else {
outputURL = whatwgURL
outputURLSearchParams = whatwgURLSearchParams
}
export { outputURL as URL, outputURLSearchParams as URLSearchParams }