fetchoraw紹介するよ（画像URLを環境変数に応じて置き換えるライブラリ）

毎回スクラッチで書くのが面倒になったんでライブラリ作ってnpmに登録しました。
ちなみに公開ライブラリは初です。
基本は画像データを想定して作っていますが、割と幅広く使えそうな。

## 経緯
- JamStackでプレビューSSR、本番SSGみたいなことあるじゃん？
- 画像類、SSRでプレビューならCMSから直リンクがいいけど、本番は内部に置きたいじゃん？
- でも環境ごとにURL切り替えたり変更分ダウンロードしたりするの面倒じゃん？
- じゃあCMSのURLだけ書いといて、環境変数で勝手にやってくれたらいいな！

プログラマの美徳ですよね、めんどくさがり。

## 何を作った？
もう経緯をそのまま実装しただけです。
- fetchoraw という Node.js 向けの HTML URL 変換ライブラリ
- img[src] などの属性の値（画像URLなど）を、指定ルール(リゾルバ)で差し替える
- microCMS や Contentful などのCMS使っていて
「本番はローカルパスで使いたい」
「データURLにしたい」
「でも条件(環境変数)によって分けたい」
みたいなときに使えます。
- htmlを読み込んで、判断・変換したhtmlを吐きます
- URLだけ読み込んで、判断・変換したURLを吐くこともできます
- astro環境下でテストしていますが、node.js単体でも動作します(静的html変換にどうぞ)

📦 npm: https://www.npmjs.com/package/fetchoraw
📁 GitHub: https://github.com/uzuworks/fetchoraw

## ざっくり使い方
fetchoraw は環境変数の値によって「URLを書き換えるか」を制御できます。デフォルトでは次のようになっています：

環境変数名：FETCHORAW_MODE
有効値：FETCH

以下のように、書き換えたいときだけ環境変数を設定しておけばリゾルバが実行されます。
```sh
# 本番ビルド時など
FETCHORAW_MODE=FETCH npm run build
```
それ以外のときは何もしません。

astroの場合ならLayout.astroのフロントマッターでslotのhtmlに変換かけて、slotの位置にFragmentでset:htmlすればできあがりです。
```js
const fetchoraw = new Fetchoraw(createFileSaveResolver({
  saveRoot: 'dist/microcms',
  targetPattern: Fetchoraw.presets.cms.microcms,
  keyString: Fetchoraw.presets.cms.microcms,
  prependPath: 'microcms'
}));
const htmlData = await fetchoraw.html(await Astro.slots.render('default'));
const html = htmlData.output;
```
```html
<Fragment set:html={html} />
```

以下、リゾルバの使い方など。

### CMSの画像を /assets/ 以下に保存し、URLを置換(FileSaveリゾルバ)
```js
import { Fetchoraw, createFileSaveResolver } from 'fetchoraw';

const resolver = createFileSaveResolver({
  saveRoot: 'dist/assets',
  prependPath: 'assets',
  keyString: /^https?:\/\/images\.microcms-assets\.io\/assets\//,
});

const html = `<img src="https://images.microcms-assets.io/assets/abc123/image.png">`;

const fetchoraw = new Fetchoraw(resolver);
const { output: rewritten } = await fetchoraw.html(html);

console.log(rewritten);
// <img src="/assets/abc123/image.png">
```

### 小さい画像は data URL に、それ以外はスキップ(DataURLリゾルバ)
```js
import { Fetchoraw, createDataUrlResolver } from 'fetchoraw';

const resolver = createDataUrlResolver({
  inlineLimitBytes: 10 * 1024, // 10KB
  allowMimeTypes: [/^image\//],
});

const fetchoraw = new Fetchoraw(resolver);
const { output } = await fetchoraw.html('<img src="https://cdn.example.com/icon.png">');

console.log(output);
// <img src="data:image/png;base64,...">
```

### data URL に失敗したときだけファイル保存する(Smartリゾルバ)
```js
import { Fetchoraw, createSmartResolver } from 'fetchoraw';

const resolver = createSmartResolver({
  inlineLimitBytes: 100 * 1024,
  allowMimeTypes: [/^image\//],
  saveRoot: 'dist/assets',
  prependPath: 'assets',
});

const html = `<img src="https://cdn.example.com/image.jpg">`;

const fetchoraw = new Fetchoraw(resolver);
const { output: out } = await fetchoraw.html(html);

console.log(out);
// -> 小さければ data:image/png,... に、でなければ /assets/... に置換
```

### CDNドメインの置き換えだけしたい(カスタムリゾルバ)
```js
import { Fetchoraw } from 'fetchoraw';

// 開発環境用CDN → 本番CDN への単純置換
const resolver = async (url: string): Promise<string> =>
  url.replace('https://cdn.dev.example.com/', 'https://cdn.example.com/');

const html = `
  <html><body>
    <img src="https://cdn.dev.example.com/assets/logo.png">
  </body></html>
`;

const fetchoraw = new Fetchoraw(resolver);
const { output: out } = await fetchoraw.html(html);

console.log(out);
// <img src="https://cdn.example.com/assets/logo.png">
```

## 沼ポイント
- es/cjs両対応しようとして依存ライブラリがesオンリーなことに、1週間くらい悩んでから気付く(es専用にしました)
- tsconfigをフィーリングで書いていてドツボにハマる
- バージョンが妙に上がってるのはヤラカシの軌跡

## 手の抜きどころ(おまけ)
- おカタいドキュメントと英語が極めて苦手なので、その辺りほぼ100%AIさんに頼みました
- なんならテストコードもかなりAIさんに頼みました
- コミットメッセージも英語にしとこうと思ったのでAIさんに頼みました
- なんならこの投稿のサンプルもほとんどAIさん任せ
- AIさんまじありがとう

## まとめ
- 私はこれで今後楽ができるようになりました
- 同じようなことで面倒になってる人にも届くと良いです
- 英語ダメでドキュメント嫌いでもライブラリが公開できるって素晴らしいですよね
- 作るの好きだけどドキュメント嫌いって理由で公開してこなかった人にも届くと良いです
- 箇条書きは正義