文档 / 语言手册 / 构建性能
Edit

构建性能

ReScript 认为安装时,构建时和运行时的性能都很重要;只有在你失去时,你才会珍惜。

性能优化

有时,由于一些混乱的基础设置,你的构建可能会很慢。我们通过 bstracing 为你提供一个交互式的构建性能可视化:

SH
./node_modules/.bin/bstracing

在你的 ReScript 项目根目录运行上面的命令;它会生成一个 JSON 文件,你可以将此文件拖入 chrome://tracing 中。

bstracing 的截图

底层原理

ReScript 底层使用一个名为 Ninja 的构建系统。Ninja 和 Make 类似,但是跨平台、最小化、专注于性能,注定成为比完整的构建系统更低级的构建模块。在这方面,Ninja 是 rescript 的一个很棒的实现细节。

ReScript 读取 bsconfig.json 并在 lib/bs 中生成 Ninja 构建文件。该文件包含底层的编译器命令、命名空间规则、中间产物的生成和其他内容。最后运行 ninja 进行实际构建。

JS Wrapper

rescript 本身是一个 Node.js 的 wrapper,它负责处理繁杂的任务,以及监听文件变化。底层的,无监听的,且快速的原生 rescript 被称为 rescript.exe。它位于 node_modules/rescript/{your-platform}/rescript.exe

如果你不需要监听文件变化,你可以运行上面的 rescript.exe。这就避免了 Node.js 漫长的启动时间,它的启动时间可能在 100ms 左右。我们的编辑器插件会找到并使用这个本地的 rescript.exe,以获得更好的性能。

数据

使用原生 rescript.exe 构建小型项目的耗时应该在 70ms 左右。当你使用附带文件监听的 JS rescript wrapper 时,构建耗时会翻倍,但实际上构建速度变快了,因为你不需要在每次修改时手动运行构建(尽管你应该选择原生 rescript.exe 用于程序化的用途,例如将 rescript 插入到你现有的 JS 构建管线)。

无操作构建(当没有文件改变时)应该在 15ms 左右。项目中单个文件的增量重新构建(即将介绍)也在 70ms 左右。

清理构建制品应该是立即完成的。

极端测试

在一台 Retina Macbook Pro Early 2015(3.1 GHz Intel Core i7)上,我们使用 https://github.com/rescript-lang/build-benchmark ,在一个拥有 10,000 个文件的大型项目(2 个目录,每个 5000 个文件,前 5000 个没有依赖项,后 5000 个依赖 10 个前一个目录的文件)上,对 rescript.exe 进行了压力测试。

  • 10k 文件的无操作构建:800ms(检查 10k 文件的 mtimes 所需的最少时间)。

  • 清除后构建:小于 3 分钟。

  • 增量构建:取决于文件的依赖数量,没有依赖意味着 1s 即可完成。

稳定性

rescript 是基于文件的构建系统。尽管在内存中构建会极大地提升构建速度,但是我们选择不在内存中构建。在内存中构建存在内存泄漏、内存耗尽、构建半途中断以及其他风险。我们的 watch 模式可以整天整月地开着,不会出现内存泄漏。

监听器实际上也是一个调用 rescript.exe 的小型文件监听器。我们不喜欢保姆级的守护进程。

渐进性 & 正确性

ReScript 并不是每次都要花整整几秒钟来运行。大部分的构建开销来自于增量构建,也就是当只有一些文件发生变化时,才重新构建一个先前已经构建过的项目。

简而言之,得益于我们的编译器和构建系统架构,我们能够只构建需要构建的内容。例如,如果 MyFile.res 没有改变,那么它就不会被重新编译。你可以在 JavaScript 等语言中粗略地模仿这种渐进方式,但不幸的是,其正确程度很低。例如,如果你重命名或移动一个 JS 文件,那么监听器可能会混乱,无法找到“新”文件,或不能正确地清理内容,导致你需要清理你的构建并重新构建,这就没有达到增量的目的。

和 JavaScript 生态中陈旧的构建系统说再见吧!

加快增量构建的速度

ReScript 使用接口文件的概念(.resi)(等同于模块签名),只公开你需要的东西,这自然会加快增量构建的速度。例如,如果你改变了一个 .res 文件,而该文件对应的 .resi 文件没有暴露所改变的部分,那么就减少了必须重新构建的依赖文件的数量。

程序化用法

不幸的是,如今的 JS 构建系统通常是构建一个 JS 项目的瓶颈。如果构建其他部分需要数秒钟或数分钟,那么即使其余部分的构建速度快得惊人,也并没有什么意义。下面是几个建议:

  • 将更多的文件转换成 ReScript =)。让更少的文件通过更少的 JS 构建管线会有很好的效果。

  • 谨慎引入更多的依赖项:库,语法转换(例如非官方支持的 PPX),构建加载器等。拖后腿的编辑和构建体验,可能会超过它们提供的 API 优势。

热重载

热重载是指维护一个开发服务器并监听文件的变化,允许服务器将一些差异变化直接输送到当前运行的浏览器页面。在特定的框架中工作时,这种方式提供了一个相对快速的迭代工作流。

然而,热重载在本质上是脆弱的,并且偶尔会出现不一致性(状态错误、执行错误等),也依赖繁重的开发服务器设置/配置,总体来说弊大于利。我们从谨慎和稳定的角度出发,决定 暂时 不提供内置的热重载。注意:你仍然可以使用你的 JS 构建管线所提供的热重载。