概述
本文是笔者的系列博文 《Bun技术评估》 中的第十二篇。
在本文的内容中,笔者主要想要来探讨一下bun程序运行、运行环境和运行管理相关的问题,笔者愿意将其统称为running。
启动
bun程序的启动非常简单,就是使用bun作为命令,使用脚本文件名称作为参数来执行就可以了。这一点和nodejs类似,但比其要灵活很多。比如bun可以忽略脚本后缀名(在不产生歧义的情况下),而且bun支持js、ts、jsx、tsx等多种类型的文件,而且尽量提供兼容性,相对而言nodejs执行就有一些限制。
下面是一些常用的执行命令示例:
js
// 执行脚本文件
bun index.ts
bun index.js
bun index.jsx
bun index.jsx
// 执行默认脚本文件
bun . (可能是index.ts/js)
// 执行特定脚本文件
bun web.ts
// 监视模式
bun --watch upweb.ts
这里有一个常用而且非常有用的执行方式,就是bun可以使用"监控"模式来执行脚本。在执行脚本的时候,bun还监视这个脚本是否被修改,如果修改,则自动重新启动执行。这个特性,显然大大方便了开发过程中的修改和调试(想象一下,如果没有这个)。
需要特别注意的是,--watch指令,必须立刻紧跟bun命令(它是bun的选项,而非脚本参数),笔者在刚开始使用的时候,就没有注意到这一点,老以为是bun自己有问题。
执行参数
bun在执行ts/js代码文件的时候,除了指定文件名称,还可以带有一些参数,来动态的控制启动和执行过程。在bun应用程序内部,可以使用process.argv数组来获取这些参数的内容,示例如下:
js
// test.ts 内容
console.log(process.argv);
// 执行
bun test.js foo bar name=yanjh email = yanjh@sina.com "what now"
[
"/home/yanjh/.bun/bin/bun",
"/home/yanjh/upfile/test.js",
"foo",
"bar",
"name=yanjh",
"email",
"=",
"yanjh@sina.com",
"what now"
]
这个规则非常清晰而简单:
- 参数是一个字符串数组
- 参数是有序的
- 第一个固定的参数是bun程序本身,绝对真实的路径
- 第二个固定的参数是bun执行的脚本文件的位置
- 然后是实际的命令行参数,使用空格分隔,多个连续空格,也算作一个
- 根据这个规程,其实参数是完全由开发者自定义解释方式的(比如k=v这种,只是一个参数)
- 如果参数内部需要空格,参数需要使用双引号围起来
process属性和方法
和nodejs一样,bun程序在启动后,会自动使用一个process对象,来引用当前执行的应用程序实例。上面的命令行参数,其实只是process对象一个比较常用的方式,开发者还可以使用它来获取很多程序相关的信息和状态。
常见的属性和方法包括:
-
process.plarform: 操作系统平台如linux/win/macos等;
-
process.arch: CPU架构如amd64,arm64等
-
process.uptime(): 当前进程启动时间,秒
-
process.cwd(): 当前进程所使用的当前的文件夹,相对于执行时的pwd结果?
-
process.pid|ppid: 进程号,和父级进程编号
输入输出
bun应用中的precess对象,还有三个和输入输出相关的属性: stdin、stdout和strerr。这个属性本质上就是bun程序留给操作系统的标准接口。它们继承自Node.js的设计,但在 Bun 的优化和扩展下提供了更高效的跨平台支持。以下从概念、特性和使用场景展开说明:
- stdin标准输入,使用文件描述符0
在Bun中,process.stdin 是一个可读流(Readable Stream),可以通过监听data事件获取输入,最常见的场景就是命令行应用中的键盘输入,下面的代码,能够帮助我们理解这一机制(包括stdout和stderr的输出):
js
process.stdin.on("data", (chunk) => {
console.log("Input:", chunk.toString());
process.stdout.write("Stdout: " + chunk.toString());
process.stderr.write("SomeErr: Error\n");
});
- stdout标准输出,使用文件描述符1
用于输出正常结果(如 console.log 的内容)。在 Bun 中,process.stdout 是可写流(Writable Stream),可直接写入数据,并且作为程序输出输出到控制台,一般就是终端。
- stderr标准错误,使用文件描述符2
专用于输出错误或诊断信息(等同于 console.error)。 stderr独立于stdout,确保错误信息不和普通输出信息混淆(如可以单独输出到errorlog文件),而且默认是无缓冲的(立即输出)。
- 重定向
前面我们看到,默认的stdout和stderr输出都是控制台或者终端,这是可以修改的,最常见的就是重定向到文件,它们可以使用以下的方式:
js
// 命令行设置,输出日志和错误日志
bun app.js 1> output.log 2> error.log
// 程序中修改目标
const fs = require("fs");
process.stderr = fs.createWriteStream("error.log");
终止或退出程序
如果是从命令行启动,并且是以默认方式启动的,可以使用ctrl-c来终止程序。
如果是以命令行的后台模式( & 结尾 ),或者服务方式启动的,可以使用kill命令配合pid来结束程序。当然,最简单的方式,是使用pkill指令,直接找到并终止进程:
js
// ps 搜索
ps aux | grep "[u]pweb.ts" | awk '{print $2}' | xargs kill
[1] + terminated bun upweb.ts
// pkill
pkill -f "upweb.ts"
[1] + terminated bun upweb.ts
// reboot app
pkill -f "upweb.ts" && bun upweb.ts
前面都是从外部(操作系统)的角度,来终止运行中的bun程序。在bun程序内部,可以编码来主动终止程序,非常简单,就是调用process.exit()方法即可。下面是一个简单的倒计时程序:
js
let i = 10;
setInterval(()=>{
console.log(i--);
if (i<=0) process.exit(0);
},1000);
exit可以带参数退出,0表示正常退出,其他表示错误状态,一般情况根据操作系统的进程管理要求来确定,常见的如:
- 1: 通用错误
- 2: shell命令错误
- 130: 进程中断,模拟ctrl+c
package脚本
理论上而言,由于高度的集成性,在bun中,是可能实现"无依赖"的应用程序的。也就是说,其实对于一个bun应用而言,是可以没有对应的应用程序项目,和配套的项目/包管理机制的。
当然,bun也支持和nodejs应用类似的,使用package.json来管理的标准形式的应用程序项目。这时,bun支持package.json的脚本执行方式。就是在配置文件中,配置相关(script)的脚本项目,来达成相关的项目执行或者管理的任务的要求。这类的脚本执行时,需要配合使用run指令。
例如下面的配置信息:
js
// package.json文件内置
{
// ... other fields
"scripts": {
"clean": "rm -rf dist && echo 'Done.'",
"dev": "bun server.ts"
}
}
// 执行脚本
bun run dev
// 完整形式
bun [bun flags] run <script> [script flags]
显然,在团队开发,或者标准化流程化的开发体系中,这是一个非常好的工程实践。包括各种启动项目,维护脚本都可以标准化,大大降低了开发配置和迁移沟通的成本。
环境变量
Bun内置支持.env环境变量配置文件,也就是说,bun程序启动时,bun可以自动读取.evn文件中的内容,并且将其注入到当前程序的执行环境当中。就如使用操作系统环境变量配置命令一样。bun文件,一般放在bun项目的主文件夹当中,如果没有项目信息,就放在执行脚本所在文件夹。
bun还可以基于当前配置的业务执行环境,自动处理对应的环境变量配置文件。比如通常我们将执行环境分为开发、测试和生产三个环境,为了区分这些环境,方便运维管理,通常需要配置不同的环境变量文件,这是我们可以使用文件后缀进行区分。这是一种比较好的做法和实践。bun支持下面几种配置方式:
- .env 一般(默认)
- .env.production(生产环境)
- .env.development(开发环境)
- .env.test (测试环境)
- .env.local(本地环境)
生产、测试和开发环境,可以使用NODE_ENV这个环境变量,在操作系统的层面进行配置。
在环境变量文件中,配置的内容和信息,一般就是针对对应环境的一些配置信息,比如调试级别、数据库、缓存系统连接信息、文件存储位置等等。配置的格式,遵循环境变量配置的"键=值"对的方式,每个配置信息一行。键值使用等号连接,不分数值和字符串。
js
FOO=hello
BAR=world
有些材料提到,env配置方式,在某些环境中的兼容性不是很好,比如中文配置信息,需要注意.env文件的编码方式,确认其支持并使用utf-8格式。另外需要注意的是,.env环境变量文件,在arm64/linux平台上,可能有兼容性的问题。这些都需要开发者在实际环境中进行测试和确认。
import.meta
在nodejs中,我们都知道有一套内置的全局变量如__dirname,可以获取当前脚本文件所在的路径。bun中也有类似的机制,就是import.meta(导入元信息)。
比较奇怪的是,import.meta并不是一个标准的JS对象,但确实是可以直接使用属性,或者调用其方法的。常用的属性和方法,可以参考下面的代码,笔者感觉比__dirname好用一些:
js
import.meta.dir; // => "/path/to/project"
import.meta.file; // => "file.ts"
import.meta.path; // => "/path/to/project/file.ts"
import.meta.url; // => "file:///path/to/project/file.ts"
import.meta.main; // `true` if this file is directly executed by `bun run`
// `false` otherwise
// 模块查询
import.meta.resolve("zod"); // => "file:///path/to/project/node_modules/zod/index.js"
需要很好理解的是,import.meta参考的是当前脚本文件的信息,和当前执行的process是有区别和联系的。
import.meta的属性包括: dir,dirname(同dir),file, path,filename(同path),url,main, env, resolve(方法)等,都比较好理解,详情可以查询bun技术文档。
启动性能
这个问题不是很重要,因为相对而言nodejs启动命令执行的性能已经非常好了。但也可以说明,JSC引擎加载执行脚本的性能,确实比V8要好一点(参考下图)。

