Yew是React和Elm的类似物,完全用Rust编写,并编译成一个诚实的WebAssembly。 在这篇文章中,Yew开发人员Denis Kolodin讨论了如何在没有垃圾收集器的情况下创建框架,如何有效地确保不可变,无需由于Rust数据的所有权规则而复制状态以及将Rust转换为WebAssembly的功能。
该帖子是根据Denis在HolyJS 2018 Piter会议上的报告准备的。 在剪切下-报告的视频和文本成绩单。Denis Kolodin在Bitfury Group工作,该公司开发各种区块链解决方案。 两年多来,它一直使用Mozilla Research的编程语言Rust进行编码。 在此期间,Denis设法彻底研究了该语言,并使用它来开发各种系统应用程序(后端)。 现在,随着WebAssembly标准的出现,我开始将目光投向前端。议事日程
今天,我们将了解Yew是什么(框架的名称与英语单词“ you”-you相同;“ yew”是从英语翻译而来的紫杉树)。
让我们讨论一下架构方面的问题,框架所基于的思想,嵌入其中的可能性以及与其他语言相比Rust额外提供给我们的功能。
最后,我将向您展示如何立即开始使用Yew和WebAssembly。
紫杉是什么?
首先是WebAssembly,即 在浏览器中工作的可执行字节码。 为了在用户端运行复杂的算法(例如加密,编码/解码),这是必需的。 用系统语言实现这一点比拧紧拐杖要容易得多。
WebAssembly是所有现代浏览器都清楚描述,理解和支持的标准。 它允许您使用各种编程语言。 这很有趣,主要是因为您可以重用社区以其他语言创建的代码。
如果愿意,您可以在WebAssembly上完全编写一个应用程序,Yew允许您这样做,但是重要的是不要忘记即使在这种情况下,JavaScript仍保留在浏览器中。 有必要准备WebAssembly-获取模块(WASM),向其中添加环境并运行它。 即 JavaScript是必不可少的。 因此,WebAssembly值得考虑作为JS的扩展而不是革命性的替代品。
什么样的发展

您有一个源代码,有一个编译器。 您将所有这些转换为二进制格式并在浏览器中运行。 如果浏览器较旧,没有WebAssembly支持,则需要emscripten。 大致来说,这是用于浏览器的WebAssembly模拟器。
是的-准备使用wasm框架
让我们继续到紫杉。 我在去年年底开发了此框架。 然后我在Elm上编写了某种加密货币应用程序,面对这样一个事实,由于语言限制,我无法创建递归结构。 那时我想:在Rust中,我的问题将很容易解决。 而且由于有99%的时间我用Rust编写代码,并且只是因为它的功能而热爱这种语言,所以我决定尝试-用Rust中相同的更新功能编译应用程序。
第一个草图花了我几个小时,我不得不弄清楚如何编译WebAssembly。 我启动了它,意识到在短短几个小时内它就奠定了核心,这很容易开发。 我只花了几天的时间就将所有内容带到最小的框架引擎。
我将其发布在开源中,但没想到它会受到欢迎。 但是,今天他已经在GitHub上收集了4000多个星星。 您可以在
此处查看项目。 有很多例子。
该框架完全用Rust编写。 Yew支持直接编译为WebAssembly(wasm32-unknown-unknown目标),而无需使用脚本。 如有必要,您可以遍历脚本。
建筑学
现在,简要介绍一下该框架与JavaScript世界中存在的传统方法有何不同。
首先,我将向您展示我在榆木中遇到的语言限制。 以存在模型的情况为例,并有一条消息允许您转换此模型。
type alias Model = { value : Int } type Msg = Increment | Decrement
case msg of Increment -> { value = model.value + 1 } Decrement -> { value = model.value - 1 }
在Elm中,我们只需创建一个新模型并将其显示在屏幕上即可。 该模型的先前版本保持不变。 我为什么要专注于此? 因为在Yew中,模型是可变的,这是最常见的问题之一。 接下来,我将解释为什么这样做。
最初,在重新创建模型时,我遵循经典路径。 但是随着框架的发展,我发现存储模型的先前版本没有任何意义。 Rust允许您跟踪所有数据的生命周期,无论它们是否被修改。 因此,我知道Rust控制了没有冲突,所以我可以安全地更改模型。
struct Model { value: i64, } enum Msg { Increment, Decrement, }
match msg { Msg::Increment => { self.value += 1; } Msg::Decrement => { self.value -= 1; } }
这是第一刻。 第二点:为什么我们需要旧版本的模型? 在同一榆树中,几乎没有竞争竞争机会的问题。 仅需要旧模型即可了解何时进行渲染。 意识到这一刻,使我完全摆脱了不变,而不保留旧版本。

