
前端模块化的最后一块拼图——浏览器中如何运行模块
前端模块化的演变包括CJS、AMD、CMD、UMD和ESM,现代浏览器支持ESM,webpack负责模块处理和打包,确保在浏览器中高效运行模块,支持代码拆分和Tree Shaking以优化性能。
Bun 1.0是一个新的JavaScript运行时,作为Node.js的替代品,提供更快的包管理和开发解决方案。尽管目前在生产环境中尚不成熟,但它集成了多种工具,支持统一模块标准和Web API,具有显著的性能优势。Bun的包管理器速度比pnpm快4倍,支持原生JSX和TS,且具备构建工具的潜力。整体来看,Bun在运行时和工具链上均有加速效果,但仍需进一步发展以满足生产需求。
省流版:大体不可用,现阶段没有引进必要
Bun现阶段作为更快的包管理器是可用的;作为运行时加速Node全栈应用可作为可能的技术储备,现阶段不能用于生产。其他功能如测试、构建器由于生态和API不够健全,不具备生产价值。
2023 年 9 月 8 日,Javascript 运行时 Bun 正式发布 1.0 版本,标志着这个由前 Facebook 工程师创建的项目正式进入稳定生产可用阶段。
从官网来看,Bun具有如下特点(或者说它是以下这些东西)
fs
、path
、Buffer
等。本文主要从Web前端开发对Bun进行体验,对BFF和全栈Node开发并无涉及。
个人总结Bun相对于Node主要的优势有下,优先级从上到下递减:
fetch
等Web API,不再需要polyfilldot-env
, nodemon
等Bun的加速体现在两方面——运行时和工具链上。
工具链加速部分采用和rspack和swc等一样的思路,就是内置化、集成化。插件化虽然拓展性好,但集成更快!
因此Bun提供了包管理、内置语言支持、环境变量管理等,将原本分散的功能集中在一起优化实现。
Bun作为包管理器比pnpm更快,下图来自Bun官方:
在本地的一个中型项目中,此前已经运行过一次bun install
和若干次pnpm install
的情况下,使用hyperfine跑分如下,可以看出相对pnpm都有惊人的4倍提升。
根据现有的资料,Bun作为包管理器的性能优势来自:
根据官方的说法,我们可以只把Bun作为包管理器而不作为运行时使用,这也是我认为Bun现阶段能对现有工具链最大的提升。
作为包管理器它仍具备大部分情况下符合我们预期的行为:
package.json
和node_modules
,不会另起炉灶bun.lockb
,采用二进制数据,不必序列化,存取更快使用全局的安装缓存~/.bun/install/cache
,尽量避免重复下载,并采用最快的系统调用进行复制或链接
.npmrc
,而是使用自己的bunfig.toml
配置,这点需要注意个人对测试不是很了解,这里不多做介绍。下图来自Bun官方:
Bun采用了Apple Webkit的JS Core引擎取代Node/Deno中的V8引擎,使用Zig作为开发语言,实现了更快的速度。其主要目的是加速那些原本运行在Node上的JS服务端应用,如SSR Worker、BFF等。
下图性能对比来自Bun官网:
需要阐明的是,作为Native语言,C/C++/Rust/Zig在性能上并没有明显的差异,Bun的性能提升除了JS Core外主要来自其内部实现较高的Native占比。
如下图所示,Node中JS实现高达60%以上,而Bun只有20%左右,这可能是Bun加速的重要原因。
Bun除了提供Node的API实现外,还提供了自己的API实现,以提供更高的性能:
Bun.serve()
:使用 uWebSockets 实现,据称有5~10倍的性能提升Bun.file()
:使用最优系统调用优化了文件API为什么Bun已经原生支持了JSX和TS还需要构建?
很简单,浏览器不支持,最终代码需要在浏览器上而不是Bun上跑。
Bun.build()
可以将Bun作为构建工具使用(in Beta)。同样的,附上一张官方跑分图:
当然,现有的项目一般都依赖于围绕构建工具建立起的生态,所以甚至从webpack迁移到rspack或esbuild都是十分困难的事。
因此,使用Bun作为构建工具在现阶段个人感觉更多是一种技术探索和储备,并不具备生产价值。
Bun的构建API和esbuild类似(但不是Go而是Zig实现的,只是提供了相似的API),都是从一个入口处解析依赖,生成转译后的chunk代码。
await Bun.build({
entrypoints: ['./index.tsx'],
outdir: './out',
})
如上面的代码所示,每个入口会生成一个JS文件。
完整的API定义如下,需要注意的是loader
仅仅指内置的Loader,并不支持拓展。
interface Bun {
build(options: BuildOptions): Promise<BuildOutput>;
}
interface BuildOptions {
entrypoints: string[]; // required
outdir?: string; // default: no write (in-memory only)
format?: "esm"; // later: "cjs" | "iife"
target?: "browser" | "bun" | "node"; // "browser"
splitting?: boolean; // true
plugins?: BunPlugin[]; // [] // See https://bun.sh/docs/bundler/plugins
loader?: { [k in string]: Loader }; // See https://bun.sh/docs/bundler/loaders
manifest?: boolean; // false
external?: string[]; // []
sourcemap?: "none" | "inline" | "external"; // "none"
root?: string; // computed from entrypoints
naming?:
| string
| {
entry?: string; // '[dir]/[name].[ext]'
chunk?: string; // '[name]-[hash].[ext]'
asset?: string; // '[name]-[hash].[ext]'
};
publicPath?: string; // e.g. http://mydomain.com/
minify?:
| boolean // false
| {
identifiers?: boolean;
whitespace?: boolean;
syntax?: boolean;
};
}
type Loader =
| "js"
| "jsx"
| "ts"
| "tsx"
| "json"
| "toml"
| "file"
| "napi"
| "wasm"
| "text";
和esbuild或rspack不同,Bun并没有集成文件监听(热更新)和Dev Server,需要结合--watch
和Bun.serve()
实现。这点上Bun并不能替代rspack或esbuild,提供完整的开发体验。
而Bun.serve()
更多地是用于服务BFF用于渲染前端页面的,面向生产的前端开发这方面还是更推荐使用现有的脚手架。
import
中遇到一个间接的require()
的崩溃。需要注意的是,只有将Bun作为运行时才能享受到这种模块解析策略:
使用import
导入不需要拓展名(可选),不区分大小写,将按顺序遍历以下拓展名
require
和import
,如下表所示:模块类型 | require() | import * as |
ESM | Module Namespace | Module Namespace |
CJS | module.exports 对象 | default 是module.exports ,其中keys作为命名导出 |
node_modules
中package的寻找策略与Node保持一致这一部分我们使用Bun和Vite构建一个CSR React 单页应用。
首先安装Bun:
curl -fsSL https://bun.sh/install | zsh # 选择自己的终端
根据官方引导初始化脚手架:(不得不说真的很快)
需要注意的是,使用Bun运行命令(包括bunx
和bun run
)都需要加上—-bun
参数才会使用Bun而不是Node运行,否则Bun仅仅起到了包管理器的作用。
同样可以用于构建:
同样,我们使用hyperfine对比使用bun、pnpm、npm的构建时间。
第一组使用bunx, pnpx, npx运行vite build
,结果有些出乎意料,npx居然是最快的,bunx的表现和npm相近,pnpx出乎意料地慢。
第二组测试带--bun
对时间产生的影响,不带参数表示仅将Bun作为包管理器,不会将shebang中的!env node
调用为Bun。
结果显示使用Bun反而使时间变长了,what a shame!
Bun.build()
我们同样使用hyperfine来测试使用Bun的打包效率。
这里我们使用Bun的CLI bun build ./src/main.tsx --outdir dist
和 bunx vite build
来对比,发现Bun.build()
非常快,比起Vite使用的rollup方案有24倍的加速。
使用官方教程初始化一个EdenX项目:
bunx @modern-js/create@latest edenx-bun # 选择yarn和rspack
cd edenx-bun && rm -rf node_modules # 删除原有的node_modules
接着尝试使用Bun运行rspack的dev-server,使用bun run --bun
指令出现了以下错误:
值得注意的是,这个关于Module
错误出现频率较高,值得后续探究。
使用bun run dev
(Node运行时)后问题解决,可以正常运行,Bun作为包管理器的优势还是很大的。
这里拿之前的一个EdenX应用测试,在删除node_modules
后使用Bun全新安装后rspack打包卡住。
猜测是依赖解析出现了问题,表现如下:
使用run build
进行rspack打包时(包括npm、pnpm和bun)均会卡在中间60%左右的位置
run build
都会卡住bun run build
正常运行这里的插件和loader通常指将Bun作为构建器使用的场景,同时也要运行时能力作基础
由于Bun也可以兼职Bundler,因此也有一套基于esbuild的插件API,用于拦截导入行为并执行自定义操作。使用其他esbuild插件时应注意兼容性,因为Bun并不支持全部API。
同样地,Bun也支持loader,其定义也是Plugin
类型,用于拓展Bun支持的类型。
与Node + Bundler的方案不同,Bun中定义的插件可以无需构建转译过程,直接在运行时层面支持loader定义的类型。
Bun is designed as a drop-in replacement for Node.js. It natively implements hundreds of Node.js and Web APIs, including fs
, path
, Buffer
and more.
node_modules
的组织方式bun create
== pnpm create
Bun和Deno采用了相似的技术栈,如下表所示,为什么Bun相对Deno仍有较大的性能领先(根据官网对比图)是值得探究的。
Bun | Deno | |
主要语言 | Zig (C-like) | Rust |
编译平台 | llvm | llvm |
引擎 | JS Core | V8 |
作者: ChlorineC
创建于: 2023-10-25 00:00:00
更新于: 2025-01-19 05:02:00
版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
前端模块化的演变包括CJS、AMD、CMD、UMD和ESM,现代浏览器支持ESM,webpack负责模块处理和打包,确保在浏览器中高效运行模块,支持代码拆分和Tree Shaking以优化性能。
本文探讨了在React中使用react-use-websocket库来实现WebSocket功能的最佳实践,包括如何封装WebSocket逻辑、处理连接状态、发送和接收消息,以及使用ArrayBuffer传输文件。此外,文中还介绍了Socket.IO的工作原理及其与WebSocket的区别,提供了Node.js后端服务器的示例代码,展示了如何实现低延迟的双向通信和进度反馈。
pnpm是一个高效且节省空间的前端包管理器,通过改进的非扁平node_modules目录和硬链接机制优化依赖管理。与npm和yarn相比,pnpm在性能和兼容性上表现优越,尤其在缓存情况下安装速度更快。pnpm的安装过程分为解析、目录结构计算和链接依赖项三个步骤,采用符号链接解决幽灵依赖问题,并通过硬链接机制减少硬盘占用。pnpm还支持monorepo,并提供了操作全局store的命令。
本文介绍了前端包管理器npm的发展历史、功能和重要性。npm不仅是一个包管理器,还是前端项目管理的基础,管理依赖、项目信息和脚本。文章详细探讨了npm的历史、模块化概念、依赖管理的演变,以及如何使用npm管理项目和发布自己的npm包。尽管npm在现代前端开发中逐渐被yarn和pnpm等新工具取代,但其核心思想和机制仍然对开发者有重要的学习价值。