WebAssembly
Разработанный для использования вместе с JavaScript, чтобы ускорить ключевые компоненты приложения, WebAssembly (Wasm) может обеспечивать гораздо более высокую и стабильную скорость выполнения, чем JavaScript, — сравнимую с C, C++ или Rust. Deno может выполнять модули WebAssembly с теми же интерфейсами, которые предоставляют браузеры, а также импортировать их как модули.
Модули Wasm
Начиная с Deno 2.1, модули WebAssembly можно импортировать, а их использование проверяется на соответствие типам.
Предположим, у нас есть файл в текстовом формате WebAssembly, экспортирующий функцию add, которая складывает два числа и возвращает результат:
(module
(func (export "add") (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add
)
)
Мы можем скомпилировать его в add.wasm с помощью wat2wasm:
wat2wasm add.wat
Затем используем этот модуль WebAssembly с помощью оператора импорта:
import { add } from "./add.wasm";
console.log(add(1, 2));
> deno run main.ts
3
Проверка типов
Deno понимает, что экспортируют модули Wasm, и проверяет использование этих экспортов на соответствие типам. Если в предыдущем примере мы неправильно вызовем функцию add, то увидим ошибку проверки типов.
import { add } from "./add.wasm";
console.log(add(1, ""));
> deno check main.ts
Check file:///.../main.ts
error: TS2345 [ERROR]: Argument of type 'string' is not assignable to parameter of type 'number'.
console.log(add(1, ""));
~~
at file:///.../main.ts:3:20
Импорты
Как и модули JavaScript, модули Wasm тоже могут импортировать другие модули.
Например, мы можем создать модуль Wasm, который импортирует спецификатор "./values.js" и вызывает экспорт getValue:
(module
(import "./time.ts" "getTimeInSeconds" (func $get_time (result i32)))
(func (export "getValue") (result i32)
call $get_time
)
)
export function getTimeInSeconds() {
return Date.now() / 1000;
}
import { getValue } from "./toolkit.wasm";
console.log(getValue());
Теперь выполним:
> wat2wasm toolkit.wat
> deno run main.ts
1732147633
V:\scratch
> deno run main.ts
1732147637
Переопределение спецификаторов импорта
Часто в модулях Wasm не используется относительный спецификатор, который упрощает импорт другого модуля JavaScript. Предположим, у нас есть похожая на предыдущую конфигурация, но обратите внимание, что модуль Wasm импортируется через спецификатор "env".
(module
(import "env" "get_time_in_seconds" (func $get_time (result i32)))
(func (export "getValue") (result i32)
call $get_time
)
)
function getTimeInSeconds() {
return Date.now() / 1000;
}
export { getTimeInSeconds as get_time_in_seconds };
import { getValue } from "./toolkit.wasm";
console.log(getValue());
> wat2wasm toolkit.wat
> deno run main.ts
error: Relative import path "env" not prefixed with / or ./ or ../
at file:///.../toolkit.wasm
Это не очень удобно, потому что мы хотим импортировать "./env.ts".
К счастью, это легко исправить, сопоставив спецификатор в карте импорта через файл deno.json:
{
"imports": {
"env": "./env.ts"
}
}
Теперь всё работает:
> deno run main.ts
1732148355
Использование WebAssembly через API WebAssembly
Чтобы запустить WebAssembly в Deno, нужен только модуль Wasm для выполнения. Следующий модуль экспортирует функцию main, которая при вызове просто возвращает 42:
// deno-fmt-ignore
const wasmCode = new Uint8Array([
0, 97, 115, 109, 1, 0, 0, 0, 1, 133, 128, 128, 128, 0, 1, 96, 0, 1, 127,
3, 130, 128, 128, 128, 0, 1, 0, 4, 132, 128, 128, 128, 0, 1, 112, 0, 0,
5, 131, 128, 128, 128, 0, 1, 0, 1, 6, 129, 128, 128, 128, 0, 0, 7, 145,
128, 128, 128, 0, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 4, 109, 97,
105, 110, 0, 0, 10, 138, 128, 128, 128, 0, 1, 132, 128, 128, 128, 0, 0,
65, 42, 11
]);
const wasmModule = new WebAssembly.Module(wasmCode);
const wasmInstance = new WebAssembly.Instance(wasmModule);
const main = wasmInstance.exports.main as CallableFunction;
console.log(main().toString());
Чтобы загрузить WebAssembly через API WebAssembly, необходимо выполнить следующие действия:
- Получить бинарный файл (обычно это файл
.wasm, хотя пока мы используем простой массив байтов) - Скомпилировать бинарный файл в объект
WebAssembly.Module - Создать экземпляр модуля WebAssembly
WebAssembly — это двоичный формат данных, который не предназначен для чтения человеком или написания вручную. Файлы .wasm должны создаваться компилятором для такого языка, как Rust, Go или AssemblyScript.
Например, программа на Rust, которая компилируется в упомянутые выше байты, может выглядеть так:
#[unsafe(no_mangle)]
pub fn main() -> u32 { // u32 stands for an unsigned integer using 32 bits of memory.
42
}
Использование потоковых API WebAssembly
Самый эффективный способ получить, скомпилировать и создать экземпляр модуля WebAssembly — использовать потоковые варианты API WebAssembly. Например, можно использовать instantiateStreaming вместе с fetch, чтобы выполнить все три действия за один раз:
const { instance, module } = await WebAssembly.instantiateStreaming(
fetch("https://wpt.live/wasm/incrementer.wasm"),
);
const increment = instance.exports.increment as (input: number) => number;
console.log(increment(41));
Обратите внимание, что файл .wasm должен передаваться с MIME-типом application/wasm. Если перед созданием экземпляра нужно выполнить дополнительные действия с модулем, вместо этого можно использовать compileStreaming:
const module = await WebAssembly.compileStreaming(
fetch("https://wpt.live/wasm/incrementer.wasm"),
);
/* do some more stuff */
const instance = await WebAssembly.instantiate(module);
instance.exports.increment as (input: number) => number;
Если по какой-либо причине потоковые методы использовать нельзя, можно прибегнуть к менее эффективным методам compile и instantiate.
Подробнее о том, почему потоковые методы работают эффективнее, можно узнать в этой статье.
API WebAssembly
Дополнительную информацию обо всех компонентах API WebAssembly можно найти в справочнике Deno и на сайте MDN.
Работа с ненумерическими типами
В примерах кода этого документа в модулях WebAssembly использовались только числовые типы. Чтобы использовать WebAssembly с более сложными типами (например, строками или классами), понадобятся инструменты для создания привязок типов между JavaScript и языком, используемым для компиляции в WebAssembly.
Пример создания привязок типов между JavaScript и Rust, компиляции в бинарный файл и вызова из программы на JavaScript можно найти на сайте MDN.
Если вы планируете активно использовать веб-API в Rust и WebAssembly, вам могут пригодиться крейты Rust web_sys и js_sys. web_sys содержит привязки к большинству веб-API, доступных в Deno, а js_sys предоставляет привязки к стандартным встроенным объектам JavaScript.
Использование wasmbuild для Rust WebAssembly в Deno
wasmbuild — это официальный инструмент Deno, упрощающий работу с Rust и WebAssembly в проектах Deno. Он автоматизирует компиляцию кода Rust в WebAssembly и создание привязок TypeScript, упрощая вызов функций Rust из JavaScript.
wasmbuild создает определения TypeScript для функций Rust, обеспечивая полную проверку типов. Сгенерированный JavaScript можно использовать со сборщиками, такими как esbuild. Для удобства развертывания сгенерированные файлы можно сразу добавить в систему контроля версий.
Системный интерфейс WebAssembly (WASI)
В примерах выше создаются экземпляры модулей Wasm, которые обмениваются с JavaScript только числами и данными в памяти. Системный интерфейс WebAssembly (WASI) — это стандартный набор импортов, который предоставляет модулю Wasm доступ к возможностям, аналогичным возможностям операционной системы, например к чтению аргументов командной строки и переменных окружения, системным часам и (если вы предоставите разрешение) файловой системе. Благодаря ему программа, написанная на Rust, C или другом языке и скомпилированная в Wasm, может выполняться за пределами браузера так же, как обычная программа командной строки.
Deno поддерживает оба поколения WASI:
-
WASI Preview 1 (
wasip1) — модули выполняются с помощью модуляnode:wasi. -
WASI Preview 2 (
wasip2) — компоненты, построенные на основе модели компонентов, выполняются с помощью инструмента Bytecode Alliancejco, который также может транспилировать компонент в JavaScript, импортируемый как любой другой модуль.
Запуск модуля WASI Preview 1
Скомпилируйте программу для целевой платформы wasm32-wasip1. Подойдет любая цепочка инструментов для WASI; например, для Rust:
rustc --target wasm32-wasip1 -O hello.rs -o hello.wasm
Запустите программу из Deno с помощью модуля node:wasi. Создайте экземпляр WASI с аргументами и переменными окружения, которые должны быть доступны гостевой программе, создайте экземпляр модуля с импортами WASI и вызовите start:
import { WASI } from "node:wasi";
const wasi = new WASI({
version: "preview1",
args: ["hello", "alpha", "beta"],
env: { GREETING: "hello-wasi" },
});
const bytes = await Deno.readFile(new URL("./hello.wasm", import.meta.url));
const module = await WebAssembly.compile(bytes);
const instance = await WebAssembly.instantiate(module, wasi.getImportObject());
wasi.start(instance);
deno run --allow-read run.ts
Экземпляр WASI запускается без доступа к реальной файловой системе. Чтобы предоставить гостевой программе доступ к каталогам, передайте карту preopens, связывающую пути, видимые модулю, с реальными путями на диске, например preopens: { "/data": "./data" }. После этого модуль сможет читать и записывать данные в ./data (а процессу Deno потребуются соответствующие разрешения --allow-read и --allow-write).
node:wasi — это экспериментальный API Node.js, поэтому при его использовании Deno выводит предупреждение об экспериментальной функции.
Запуск компонента WASI Preview 2
Компоненты Preview 2 компилируются для целевой платформы wasm32-wasip2:
rustc --target wasm32-wasip2 -O hello.rs -o hello.wasm
Компонент — это не обычный модуль Wasm, поэтому его нельзя загрузить через API WebAssembly или node:wasi. Запустите его напрямую с помощью jco:
deno run -A npm:@bytecodealliance/jco run hello.wasm alpha beta
Чтобы вызывать компонент из собственного кода, а не запускать его как команду, транспилируйте его в JavaScript с привязками:
deno run -A npm:@bytecodealliance/jco transpile hello.wasm -o out
В результате в out/ будет записан ES-модуль, который можно импортировать, как любой другой модуль JavaScript.
Оптимизация
Для production-сборок можно оптимизировать бинарные файлы WebAssembly. Если вы передаете бинарные файлы по сети, оптимизация размера может существенно помочь. Если же вы в основном запускаете WebAssembly на сервере для выполнения ресурсоемких вычислений, полезной может быть оптимизация скорости. Хорошее руководство по оптимизации production-сборок можно найти здесь. Кроме того, в группе rust-wasm представлен список инструментов для оптимизации и обработки бинарных файлов WebAssembly.
Последнее обновление:
© 2018–2025 the Deno authors
Licensed under the MIT License.
https://docs.deno.com/runtime/reference/wasm