Bonsai: Janestreet's UI Library
Bonsai: Jane Street 的 UI 库
Bonsai is a UI library for building performant, reactive web applications in OCaml, partly inspired by Elm. It is used to build almost all web applications inside Jane Street, everything from the corporate directory to tools that monitor and interact with our trading systems. Bonsai 是一个用于在 OCaml 中构建高性能、响应式 Web 应用程序的 UI 库,其灵感部分源自 Elm。它被用于构建 Jane Street 内部几乎所有的 Web 应用程序,从公司通讯录到监控和交互交易系统的工具,应有尽有。
A simple Bonsai component with a little interactivity looks like this: 一个带有简单交互功能的 Bonsai 组件示例如下:
module Dice = struct
let faces = [ "⚀"; "⚁"; "⚂"; "⚃"; "⚄"; "⚅" ] ;;
let component (graph @ local) =
(* Components are implemented as purely functional state machines. *)
let face, set_face = Bonsai.state (List.hd_exn faces) graph in
(* Components are incrementally rendered, only when the relevant parts of the state change. *)
let%arr face and set_face in
{%html|
<div>
You rolled a #{face}
<button style="" on_click=%{fun _ ->
let index = Random.int (List.length faces) in
set_face (List.nth_exn faces index)}>
Roll the dice
</button>
</div>
|} ;;
end
Most internal Jane Street web applications are built with Bonsai. Components are implemented as purely functional state machines, and are easily composable. Incrementalization inside the framework means that values don’t get recomputed until necessary. This applies to every value, not just the view. Jane Street 内部的大多数 Web 应用程序都是使用 Bonsai 构建的。组件被实现为纯函数式状态机,并且易于组合。框架内部的增量化机制意味着数值只有在必要时才会被重新计算。这适用于每一个数值,而不仅仅是视图。
Why Bonsai? Other web frameworks tend to lump together state, incrementality, and rendering into a single abstraction, the UI component. By contrast, Bonsai allows you to compose state and incrementality primitives a la carte. The same primitives that prevent re-rendering the entire page during user interaction can also be used to incrementalize an expensive business logic computation on a live-updating dataset. (If you’re used to React, imagine if everything used something very similar to hooks, and state was managed outside of the component hierarchy.) 为什么选择 Bonsai?其他 Web 框架倾向于将状态、增量化和渲染打包成一个单一的抽象——即 UI 组件。相比之下,Bonsai 允许你按需组合状态和增量化原语。那些在用户交互期间防止页面整体重绘的原语,同样可以用于对实时更新数据集中的昂贵业务逻辑计算进行增量化。(如果你习惯使用 React,可以想象一下如果所有东西都使用类似于 Hooks 的机制,并且状态是在组件层级之外进行管理的。)
Since state is not associated with explicit components, there is an extensive API for managing the lifecycle and scoping of state as users interact with the page. For instance, if you wanted to embed a collection of stateful UI components inside another UI component (in a tabbed interface, say), Bonsai will handle the state management for you instead of requiring that you manually hoist every internal component’s state to the app’s top-level model. For more examples of how state is composed, see this composition comparison written by the creator of Bonsai. 由于状态并不与显式组件绑定,因此有一套丰富的 API 用于管理用户与页面交互时的状态生命周期和作用域。例如,如果你想在另一个 UI 组件中嵌入一组有状态的 UI 组件(比如在标签页界面中),Bonsai 会为你处理状态管理,而不需要你手动将每个内部组件的状态提升到应用程序的顶层模型中。有关状态如何组合的更多示例,请参阅由 Bonsai 创建者编写的组合对比文档。
And because Bonsai is written in OCaml, it becomes possible to use the same language and types on both the backend and frontend. It’s hard to overstate the impact this has on legibility and keeping a large web app’s codebase manageable, especially when you make pervasive use of OCaml’s type system to reduce errors. At Jane Street, many internal systems previously only had terminal UIs, and the framework has made it easy to port the existing types and business logic to the web. 由于 Bonsai 是用 OCaml 编写的,因此可以在后端和前端使用相同的语言和类型。这种一致性对于提高代码可读性和保持大型 Web 应用代码库的可维护性有着巨大的影响,尤其是当你广泛利用 OCaml 的类型系统来减少错误时。在 Jane Street,许多内部系统以前只有终端 UI,而该框架使得将现有的类型和业务逻辑移植到 Web 端变得非常容易。
Bonsai also comes with a powerful templating language, support for component-specific stylesheets, and a system for whole-app automated tests. Bonsai 还配备了强大的模板语言、对组件特定样式表的支持,以及一套用于全应用自动化测试的系统。
Expressive tests that save you from manually clicking through your app
极具表现力的测试,让你无需手动点击应用进行测试
One of Bonsai’s most powerful features is its ability to let you easily write realistic tests, in which you programatically manipulate UI elements and watch your DOM evolve. In the following example, we’re testing the behavior of a user-selector. Whatever you type in the text box gets appended to a little “hello” message: Bonsai 最强大的功能之一是让你能够轻松编写逼真的测试,通过编程方式操作 UI 元素并观察 DOM 的演变。在下面的示例中,我们正在测试一个用户选择器的行为。无论你在文本框中输入什么,都会被附加到一个简短的“hello”消息中:
let%expect_test "shows hello to a specified user" =
let handle = Handle.create (Result_spec.vdom Fn.id) hello_textbox in
Handle.show handle;
[%expect {| <div> <input oninput> </input> <span> hello </span> </div> |}];
Handle.input_text handle ~get_vdom:Fn.id ~selector:"input" ~text:"Bob";
Handle.show_diff handle;
[%expect {|
<div>
<input oninput> </input>
- <span> hello </span>
+ <span> hello Bob </span>
</div>
|}]
Notice that there are two expect blocks. (This allows you to make multiple assertions within a given scenario and to scope setup/helper code to just that scenario.) The first makes our UI visible, and the second---which contains a diff---shows some behavior after you programatically input some text. Bonsai will even show you how html attributes or class names change in response to user input. Tests can include mock server calls, and can involve changes not just to the UI but to the state that drives it. With tests like these you can write an entire component without opening your browser.
请注意这里有两个 expect 块。(这允许你在给定的场景中进行多次断言,并将设置/辅助代码的作用域限制在该场景内。)第一个块使我们的 UI 可见,第二个块(包含 diff)展示了在通过编程输入文本后的一些行为。Bonsai 甚至会向你展示 HTML 属性或类名如何响应用户输入而发生变化。测试可以包含模拟服务器调用,并且不仅涉及 UI 的变化,还涉及驱动 UI 的状态变化。有了这样的测试,你无需打开浏览器就能编写整个组件。
Documentation
文档
The Bonsai Quick Start and Thinking in Bonsai provide a hands on introduction to Bonsai and are the best places to start learning about it. There’s also: 《Bonsai 快速入门》和《Bonsai 思维》提供了 Bonsai 的实践介绍,是开始学习它的最佳起点。此外还有:
- A series of how-to articles. (一系列操作指南文章)
- A short series of posts about Bonsai’s history. (关于 Bonsai 历史的系列短文)
- An episode of our Signals & Threads podcast about “Building a UI Framework.” (我们的 Signals & Threads 播客中关于“构建 UI 框架”的一期节目)
- Another episode on “Building Tools for Traders” discussed some of the benefits of using Bonsai. (另一期关于“为交易员构建工具”的节目讨论了使用 Bonsai 的一些好处)
- A library filled with example websites built with Bonsai. (一个包含使用 Bonsai 构建的示例网站的库)
- API documentation can be found in the
cont.mlifile. (API 文档可以在cont.mli文件中找到)
Bonsai is really a collection of libraries
Bonsai 实际上是一个库的集合
Er, one wrinkle: Bonsai itself — this library — is actually more generic than the above makes it sound. It allows you to build general-purpose incremental, composable state machines. Bonsai_web builds on top of that core library, specializing it for interactive browser-based UIs, but we also have Bonsai_term for building interactive terminal-based UIs. There was even a prototype for April Fools’ Day of Bonsai_vr for reactive virtual-reality UIs.
呃,有一个细节:Bonsai 本身——这个库——实际上比上面描述的要通用得多。它允许你构建通用的、增量的、可组合的状态机。Bonsai_web 构建在核心库之上,专门用于交互式浏览器 UI,但我们也有用于构建交互式终端 UI 的 Bonsai_term。甚至还有一个愚人节原型 Bonsai_vr,用于响应式虚拟现实 UI。
The full suite of Bonsai libraries: Bonsai 库的全套组件:
- General-purpose libraries: Bonsai is a library for building incremental, composable state machines. (通用库:Bonsai 是一个用于构建增量、可组合状态机的库。)
- Testing: Browser-based UI Libraries (测试:基于浏览器的 UI 库)
- Bonsai_web: is a library for building interactive browser-based UI using bonsai. (Bonsai_web:是一个使用 Bonsai 构建交互式浏览器 UI 的库。)
- Examples, Components, Testing, Benchmarking (示例、组件、测试、基准测试)
- Terminal-based UI Libraries: Bonsai_term is a library for building interactive terminal-based UIs using bonsai. (基于终端的 UI 库:Bonsai_term 是一个使用 Bonsai 构建交互式终端 UI 的库。)
- Examples, Components, Testing (示例、组件、测试)
- Pre-processors: Bonsai web applications often use the following preprocessors: (预处理器:Bonsai Web 应用程序通常使用以下预处理器:)
ppx_htmlis a preprocessor for writing HTML that is similar to JSX. (ppx_html是一个用于编写类似于 JSX 的 HTML 的预处理器。)ppx_cssis a preprocessor… (ppx_css是一个预处理器……)