波浪线 ~/ 一种优雅的 JS import 路径简化写法

本文对提升您的编程开发能力毫无帮助。仅供代码洁癖患者食用。

在大型前端项目中,用文件夹来组织代码很常见。但是 JS 的引用是基于相对路径的,一旦路径到达一定深度,就会出现非常长的路径:

import Button from '../../../../components/button';
import Factory from '../../../../types/Factory';

这样会带来两个烦恼:

  1. 要精确的计算 ../.. 的层级数量是一项很耗精力的事情。
  2. 如果相对路径很长,就会折行,让代码更难阅读。
继续阅读 →

前端构建工具 2023

标准

这里定义的前端构建工具需要符合以下条件:

  • 能够构建生产用的前端资源。
  • 提供带 HMR 的 Dev Server。
  • 支持常见的前端技术,比如 Sass, LESS, CSS modules, code splitting。
  • 支持插件扩展。
  • 不限于某种 UI 框架,不捆绑不必要的运行时内容,比如 Redux。(排除 UMI 之类的深度定制框架)

生产可用

这里定义的生产可用条件:

  • 流行度高。(npm 周下载量 >100k)
  • 维护活跃。(六个月内至少发布了一个版本。)
  • 没有严重的性能或兼容性问题。

Vite 4

Vite 基于 esbuild (用 Go 语言编写) 和 Rollup。它采用了 Unbundled Dev Server 模式,通过浏览器原生 ESM 特性,省去了 Bundle 步骤,以达到比 Webpack Dev Server 更快的 HMR 和启动速度。Vite 4 提供了一个新的基于 SWC 的 React 插件 @vitejs/plugin-react-swc ,用于替换旧的基于 Babel 的插件。这让 React Fast Refresh 的速度快了 20 倍。目前,Vite 仍然是速度最快的前端构建解决方案之一。

Vite 易于使用。即使不进行任何配置,也能开始使用。如果需要定制,它的文档也非常详细好用。Vite 兼容 Rollup 插件和配置接口,甚至可以借用 Rollup 的文档。用户更容易找到需要的插件,遇到问题也更容易找到解决方案。编写 Vite 插件也相对容易。所以 Vite 的社群和生态系统成长很快。Vite 的 JS API 设计简单清晰,功能丰富。基于 Vite 开发你自己的构建工具很容易。

Parcel 2

Parcel 2 基于一系列用 Rust 重写的高性能模块,比如 SWC 和 Parcel CSS,实现了优于 Webpack 的性能。默认配置下,Parcel 2 的性能介于 Vite 4 和 Webpack 5 之间。但是可以通过启用一些实验性功能提升性能,比如 swc 压缩。

Parcel 2 的另外一个特点是开箱即用。它的默认配置已经涵盖了 TypeScript,CSS modules,JSX,React Fast Refresh,图片压缩,SVG 优化等特性支持。另外,当检测到 Sass 或 LESS 时,Parcel 2 会自动帮你安装需要的依赖包。对于常规的前端应用,你完全不需要配置文件。

Parcel 有自己独特的架构和接口设计,这让它几乎不可能直接借用 Webpack 和 Rollup 的插件。因此它的生态系统中第三方插件很少。另外,由于 Parcel 发布大版本的频率很低,内置插件为了避免 Breaking Change 而很难升级,比如 MDX 2.0 至今没有被支持。这可能是 Parcel 2 最大的劣势。

Webpack 5

Webpack 依然是最流行的选择,有最完善的特性,以及最大的生态系统。

Webpack 很慢,但是你可以用一些 loader 和插件替换默认组件来提速:

观察列表

一些还处于早期阶段的有潜力项目。

Rspack

用 Rust 写的 Webpack 替代品,由字节跳动创建。虽然还处于早期阶段,但是已经表现出比 Vite 更快的构建速度。同时兼容 Webpack 插件,可扩展性很强。

未入选

Turbopack

为 Next.js 框架设计的高性能构建工具,原生支持 SSR。未入选原因是因为目前只能用于 Next.js 框架。

UMI

蚂蚁集团的构建工具。未入选原因是因为过度定制化,只支持 React,内置了过多 Redux 之类的运行时。

ICE

阿里巴巴的构建工具。未入选原因是因为过度定制化,只支持 React,内置了过多 Redux 之类的运行时。

Create React App

React 官方的构建工具。未入选的原因是因为过度定制化,只支持 React,不支持配置和插件。

性能测试