当我们具有
update
功能和两个字段
value
和
name
时,请查看该选项。 当我们在
input
字段中输入数据时,将保存一个值。 模式正在改变。

在渲染中不涉及
value
这一点很重要。 因此,我们可以根据需要进行任意更改。 但是我们不需要影响DOM树,也不需要启动这些更改。
这导致我想到只有开发人员才能知道真正需要启动渲染的正确时机。 首先,我开始使用标志-只是一个布尔值
ShouldRender
,该信号表明模型已更改,我们需要开始渲染。 同时,进行常量比较没有开销,也没有内存消耗-用Yew编写的应用程序最有效。
在上面的示例中,除了生成和发送的消息外,根本没有内存分配。 模型保留其状态,并且仅在标记的帮助下,这才反映在呈现中。
可能性
编写可在WebAssembly中使用的框架并非易事。 我们有JavaScript,但是它应该创建某种需要与之交互的环境,这是大量的工作。 这些捆绑包的初始版本如下所示:

我从另一个项目进行了演示。 有许多项目都采用这种方式,但是很快就会陷入僵局。 毕竟,该框架是一个相当大的开发,您必须编写许多对接代码。 我开始在Rust中使用称为板条箱的库,尤其是
Stdweb
。
集成JS
借助Rust宏,您可以扩展语言-我们可以将JavaScript片段嵌入Rust代码中,这是该语言非常有用的功能。
let handle = js! { var callback = @{callback}; var action = function() { callback(); }; var delay = @{ms}; return { interval_id: setInterval(action, delay), callback: callback, }; };
使用宏和Stdweb使我能够快速有效地编写所有必要的链接。
Jsx模板
首先,我沿着Elm的道路,开始使用通过代码实现的模板。
fn view(&self) -> Html<Context, Self> { nav("nav", ("menu"), vec![ button("button", (), ("onclick", || Msg::Clicked)), tag("section", ("ontop"), vec![ p("My text...") ]) ]) }
我从来没有成为React的支持者。 但是当我开始编写框架时,我意识到React中的JSX是一件很酷的事情。 这是代码模板的非常方便的演示。
结果,我在Rust上使用了一个宏,并直接在Rust内部实现了编写HTML标记的功能,该功能可立即生成虚拟树元素。
impl Renderable<Context, Model> for Model { fn view(&self) -> Html<Context, Self> { html! { <div> <nav class="menu",> <button onclick=|_| Msg::Increment,>{ "Increment" }</button> <button onclick=|_| Msg::Decrement,>{ "Decrement" }</button> </nav> <p>{ self.value }</p> <p>{ Local::now() }</p> </div> } } }
我们可以说,类似于JSX的模板是纯代码模板,但是在类固醇上。 它们以方便的格式显示。 还要注意,这里我将Rust表达式直接插入按钮中(Rust表达式可以插入这些模板中)。 这使您可以非常紧密地集成。
结构合理的组件
然后我开始开发模板并意识到使用组件的可能性。 这是存储库中出现的第一个问题。 我已经实现了可以在模板代码中使用的组件。 您只需在Rust中声明一个诚实的结构并为其编写一些属性。 这些属性可以直接从模板设置。

再次提醒我,重要的是这些模板是诚实生成的Rust代码。 因此,编译器会注意到此处的任何错误。 即 您不会弄错,就像JavaScript开发中经常遇到的那样。
类型区域
另一个有趣的功能是,当一个组件放置在另一个组件内部时,它可以看到父级的消息类型。

编译器严格绑定这些类型,不会给您带来犯错的机会。 处理事件时,组件期望或可以发送的消息必须与父项完全匹配。
其他功能
我将实现从Rust直接转移到框架,该框架使您可以方便地使用各种序列化/反序列化格式(为它提供附加包装器)。 下面是一个示例:我们转到本地存储,然后还原数据,指定一个特定的包装器-我们期望的是json。
Msg::Store => { context.local_storage.store(KEY, Json(&model.clients)); } Msg::Restore => { if let Json(Ok(clients)) = context.local_storage.restore(KEY) { model.clients = clients; } }
它可以是任何格式,包括二进制。 因此,序列化和反序列化变得透明和方便。
我实现的另一个机会的想法来自框架的用户。 他们要求制作碎片。 在这里,我遇到了一件有趣的事情。 看到JavaScript具有将片段插入DOM树的能力,我首先决定在我的框架中实现这样的功能非常容易。 但是我尝试了这个选项,结果证明它不起作用。 我必须弄清楚,走在这棵树上,看看那里发生了什么变化,等等。
Yew框架使用虚拟DOM树,最初所有内容都存在于其中。 实际上,当模板中有一些更改时,它们变成了已经更改了呈现的DOM树的补丁。
html! { <> <tr><td>{ "Row" }</td></tr> <tr><td>{ "Row" }</td></tr> <tr><td>{ "Row" }</td></tr> </> }
其他好处
Rust提供了更多不同的强大功能,我只会谈论最重要的功能。
服务:与外界的互动
我想谈的第一个机会是服务。 您可以以某种服务的形式描述必要的功能,将其发布为板条箱然后再使用。
在Rust中,可以很好地实现创建库,它们的集成,对接和粘合的功能。 实际上,您可以创建各种API与服务进行交互,包括JavaScript。 同时,尽管框架可以在WebAssembly运行时内部运行,但它仍可以与外界交互。
服务示例:
- TimeOutService;
- IntervalService;
- FetchService;
- WebSocketService;
- 定制服务...
Rust服务和板条箱:
crates.io 。
上下文:状态要求
我在框架中实现的另一件事并非完全是传统,而是上下文。 React有一个Context API,但是我以不同的方式使用了Context。 紫杉框架由您制作的组件组成,上下文是某种全局状态。 组件可能不考虑此全局状态,但可能会提出一些要求-以便全局实体满足某些条件。
假设我们的抽象组件需要能够将某些内容上传到S3。

