Bun に入門する
前提
$ sw_vers
ProductName: macOS
ProductVersion: 13.3.1
ProductVersionExtra: (a)
BuildVersion: 22E772610
ローカルでの環境構築
$ curl -fsSL https://bun.sh/install | bash
シェルを起動しなおして
$ bun --help
-h, --help Display this help and exit.
-b, --bun Force a script or package to use Bun's runtime instead of Node.js (via symlinking node)
--cwd <STR> Absolute path to resolve files & entry points from. This just changes the process' cwd.
-c, --config <PATH>? Config file to load Bun from (e.g. -c bunfig.toml
--extension-order <STR>... Defaults to: .tsx,.ts,.jsx,.js,.json
--jsx-factory <STR> Changes the function called when compiling JSX elements using the classic JSX runtime
--jsx-fragment <STR> Changes the function called when compiling JSX fragments
--jsx-import-source <STR> Declares the module specifier to be used for importing the jsx and jsxs factory functions. Default: "react"
--jsx-runtime <STR> "automatic" (default) or "classic"
-r, --preload <STR>... Import a module before other modules are loaded
--main-fields <STR>... Main fields to lookup in package.json. Defaults to --target dependent
--no-summary Don't print a summary (when generating .bun)
-v, --version Print version and exit
--revision Print version with revision and exit
--tsconfig-override <STR> Load tsconfig from path instead of cwd/tsconfig.json
-d, --define <STR>... Substitute K:V while parsing, e.g. --define process.env.NODE_ENV:"development". Values are parsed as JSON.
-e, --external <STR>... Exclude module from transpilation (can use * wildcards). ex: -e react
-l, --loader <STR>... Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: js, jsx, ts, tsx, json, toml, text, file, wasm, napi
-u, --origin <STR> Rewrite import URLs to start with --origin. Default: ""
-p, --port <STR> Port to serve Bun's dev server on. Default: "3000"
--smol Use less memory, but run garbage collection more often
--minify Minify (experimental)
--minify-syntax Minify syntax and inline data (experimental)
--minify-whitespace Minify whitespace (experimental)
--minify-identifiers Minify identifiers
--no-macros Disable macros from being executed in the bundler, transpiler and runtime
--target <STR> The intended execution environment for the bundle. "browser", "bun" or "node"
--inspect <STR>? Activate Bun's Debugger
--inspect-wait <STR>? Activate Bun's Debugger, wait for a connection before executing
--inspect-brk <STR>? Activate Bun's Debugger, set breakpoint on first line of code and wait
--hot Enable auto reload in the Bun runtime, test runner, or bundler
--watch Automatically restart the process on file change
--no-install Disable auto install in the Bun runtime
-i Automatically install dependencies and use global cache in Bun's runtime, equivalent to --install=fallback
--install <STR> Install dependencies automatically when no node_modules are present, default: "auto". "force" to ignore node_modules, fallback to install any missing
--prefer-offline Skip staleness checks for packages in the Bun runtime and resolve from disk
--prefer-latest Use the latest matching versions of packages in the Bun runtime, always checking npm
--silent Don't repeat the command for bun run
--dump-environment-variables Dump environment variables from .env and process as JSON and quit. Useful for debugging
--dump-limits Dump system limits. Useful for debugging
-------
Bun: a fast JavaScript runtime, package manager, bundler and test runner. (1.0.3)
run ./my-script.ts Run JavaScript with Bun, a package.json script, or a bin
test Run unit tests with Bun
x prettier Install and execute a package bin (bunx)
repl Start a REPL session with Bun
init Start an empty Bun project from a blank template
create elysia Create a new project from a template (bun c)
install Install dependencies for a package.json (bun i)
add zod Add a dependency to package.json (bun a)
remove backbone Remove a dependency from package.json (bun rm)
update tailwindcss Update outdated dependencies
link Link an npm package globally
unlink Globally unlink an npm package
pm More commands for managing packages
build ./a.ts ./b.jsx Bundle TypeScript & JavaScript into a single file
upgrade Get the latest version of Bun
bun --help Show all supported flags and commands
Learn more about Bun: https://bun.sh/docs
Join our Discord community: https://bun.sh/discord
動いた
バージョンの切り替えをしたくなるけど nodenv とか nvm 相当のことをできるツールはさすがにまだないのかな
nvm でのサポート:
availableなバージョン一覧が取得できないためあまり積極的ではないvolta でのサポート:
前向きに議論されているが、Windowsのサポートがないことを理由にまだ実施されていない。
We'll be rapidly improving support for Windows over the coming weeks. If you're excited about Bun for Windows, we'd encourage you to join the #windows channel on our Discord for updates.
とのことで近々正式サポートが入れば volta もサポートされそう。
nodenv でのサポート:
Issueは見当たらない、そもそも nodenv より bunenv的なものができる気もする?
てことで bun 自体のバージョン切り替えをしたいなら volta を待つのが有力そう。現時点では curl -fsSL https://bun.sh/install | bash
で入れてあげるか、コンテナで固定するとかしかないっぽい
ついでに Docker での利用についても調べる
docker hub に公式のImageが上がっていた
Dockerfile 書くときは
この辺が参考になりそう。余力があったら動かしてみる
bun を対話的に動かす
ドキュメント読んでも見当たらないのでサポートないのかなと思ったら
これがあった。
特に明示的なインストールは不要で(裏でインストールしてるっぽい)
$ bun repl
すると良い
$ bun repl
Welcome to Bun v1.0.3
Type ".help" for more information.
[!] Please note that the REPL implementation is still experimental!
Don't consider it to be representative of the stability or behavior of Bun overall.
>
動いた。書いてあるとおりまだ experimental
➜ bun repl
Welcome to Bun v1.0.3
Type ".help" for more information.
[!] Please note that the REPL implementation is still experimental!
Don't consider it to be representative of the stability or behavior of Bun overall.
> const x = 'hello'
undefined
> console.log(x)
hello
undefined
> const x = 'hello2'
SyntaxError: Can't create duplicate variable: 'x'
> const y: string = 'hello'
undefined
> const z: string = 10
undefined
- 基本的な使い心地は node と同じ
- 型注釈もパースエラーにならずかける
- とはいえ型チェックが入るわけでは当然なく、型は書いても特に意味がない
- 補完が入るとかも現時点ではないので本当に型付きのままでも動きはするよ、ってだけのもので(前に話題になってた tc39/proposal-type-annotations と同じイメージ)
bun でスクリプトを実行する
bun でなにが嬉しいって ts がそのまま動くことだよね
まずはこれから試していく
type Main = () => Promise<void>
const main: Main = async () => {
console.log('hello! from bun')
}
main()
.then(() => {
console.log('successfully completed!')
})
.catch((err) => {
console.error(err)
})
package.json 等ない状態で動かしてみる
$ bun run ./sample.ts
hello! from bun
successfully completed!
正常に実行できた :tada:
一応型チェックが暗黙的に入るのか確かめる
type Main = () => Promise<string>
const main: Main = async () => {
console.log('hello! from bun')
}
main()
.then(() => {
console.log('successfully completed!')
})
.catch((err) => {
console.error(err)
})
$ bun run ./sample.ts
hello! from bun
successfully completed!
特にエラーが起きないのでやっぱり入ってない、型チェックは tsc でやって実行は bun でできるよって感じで他のバンドラと同じ
次は同じく package がない状態でも node の API を利用できるか
import { readFileSync } from 'node:fs'
type Main = () => Promise<void>
const main: Main = async () => {
const content = readFileSync('./sample.ts', { encoding: 'utf8' })
console.log('hello! from bun', content)
}
main()
.then(() => {
console.log('successfully completed!')
})
.catch((err) => {
console.error(err)
})
$ bun run ./sample.ts
hello! from bun import { readFileSync } from 'node:fs'
type Main = () => Promise<void>
const main: Main = async () => {
const content = readFileSync('./sample.ts', { encoding: 'utf8' })
console.log('hello! from bun', content)
}
main()
.then(() => {
console.log('successfully completed!')
})
.catch((err) => {
console.error(err)
})
successfully completed!
できる
packageManager を試す
$ bun init
bun init helps you get started with a minimal project and tries to guess sensible defaults. Press ^C anytime to quit
package name (bun-playground):
entry point (index.ts): src/index.ts
Done! A package.json file was saved in the current directory.
+ src/index.ts
+ .gitignore
+ tsconfig.json (for editor auto-complete)
+ README.md
To get started, run:
bun run src/index.ts
でプロジェクト初期化用のファイル群が作られる
- README.md
- tsconfig.json
- bun.lockb
- package-lock.json 等のロックファイル相当
- package.json
- .gitignore
が自動生成される
他のパッケージマネージャだと package.json くらいなので良くも悪くも色々やってくれる感じ。
bun-types なるパッケージを自動でインストールしておいてくれて、tsconfig にもこれが入っている
{
"compilerOptions": {
"lib": ["ESNext"],
"module": "esnext",
"target": "esnext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"allowImportingTsExtensions": true,
"noEmit": true,
"composite": true,
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
"jsx": "react-jsx",
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"types": [
"bun-types" // add Bun global
]
}
}
types に自動で入ってきて、@types/node
に相当する型等が入っているので init したらすぐ node の API を型付きで利用できる。至れり尽くせり。
$ bun add -D typescript
bun add v1.0.3 (25e69c71)
installed typescript@5.2.2 with binaries:
- tsc
- tsserver
1 packages installed [97.00ms
パッケージ追加の仕方も他のパッケージマネージャと特に変わらず
yarn list
や pnpm list
相当のコマンドは見当たらず(当然といえば当然だけど)パッケージマネージャとしてはやっぱり既存のもののほうがリッチな感じはする。併用できるかは気になる
packageManager といえば workspace 提供の責務もあるのでこっちも見ていく
Bun supports workspaces in package.json. Workspaces make it easy to develop complex software as a monorepo consisting of several independent packages.
てことで対応されている
せっかくなのでモノレポ化してみる
{
"name": "sample",
"module": "index.ts",
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "run-s 'build:*'",
"build:bundle": "bun bun ./index.ts --outdir dist",
"build:types": "tsc"
},
"devDependencies": {
"bun-types": "latest",
"npm-run-all": "^4.1.5",
"typescript": "^5.2.2"
},
"peerDependencies": {
"typescript": "^5.0.0"
}
}
{
"compilerOptions": {
"lib": ["ESNext"],
"module": "esnext",
"target": "esnext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"allowImportingTsExtensions": true,
"composite": true,
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
"jsx": "react-jsx",
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"types": [
"bun-types" // add Bun global
],
// added
"outDir": "dist",
"noEmit": false,
"emitDeclarationOnly": true
}
}
{
"name": "bun-playground",
"module": "src/index.ts",
"type": "module",
"workspaces": [
"packages/*"
],
"dependencies": {
"sample": "workspace:*"
},
"devDependencies": {
"bun-types": "latest",
"typescript": "^5.2.2"
},
"peerDependencies": {
"typescript": "^5.0.0"
}
}
import { valueInSampleApp } from 'sample'
type Main = () => Promise<void>
const main: Main = async () => {
console.log('hello! from bun', valueInSampleApp)
}
main()
.then(() => {
console.log('successfully completed!')
})
.catch((err) => {
console.error(err)
})
動かしてみる
$ bun --cwd ./packages/sample run build
sample scripts:
bun run build
run-s 'build:*'
bun run build:bundle
bun bun ./index.ts --outdir dist
bun run build:types
tsc
3 scripts
$ bun run ./sample.ts
hello! from bun valueInSampleApp
successfully completed!
期待どおり。
てことで最低限の要求は満たしてくれそう。
一方、node_modules は flat な構成になってそうだったので pnpm の
- 厳格なパッケージ管理(暗黙依存のパッケージは参照できない)
- 細かな作り込み (pnpm fetch や 正規表現での複数のスクリプトの同時実行等)
等は pnpm に優位がありそうで、併用できるなら bun + pnpm もありかなと思ったりした
NestJS は使える??
古いデコレータ依存なので無理かな...
$ cd apps
$ git clone https://github.com/nestjs/typescript-starter.git nestjs-sample
$ cd nestjs-sample
$ bun run start
$ nest start
[Nest] 7413 - 09/26/2023, 1:58:12 PM LOG [NestFactory] Starting Nest application...
[Nest] 7413 - 09/26/2023, 1:58:12 PM LOG [InstanceLoader] AppModule dependencies initialized +7ms
[Nest] 7413 - 09/26/2023, 1:58:12 PM LOG [RoutesResolver] AppController {/}: +10ms
[Nest] 7413 - 09/26/2023, 1:58:12 PM LOG [RouterExplorer] Mapped {/, GET} route +1ms
[Nest] 7413 - 09/26/2023, 1:58:12 PM LOG [NestApplication] Nest application successfully started +3ms
普通に動いた。すごい
swagger の OAS 書き出しも普通に動いた
bundle はいけるか?
なんか依存するパッケージが依存する他のパッケージも全部インストールさせられてちょっとそのまま使うのは無理そうな感じだったけど一応バンドルできた
$ bun build ./src/main.ts --outdir ./out --target bun --external class-transformer/storage
実行もできるけど
メタデータが拾えなくなって型定義消えてた、これだと class-validator とかもきついかも?
まとめ:
- bundleしなければ動く
- Bun.build の方はなぜか動かなかった
- CLI は動いてバンドルもできたが、依存パッケージが依存するパッケージも直接インストールしないといけないこと・legacyDecorator のメタデータは吸い出せないのでバンドルしてから動かす、は厳しい
現時点でのまとめ
- ローカルでの bun 自体のバージョン管理はまだ実用的なやり方がなさそうだが、bun で Windows サポートが入り次第 Volta がサポートしそう
-
.ts
がネイティブで動くのがかなり嬉しくてちょっとしたスクリプト(例えば build 用に esbuild のAPIを叩くとか)を mjs で書いていたのが.ts
でかけるようになる - packageManager としては workspace のサポートもあるし必要十分
- +αがほしいときは pnpm 併用したい
- repl も experimental でついてるがこっちは別に node に対して優れてるとかはない
- bundle は beta 歌ってる通りまだ実用って感じはしない
- bundle しなければ legacyDecorator を使う NestJS もちゃんと動いており、BE/FE問わず試験的に本番でもう使える感触だった