Bun の非互換な拡張 API
https://gyazo.com/a71f94cf1c15e71313d8a2c16c59d1ab
Bun は WinterCG meetings の招待を無視し、まだプラットフォームで議論中の仕様や標準から外れた拡張を利便性のために取り入れている。またエコシステムとして合意の取れていない実装をすることもある。 @jarredsumner: JS runtimes obsess about web standards but web standards orgs are incentivized to only care about browsers https://pbs.twimg.com/media/GGjn7Q3a4AAXGKf.jpg
@lcasdev: @jarredsumner Just want to mention that we’ve invited you to WinterCG meetings for nearly 2 years now without any response from you - I think intentionally not participating and then saying “uh they don’t listen” is not very reasonable 😀 これら API を使ってしまうと Node.js や Deno、Cloudflare Workers などで扱えず相互運用性の問題となる。知らず知らずのうちに使ってしまわないようにまとめておく。
ECMAScript (JavaScript)
ES Modules のキャッシュ操作
Node.js や Deno では ES Modules と CommonJS が明確に区別されている。一方で Bun はモジュールを扱いやすくするために CommonJS と ES Modules を意図的に混ぜている。その結果、ES Modules で読み込んだモジュールのキャッシュを require.cache で操作出来るようになってしまっている。
code: js
import { foo } from "./foo.mjs";
const path = Bun.fileURLToPath(import.meta.resolve("./foo.mjs"));
console.log(require.cachepath); ECMAScript でモジュールの種類が Abstruct Module Records、Cyclic Module Records そして Source Text Module Records に分類されている(列挙順の後ろにあるほどサブクラスとなる)。
Cyclic Module Records 以降のモジュールタイプはキャッシュされることが明記されており、またそのキャッシュを操作することは出来ない。つまり Source Text Module Records である JavaScript モジュール(ES Modules)はキャッシュを操作出来ない。TC39 member である ljharb さんにも ECMAScript にキャッシュを消すような仕様はないと教えてもらった。
@jordan.har.band: CJS modules aren’t governed by the spec, and there’s no mechanism in the spec to alter a hypothetical ESM cache. 一方で CommonJS によって読み込んだモジュールは require.cache の値を触ることが出来るが、これは Abstruct Module Records であると解釈することで ECMAScript 仕様違反ではないと理解することができる。この辺りは uhyo さんのスライドが詳しい。
つまり Bun は JavaScript 仕様違反をしていると言っても過言ではなさそう……。
Web 標準 API
AsyncIterable な console で標準入力から一行ずつ読む
In Bun, the console object can be used as an AsyncIterable to sequentially read lines from process.stdin.
code: js
for await (const line of console) {
console.log(line);
}
もちろんこの機能は Console Standard にはない。
Response のコンストラクタに AsyncIterable を渡す
code: js
const response = new Response(async function* () {
yield "hello";
yield "world";
}());
await response.text(); // "helloworld"
これは Web 標準に沿っていない。ただし Node.js (undici) でも同じように実行できてしまう問題がある。
標準では ReadableStream.from を経由することで対処できる。ただしこの辺りは(特にプリミティブである string に対してどうするか)議論中で実装されているランタイムは少ない。
code: js
const response = new Response(ReadableStream.from(async function* () {
yield "hello";
yield "world";
}()));
await response.text(); // "helloworld"
Headers の getAll と toJSON メソッド
Set-Cookie HTTP フィールド(ヘッダー)対応のために Headers に getAll メソッドを追加しようという議論が起きていた。
最終的に getSetCookie メソッドを追加することになったが、Bun は結論が出る前に getAll と toJSON を独自実装した。
The Headers class now implements the .getAll() and .toJSON() methods. These are both technically non-standard methods, but we think it will make your life easier.
code: js
const headers = new Headers();
headers.append("Set-Cookie", "a=1");
headers.append("Set-Cookie", "b=1; Secure");
console.log(headers.toJSON()); // { "set-cookie": "a=1, b=1; Secure" }
各ランタイムについての流れは Zenn に書いてある。
Fetch API の proxy オプション
In Bun, fetch supports sending requests through an HTTP or HTTPS proxy. This is useful on corporate networks or when you need to ensure a request is sent through a specific IP address.
code: js
// The URL of the proxy server
});
Worker の "open" イベント
The "open" event is emitted when a worker is created and ready to receive messages. This can be used to send an initial message to a worker once it's ready. (This event does not exist in browsers.)
code: js
const worker = new Worker(new URL("worker.ts", import.meta.url).href);
worker.addEventListener("open", () => {
console.log("worker is ready");
});
Worker が起動されるまでに送られたメッセージはキューに入れられるため、特に待つ必要はない。
Worker の preload オプション
Bun v1.1.35 introduces a preload option for Worker, which allows you to evaluate a script before the worker script is executed.
code: js
const worker = new Worker(new URL("worker.js", import.meta.url).href, {
preload: new URL("preload.js", import.meta.url).href
});
Worker を実行する前に実行するコードを指定できるらしい。
Text Modules
WHATWG で Import Attributes を使った type: "text" の提案がなされている。エンコーディングをどう指定するかなどの議論が起きている。 Bun は結論を待たず、すでに独自実装している。
code: js
import html from "./index.html" with { type: "text" };
console.log(html);
Node.js
package.json で JSONC 形式の対応
突然 package.json で JSONC 形式の対応を表明したため賛否両論が巻き起こった。
Bun won't error when package.json has comments or trailing commas
https://pbs.twimg.com/media/GLPRdS2asAAaMQh.png
PR にネガティブな意見が集まったが、最終的に Jarred 氏が Twitter 上のアンケート結果を根拠として Bun に取り込んだ。
JSONC 形式の場合当然 npm にパブリッシュ出来ない。互換性がないのだから拡張子を jsonc にしてサポートした方がいいのにと個人的には思う。
JSX
ショートハンド記法
code: jsx
function Div(props: {className: string;}) {
const {className} = props;
// without punning
return <div className={className} />;
// with punning
return <div {className} />;
}
JSX の仕様は Meta (Facebook) により管理されている。
特に仕様に対して提案するといったアプローチをとることなく拡張し、他ランタイムへ足並みを揃えるよう呼びかけず、急に TypeScript のパーサーへの対応を要求したため議論を呼んだ。リジェクトされた。
In bun's current implementation, we don't support multiple identifiers in one {}, just <div {foo} />. The rationale was to keep the parser changes as simple as possible and minimize edgecases. From a DX perspective, it would be better to support multiple but I think it's still net improvement to ship without supporting it
その他
TOML Modules
code: js
import data from "./data.toml";
// there's no toml extension, but type makes it read as toml.
import cfg from "./Configfile" with { type: "toml" };