dev server cold startdev server warm startproduction build
rspack 0.1.12.2s2.0s1.4s
vite 4.2.14.3s1.3s6.5s
parcel 2.8.313.0s1.3s15.7s
webpack 5.76.214.2s14.2s15.8s

详情参见 https://github.com/guoyunhe/front-end-builder-benchmark

技术趋势

一些可能会让前端构建工具更快更好的技术。

原生 Node.js 模块与 WASM 模块

用 Rust,Go 等语言编写的 Node.js 模块,其性能与产物格式有很大的关系。不考虑多线程的情况,从高到低分别是 Native > WASM > JS。JS 和 WASM 本身都是单线程的,除非使用 Cluster 模式(目前还没有在前端构建工具上比较成功的)。支持多线程的 Native 模块,性能会更强,esbuild 就是一个例子。

但是,原生模块必须为不同的平台预编译对应的二进制文件,不如 WASM 或 JS 的“一次编译,到处运行”方便。

在实践中,原生模块通常用于核心高负载模块,比如 rspack,swc,esbuild。而插件还是广泛采用 JS。WASM 仍然比较少见,一个例子是 swc 的插件开发。

从 Unbundled 回到 Bundled?

用 JS 写的 Webpack Dev Server 采用 Bundled 模式启动。即先由 Webpack 将多个源码文件打包成一个,再传输给浏览器。在浏览器的网络记录中,你只会看到几个网络请求。因为 Webpack 打包的效率低,启动速度很慢,热加载也很慢。

后来出来了 Snowpack (已废弃),可能是第一个 Unbundled 模式的 Dev Server。采用 Unbundled 模式启动 Dev Server,通过原生 ESM 加载,省去了 Bundle 的过程,提高了启动速度。当 Dev Server 启动时,不会进行编译,而是在接到浏览器请求时,动态编译每个文件,每次传输一个源文件对应的代码。过程大致是:

请求 → 编译 → 响应 → 请求 → 编译 → 响应 → … → 请求 → 编译 → 响应

这导致 Snowpack 的启动速度极其缓慢,对比 Webpack Dev Server 并没有显著优势。

后来 Vite 继承并改进了 Snowpack 的思路。它将 node_modules 中大量的依赖用 esbuild 先 Bundle 成单个文件,而项目源码依旧采用 Unbundled 模式。得益于 esbuild 的强大性能,不管是依赖的 Bundle,还是单个源码文件的转译,都非常快。因此 Vite 对比 Webpack Dev Server 取得了巨大的性能优势。

然而,随着项目体积增大,需要加载的 ESM 模块越来越多,Vite 的启动速度会变慢。假设项目的 src 中有 1000 个 JS/TS 文件,就会有 1000 个 HTTP 请求。现代的浏览器只允许每个服务器建立 6 个连接,也就是说最多只能并发 6 个 HTTP 请求,导致阻塞。

因此 Parcel 2,Turbopack 和 Rspack 选择了回到 Webpack Dev Server 的 Bundled 模式,通过用 Rust 重写关键部件,改进打包和编译性能来提高性能。

但是根据 Turbopack 的 Benchmark,只有在 src 中有 1000 个以上的 React 组件时,Turbopack 对比 Vite 才会有比较明显的优势。但实际应用中,1000 个组件(约100K到1M行代码)已经是相当大规模的应用了。而 Vite 的冷启动速度仍然可以接受:4.2s。另外,我觉得这个 Benchmark 用 Turobopack 的 Server Side Rendering 对比 Vite 的 Client Side Rendering,并不公平,因为 Vite 也是支持 Server Side Rendering 的。

总结一下,我觉得 Vite 的 Unbundled 策略和 Parcel 等的 Bundled 策略各有所长,在实际应用中并不能拉开明显的性能差距,未来很长一段时间仍然会共存,而不是此消彼长。

更快的 Sass 编译器

Sass 的老编译器 node-sass 已经被官方废弃,不再维护。新的官方编译器 Dart Sass 令人迷惑:

  • 原生构建二进制程序没有 Node.js 接口,无法集成到现有的 Node.js 工具中。除非你用 Node.js 的 exec 函数执行。(不推荐在生产中这样做)
  • JavaScript 构建要比 node-sass 慢 4 到 30 倍 (取决于项目大小)。如果你的构建工具调用 Sass 接口的方式不正确,速度甚至会更慢。详见 Issue #1557#1534

