mini_racerでNode.jsのURL APIを使うときに注意すること

## 結論

:::note
正確にはmini_racerを使う時に限ったことではないのですが、Node.jsのAPIのURLには、legacy APIとWHATWG準拠のAPIがあることに注意しましょう

(ブラウザのURL APIもWHATWG API準拠)
:::

https://nodejs.org/dist/latest-v19.x/docs/api/url.html#url-strings-and-url-objects

上記の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()`をするという違いがあります。

```js
// WHATWG API
const myURL = new URL('https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash');
```
```js
// 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`のパッケージを使おうとしていました。

https://github.com/defunctzombie/node-url

このパッケージはwebpack 4までfallbackとして自動で追加されていたものです。

https://webpack.js.org/configuration/resolve/#resolvefallback

しかしこのパッケージはNode.jsのURLモジュール (legacy API)を提供しているもので、WHATWG準拠のAPIのように `new URL()`のように使えませんでした。
Node.jsのURL APIには2種類あるのを知りませんでした...

## 対応方法

`whatwg-url`を用いて、ブラウザと同様にWHATWG準拠のURL APIを使いました。

https://github.com/jsdom/whatwg-url

```js
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を判定する処理を入れたいと思っています。

```js:util/universal-url.js
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 }
```

参考: https://github.com/stevenvachon/universal-url