かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Node.js URL を作りたくて path.join を使うと https:// が https:/ に変換されてしまう

SSG 時にパスをドメインから始まるものに変更したいという要件があって、path.joinドメインとパスを合体させてハマってしまったのでメモ

環境
  • Node.js v18.18

path.join に URL を渡すとhttps://https:/ になってしまう

http://, https:// から始まる URL もパスだよね?と思って、path.join() で結合したら protocol の /// に変換されてしまい 404 な URL になってしまった

const path = require('path');

const host 1= 'https://example.com';
path.join(host1, '/foo');
// => https:/example.com/foo

const host2 = 'http://example.com';
path.join(host2, '/bar');
// => http:/example.com/bar

path.resolve はもっと壊れる

const path = require('path');

const host 1= 'https://example.com';
path.resolve(host1, '/foo');
// => /foo

const host2 = 'http://example.com';
path.resolve(host2, '/bar');
// => /bar

どうやら path モジュールは URL はパスではないという扱いなのかもしれないようです

URL を組み立てる時は new URL を使うほうが良さそう

new URL(input[, base])
input The absolute or relative input URL to parse. If input is relative, then base is required. If input is absolute, the base is ignored. If input is not a string, it is converted to a string first.
base The base URL to resolve against if the input is not absolute. If `bas is not a string, it is converted to a string first.

new URL() はオブジェクトを返すので .href プロパティで結合した URL が取得できる

const host 1= 'https://example.com';
new URL('/foo', host1).href;
// => https://example.com/foo

const host2 = 'http://example.com';
new URL('bar', host2).href;
// => http://example.com/bar

new URL は base が URL でない時はエラーになる

undefined や URL でない文字列の時 Invalid URL というエラーが発生する

new URL('/foo', undefined);
// => TypeError [ERR_INVALID_URL]: Invalid URL
new URL('/foo', '/');
// => TypeError [ERR_INVALID_URL]: Invalid URL
new URL('/foo', '//');
// => TypeError [ERR_INVALID_URL]: Invalid URL

/ 始まりな絶対パスにしたいケースと http から始まる URL にしたいケースが混ざる場合は、URL にしたい時だけ new URL に渡す場合分けが必要になる

new URL の input が URL の場合 base は無視される

パスの生成をユーティリティ化すると、input に渡される値が制御出来ない事も想定される。
new URL の場合 input が URL だと base の値は無視される仕様になっているっぽい

const HOST = 'https://example.com' as const;
const buildPath = (input: string) => {
  return new URL(input, HOST).href;
};

buildPath('/images/sample.png');
// => https://example.com/images/sample.png

buildPath('https://sample/images/test.png')
// => https://sample/images/test.png

渡されるパスが / 始まりか担保されない、環境変数にあるドメイン/ で終わるか担保されない。という条件だったので、path.join() で URL 作るのが安全やろ〜って軽い気持ちでユーティリティ作ってなんで https:/ になってるの?????ってはまってしまいました

new URL protocol を https://, https:// 以外だと実装差もあるみたいですが、URL を組み立てる時はこちらを使ったほうが良さそう。と学びました!
学び〜 ₍ᐢ⑅•ᴗ•⑅ᐢ₎


[参考]