文章目录
  1. 1. Boa_engine的基础用法
  2. 2. Boa引擎中注册全局变量
  3. 3. 实现简单的ejs模板引擎
  4. 4. 后记

Boa是Rust语言实现的实验性Javascript语言编译与执行引擎,参考实现EcmaScript规范。在Rust项目中引用的包名为boa_engine, 当前版本为0.17.3。Boa提供了基于WASM实现网页版playground,可动手实践,实时查看执行结果 https://boajs.dev/boa/playground/

Boa_engine的基础用法

新建一个Rust项目后,然后添加依赖boa_engine

1
2
3
cargo new boa_tpl --bin
cd boa_tpl
cargo add boa_engine

编辑src/main.js,将生成的代码替换成如下内容,来自boa项目文档的示例。 先从boa_engine引入类型, Source用于处理JavaScript源代码,Context是执行代码的上下文环境。如下代码直接从string来创建Source对象, 然后使用context.eval()执行。如果要从文件加载js代码,使用Source::from_filepath(Path::new('js_file_path.js'))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use boa_engine::{Context, Source};
fn main() {
let js_code = r#"
let two = 1 + 1;
let definitely_not_four = two + "2";
definitely_not_four
"#;
// Instantiate the execution context
let mut context = Context::default();
// Parse the source code
match context.eval(Source::from_bytes(js_code)) {
Ok(res) => {
println!(
"{}",
res.to_string(&mut context).unwrap().to_std_string_escaped()
);
}
Err(e) => {
// Pretty print the error
eprintln!("Uncaught {e}");
}
};
}

接下来编译并执行,项目输出结果为22, 符合预期, 数值类型与字符类型相加结果为字符类型。

1
2
3
$ cargo run
..
22

参考:

Boa引擎中注册全局变量

Boa中支持注册全局变量、回调方法,使用Context.register_global_property(), Context.register_global_callable()

实现简单的ejs模板引擎

经试验, Boa支持ES6中的模板语法、正则表达式、JSON等,也有对模块语法import/export的支持。这里参考网络上对ejs模板的实现原理,尝试在Boa中实现ejs模板。

如下是一个简单的ejs模板,就是在html内容中使用标签嵌入js代码。个人理解, 主要是2类标签: 1)<% xx %>用于流程控制,不输出内容; 2)<%= xx %>一类主要用于输出的,包含<%=html内容,<%_去除空格, <%-不转义html内容。

1
2
3
4
5
6
7
8
<div>
hello <%= name %>, sleep:
<% if (sleep) { %>
yes
<% } else { %>
no
<% } %>
</div>

ejs模板引擎实现的基本原理,是将模板内容转换成可执行的javascript方法:模板中非标签的内容都转换成字符串用于直接输出, <%= xx %>替换为变量,而<% xx %>中的内容则直接作为编译后方法体中的语句。 如下是简单实现, 使用正则表达式进行替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 将ejs模板转换为js代码
function convert_to_js(tpl) {
tpl = tpl.replace(/<%=(.+?)%>/g, '${$1}')
const head = 'let str = ``\r\nwith(data){\r\nstr+=`'
const body = tpl.replace(/<%(.+?)%>/g, '`\r\n$1\r\nstr+=`')
const tail = '`}\r\nreturn str'
tpl = head + body + tail
return tpl;
}
//创建编译后的方法Function
function compile_template(tpl) {
var js_content = convert_to_js(tpl);
return new Function('data', js_content);
}
// 执行
//var tpl_func = compile_template(tpl_content)
//tpl_func({name: 'good', sleep: false})

前面ejs模板内容,在执行convert_to_js()后,返回的内容如下:

1
2
3
4
5
6
7
8
9
10
11
let str = ``
with(data){
str+=`<div>hello ${ name }, sleep: `
if (sleep) {
str+=` yes`
} else {
str+=` no `
}
str+=`</div>`
}
return str

接下来,将在Rust使用Boa执行这个简单的模板。一种简单的方式是将模板引擎方法、执行模板引擎过程写在同一个js文件中,然后在Boa引入文件内容执行。这里将采用一种复杂的方式,将引入模板引擎与执行模板引擎过程分开,目的是要复用引擎,一次引入后可以针对不同的模板内容多次执行。

将ejs模板引擎文件保存为ejs-convert.js, 将模板内容保存为hello.tpl。Rust代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use std::{path::Path, fs::read_to_string};
use boa_engine::{Context, Source, js_string, property::Attribute};
fn display_template_result() {
// js执行上下文
let mut context = Context::default();
// 从文件加载js内容,生成方法compile_template
let js_file = "./ejs-convert.js";
let source1 = Source::from_filepath(Path::new(js_file)).unwrap();
context.eval(source1).unwrap();
// 将模板内容注册为全局变量tpl_content
let tpl_file = "./hello.tpl";
let tpl_content = read_to_string(tpl_file).unwrap();
context
.register_global_property(js_string!("tpl_content"), js_string!(tpl_content), Attribute::all())
.expect("property shouldn't exist");
// 生成编译后的方法 tpl_func
context.eval(Source::from_bytes("var tpl_func = compile_template(tpl_content);")).unwrap();
// 2次执行模板方法
println!("{}", context.eval(Source::from_bytes("tpl_func({name: 'good', sleep: false})")).unwrap().display());
println!("{}", context.eval(Source::from_bytes("tpl_func({name: 'Bigone', sleep: true})")).unwrap().display());
}

2次执行模板方法的打印输出如下,说明执行成功。

1
2
3
4
5
6
7
8
9
<div>
hello good, sleep:
no
</div>
<div>
hello Bigone, sleep:
yes
</div>

参考:

后记

最近研究Rust中的Javascript引擎以及ejs模板, 出发点只是想将个人博客引擎使用的Hexo进行替换,而Hexo中使用的theme模板中使用了ejs。个人并不太想更改页面样式,于是开始了解是否有Rust库实现ejs,并进行了些尝试。github上有用golang实现ejs, 但没有Rust的实现。Boa只是Javascript引擎,要实现直接引用ejs项目代码的话,require模块引用以及nodejs中fs等模块缺失,要实现可用的ejs模板还要下一番功夫才行。

参考:

文章目录
  1. 1. Boa_engine的基础用法
  2. 2. Boa引擎中注册全局变量
  3. 3. 实现简单的ejs模板引擎
  4. 4. 后记