从下面可以看出它使用了此上传,即 发送数据到S3。 这样的部件可以以机架的形式布置。 下载此组件并将其添加到模板中的用户会遇到错误-编译器会问他S3支持在哪里? 用户将必须实施此支持。 之后,该组件将自动开始充实使用。
在哪里需要? 想象一下:您正在使用智能密码创建组件。 他要求周围环境应允许他登录某个地方。 您需要做的就是在模板中添加一个授权表单,并在您的上下文中实现与服务的连接。 即 它实际上是三行代码。 之后,组件开始工作。
想象一下,我们有许多不同的组件。 它们都有相同的要求。 这样,您就可以实施某种功能来恢复所有组件并提取所需的数据。 脱离上下文。 而且编译器不会让您犯错误:如果您没有需要组件的接口,则将无法使用。
因此,您可以轻松创建非常挑剔的按钮,这些按钮将要求某些API或其他功能。 多亏了Rust和这些接口的系统(在Rust中称为trait),才有可能声明组件需求。
编译器不会让您出错
想象一下,我们正在创建一个具有一些属性的组件,其中之一是设置回叫的功能。 并且,例如,我们设置属性并在其名称中遗漏了一个字母。

尝试编译时,Rust会迅速做出响应。 他说我们错了,没有这样的财产:

如您所见,Rust直接使用此模板,并且可以在宏中呈现所有错误。 他告诉您应该真正调用该属性。 如果您通过了编译器,则不会出现像错字之类的愚蠢的运行时错误。
现在想象一下,我们有一个按钮,要求我们的全局上下文能够连接到S3。 并创建一个不实现S3支持的上下文。 让我们看看会发生什么。

编译器报告我们已经插入了一个按钮,但是没有为上下文实现此接口。

