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
|
---
title: Компиляция Rust в WebAssembly
slug: WebAssembly/Rust_to_wasm
translation_of: WebAssembly/Rust_to_wasm
---
<div>{{WebAssemblySidebar}}</div>
<p class="summary">Если уже вы написали некоторый код на Rust, вы можете скомпилировать его в WebAssembly! Из этого руководства вы узнаете всё, что вам нужно знать, чтобы скомпилировать проект на Rust в wasm и использовать его в существующем веб-приложении.</p>
<h2 id="Примеры_использования_Rust_и_WebAssembly">Примеры использования Rust и WebAssembly</h2>
<p>Существует два основных варианта использования Rust и WebAssembly:</p>
<ul>
<li>Чтобы создать целое приложение — целое веб-приложение, основанное на Rust!</li>
<li>Чтобы построить часть приложения — используйте Rust в существующем интерфейсе JavaScript.</li>
</ul>
<p>На данный момент команда Rust фокусируется на последнем примере, его мы рассмотрим здесь. Для первого примера, посмотрите проекты, такие как <a href="https://github.com/DenisKolodin/yew"><code>yew</code></a>.</p>
<p>В этом руководстве вы создадите npm-пакет, используя <code>wasm-pack</code>, инструмент построения npm-пакетов в Rust. Этот пакет будет содержать только код WebAssembly и JavaScript, так что его пользователям не нужен будет установщик Rust. Они могут даже не заметить, что он был написан на WebAssembly!</p>
<h2 id="Настройка_окружения_Rust">Настройка окружения Rust</h2>
<p>Давайте пройдёмся по всем пунктам, необходимым для настройки нашего окружения.</p>
<h3 id="Установка_Rust">Установка Rust</h3>
<p>Чтобы установить Rust, посетите <a href="https://www.rust-lang.org/install.html">Install Rust</a> страницу и проследуйте всем инструкциям. Так вы установите тулзу, называемую "rustup", которая позволит вам управлять несколькими версиями Rust. По умолчанию, она устанавливает последний стабильный релиз Rust, который вы будете использовать для стандартной разработки на Rust. Rustup устанавливает <code>rustc</code>, компилятор Rust, вместе с <code>cargo</code>, Rust-овским пакетным менеджером, <code>rust-std</code>, стандартной библиотекой Rust, и несколькими вспомогательными доками — <code>rust-docs</code>.</p>
<div class="note">
<p><strong>Примечание</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-аккаунт. Они бесплатны! <em>Технически</em>, вы не обязаны ничего публиковать, но так будет проще, так что будем считать, что вы сделаете это в этом руководстве.</p>
<p>Чтобы получить Node.js и npm, посетите <a href="https://www.npmjs.com/get-npm">Get npm!</a> страницу и проследуйте инструкциям. Когда настанет время выбрать версию, выберите любую, которая вам нравится; это руководство не зависит от версии.</p>
<p>Чтобы создать npm-аккаунт, посетите <a href="https://www.npmjs.com/signup">npm signup</a> станицу и заполните форму.</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>Вам понадобится ввести своё пользовательское имя, пароль и email. Если все получится, вы увидите:</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
Создаст проектную библиотеку `hello-wasm`
</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>; с его помощью мы можем сконфигурировать наш билд. Если вы пользуетесь <code>Gemfile</code> из Bundler или <code>package.json</code> из npm, то вы почувствуете себя, как дома; Cargo работает аналогично обоим.</p>
<p>Дальше, Cargo сгенерировал кое-какой код для нас на Rust в <code>src/lib.rs</code>:</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, пожалуйста, просмотрите бесплатную online-книгу <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 называются "<code class="rust language-rust">crates</code>" (контейнеры), а так как мы используем внешнюю, то "<code class="rust language-rust">extern</code>".</p>
<p>Поняли? <strong>Cargo поставляет контейнеры</strong>.</p>
<p>Третья строка содержит команду <code>use</code>, которая импортирует код из библиотеки в наш код. В нашем случае, мы импортируем все из модуля <code>wasm_bindgen::prelude</code>. Мы будем использовать его функции в следующей секции.</p>
<p>Прежде чем перейти к следующей секции, давайте поговорим немного о <code>wasm-bindgen</code>.</p>
<p><code>wasm-pack</code> использует <code>wasm-bindgen</code>, другую тулзу, чтобы предоставить соединение между типами в JavaScript и Rust. Это позволяет JavaScript вызывать Rust-API со строками или функциям Rust перехватывать исключения JavaScript.</p>
<p>Мы будем использовать функциональность <code>wasm-bindgen</code> в нашем пакете. По факту, это следующая секция!</p>
<h4 id="Вызов_внешних_функций_JavaScript_из_Rust">Вызов внешних функций JavaScript из Rust</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-у, что мы хотим вызвать некоторую функцию, определённую во внешнем пространстве. Атрибут говорит: "wasm-bindgen знает, как найти эти функции".</p>
<p>Третья строка это имя функции, написанной на Rust. Она говорит: "функция <code>alert</code> принимает один аргумент, строку с именем <code>s</code>."</p>
<p>У вас, возможно, есть предположение, что это за функция, и, возможно, ваше предположение верное: это функция<a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/alert"> <code>alert</code>, предоставляемая JavaScript</a>! Мы будем вызывать эту функцию в следующей секции.</p>
<p>Когда бы вы не захотели вызвать новую функцию JavaScript, вы можете написать её здесь, и <code>wasm-bindgen</code> позаботится о том, чтобы настроить все для вас. Пока ещё поддерживается не все, но мы работаем над этим! Пожалуйста, <a href="https://github.com/rustwasm/wasm-bindgen/issues/new">сообщайте о проблемах</a>, если что-то было упущено.</p>
<h4 id="Создание_функций_Rust_который_может_вызывать_JavaScript">Создание функций Rust, который может вызывать JavaScript</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>; это значит, что мы хотим, чтобы эта функция на Rust была доступна для JavaScript. Прямо противоположно <code>extern</code>: это не те функции, которые нам нужны, а те, что мы предоставляем миру!</p>
<p>Наша функция называется <code>greet</code>, и она принимает один аргумент, строку (пишется <code>&str</code>), <code>name</code>. Затем она вызывает функцию <code>alert</code>, которую мы запросили в блоке <code>extern</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>authors</code>, основываясь на информации <code>git</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><code><font face="Arial, x-locale-body, sans-serif"><span style="background-color: #ffffff;">Запускает </span></font>wasm-bindgen</code> с этим WebAssembly, генерируя 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>Конечный результат? У вас есть npm-пакет внутри папки <code>pkg</code>.</p>
<h4 id="Отступление_о_размере_кода">Отступление о размере кода</h4>
<p>Если вы посмотрите на размер кода, сгенерированного для WebAssembly, это может быть около сотни килобайт. Мы вообще не инструктировали Rust оптимизировать размер, и он <em>сильно</em> его снизил. Это не является частью этого руководства, но если вам интересно, прочитайте документацию Rust WebAssembly Working Group на <a href="https://rustwasm.github.io/book/game-of-life/code-size.html#shrinking-wasm-size">Shrinking .wasm Size</a>.</p>
<h3 id="Публикация_нашего_пакета_на_npm">Публикация нашего пакета на npm</h3>
<p>Давайте опубликуем наш новый пакет на npm:</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="Использование_пакета_в_web">Использование пакета в web</h2>
<p>Давайте создадим сайт, который будет использовать наш пакет! Многие пользуются пакетами npm с помощью разных сборщиков, и мы будем использовать один из них, <code>webpack</code>, в этом руководстве. Он только немного более усложнённый, но описывает более реалистичный вариант использования.</p>
<p>Давайте выйдем из нашей папки <code>pkg</code> и создадим новую, <code>site</code>, чтобы попробовать в ней следующее:</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>Заметьте, что вам нужно ввести своё пользовательское имя после <code>@</code> в секции зависимостей.</p>
<p>Дальше нам нужно сконфигурировать Webpack. Создайте <code>webpack.config.js</code> и введите следующее:</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>Теперь нам нужен HTML-файл; создайте <code>index.html</code> и поместите в него:</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>Наконец, создайте <code>index.js</code>, на который мы сослались в HTML-файле, и вставьте:</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>Заметьте, что вам нужно будет снова ввести ваше имя для npm.</p>
<p>Так мы импортируем наш модуль из папки <code>node_modules</code>. Это не считается лучшей практикой, но это пример, так что пока сойдёт. Как только файл загрузится, он вызовет функцию <code>greet</code> из этого модуля, передав <code>"WebAssembly"</code>, как строку. Обратите внимание, что здесь нет ничего особенного, и всё же мы вызываем код на Rust! Насколько JavaScript-код может судить, это просто обычный модуль.</p>
<p>Мы закончили! Давайте попробуем:</p>
<pre class="brush: bash notranslate"><code class="shell language-shell">$ npm install
$ npm run serve
</code></pre>
<p>Так мы запустим сервер. Откройте <a href="http://localhost:8080">http://localhost:8080</a> и вы увидите алерт с надписью <code>Hello, WebAssembly!</code> в нем! Мы успешно обратились из JavaScript в Rust и из Rust в JavaScript.</p>
<h2 id="Заключение">Заключение</h2>
<p>На этом руководство заканчивается, мы надеемся, что вы сочли его для себя полезным.</p>
<p>В этом направлении кипит бурная и при этом очень интересная деятельность, так что если вы бы хотели помочь что-то улучшить, то загляните в <a href="http://fitzgeraldnick.com/2018/02/27/wasm-domain-working-group.html">the Rust Webassembly Working Group</a>.</p>
|