WebAssembly: импорт .wasm файлов
## Механизм работы WebAssembly в Vite
Vite рассматривает WebAssembly (.wasm) как полноценный модульный ресурс, который может быть подключён несколькими способами в зависимости от того, требуется ли:
* загрузка бинарного модуля как URL,
* получение `ArrayBuffer`,
* автоматическая инициализация через `WebAssembly.instantiate`,
* или интеграция с ESM-окружением через плагины и утилиты сборки.
В основе лежит разделение режимов импорта, где поведение определяется query-параметрами.
---
## Базовый импорт .wasm как URL
Самый простой способ — получить путь к файлу, который затем используется вручную:
```javascript
import wasmUrl from './module.wasm?url';
const response = await fetch(wasmUrl);
const buffer = await response.arrayBuffer();
const module = await WebAssembly.instantiate(buffer);
```
Ключевая особенность режима `?url`:
* файл не загружается автоматически;
* Vite помещает его в ассеты сборки;
* возвращается строка с финальным URL;
* управление загрузкой остаётся на стороне разработчика.
Такой подход используется, когда требуется кастомная логика загрузки или кэширования.
---
## Импорт WebAssembly как ArrayBuffer
Vite позволяет получить бинарные данные напрямую:
```javascript
import wasmBinary from './module.wasm?raw';
const buffer = wasmBinary;
const module = await WebAssembly.instantiate(buffer);
```
Однако в актуальных конфигурациях чаще используется явная работа через `?url`, так как `?raw` ориентирован на текстовые ресурсы. Для WebAssembly более типичен поток:
```javascript
const url = new URL('./module.wasm', import.meta.url);
const buffer = await fetch(url).then(r => r.arrayBuffer());
```
---
## Автоматическая инициализация через ?init
Один из наиболее удобных режимов Vite — `?init`, который генерирует функцию инициализации модуля:
```javascript
import initWasm from './module.wasm?init';
const { instance, module } = await initWasm();
```
Поведение:
* Vite генерирует обёртку над `WebAssembly.instantiate`;
* загрузка и компиляция происходят автоматически;
* возвращается Promise с результатом инстанцирования.
Дополнительно возможно передать импорты:
```javascript
const { instance } = await initWasm({
env: {
log: (value) => console.log(value)
}
});
```
Это особенно полезно при работе с Rust (wasm-bindgen) и AssemblyScript.
---
## Прямая работа с WebAssembly.instantiateStreaming
При необходимости оптимальной загрузки используется потоковая компиляция:
```javascript
const url = new URL('./module.wasm', import.meta.url);
const { instance } = await WebAssembly.instantiateStreaming(
fetch(url),
{
env: {
memory: new WebAssembly.Memory({ initial: 10 })
}
}
);
```
Преимущества:
* компиляция начинается до полной загрузки файла;
* снижает latency;
* эффективнее для крупных модулей.
Ограничение: сервер должен отдавать корректный MIME type `application/wasm`.
---
## Типизация и поддержка TypeScript
Vite автоматически предоставляет базовые типы для `.wasm` при подключении `vite/client`:
```typescript
///
import wasmUrl from './module.wasm?url';
import init from './module.wasm?init';
```
Типизация зависит от режима импорта:
* `?url` → `string`
* `?init` → функция инициализации
* `?raw` → `string | Uint8Array` (в зависимости от конфигурации)
Для более строгой типизации часто добавляются декларации:
```typescript
declare module '*.wasm?init' {
const init: (imports?: any) => Promise;
export default init;
}
```
---
## Интеграция с Rust и wasm-pack
Наиболее распространённый сценарий — использование Rust-компиляции через `wasm-pack`.
Типичная структура:
```
pkg/
my_wasm.js
my_wasm_bg.wasm
```
В Vite подключение выглядит так:
```javascript
import init, { add } from './pkg/my_wasm.js';
await init();
console.log(add(2, 3));
```
Особенности:
* `.wasm` файл скрыт за JS-обёрткой;
* Vite обрабатывает его как ESM-ресурс;
* инициализация обязательна до использования функций.
При необходимости оптимизации можно отключать лишние бандлы и использовать `?init` напрямую для чистого `.wasm`.
---
## Работа с импортами WebAssembly
WebAssembly модули часто требуют внешних зависимостей через import object:
```javascript
import initWasm from './module.wasm?init';
const { instance } = await initWasm({
env: {
js_log: (msg) => console.log(msg),
memory: new WebAssembly.Memory({ initial: 256 })
}
});
```
Основные секции import object:
* `env` — память, функции среды выполнения
* `wasi_snapshot_preview1` — при использовании WASI
* кастомные пространства имён для прикладной логики
---
## Использование WASI в Vite
При работе с WASI-модулями требуется дополнительная инфраструктура:
```javascript
import initWasm from './module.wasm?init';
const wasi = new WASI({
args: [],
env: {},
preopens: {}
});
const { instance } = await initWasm({
wasi_snapshot_preview1: wasi.wasiImport
});
wasi.start(instance);
```
Ограничение: браузерная среда не поддерживает WASI нативно, поэтому требуется полифилл или библиотека.
---
## Оптимизация загрузки WebAssembly
Vite автоматически:
* помещает `.wasm` в ассеты сборки;
* хэширует файл для кэширования;
* поддерживает code-splitting.
Дополнительные приёмы:
### Lazy loading
```javascript
const loadWasm = async () => {
const init = await import('./module.wasm?init');
return init();
};
```
### Dynamic imports
```javascript
const wasmModule = await import('./module.wasm?url');
```
### Кэширование браузера
Благодаря хэшированным именам файлов:
```
module.8fd3a1.wasm
```
обеспечивается стабильное долгосрочное кэширование.
---
## Ограничения и особенности поведения
При работе с WebAssembly в Vite необходимо учитывать:
* `?init` не работает в Node.js без эмуляции WebAssembly API;
* streaming compilation требует корректного MIME типа;
* большие модули могут увеличивать initial load time без lazy loading;
* старые браузеры могут не поддерживать `instantiateStreaming`.
---
## Взаимодействие с ESM-модулями Vite
WebAssembly в Vite рассматривается как часть единой ESM-системы:
* импортируется как модуль;
* участвует в графе зависимостей;
* поддерживает hot module replacement частично (через reload);
* может комбинироваться с JavaScript и TypeScript без промежуточных сборок.
---
## Использование с React и другими фреймворками
Типичный сценарий — инициализация внутри эффекта:
```javascript
import initWasm from './module.wasm?init';
export function Component() {
useEffect(() => {
let instance;
initWasm().then(res => {
instance = res.instance;
});
return () => {
instance = null;
};
}, []);
return null;
}
```
Особенность: WebAssembly инициализируется асинхронно, поэтому требует управления состоянием загрузки.
---
## Комбинирование с Web Workers
Для разгрузки main thread WebAssembly часто выносится в worker:
```javascript
// worker.js
import initWasm from './module.wasm?init';
onmess age = async () => {
const { instance } = await initWasm();
postMessage(instance.exports.compute(42));
};
```
Vite при этом корректно бандлит `.wasm` в worker-сборку, сохраняя изоляцию контекста.
---