对于大量使用 Sass 的生产项目,这肯定是不可接受的。很多项目选择继续使用废弃的 node-sass 编译器。

现在已经有人在开发用 Rust 重写的 Sass 编译器:

  • grass。只提供编译成 WASM 的版本,但是性能并不好。
  • rsass。可惜,这个项目也没支持 Node.js API,也无法直接用于现在的前端构建工具生态。

更快的 TypeScript 检查器

TypeScript 很慢,因为它完全是 JavaScript 写的。幸运的是,我们通常不需要在构建前端项目的时候检查类型。Babel, esbuild 或 swc 会直接忽略所有类型。但是,如果你项目有很严格的 QA 要求,你不得不运行 tsc 命令检查类型,CI 工作流就会被拖慢,即使对于小型项目 (10k lines) 或中型项目 (100k lines) 也很明显。

于是乎,重写 TypeScript 检查器的呼声越来越高。我们已经能看到一些这方面的尝试:

浏览器兼容性与 Polyfill 技术

开发者通常在最新的浏览器上进行开发,以使用最新的 JavaScript 语法,浏览器接口,让工作更有效率,代码质量更高,更不容易过时。

而用户则可能仍然在使用旧版的浏览器。比如,某些政府教育系统仍然在使用 Windows XP,用户能安装的最新的浏览器是 Chrome 49。有些用户可能只是没有升级浏览器的习惯,导致他们的 Chrome 还是 86 版本。

为了平衡两方面,开发者通常会借助一些工具,自动转译语法,并 Polyfill 新的接口,以兼容某个特定版本的旧浏览器。通过语法分析,按需插入 core-js 中的 Polyfill 代码。而 core-js 缺少的部分,可以用 polyfill-service 补全。

ES 语法转译

Optional Chaining 这种比较新的 ES 语法在老版本浏览器无法支持,运行时会报错。

const dogName = adventurer.dog?.name;

为了兼容旧版浏览器,必须在构建过程中就将其转译成 ES5/ES3 语法。

var _adventurer$dog;
const dogName = (_adventurer$dog = adventurer.dog) === null || _adventurer$dog === void 0 ? void 0 : _adventurer$dog.name;

转译功能通常由 babel,swc,esbuild 等 JavaScript 转译工具提供。

ES 特性 Polyfill

Promise,Stream 等特性需要插入特定的 Polyfill 代码。

esbuild 无法自动分析并 Polyfill 特性,只能手动导入。而 swc 和 babel 可以自动分析源码,按需自动导入。swc 和 babel 都是基于 core-js 进行 polyfill。

浏览器特性 Polyfill

core-js 只提供 ES 特性的 Polyfill(准确的说,是浏览器和 Node.js 交集的部分)。但是浏览器除了 ES 标准外,还提供其他浏览器特有的特性,比如 ResizeObserver。这就需要一个补充方案。

方案1是手动取导入 Polyfill 代码,比如在项目中 import 'whatwg-fetch' 就可以支持 fetch() 接口。但是这对开发者而言是一个比较大的负担。

方案2是在页面中引用 polyfill.io 的脚本,服务器会根据 UA 判断需要加载哪些 Polyfill 代码。开发者也可以用 polyfill-service 自建服务器。缺点是增加了一些服务器开销,也会阻塞应用本体脚本的加载,造成一些成本提高和性能损失。

CSS 语法转译

很多 CSS 特性在未成为正式标准之前便已经被浏览器支持了。但是在这个成为标准的过程中,CSS 特性的提案可能会修改。浏览器厂商为了防止自己目前的实现与之后的正式标准冲突,会在选择器和属性名之前,加上自己的前缀,Firefox 是 -moz-,Chrome 和 Safari 是 -webkit-,而 IE 是 -ms-。

比如下面这段代码:

:fullscreen {
}

为了兼容不同的浏览器,尤其是老浏览器,需要转译成:

:-webkit-full-screen {
}
:-ms-fullscreen {
}
:fullscreen {
}

转译过程主要通过 postcss 实现。当前流行的前端应用构建工具,比如 create-react-app, vite, parcel, 都集成了 postcss。

横向对比

babelesbuildswcpolyfill servicepostcss
ES 语法转译
ES 特性 Polyfill
浏览器特性 Polyfill
CSS 语法转译

总结

综合来看,目前没有一种技术能够单独达成全自动的浏览器兼容性。建议使用 babel + polyfill service + postcss 之类的融合方案。

