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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
|
---
title: 编译 Rust 为 WebAssembly
slug: WebAssembly/Rust_to_wasm
tags:
- WebAssembly
- rust
- wasm
- 编译
translation_of: WebAssembly/Rust_to_wasm
---
<div>{{WebAssemblySidebar}}</div>
<p class="summary">如果你写了一些 Rust 代码,你可以把它编译成 WebAssembly!这份教程将带你编译 Rust 项目为 wasm 并在一个现存的 web 应用中使用它。</p>
<h2 id="Rust_和_WebAssembly_用例">Rust 和 WebAssembly 用例</h2>
<p>Rust 和 WebAssembly 有两大主要用例:</p>
<ul>
<li>构建完整应用 —— 整个 Web 应用都基于 Rust 开发!</li>
<li>构建应用的组成部分 —— 在现存的 JavaScript 前端中使用 Rust。</li>
</ul>
<p>目前,Rust 团队正专注于第二种用例,因此我们也将着重介绍它。对于第一种用例,可以参阅 <code><a href="https://github.com/DenisKolodin/yew">yew</a></code> 这类项目。</p>
<p>在本教程中,我们将使用 Rust 的 npm 包构建工具 <code>wasm-pack</code> 来构建一个 npm 包。这个包只包含 WebAssembly 和 JavaScript 代码,以便包的用户无需安装 Rust 就能使用。他们甚至不需要知道这里包含 WebAssembly!</p>
<h2 id="安装_Rust_环境">安装 Rust 环境</h2>
<p>让我们看看安装 Rust 环境的所有必要步骤。</p>
<h3 id="安装Rust">安装Rust</h3>
<p>前往 <a href="https://www.rust-lang.org/install.html">Install Rust</a> 页面并跟随指示安装 Rust。这里会安装一个名为 “rustup” 的工具,这个工具能让你管理多个不同版本的 Rust。默认情况下,它会安装用于惯常Rust开发的 stable 版本 Rust Release。Rustup 会安装 Rust 的编译器 <code>rustc</code>、Rust 的包管理工具 <code>cargo</code>、Rust 的标准库 <code>rust-std</code> 以及一些有用的文档 <code>rust-docs</code>。</p>
<div class="note">
<p><strong>Note</strong>: 需要注意,在安装完成后,你需要把 cargo 的 <code>bin</code> 目录添加到你系统的 <code>PATH</code> 。一般来说它会自动添加,但需要你重启终端后才会生效。 </p>
</div>
<h3 id="wasm-pack">wasm-pack</h3>
<p>要构建我们的包,我们需要一个额外工具 <code>wasm-pack</code>。它会帮助我们把我们的代码编译成 WebAssembly 并制造出正确的 <code>npm</code> 包。使用下面的命令可以下载并安装它:</p>
<pre class="brush: bash notranslate"><code class="shell language-shell">$ cargo install wasm-pack
</code></pre>
<h3 id="安装_Node.js_并获取_npm_账户">安装 Node.js 并获取 npm 账户</h3>
<p>在这个例子中我们将会构建一个 npm 包,因此你需要确保安装 Node.js 和 npm 已经安装。另外,我们将会把包发布到 npm 上,因此你还需要一个 npm 账号。它们是免费的。发布这个包并不是必须的,但是发布它非常简单,因此在本例中我们默认你会发布这个包。</p>
<p>在 <a href="https://www.npmjs.com/get-npm">Get npm!</a> 页面按照说明下载并安装 Node.js 和 npm。在选择版本时,选择一个你喜欢的版本;本例不限定特定版本。</p>
<p>在 <a href="https://www.npmjs.com/signup">npm signup page</a> 注册 npm 账户,并填写表格。</p>
<p>接下来,在命令行中运行 <code>npm adduser</code>:</p>
<pre class="brush: bash notranslate"><code class="rust language-rust">> npm adduser
Username: yournpmusername
Password:
Email: (this IS public) you@example.com
</code></pre>
<p>你需要完善你的用户名,密码和邮箱。如果成功了,你将会看到:</p>
<pre class="brush: bash notranslate"><code>Logged in as yournpmusername on https://registry.npmjs.org/.
</code></pre>
<p>如果并未正常运行,请联系 npm 解决。</p>
<h2 id="构建我们的_WebAssembly_npm_包">构建我们的 WebAssembly npm 包</h2>
<p>万事俱备,来创建一个新的 Rust 包吧。打开你用来存放你私人项目的目录,做这些事:</p>
<pre class="brush: bash notranslate"><code class="shell language-shell">$ cargo new --lib hello-wasm
Created library `hello-wasm` project
</code></pre>
<p>这里会在名为 <code>hello-wasm</code> 的子目录里创建一个新的库,里面有下一步之前你所需要的一切:</p>
<pre class="notranslate"><code class="shell language-shell">+-- Cargo.toml
+-- src
+-- lib.rs
</code></pre>
<p>首先,我们有一个 <code>Cargo.toml</code> 文件,这是我们配置构建的方式。如果你用过 Bundler 的 <code>Gemfile</code> 或者 npm 的 <code>package.json</code>,你应该会感到很熟悉。Cargo 的用法和它们类似。</p>
<p>接下来,Cargo 在 <code>src/lib.rs</code> 生成了一些 Rust 代码:</p>
<pre class="notranslate"><code class="rust language-rust">#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
</code></pre>
<p>我们完全不需要使用这些测试代码,所以继续吧,我们删掉它。</p>
<h3 id="来写点_Rust_代码吧!">来写点 Rust 代码吧!</h3>
<p>让我们在 <code>src/lib.rs</code> 写一些代码替换掉原来的:</p>
<pre class="notranslate"><code class="rust language-rust">extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}
</code></pre>
<p>这就是我们这个 Rust 项目的内容。它有三个主要部分,让我们按顺序来讲。这里将会给出一个缺少部分细节的高级说明;如果想要了解更多 Rust 知识,请查看在线书籍 <a href="https://doc.rust-lang.org/book/">The Rust Programming Language</a>。</p>
<h4 id="使用_wasm-bindgen_在_Rust_与_JavaScript_之间通信">使用 <code>wasm-bindgen</code> 在 Rust 与 JavaScript 之间通信</h4>
<p>第一部分看起来像这样:</p>
<pre class="notranslate"><code class="rust language-rust">extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
</code></pre>
<p>第一行就像在说 “哇 Rust,我们在用一个叫做 wasm_bindgen 的库”。在 Rust 当中,库被称为 “crates”,因为我们使用的是一个外部库,所以有 "extern"。</p>
<p>明白了吗? <strong>Cargo ships crates</strong>.</p>
<p>第三行包括了一个将库中的代码引入到你的代码中的使用命令。在这个情况下,将会引入 <code>wasm_bindgen::prelude</code> 的全部模块。我们将在下一节中使用这些内容。</p>
<p>在我们开始下一节之前,我们将讲一讲 <code>wasm-bindgen</code>.</p>
<p><code>wasm-pack</code> 使用 <code>wasm-bindgen</code>,其它工具,去提供一个连接 JavaScript 和 Rust 的桥。它允许 JavaScript 使用 string 调用 Rust API,或者调用一个 Rust function 去捕获 JavaScript 异常。</p>
<p>我们将在我们的包中使用 <code>wasm-bindgen</code> 的功能。事实上,这是下一节的内容!</p>
<h4 id="在_Rust_中调用来自_JavaScript_的外部函数">在 Rust 中调用来自 JavaScript 的外部函数</h4>
<p>接下来的部分看起来像这样:</p>
<pre class="notranslate"><code class="rust language-rust">#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}
</code></pre>
<p>在 <code>#[]</code> 中的内容叫做 "属性",并以某种方式改变下面的语句。在这种情况下,下面的语句是一个 <code>extern</code>,它将告诉 Rust that <span class="tlid-translation translation" lang="zh-CN"><span title="">我们想调用一些外部定义的函数</span></span>。这个属性告诉我们 "wasm-bindgen 知道如何找到这些函数"。</p>
<p>第三行是用 Rust 写的函数签名。它告诉我们 "<code>alert</code> 函数接受一个叫做 s 的字符串作为参数。"</p>
<p>你可能会疑惑这个函数是什么,你的疑惑可能是正确的:这是 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/alert">the <code>alert</code> function provided by JavaScript</a>!我们将在下一节中调用这个函数。</p>
<p>当你想调用新的 JavaScript 函数时,你可以在这里写他们,<code>wasm-bindgen</code> 将负责为您设置一切。<span class="tlid-translation translation" lang="zh-CN"><span title="">并非一切都得到支持,但我们正在努力!如果缺少某些内容,</span></span>请 <a href="https://github.com/rustwasm/wasm-bindgen/issues/new">file bugs</a> 。</p>
<h4 id="编写能够在JavaScript中调用的_Rust_函数">编写能够在JavaScript中调用的 Rust 函数</h4>
<p>最后一部分是这样的:</p>
<pre class="notranslate"><code class="rust language-rust">#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}
</code></pre>
<p>我们又看到了 <code>#[wasm_bindgen]</code> 属性。在这里,它并非定义一个 <code>extern</code> 块,而是 <code>fn,</code>这代表我们希望能够在 JavaScript 中使用这个 Rust 函数。 这和 <code>extern</code> 正相反:我们并非引入函数,而是要把函数给外部世界使用。</p>
<p>这个函数的名字是 <code>greet</code>,它需要一个参数,一个字符串 (写作 <code>&str</code>)。它调用了我们前面在 <code>extern</code> 块中引入的 <code>alert</code> 函数。它传递了一个让我们串联字符串的 <code>format!</code> 宏的调用。</p>
<p><code>format!</code> 在这里有两个参数,一个格式化字符串和一个要填入的变量。格式化字符串是 <code>"Hello, {}!"</code> 部分。它可以包含一个或多个 <code>{}</code>,变量将会被填入其中。传递的变量是 <code>name</code>,也就是这个函数的参数。所以当我们调用 <code>greet("Steve")</code>时我们就能看到 <code>"Hello, Steve!"。</code></p>
<p>这个传递到了 <code>alert()</code>,所以当我们调用这个函数时,我们应该能看到他谈弹出了一个带有 "Hello, Steve!" 的消息框。</p>
<p>我们的库写完了,是时候构建它了。</p>
<h3 id="把我们的代码编译到_WebAssembly">把我们的代码编译到 WebAssembly</h3>
<p>为了能够正确的编译我们的代码,首先我们需要配置 <code>Cargo.toml</code>。打开这个文件,将内容改为如下所示:</p>
<pre class="notranslate"><code class="toml language-toml">[package]
name = "hello-wasm"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A sample project with wasm-pack"
license = "MIT/Apache-2.0"
repository = "https://github.com/yourgithubusername/hello-wasm"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
</code></pre>
<p>你需要改为自己的仓库,同时 Cargo 需要通过 <code>git</code> 来完善 <code>authors</code> 部分。</p>
<p>最重要的是添加底下的部分。第一个部分 — <code>[lib]</code> — 告诉 Rust 为我们的包建立一个 <code>cdylib</code> 版本;在本教程中我们不会讲解它的含义。有关更多信息,请参阅 <a href="https://doc.rust-lang.org/cargo/guide/">Cargo</a> 和 <a href="https://doc.rust-lang.org/reference/linkage.html">Rust Linkage</a> 文档。</p>
<p>第二个部分是 <code>[dependencies]</code> 部分。在这里我们告诉 Cargo 我们需要依赖哪个版本的 <code>wasm-bindgen</code> ;在这个例子中,它是 <code>0.2.z</code> 版本的 (不是 <code>0.3.0</code> 或者其他版本)。</p>
<h3 id="构建包">构建包</h3>
<p>现在我们已经完成了所有配置项,开始构建吧!在命令行输入以下命令:</p>
<pre class="brush: bash notranslate"><code class="shell language-shell">$ wasm-pack build --scope mynpmusername
</code></pre>
<p>这个命令将做一系列事情 (这会花一些时间,特别是当你第一次运行 <code>wasm-pack</code>)。想了解详细情况,查看<a href="https://hacks.mozilla.org/2018/04/hello-wasm-pack/">这篇在 Mozilla Hacks 上的文章</a>。简单来说,<code>wasm-pack build</code> 将做以下几件事:</p>
<ol>
<li>将你的 Rust 代码编译成 WebAssembly。</li>
<li>在编译好的 WebAssembly 代码基础上运行 <code>wasm-bindgen</code>,生成一个 JavaScript 文件将 WebAssembly 文件包装成一个模块以便 npm 能够识别它。</li>
<li>创建一个 <code>pkg</code> 文件夹并将 JavaScript 文件和生成的 WebAssembly 代码移到其中。</li>
<li>读取你的 <code>Cargo.toml</code> 并生成相应的 <code>package.json。</code></li>
<li>复制你的 <code>README.md</code> (如果有的话) 到文件夹中。</li>
</ol>
<p>最后的结果?你在 <code>pkg</code> 文件夹下有了一个 npm 包。</p>
<h4 id="对代码体积的一些说明">对代码体积的一些说明</h4>
<p>如果你检查生成的 WebAssembly 文件体积,它可能有几百 kB。我们没有让 Rust 去压缩生成的代码,从而大大减少生成包的体积。这和本次教程主题无关,但如果你想了解更多,查看 Rust WebAssembly 工作组文档上关于 <a href="https://rustwasm.github.io/book/game-of-life/code-size.html#shrinking-wasm-size">减少 .wasm 体积</a> 的说明。</p>
<h3 id="把我们的包发布到_npm">把我们的包发布到 npm</h3>
<p>把我们的新包发布到 npm registry:</p>
<pre class="brush: bash notranslate"><code class="shell language-shell">$ cd pkg
$ npm publish --access=public
</code></pre>
<p>我们现在有了一个 npm 包,使用 Rust 编写,但已经被编译为 WebAssembly 了。现在这个包已经可以被 JavaScript 使用了,而且使用它完全不需要用户安装 Rust ;包中的代码是 WebAssembly 代码,而不是 Rust 源码!</p>
<h2 id="在网站上使用我们的包">在网站上使用我们的包</h2>
<p><span class="tlid-translation translation" lang="zh-CN"><span title="">让我们建立一个使用我们包的网站!</span> <span title="">人们通过各种打包工具使用 npm包,在本教程中,我们将使用 </span></span><code>webpack</code><span class="tlid-translation translation" lang="zh-CN"><span title="">。</span> <span title="">它比其他某些打包工具稍微复杂一点,但展示了更实际的用法。</span><br>
<br>
<span title="">让我们离开</span></span><code>pkg</code><span class="tlid-translation translation" lang="zh-CN"><span title="">目录,并创建一个新目录</span></span><code>site</code><span class="tlid-translation translation" lang="zh-CN"><span title="">,尝试以下操作:</span></span></p>
<pre class="brush: bash notranslate"><code class="bash language-bash">$ cd ../..
$ mkdir site
$ cd site
</code></pre>
<p>创建一个新文件 <code>package.json</code>,然后输入如下代码:</p>
<pre class="brush: json notranslate"><code class="json language-json">{
"scripts": {
"serve": "webpack-dev-server"
},
"dependencies": {
"@mynpmusername/hello-wasm": "^0.1.0"
},
"devDependencies": {
"webpack": "^4.25.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10"
}
}
</code></pre>
<p><span class="tlid-translation translation" lang="zh-CN"><span title="">请注意,您需要在依赖项部分的 </span></span><code>@</code><span class="tlid-translation translation" lang="zh-CN"><span title=""> 之后填写自己的用户名。</span><br>
<br>
<span title="">接下来,我们需要配置Webpack。</span> <span title="">创建 </span></span><code>webpack.config.js</code><span class="tlid-translation translation" lang="zh-CN"><span title=""> 并输入:</span></span></p>
<pre class="brush: js notranslate"><code class="javascript language-javascript">const path = require('path');
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "index.js",
},
mode: "development"
};
</code></pre>
<p><span class="tlid-translation translation" lang="zh-CN"><span title="">现在我们需要一个HTML文件。</span> <span title="">创建一个<code>index.html</code>并写入如下内容:</span></span></p>
<pre class="notranslate"><code class="html language-html"><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hello-wasm example</title>
</head>
<body>
<script src="./index.js"></script>
</body>
</html>
</code></pre>
<p><span class="tlid-translation translation" lang="zh-CN"><span title="">最后,从HTML文件中引用<code>index.js</code>:</span></span></p>
<pre class="brush: js notranslate"><code class="javascript language-javascript">const js = import("./node_modules/@yournpmusername/hello-wasm/hello_wasm.js");
js.then(js => {
js.greet("WebAssembly");
});
</code></pre>
<p><span class="tlid-translation translation" lang="zh-CN"><span title="">请注意,您需要再次填写您的npm用户名。</span><br>
<br>
<span title="">这将从<code>node_modules</code>文件夹导入我们的模块。</span><span title="">这不是最佳做法,但这里只做一个演示,因此暂时就这样用。</span> <span title="">加载后,它将从该模块调用<code>greet</code>函数,并传入字符串“WebAssembly”参数。</span><span title="">注意这里看上去没有什么特别的,但是我们正在调用 Rust 代码!</span> <span title="">就JavaScript代码所知,这只是一个普通模块。</span><br>
<br>
<span title="">我们已经完成了所有的文件!</span> <span title="">让我们试一下:</span></span></p>
<pre class="brush: bash notranslate"><code class="shell language-shell">$ npm install
$ npm run serve
</code></pre>
<p><span class="tlid-translation translation" lang="zh-CN"><span title="">这将启动一个Web服务器。</span> <span title="">访问 </span></span><a href="http://localhost:8080">http://localhost:8080 </a><span class="tlid-translation translation" lang="zh-CN"><span title="">,您应该会在屏幕上看到一个警告框,其中包含 </span></span><code>Hello, WebAssembly!</code><span class="tlid-translation translation" lang="zh-CN"> !<span title="">我们已经成功地从 JavaScript 调用了 Rust,并从 Rust 调用了 JavaScript。</span></span></p>
<p>
</p><h2 id="结论">结论</h2>
<p>本教程到此结束。希望你觉得它有用。</p>
<p>在这个领域,有很多工作正在推进当中。如果你希望它变得更好,可以参阅 <a href="http://fitzgeraldnick.com/2018/02/27/wasm-domain-working-group.html">Rust Webassembly 工作组</a>。</p>
|