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-сборку, сохраняя изоляцию контекста. ---