当使用一种新特性时,可以先查一下 caniuse.com 支持的浏览器版本。如果需要 Polyfill,要看自己选用的 Polyfill 方案(core-js,polyfill-service)是否支持。如果不支持,则需要手动 Polyfill 这个特性。

实践

Create React App

以 create-react-app 项目为例。首先我们需要配置项目支持的浏览器版本,让底层的 babel 和 postcss 将 JS 和 CSS 转译到目标浏览器支持的语法,并尽可能注入所需的 Polyfill 代码:

{
  "browserslist": {
    "production": [
      "chrome > 49",
      "firefox > 53",
      "ie 11"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

然后,我们需要修改 HTML 模板,注入 Polyfill.io 的脚本,以支持额外的 Polyfill 特性:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polyfill.io/v3/polyfill.min.js"></script>
  </head>
  <body>
    ...
  </body>
</html>

Vite

Vite 默认只支持 Chrome 87+,这个要求对大部分实际应用可能太过苛刻。如果要支持更早的浏览器,则必须使用 legacy 插件:

import react from '@vitejs/plugin-react';
import legacy from '@vitejs/plugin-legacy';
import { defineConfig } from 'vite';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    legacy({
      targets: [
        "chrome > 49",
        "firefox > 53",
        "ie 11"
      ],
    }),
  ],
});

然后,我们需要修改 HTML 模板,注入 Polyfill.io 的脚本,以支持额外的 Polyfill 特性:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polyfill.io/v3/polyfill.min.js"></script>
  </head>
  <body>
    ...
  </body>
</html>

下一代前端构建工具

2022 年,Webpack + Babel + Terser 仍然是前端项目构建工具的主流,广泛用于各种生产环境。不可否认,Webpack 和 Babel 仍然在持续进化,对各类研发提供了最稳定且全面的支持。但是随着前端项目规模的增长,CI 流水线的普及,构建性能问题已经成为了影响开发效率的一个关键因素。下一代构建工具,试图从不同的角度实现对构建效率的提升。

esbuild

用 Go 语言实现的构建工具,提供了编译,打包,压缩能力。由于是原生程序,且几乎不依赖第三方库,充分利用 CPU 多核架构等原因,esbuild 实现了夸张的毫秒级编译速度,效率数百倍于 Webpack + Babel。

优点:

  • 高性能。目前最快的实现,整体上略优于 swc。
  • 低占用。Go 语言对字符串的内存优化很好,即使采用多线程工作方式,内存占用仍然远低于基于 Webpack 的工具。

缺点:

  • 自身不提供 HMR,需要依赖其他工具实现。
  • 不支持 React Refresh,对 React 的 HMR 不能用 webpack-dev-server + esbuild 编译的方案。
  • 产物代码分割功能仍处在实验阶段,相比 Webpack 和 Rollup 还是比较简单,不太可靠。
  • 插件生态不健全。

适用场景:

  • 纯 JS 库构建。不需要 HMR,只需要 watch 能力。
  • Node.js 后端应用构建。不需要 HMR,只需要 watch 能力。
  • Jest 和 SSR 等 Node 运行环境的代码转译。
  • 嵌入其他构建工具。比如 Vite 用了 esbuild 的编译和压缩能力,不足的部分用 Rollup 插件体系和自己实现的 HMR 补全。

swc

用 Rust 语言实现的构建工具,swc 实现了编译和压缩能力,swcpack 实现了打包能力(实验性)。swc 设计之初就是完全对标 Babel 的,相比 esbuild 拥有更广泛的适用场景。

优点:

  • 高性能。实际测试略慢于 esbuild,但也比 webpack+babel+terser 快上几十倍。
  • 支持 React Refresh。这一点是对 esbuild 的巨大优势,且短期内 esbuild 无法克服的问题。

缺点:

  • 自身不提供 HMR,需要依赖其他工具实现。
  • 打包能力还是实验性质,缺乏高级配置支持。
  • 插件生态不健全,用 Rust 编写插件门槛较高。

适用场景:

  • 纯 JS 库构建。不需要 HMR,只需要 watch 能力。
  • Node.js 后端应用构建。不需要 HMR,只需要 watch 能力。
  • Jest (@swc/jest) 和 SSR 等 Node 运行环境的代码转译。
  • 嵌入其他构建工具。比如 Parcel 用了 swc 的编译和压缩能力,不足的部分用自己实现的 HMR 补全,并有 Parcel 自己的插件体系。

Vite

