大家好,很高兴又见面了,我是"高级前端?进阶?",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力。
今天给大家带来的主题是 Boa ,即一款纯 Rust 编写的优秀 Javascript 引擎。话不多说,直接进入正题!
1.什么是 Boa
Boa 是一个完全用 Rust 编写的 Javascript 引擎。 目前,它可以用在需要 JavaScript 语言才能工作的大多数场景。尽管如此,Boa 官方建议先等待所有已知的阻止程序错误得到解决,然后再将其用于关键工作场景。
总体来看,Boa 是一个用 Rust 编写的实验性 Javascript 词法分析器、解析器和解释器。Boa v0.17 已于 2023 年 7 月 8 日发布,该版本是自项目启动以来最大的 Boa 版本之一,经过大约 7 个月的开发时间。从此,Boa 可以让开发者轻松地将 JS 引擎嵌入到开发项目中,甚至可以从 WebAssembly 使用它。
Boa v0.17 版本在官方 ECMAScript 测试套件 (Test262) 中的一致性从 74.53% 增长到 78.74%。 虽然看起来是小幅增长,但比之前的版本多通过了 6,079 项测试。 无论如何,此版本中的重大变化与一致性无关,而是与巨大的内部增强和开发者能使用新 API 相关。
目前 Boa 在 Github 上通过 MIT 协议开源,有超过 4.1k 的 star、代码贡献者 160+,是一个值得关注的前端开源项目。
2.Boa 新特性
Boa 具有以下特征:
- serde:启用 AST(抽象语法树)的序列化和反序列化
- console : 启用 boas WHATWG 控制台对象实现。
- profiler : 使用 measureme 启用分析(这主要是内部的)。
同时 Boa 实现了大多数的模块,比如:
- bigint:实现了 JavaScript bigint 原始 rust 类型。
- 内置模块:例如 Object、String、Math 等。 用于实现本机类的类特征和结构。
- 上下文: JavaScript 上下文。
- 环境 :环境处理、词汇、对象、函数和声明性记录
- exec: 执行 AST,解释器实际运行的地方
- gc:代表了与垃圾收集器交互的主要方式。
- object:实现了 JavaScript 对象的 Rust 表示。
- prelude :重新导出最常用的 Boa API
- profiler
- property: 实现属性描述符。
- realm: realm 由一组内在对象、ECMAScript 全局环境、在该全局环境范围内加载的所有 ECMAScript 代码以及其他关联的状态和资源组成。
- string
- symbol:实现了全局符号对象。
- syntax: 语法分析,例如抽象语法树(AST)、解析和词法分析
- value :该模块实现了 JavaScript Value。
接下来一起看看 Boa v0.17 又为开发者带来了那些新特性。
2.1 支持模块
Boa v0.17 已经支持模块系统,此实现尝试严格遵循 ECMAScript 的模块规范,其中包括一些有用的 Hooks 来自定义模块加载,从而可以从多个源加载模块、从 URL 获取模块,甚至异步加载和解析模块以避免阻塞执行。
Boa v0.17 还实现了一个简单的加载器(当前是默认的模块加载器),该加载器能满足大多数更简单的用例:
// 创建一个新的模块加载器,使用当前目录来解析模块导入。
let loader = &SimpleModuleLoader::new(Path::new(".")).unwrap();
// 需要按顺序将其转换为 `&dyn ModuleLoader` 或 `Rc<dyn ModuleLoader>`
// 将其传递给上下文
let dyn_loader: &dyn ModuleLoader = loader;
let mut context = &mut Context::builder().module_loader(dyn_loader).build().unwrap();
let source = Source::from_bytes("1 + 3");
let module = Module::parse(source, None, context).unwrap();
// `main.mjs` 或其任何导入都可以导入 `main.mjs` 本身,所以
// 将其插入加载器以进行良好的测量。
loader.insert(Path::new("main.mjs").to_path_buf(), module.clone());
// 所有模块都使用 Promise 来表示其生命周期的完成。
// 实用方法 `load_link_evaluate` 调用 `load`,然后调用 `link` 和
// 最后`evaluate`,如果任何调用失败则返回错误。
let promise = module.load_link_evaluate(context).unwrap();
// 将作业队列向前推很重要! 否则,模块将无法进展
// 关于它们的生命周期。
context.run_jobs();
// 如果成功执行,所有模块都会返回“undefined”。
assert_eq!(promise.state().unwrap(), PromiseState::Fulfilled(JsValue::undefined()));
2.2 性能优化
常量折叠优化
常量折叠表达式是一种强大的编译器优化技术,可以显著提高编译程序的效率和性能, Boa v0.17 已经集成相关优化,旨在通过在编译时执行常量表达式来减少运行时开销。
通过常量折叠表达式优化,编译器会分析涉及常量的表达式,并将其替换为计算结果。 此过程允许编译器将算术运算、比较和逻辑表达式转换为简化形式,从而消除不必要的运行时计算。 通过消除这些计算,优化的程序受益于减少的执行时间和提高的整体性能。
对象形状(隐藏类)
隐藏类(内部称为“形状”,以避免与 JavaScript 类混淆)是构造对象的另一种方法,它将属性键(字符串或符号)(即 object.propertyName)及其属性(可写、可枚举、可配置)存储为转换 来自根形状,值作为密集数组。 这与将属性存储为从属性键到值的哈希图的传统方式不同。
这些形状创建一个转换树,其中转换是从根形状开始的属性名称和原型更改(没有属性,没有原型)。
let o = {}
// 形状 1:原型 `Object.prototype` 和属性:空
o.a = 10
// 形状 2:原型 `Object.prototype` 和属性:'a'
o.b = 20
// 形状 3:原型 `Object.prototype` 和属性: 'a', 'b'
let o2 = { a: 30; }
// 形状 2:原型 `Object.prototype` 和属性:'a'
o2.d = 50
// 形状 4:原型 `Object.prototype` 和属性: 'a', 'd' -- 从形状 2 fork
这种属性键和值的分离允许具有相同属性名称的对象共享相同的形状,从而减少内存消耗并释放其他优化(例如内联缓存)的可能性。
注意:当创建具有相同属性键的对象时,最好以相同的顺序创建它们,这可以确保对象共享相同的形状。
2.3 调试对象
$boa 调试对象已实现,以便使用 Boa 的 CLI 界面进行方便的 JavaScript 调试。 如果想使用它,需要使用 --debug-object 命令行标志运行 Boa CLI / REPL。
$boa 调试对象被分为多个模块,因此开发者可以使用 $boa.gc.collect() 触发垃圾收集,或者通过运行 $boa.function.bytecode(fn_name) 获取函数的字节码。 开发者还可以跟踪函数调用、处理编译器优化、设置运行时限制和检查对象形状。
4. 新 API
4.1 Rust 和 JavaScript 之间互操作
Boa v0.17 添加了新的内置对象包装器,例如: JsPromise、JsRegExp、JsGenerator、JsDate 和 JsDataView。
Boa v0.17 还提供了新特性,用于在 Rust 和 JavaScript 之间进行互操作,即 TryFromJs。 JavaScript 中存在的所有内置函数和 Rust 基本类型都实现了此特征,并且添加了一个新的静态方法,允许开发者将 [JsValue][js_value] 转换为 Rust 结构。
开发者还可以使用 JsValue::try_js_into() 函数将任何 JsValue 转换为 TryFromJs Rust 类型。
let js_str = r#"
let x = /[a-z0-9]@[a-z0-9]/;
x;
"#;
let js = Source::from_bytes(js_str);
let mut context = Context::default();
let res = context.eval(js).unwrap();
let rs_regexp: JsRegExp = res.try_js_into(context).unwrap();
let test_result = rs_regexp.test("hello@domain", context)?;
assert!(test_result);
此外,开发者可以为任何 Rust 结构派生 TryFromJs,如果开发者想手动转换某些结构体属性,可以覆盖它:
/// Converts the value lossly.
fn lossy_conversion(value: &JsValue, _context: &mut Context) -> JsResult<i16> {
match value {
JsValue::Rational(r) => Ok(r.round() as i16),
JsValue::Integer(i) => Ok(*i as i16),
_ => Err(JsNativeError::typ().with_message("cannot convert value to an i16").into()),
}
}
#[derive(Debug, TryFromJs)]
struct TestStruct {
inner: bool,
hello: String,
// 可以覆盖属性的转换。
#[boa(from_js_with = "lossy_conversion")]
my_float: i16,
}
let js_str = r#"
let x = {
inner: false,
hello: "World",
my_float: 2.9,
};
x;
"#;
let context = &mut Context::default();
let result = context.eval(Source::from_bytes(js_str))?;
let str = TestStruct::try_from_js(&result, context)?;
println!("{str:?}");
4.2 源 API
Boa v0.17 向 Boa 引入了新的 Source API。 新的 API 表示从路径存储的 JavaScript,如果来自纯字符串,则表示 None。
Boa v0.17 更改改进了 boa_tester 的显示,以显示正在运行的测试的路径。 它还可以通过超链接直接从 VS 终端跳转到被测试的文件,从而进一步帮助将来的错误显示和调试。
use boa_engine::{Context, Source};
fn main() {
let js_file_path = "./scripts/helloworld.js";
match Source::from_filepath(Path::new(js_file_path)) {
...
5. 其他新特性
5.1 Hooks 和 Job Queues
Boa v0.17 向 Context 添加了 HostHooks 和 JobQueue 特征。 这将允许主机实现自定义事件循环和其他主机特定功能。 这使得 Boa 对于用户和任何需要添加更复杂事件循环的未来运行时(例如 Tokio 或 Mio)来说更具可配置性。
由于此更改,Boa 的 CLI 将运行所有作业,直到队列为空,即使作业返回 Err。
5.2 国际化支持
Boa 现在有了国际化支持,尽管仍在努力完全遵守 ecma402 规范,但已经准备了几个 Intl 实用程序:
- Intl.Collator
- Intl.ListFormat
- Intl.Locale
- Intl.Segmenter
此外,Boa v0.17 添加了一个 intl 功能标志,默认情况下启用该标志,但可以禁用它以减少 Boa 的二进制大小。 WeakRef、WeakSet 和 WeakMap
6.Boa 尝鲜
Boa 解释器可以暴露给 JavaScript,开发者可以使用以下命令在本地构建示例:
npm run build
在控制台中,可以使用 window.evaluate 传入 JavaScript。要在 Web 程序集端进行开发,可以运行如下命令:
npm run serve
// 接着访问http://localhost:8080
7.本文总结
本文主要和大家探讨 Boa ,即一个完全用 Rust 编写的 Javascript 引擎。 目前,它可以用在需要 JavaScript 语言才能工作的大多数场景。相信通过本文的阅读,大家对 Boa 会有一个初步的了解,同时也会有自己的看法。
因为篇幅有限,文章并没有过多展开,如果有兴趣,可以在我的主页继续阅读,同时文末的参考资料提供了大量优秀文档以供学习。最后,欢迎大家点赞、评论、转发、收藏!
参考资料
https://boajs.dev/posts/2023-07-08-boa-release-17/
https://github.com/boa-dev/boa
https://boajs.dev/boa/playground/
https://docs.rs/Boa/latest/boa/#modules
https://boajs.dev/posts/2022-10-24-boa-usage/