Open38

個人的に使うesm.shにオフロードするbundlerを作る

podhmopodhmo

https://zenn.dev/podhmo/scraps/3cdc492ca00380

こちらの作業の続き。色々思いつくことがあったのでスクラップを別にする。

podhmopodhmo

プロトタイプはこれ

  • esbuildのpluginを書いて npm:<package>https://esm.sh/<package> などに変換している
  • tsxのmainで書いたHTMLをhonoのjsxと見做して変換している

https://gist.github.com/podhmo/1de015310b403b9e678a734286c939c9#file-readme-md

思ったことは以下

  • 開発時にはローカルのものを参照したい -> esm.shのキャッシュさえできれば良い
  • HMR的なものに対応したい -> 都度mainを変換しまくるので良いのでは?
  • 全部をbundleしたくない -> それがこのコードの存在理由
podhmopodhmo

ライブラリを作りたい

esbuildのpluginないしは何らかのコードのtranspileをするための機能をライブラリとして提供しておく必要がある。プライベートリポジトリの範囲ではdeno-bundleという名前で作っていたがもう少し適切な名前にしたい。どちらかといえばbundleというよりlinkerという感じが近い。tiny-bundleとかでも良いかと思ったがもう少しコードを減らす状況もサポートしたい。

結局のところグルーコードということなので糊(nori)とかglueとかにしてしまえば良いのでは?あるいは粘土(nendo)とかゴム(gom)とかは思いついた。特に難しく考えずにdeno-glueあたりで良い気がした。

podhmopodhmo

どのような機能を保持したいか整理する。

目的というか特徴

  • クライアントサイドでの試行錯誤を手軽に行いたい
  • 1つのリポジトリで1つの試行ではなく複数の試行が行いたい(viteなどは1つの成果物感が強い)
  • 中身が透けて見える程度に簡素なもので良い
  • デプロイしたくないしバンドルもしたくない

ということを出発点としてどのような機能があれば良いかを整理してみる

podhmopodhmo

欲しい機能

  • クライアントサイドのコードのアプリケーション側のみをbundleしてjsに変換する
    • クライアントサイドのコードでもdeno language serverで補完・型チェックが効くようにしたい
  • テキトーなHTMLをjsxで組み立ててCSRがしたい
  • 最終的にはesm.shに依存ライブラリの配信は任せて手軽に進捗を共有したい
    • 可能なら開発環境でesm.shに読みに行くのは辞めたい(キャッシュしたい)
    • 可能ならesm.shの依存を絞ったimportに変換したい(+ bundleした依存をimportしたい)
podhmopodhmo

deno-glue

とりあえずプライベートリポジトリの名前を変えて色々いじって公開するか。
そして冒頭で例としてあげたプロトタイプをexampleとして載せることにする。

その後ローカルの開発環境でのキャッシュについて考える。
その後viteとの整合性やHMRについて考えてみるなどする。

podhmopodhmo

deno-glue, serveコマンドを追加

↑で試してみたcacheの機能を組み込んだserveコマンドを追加することにしてみる。

https://github.com/podhmo/deno-glue/issues/19

podhmopodhmo

--portができるようにする。あとcacheの機能も追加したい。

podhmopodhmo

いろいろdenosaurs/cacheに手を加える必要があった。vendorに取り込んじゃった方が良いかも。。
あとPRを1つにしたのは良くなかった。

podhmopodhmo

deno run -A jsr:@podhmo/glue/serve --port 8080 app.ts とかで動かせるようにした。
デバッグで苦労した結果直接オブジェクトを渡せた方が便利なきもした。

reactをdevelopment modeで動かしたいかもしれない?

podhmopodhmo

ファイル監視とブラウザリロード

HMRは一旦置いておいて、ファイルを監視してのブラウザリロードを試してみる。
(これも組み込むかどうかは未定)

podhmopodhmo

mainのコマンドを作る

deno run jsr:@podhmo/glue@0.2.0 init とかできるようになった

podhmopodhmo

存在しているのに見つからないとか出てだるい。

