本篇是 Bun 1.0 推出后的初尝试。Bun 是一个集合所有 js 基础工具的运行时,以迄今为止最快的 js 运行时而著称

像 Node 一样,Bun是一个运行时(runtime), 而非什么新的语言。感觉 deno 的地位有点尴尬了。大家被他的宣传的速度所折服。

安装

官网推荐的是运行

1
curl -fsSL https://bun.sh/install | bash

但鉴于国内有墙的原因,bun 现在有 npm 包了,所以我更推荐另一种安装方法,更快更直接:

1
npm i -g bun

用以下命令检查是否安装成功:

1
2
(base) ➜  ~ bun -v
1.0.12

启用

项目初始化:

1
2
3
mkdir bun-learn
cd bun-learn
bun init

可以看到

  • package.json 文件被 bun.lockb 文件替代,比起原来的 json 文件,这个文件写起来有点类似yml的语法,用了缩进和换行,不用花括号
  • 直接生成了 tsconfig.json 文件, bun 原生支持了 ts

跑单个文件,和 node 用法类似,当然run可以省略:

1
bun run index.ts

简单的服务

我们在 index.ts 里写上:

1
2
3
4
5
6
7
8
const server = Bun.serve({
port: 5678,
fetch(req) {
return new Response("hello Bun!");
},
});

console.log(`listening on port ${server.port}`);

终端跑 index.ts , 浏览器可以看到 hello Bun! 的字样。当然,直接跑的话我们改 index.ts 要重启项目,可以加上 --watch 参数:

1
bun --watch index.ts

进一步优化,创建一个 env 文件touch .env,将配置写在该文件里,

1
PORT = 8889

index.ts 文件里 port 参数改:

1
2
--- port: 5678,
+++ port: Bun.env.PORT || 5678,

可以见到,以前用node时,我们须安装 process 包,并且引入,才能用 process.env.PORT的,现在直接用Bun,课件Bun 把很多第三方库,如 process 也集成了, 无需二次下载。

处理路由

fecth 函数改写如下:

1
2
3
4
5
6
fetch (req) {
const url = new URL(req.url);
const { pathname } = url; if (pathname === '/') return new Response('home page!')
if (pathname === '/blog') return new Response('blog!')
return new Response("404")
}

bunx

类似 npx 的使用,直接运行二进制码,无须下载安装包,他自带了个 cowsay命令,可以体验 bunx的用法,命令行运行 bunx cowsay Hell Bun

1
2
3
4
5
6
7
8
9
(base) ➜  bun-learn bunx cowsay Hello Bun
___________
< Hello Bun >
-----------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||

前端脚手架

react 官方脚手架,在 Bun 中使用如下:

1
2
3
bun create react-app <yourAppName>
# or
bunx create-react-app <yourAppName>

vite 的脚手架:

1
2
3
bun create vite <yourAppName> --template vue
# or
bunx create-vite <yourAppName> --template vue

模块化引入

bun 直接支持 es modulecommonjs 两种模块引入,也不用在 package.json 里做出其他配置,解决了困扰了 node 多年的模块引入问题。

下面是一个简单的 demo,新建一个文件 module.ts

1
touch module.ts

以下我们用 path 模块举例:

1
2
3
4
5
6
7
import path from "path";
// const path = require('path'); // commonjs also ok

const filepath = path.join("foo", "bar", "img.png");
const filename = path.basename(filepath);

console.log(filename);

跑了后控制台可看到:

1
2
(base) ➜  bun-learn bun module.ts
img.png

一些常用模块在 Bun 中的使用

file 模块

Bun.write() 写入文件:
1
touch file-demo.ts

写下:

1
2
const data = "I love Bun!";
await Bun.write("file-demo.txt", data);
  • 从以上代码可以看出,Bundeno 一样,支持顶层的 await语法,无须像 node 一样包装在 async 函数里使用 await

  • Bun.write() 接口可写入文件

bun file-demo.ts 可以看到当前目录下生成了 file-demo.txt 文件,并在文件里写下了 data 的内容。

await Bun.file().text() 读取文件

在上面的代码加上:

1
2
3
const file = Bun.file("file-demo.txt");

console.log("file-demo content:", await file.text());

控制台输出

1
2
(base) ➜  bun-learn bun file-demo.ts
file-demo content: I love Bun!
  • 这里值得注意,Bun.file().text() 是异步接口,返回的是 Promise 对象,所以必须用上 await 关键字才能显示出内容

其他几个 file 的接口:

1
2
3
console.log(await file.stream());
console.log(await file.arrayBuffer());
console.log("file size:", file.size);

测试模块

bun 集成了 jest 的测试模块,我们在项目里直接创建测试文件:

1
touch index.test.ts
1
2
3
4
5
6
7
8
9
10
11
import { describe, expect, test, beforeAll } from "bun:test";

beforeAll(() => {
console.log("呵呵");
});

describe("math", () => {
test("addition", () => {
expect(2 + 1).toBe(3);
});
});

可以看出,所有的 jest 模块都集成在 "bun:test" 原生包里,控制台跑的结果:

1
2
3
4
5
6
7
8
9
10
11
(base) ➜  bun-learn bun test
bun test v1.0.12 (85c99751)

index.test.ts:
呵呵
✓ math > addition [0.19ms]

1 pass
0 fail
1 expect() calls
Ran 1 tests across 1 files. [11.00ms]

bundle 打包

我们来试试用 Bun 进行普通的 bundle 打包
在项目根目录创建以下文件夹和文件:

1
2
3
---src
|__ githubApi.ts
|__ index.ts

githubApi.ts文件

前端 js 代码打包,所以暂时不用顶层 await 语法:

1
2
3
4
5
6
7
8
import axios from "axios";

const fetchUser = async (user) => {
const res = await axios.get(`https://api.github.com/users/${user}`);
return res.data;
};

export default fetchUser;

index.ts文件

1
2
3
4
5
6
import fetchUser from "./githubApi";

(async () => {
const userData = await fetchUser("ys558");
document.querySelector("h1").innerHTML = JSON.stringify(userData);
})();

在命令行里运行:

1
bun build ./src/index.ts --outfile=./dist/bundle.js

可以看到dist文件夹下生成了 bundle.js 将该文件放入 HTML 的 script 标签里则可使用

如果开发时,可以在 bun build 的命令加上 --watch, 则可以热加载模式

1
bun build ./src/index.ts --outfile=./dist/bundle.js --watch

react 在项目中的应用

在我们原来的项目里添加 reactreact-dom

1
bun i react react-dom

src 下创建 index.tsx , 写一个简单的计数器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from "react";
import { createRoot } from "react-dom";

const root = createRoot(document.getElementById("root"));

const App = () => {
const [count, setCount] = React.useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</div>
);
};

root.render(<App />);

在 dist 目录下,创建一个根目录 index.html, 加上一下两行:

1
2
<div id="root"></div>
<script src="./bun.js"></script>

bash 下运行:

1
bun build ./src/index.tsx --outfile=./dist/budle.js --watch

打开 index.html 就可以看到该计数器