一般认为,bun和deno,都是nodejs的改进替代版本,但从一些简单的指标来看,现在bun的表现,要比早出来的deno,是要激进一点,但也要好一点。
PM2
在nodejs开发时代,笔者习惯在生产环境中使用pm2来管理node应用程序的执行。当然,基于对Nodejs生态的支持,理论上,我们也是可以安装部署一个pm2,来管理bun应用程序的,而且pm2启动bun程序,还支持cluster模式。参考的命令行和执行情况如下:
js
pm2 start upweb.ts --interpreter bun --name bunupload
[PM2] Starting /home/yanjh/upfile/upweb.ts in fork_mode (1 instance)
[PM2] Done.
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name │ mode │ ↺ │ status │ cpu │ memory │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
│ 0 │ bunupload │ fork │ 0 │ online │ 0% │ 32.5mb │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
这个方法有一点限制,就是PM2和bun都需要比较新的版本。而且虽然看起来不错,但总觉得有点奇怪。bun本身就是用来替代node和npm的,但还需要依赖npm和node的pm2来执行...,确实有点画蛇添足的感觉,但目前,好像bun并没有提供类似pm2这样优秀的解决方案。
一个笔者能够想到的简化的方案,就是使用一个看门狗程序,定期检查程序执行情况,发现程序崩溃后,自动重新启动。这就是另外一个问题了。
小结
本文探讨了有关于Bun应用程序执行的相关问题,这部分的内容比较杂,但都很实用。包括了启动bun应用的一般模式,启动参数的获取,内置process对象的属性和方法,输入输出,退出程序,package脚本执行,环境变量,import.meta,启动性能和pm2兼容等方面的问题。