$ deno run -A jsr:@podhmo/glue@0.2.0 init   error: Uncaught (in promise) NotFound: No such file or directory (os error 2): copy 'https://jsr.io/@podhmo/glue/0.2.0/examples/serve-command-example/client.tsx' -> './client.tsx'
  await Deno.copyFile(path, options.to);
  ^
    at async Object.copyFile (ext:deno_fs/30_fs.js:144:3)
    at async copyFile (https://jsr.io/@podhmo/glue/0.2.0/init.ts:57:3)
    at async main (https://jsr.io/@podhmo/glue/0.2.0/init.ts:31:7)
    at async main (https://jsr.io/@podhmo/glue/0.2.0/main.ts:46:7)
    at async https://jsr.io/@podhmo/glue/0.2.0/main.ts:56:3
podhmopodhmo

ちなみにcurlでは取れる不思議

$ curl https://jsr.io/@podhmo/glue/0.2.0/examples/serve-command-example/client.tsx
podhmopodhmo

fetchで迂回するコードを追加した(雑). 0.2.1で治る。

podhmopodhmo
  • APIを作った瞬間にサーバーが必要になる(デプロイが必要になる)
    • デプロイ先は何が良いのだろう?
  • routerのようななにかも用意しておきたいかもしれない?
podhmopodhmo

依存関係を真面目に追跡してdepsとして渡す

esm.shにリクエストするときに foo?deps=bar@xxx,boo@yyy みたいな感じで依存関係を渡せるこれを明示的に指定できるようにする。直接管理するのは面倒なのでdeno.lockから計算する。

https://github.com/podhmo/deno-glue/issues/27

podhmopodhmo

deno.json で

deno.json .imports  :: alias -> pkg-path-alias
deno.lock .specifiers :: pkg-path-alias -> version

deno.lock.jsr :: @<pkg-path>@version -> {integrity, dependencies}

それ以外に直指定のものが存在する e.g. npm:react@18

podhmopodhmo

specifierをaliasに書き換えてreplaceしないとだめなのか。

podhmopodhmo

"npm:react-dom@18": "18.3.1_react@18.3.1", こういう設定があるみたい?

これは import { createRoot } from "https://esm.sh/react-dom@18.3.1/client?deps=react@18.3.1"; とかに変換してあげないといけない。

podhmopodhmo

更にこういうのもあるらしい

"npm:react-router@7": "7.1.1_react@18.3.1_react-dom@18.3.1__react@18.3.1",

podhmopodhmo

ようやく依存関係の本体に勸められそうだ。
deno.lockを覗いてみると、jsrに関してはspecifiersのところで記述されている形式で依存が記述されている

  "specifiers": {
    "jsr:@hono/hono@4.6.15": "4.6.15",
    "jsr:@podhmo/with-help@0.5": "0.5.3",
    "jsr:@std/assert@*": "1.0.6",
    "jsr:@std/assert@1.0.6": "1.0.6",
    "jsr:@std/cli@1": "1.0.9",
    "jsr:@std/collections@1.0.9": "1.0.9",
    "jsr:@std/fs@1.0.4": "1.0.4",
    "jsr:@std/fs@^1.0.5": "1.0.8",
    "jsr:@std/internal@^1.0.4": "1.0.5",
    "jsr:@std/json@1": "1.0.1",
    "jsr:@std/jsonc@^1.0.1": "1.0.1",
    "jsr:@std/path@1.0.6": "1.0.6",
    "jsr:@std/path@^1.0.6": "1.0.8",
    "jsr:@std/path@^1.0.8": "1.0.8",
    "npm:esbuild-wasm@0.24.2": "0.24.2",
    "npm:preact@*": "10.5.13",
    "npm:preact@10.5.13": "10.5.13"
  },

    "@std/json@1.0.1": {
      "integrity": "1f0f70737e8827f9acca086282e903677bc1bb0c8ffcd1f21bca60039563049f"
    },

    "@std/jsonc@1.0.1": {
      "integrity": "6b36956e2a7cbb08ca5ad7fbec72e661e6217c202f348496ea88747636710dda",
      "dependencies": [
        "jsr:@std/json"
      ]
    },
podhmopodhmo

npmの方は specifierとは限らない場合がありそう?

  "specifiers": {
    "jsr:@hono/hono@4.6.15": "4.6.15",
    "jsr:@podhmo/glue@0.2.1": "0.2.1",
    "jsr:@std/json@1": "1.0.1",
    "jsr:@std/jsonc@^1.0.1": "1.0.1",
    "npm:@types/react@18": "18.3.18",
    "npm:@types/react@19": "19.0.2",
    "npm:esbuild-wasm@0.24.2": "0.24.2",
    "npm:react-dom@18": "18.3.1_react@18.3.1",
    "npm:react-dom@19": "19.0.0_react@19.0.0",
    "npm:react-router@7": "7.1.1_react@18.3.1_react-dom@18.3.1__react@18.3.1",
    "npm:react@18": "18.3.1",
    "npm:react@19": "19.0.0"
  },

    "react-router@7.1.1_react@18.3.1_react-dom@18.3.1__react@18.3.1": {
      "integrity": "sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==",
      "dependencies": [
        "@types/cookie",
        "cookie",
        "react@18.3.1",
        "react-dom@18.3.1_react@18.3.1",
        "set-cookie-parser",
        "turbo-stream"
      ]
    },

    "react-dom@18.3.1_react@18.3.1": {
      "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
      "dependencies": [
        "loose-envify",
        "react@18.3.1",
        "scheduler@0.23.2"
      ]
    },

    "set-cookie-parser@2.7.1": {
      "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
    },

podhmopodhmo

vite用の某を用意しても良いかも?

https://gist.github.com/podhmo/12bab21eebdd5887110a19f8985c4e23

↑で試してみていた

podhmopodhmo
  • 複数のファイルを参照することがある
  • viteだとimport cssで済む
    • html部分を書き換えたくなる (style.cssにindex.cssの一部をコピペした)
    • 追加のcssをどうにかこうにか注入したくなる
podhmopodhmo

codepenにコピペで共有することを考えた場合にはglue bundleもそれに対応したい
index.html的なものを渡せる必要もある?