只需进入编辑器,在上下文中添加到Amazon的链接,一切便会开始。 您可以使用某种API创建现成的服务,然后只需将其添加到上下文中,替换为它的链接,即可立即使用该组件。 这使您可以做一些非常酷的事情:添加组件,创建上下文,将其填充服务。 所有这些工作都是完全自动进行的;只需花费很少的精力即可将它们捆绑在一起。
如何开始使用红豆杉?
如果要尝试编译WebAssembly应用程序,从哪里开始? 以及如何使用Yew框架做到这一点?
Rust到wasm编译
首先,您需要安装编译器。 为此有一个注册工具:
curl https://sh.rustup.rs -sSf | sh
另外,您可能需要脚本。 有什么用? 大多数为系统编程语言编写的库,尤其是针对Rust(最初是系统)的库,都是为Linux,Windows和其他成熟的OS开发的。 显然,浏览器没有很多功能。
例如,在浏览器中生成随机数的方式与在Linux中不同。 如果要使用需要系统API的库,则emscripten很有用。
图书馆和整个基础架构正在悄悄地切换到诚实的WebAssembly,并且不再需要emscripten(您使用基于JavaScript的功能来生成随机数和其他东西),但是如果您需要构建浏览器根本不支持的内容,那么就不能没有emscripten 。
我还建议使用cargo-web:
cargo install cargo-web
无需其他实用程序即可编译WebAssembly。 但是cargo-web是一个很酷的工具,它提供了一些对JavaScript开发人员有用的东西。 特别是,它将监视文件:如果进行任何更改,它将立即开始编译(编译器不提供此类功能)。 在这种情况下,Cargo-web将使您加快开发速度。 Rust有不同的构建系统,但货运占所有项目的99.9%。
创建一个新项目,如下所示:
cargo new --bin my-project
[package]
name = "my-project"
version = "0.1.0"
[dependencies]
yew = "0.3.0"
然后只需启动项目:
cargo web start --target wasm32-unknown-unknown
我举了一个诚实的WebAssembly的例子。 如果您需要在emscripten下进行编译(rust编译器可以连接emscripten本身),则可以在最后一个
unknown
元素中插入
emscripten
一词,从而可以使用更多的包装箱。 不要忘记emscripten是文件的相当大的附加工具包。 因此,最好编写诚实的WebAssembly代码。
现有限制
拥有系统编程语言编码经验的任何人都可能会对框架中存在的限制感到沮丧。 并非所有库都可以在WebAssembly中使用。 例如,在JavaScript环境中,没有线程。 WebAssembly原则上不会声明它,当然您可以在多线程环境中使用它(这是一个开放的问题),但是JavaScript仍然是单线程环境。 是的,有工人,但是这是孤立的,所以那里没有人流。
看来您可以活得顺畅。 但是,例如,如果您想使用基于线程的库,则想添加某种类型的运行时,则可能不会成功。
此外,除了将要从JavaScript转移到WebAssembly的API之外,没有任何系统API。 因此,将不会移植许多库。 您不能直接读写文件,不能打开套接字,也不能写入网络。 例如,如果要制作网络套接字,则需要将其从JavaScript中拖出。
另一个缺点是WASM调试器存在,但是没有人看到它。 它仍然处于原始状态,不太可能对您有用。 因此,调试WebAssembly是一个棘手的问题。
使用Rust时,几乎所有运行时问题都将与业务逻辑错误相关联,并且很容易解决。 但是很少出现低级错误-例如,其中一个库执行了错误的对接-这已经是一个难题。 例如,目前存在这样一个问题:如果我用emscripten编译框架,并且有一个可变的存储单元,那么占有权就被删除了,它就被释放了,emscripten在中间的某个地方分崩离析了(我甚至不确定它是否是emscripten)。 要知道,如果您在较低水平上偶然发现中间件中的某个问题,那么目前很难解决。
框架的未来
紫杉将如何进一步发展? 我看到了它在创建整体组件中的主要目的。 您将拥有一个已编译的WebAssembly文件,只需将其粘贴到应用程序中即可。 例如,它可以提供加密功能,渲染或编辑。
JS整合
与JavaScript的集成将得到加强。 JavaScript已编写了大量易于使用的出色库。 并且在存储库中有一些示例,在其中我展示了如何直接从Yew框架使用现有的JavaScript库。
键入CSS
由于使用了Rust,因此很明显,您可以添加类型化的CSS,该类型的CSS可以使用与类似JSX的模板引擎示例相同的宏来生成。 在这种情况下,编译器将检查例如是否分配了其他属性而不是颜色。 这样可以节省大量时间。
准备好组件
我也希望创建易于使用的组件。 在框架上,您可以进行破解,例如提供一组将作为库连接,添加到模板并使用的一些按钮或元素。
改善私人案件的表现
性能是一个非常微妙而复杂的问题。 WebAssembly是否比JavaScript快? 我没有证据可以肯定或否定答案。 根据我进行的一些非常简单的测试,WebAssembly感觉非常快。 我完全有信心,它的性能将比JavaScript高,因为它是低级字节码,不需要内存分配和许多其他耗费资源的时间。
更多贡献者
我想吸引更多的贡献者。 参与框架的大门总是敞开的。 每个想要升级,了解内核并转换大量开发人员使用的工具的人都可以轻松连接并提供自己的编辑。
许多参与者已经参加了该项目。 但是目前尚无Core贡献者,因为为此您需要了解框架的开发载体,但尚未明确表述。 但是这里有一个骨干,他们非常了解紫杉,大约有30个人。 如果您还想向框架中添加内容,请始终发送拉取请求。
该文件
在我的计划中,必不可少的一点是创建了大量有关如何在Yew上编写应用程序的文档。 显然,这种情况下的开发方法不同于我们在React和Elm中看到的方法。
有时人们会向我展示如何使用框架的有趣案例。 尽管如此,创建框架与专业编写框架并不相同。 使用该框架的实践仍在形成中。
尝试一下,安装Rust,扩大您作为开发人员的能力。 掌握WebAssembly对我们每个人都将很有用,因为创建非常复杂的应用程序是我们等待了很长时间的时刻。 换句话说,WebAssembly不仅与Web浏览器有关,而且通常是肯定在开发中的运行时,并且将更加积极地进行开发。
如果您喜欢此报告,请注意:11月24日至25日,新的HolyJS将在莫斯科举行,那里还将有许多有趣的事情。 — , ( ).