基于 esbuild 的编译和压缩能力,Bundleless 的 HMR 实现,并用 Rollup 插件生态补足了常用的打包特性。目前性能和特性综合考量的最佳选择。

更早的 Bundleless 实现 Snowpack 已经泯然众人了。Bundleless 最大的问题在于,现在前端应用的依赖树太深太广,模块文件太多,逐个文件加载导致启动速度极其缓慢。而 Vite 则是融合了 Bundle 和 Bundleless 两种策略,对 node_modules 还是用 esbuild 去快速 bundle,一次性加载,对于源码再采用 bundleless 策略。这样就平衡了启动速度和热加载速度。

优点:

  • 构建速度更快
  • 热更新速度更快
  • 能够使用众多的 Rollup 插件

缺点:

  • 由于 esbuild 不支持 React Refresh,React 项目还是要走 Babel,一定程度上拖慢了速度。
  • Rollup 及其插件生态还是以 JS 为主,性能优化空间有限。Vite 作者寄希望于 esbuild 打包能力和插件生态成熟,用 esbuild 替换掉 Rollup。

适用场景:

  • Web 前端应用
  • 浏览器插件
  • Electron 应用
  • 小程序

Parcel 2

Parcel 基于 swc 并有自己的 HMR 和插件生态。设计上与 Vite 类似,但 Parcel 自研部分更多,而 Vite 依赖且受限于 Rollup。性能上 Parcel 甚至更快一些,尤其是缓存性能,热启动极快。但在稳定性上 Parcel 控制的并不好,一个最简单的 React 应用都需要一些 hack 才能工作。

优点:

  • 构建速度快
  • 缓存效率高
  • 热更新速度快

缺点:

  • 不兼容 pnpm
  • 插件生态不足

适用场景:

  • Web 前端应用
  • 浏览器插件
  • Electron 应用
  • 小程序

Turbopack(Alpha)

用 Rust 实现 Webpack 替代,工作原理和 Webpack + Webpack Dev Server 保持一致,而没有走时下流行的 bundleless 路线。当项目源码(不包括 node_modules)文件数量达到数千,Vite 启动速度会随文件数量降低,即使本地几乎不存在网络延迟,数以千计的 HTTP 请求以及代码转译任务仍然耗时不菲。而 Turbopack 则通过提升打包性能,在保持 Webpack 工作方式的情况下,实现了快速启动和 HMR。当前 Turbopack 的测试性能是领先 Vite 的。

假如 Vite/Parcel 也用 Rust/Go 完全重写,那么 Vite/Parcel 和 Turbopack 应该具有以下关系:

  • 在文件数量小于 N 时,Vite/Parcel 的冷启动速度更快,但是 Turbopack 也足够快。
  • 在文件数量大于 N 时,Turbopack 的冷启动速度更快,且于 Vite/Parcel 的差距越来越大。
  • Vite/Parcel 的 HMR 速度总是快于 Turbopack,但是两者都足够快,以至于差异肉眼无法分辨。

可惜 Turbopack 目前并没有推出正式版本,完全没有达到 Vite/Parcel 的成熟度,因此完全没有可比性。目前只能关注,深入分析或评测意义不大。

JavaScript 十进制库选型

众所周知,计算机以二进制存储数字,而真实世界中用十进制表示数字。二进制和十进制的差异可以用一个乍看非常怪异的例子说明:

> 0.1 + 0.2
0.30000000000000004

不管是 JavaScript, Python 还是 Java,都能够得到类似的结果。

分析原因,就像十进制中的 1/3 是无限循环小数,二进制中的 1/10 和 1/5 也是无限循环小数。计算机的基本数字类型都是有精度限制的。两个无限循环小数相加,导致精度损失,产生了 0.30000000000000004 这样的结果。

在很多情况下,这个并不会有实际影响。因为十进制同样有精度误差,十进制并不比二进制更精确。它真正导致的问题是违反直觉。对于人直接可见的数字计算,比如购物车结算总价,出现二进制精度问题显然会让不了解二进制原理的普通用户感到诧异。

另外,原生浮点数的取整方法都是四舍五入。按照统计学规律,大量累加起来,总数会偏大。对于电商,银行和支付系统,会导致交易的某一方长期多付出,是违反公平的。因此这些系统常常采用银行家算法(四舍六入五成双)。

最后,整数和浮点数都有固定的精度限制,且受限于硬件或编译器无法更改。计算超出范围的大数会出错,不安全。

