Bun build --compileで画像をAVIFに変換する実行可能バイナリを作る
BunやDenoで作ることができる実行可能バイナリは現状、ファイルサイズがでかいという課題はあるが、JS/TSで書いていたCLIをNode.jsの有無を問わず実行できるのは大きい。
とはいえ、実用的な何かを作ってみないことには理解したといえないと思ったので、画像をAVIFに変換するCLIを書いた。
- Bun
- TypeScript
- WASM
- Bun compile
Github
Section titled “Github”https://github.com/mktbsh/image-to-avif
断念したこと
Section titled “断念したこと”- sharpを使った画像変換
- アーキテクチャを指定したパッケージインストールができなかったため(やり方を知らない可能性もあり)
- 引数で下記のものをとる
<target_dirs>
: 変換対象画像があるディレクトリのパス--q
: AVIFの品質。デフォルト60--rmdir
: 指定されたディレクトリをAVIF変換後に削除する--original
: AVIFに変換せず、オリジナル画像をTARファイルにまとめて出力
- jpeg, pngをAVIFに変換する
- 実行可能バイナリとしてビルドする
実行コマンド
Section titled “実行コマンド”single directory
:
img2avif ./target-dir
multiple directories
:
img2avif ./dir1 ./dir2 ./dir3
with quality
:
img2avif ./target-dir --q 50
default quality is 60
with remove target dir
:
img2avif ./target-dir --rmdir
no convert to avif
:
img2avif ./target-dir --original
Directory Structure
Section titled “Directory Structure”.├── README.md├── bun.lockb├── package.json├── shim.ts├── src│ ├── args.ts│ ├── const.ts│ ├── fs.ts│ ├── image.ts│ └── main.ts└── tsconfig.json
Contents
Section titled “Contents”依存パッケージ
Section titled “依存パッケージ”つまづきポイント
Section titled “つまづきポイント”Bun build —compile時にWASMを埋め込んでくれない
Section titled “Bun build —compile時にWASMを埋め込んでくれない”Issue: Segfault adding embedded file to build --compile
· Issue #13522 · oven-sh/bun Bun本体としての解決はまだされていないため、回避策を使って対応するしかない。
現状の解決方法
Section titled “現状の解決方法”- コンパイルしたいファイルを直接
bun build --compile
するのではなく、一旦バンドルしたJSファイルをビルドする。 - ビルドされたJSファイル内のimport.meta.urlをrequireに置き換える
- 置き換え完了後、
bun build --compile
で実行可能バイナリを作成する
npm-scripts
Section titled “npm-scripts”{ "scripts": { "bundle": "bun build --bundle --minify src/main.ts --outfile dist/bin.js --target node", "prebundle": "rm -rf dist", "postbundle": "bun shim.ts && cp node_modules/wasm-image-optimization-avif/dist/esm/libImage.wasm dist", "compile": "bun build --compile dist/bin.js --minify --outfile dist/img2avif", "build": "bun run bundle && bun run compile" },}
shim.ts
Section titled “shim.ts”const bin = Bun.file("dist/bin.js")let content = await new Response(bin).text()
// Replace createRequire(import.meta.url) with requirecontent = content.replace(/createRequire\(import\.meta\.url\)/g, "require")
// Replace $(import.meta.url) where $ is "createRequire as something"const createRequireAsRegex = /(?<=createRequire as )(.+?)(\w*)(?=\})/gconst matches = content.match(createRequireAsRegex)
if (!matches?.length) { throw new Error("No matches found")}
for (const match of matches) { const pattern = `${match}(import.meta.url)` content = content.replaceAll(pattern, "require")}
await Bun.write("dist/bin.js", content)console.log("Successfully shimmed dist/bin.js")