コンテンツにスキップ

Bun build --compileで画像をAVIFに変換する実行可能バイナリを作る

BunやDenoで作ることができる実行可能バイナリは現状、ファイルサイズがでかいという課題はあるが、JS/TSで書いていたCLIをNode.jsの有無を問わず実行できるのは大きい。

とはいえ、実用的な何かを作ってみないことには理解したといえないと思ったので、画像をAVIFに変換するCLIを書いた。

  • Bun
  • TypeScript
  • WASM
  • Bun compile

https://github.com/mktbsh/image-to-avif

  • sharpを使った画像変換
    • アーキテクチャを指定したパッケージインストールができなかったため(やり方を知らない可能性もあり)
  • 引数で下記のものをとる
    • <target_dirs>: 変換対象画像があるディレクトリのパス
    • --q: AVIFの品質。デフォルト60
    • --rmdir: 指定されたディレクトリをAVIF変換後に削除する
    • --original: AVIFに変換せず、オリジナル画像をTARファイルにまとめて出力
  • jpeg, pngをAVIFに変換する
  • 実行可能バイナリとしてビルドする

single directory:

Terminal window
img2avif ./target-dir

multiple directories:

Terminal window
img2avif ./dir1 ./dir2 ./dir3

with quality:

Terminal window
img2avif ./target-dir --q 50

default quality is 60

with remove target dir:

Terminal window
img2avif ./target-dir --rmdir

no convert to avif:

Terminal window
img2avif ./target-dir --original
.
├── README.md
├── bun.lockb
├── package.json
├── shim.ts
├── src
│ ├── args.ts
│ ├── const.ts
│ ├── fs.ts
│ ├── image.ts
│ └── main.ts
└── tsconfig.json

Bun build —compile時にWASMを埋め込んでくれない

Section titled “Bun build —compile時にWASMを埋め込んでくれない”

Issue: Segfault adding embedded file to build --compile · Issue #13522 · oven-sh/bun Bun本体としての解決はまだされていないため、回避策を使って対応するしかない。

  1. コンパイルしたいファイルを直接 bun build --compile するのではなく、一旦バンドルしたJSファイルをビルドする。
  2. ビルドされたJSファイル内のimport.meta.urlをrequireに置き換える
  3. 置き換え完了後、bun build --compile で実行可能バイナリを作成する
package.json
{
"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"
},
}
const bin = Bun.file("dist/bin.js")
let content = await new Response(bin).text()
// Replace createRequire(import.meta.url) with require
content = content.replace(/createRequire\(import\.meta\.url\)/g, "require")
// Replace $(import.meta.url) where $ is "createRequire as something"
const createRequireAsRegex = /(?<=createRequire as )(.+?)(\w*)(?=\})/g
const 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")