基于这三点原因,在电商和金融系统中,会使用十进制类而非基本数据类型来计算和存储交易数字。

JS 常用库比较

JavaScript 目前仍然没有原生的十进制类。我们只能从一些提供十进制类的 NPM 包中选择。不同的十进制库提供的功能不完全相同。通常功能越多体积越大。目前开发比较活跃且比较流行的 JS 库都来自同一位作者。差异详见此处

NPM 包big.jsbignumber.jsdecimal.js
打包尺寸 Minified + Gzipped2.9kB8.1kB12.3kB
精度控制N位小数N位小数N位有效数字
除法精度损失有损有损有损
加、减、乘法精度损失无损无损有损
超大数支持,计算较慢支持,计算较快支持,计算较快
超小数支持,计算较慢支持,计算较慢支持,计算较快
舍入模式ROUND_DOWN
ROUND_HALF_UP
ROUND_HALF_EVEN
ROUND_UP
ROUND_UP
ROUND_DOWN
ROUND_CEIL
ROUND_FLOOR
ROUND_HALF_UP
ROUND_HALF_DOWN
ROUND_HALF_EVEN
ROUND_HALF_CEIL
ROUND_HALF_FLOOR
ROUND_UP
ROUND_DOWN
ROUND_CEIL
ROUND_FLOOR
ROUND_HALF_UP
ROUND_HALF_DOWN
ROUND_HALF_EVEN
ROUND_HALF_CEIL
ROUND_HALF_FLOOR
EUCLID
指数计算支持支持支持
对数计算不支持不支持支持
复数计算不支持不支持支持
三角函数计算不支持不支持支持

从简单到复杂排列:big.js < bignumber.js < decimal.js

从打包体积来看,big.js 最小,在浏览器端占据优势。

在电商和金融场景,更常见的精度控制方法是控制 N 位小数,big.js 和 bignumber.js 更加合适。而在科学计算和工程领域,可能 decimal.js 的控制 N 位有效数字的特性会更合适一些。

除法都是有精度损失的,这是无法避免的。但是 decimal.js 的加减乘法也有精度损失,这一点需要注意。

big.js 对于超大数的计算会比 bignumber.js 或 decimal.js 慢,要结合具体场景分析是否会性能瓶颈。在前端/客户端场景,或者小型电商后端,计算量很小,且大数不常见,那么 big.js 完全可以胜任。在大型电商平台和金融交易后端,大数计算量繁重,那么 bignumber.js 会更好一些。科学和工程领域需要的超大数计算,则 decimal.js 会更合适。(但是这些领域通常也不会用 JS 这么慢的语言,所以 decimal.js 的定位有点尴尬)

超小数通常只在金融,科学和工程领域出现,这种数据的计算 decimal.js 会比 big.js 和 bignumber.js 快。和上面的大数应用场景分析类似,要视情况选择。

默认的舍入模式都是四舍五入(ROUND_HALF_UP),且都支持银行家算法——四舍六入五成双(ROUND_HALF_EVEN)。对于大多数电商和金融场景都足够用了。

decimal.js 还支持对数,复数,三角函数等高级数学运算。

按应用场景选择

中小型电商

中小型电商的交易数字都偏小,big.js 在前后端都可以胜任,满足精度控制,舍入模式和性能的需求。同时 big.js 的体积较小,有利于优化前端传输速度。

大型电商平台和金融系统

前端/客户端通常只处理单一客户的计算需求,大数出现频率较少,通常不存在性能问题。 big.js 依然可以胜任。

后端则可能会频繁处理大数,这时候 bignumber.js 会更快一些。但是,这种情况通常表明整个系统都面临性能问题,更加实际的做法是将重度计算的服务用 Java 之类的更高性能语言重构……

科学计算和工程领域

decimal.js 功能更加丰富,对极大和极小数计算的性能更好,比 big.js 和 bignumber.js 更合适。

但是,这些领域通常对性能要求很高,用 JS 的情况非常少见。建议直接使用一种更合适的语言……

总结

前端选择 big.js 一般都没错。它简单小巧,却又能提供所有我们需要的功能。

后端 Node.js 不需要考虑打包尺寸,如果有比较多的大数计算量,也可以上 bignumber.js 的。

decimal.js 的定位则比较尴尬,除非你特别需要某些科学计算功能,否则没有必要选它。

实践

  1. 阿里巴巴 Fusion Design 从 bignumber.js 切换到 big.js PR 链接