В последнее время фронтенд все больше и больше обрастает новыми технологиями, одна из них — WebAssembly. На ней можно написать полноценное web‑приложение и почти все на что способна ваша фантазия. В статье мы рассмотрим, что такое WebAssembly, как работает и с чем её едят. И конечно же я хотел поделиться рецептом приложения. Возьмем наш любимый React и добавим Yew, приправим всё это webpack-ом и добавим щепоточку module‑federation. Статья будет интересна всем, кто хочет познакомиться с WebAssembly и добавить разнообразия в список используемых фреймворков и библиотек для написания пользовательских компонентов.

Что такое Wasm и зачем он был создан

WebAssembly (Wasm) — это новый открытый формат байт-кода, исполняемого современными браузерами. Сейчас он поддерживается во всех браузерах. Подробнее можно почитать тут.

Wasm позволяет переносить код, написанный на языках C, C++, C#, Rust и других (поддерживаемый список можно посмотреть тут), для использования в браузерной среде с производительностью, близкой к нативной. Важно понимать, максимальная производительность WebAssembly равна максимальной производительности JavaScript, но написать оптимизированный по памяти код иногда проще на WebAssembly. Подробнее можно почитать в статье Вячеслава Егорова

Язык WebAssembly предназначен для дополнения и работы вместе с JavaScript, что позволяет веб-разработчикам использовать преимущества обоих языков:

  • JavaScript — это язык высокого уровня, гибкий и достаточно выразительный для написания веб-приложений. У него много преимуществ: он динамически типизирован, не требует компиляции и обладает огромной экосистемой, которая предоставляет мощные фреймворки, библиотеки и другие инструменты.

  • WebAssembly — это низкоуровневый язык, похожий на ассемблер, с компактным двоичным форматом, который работает с почти естественной производительностью и предоставляет низкоуровневые модели памяти для таких языков как C++ и Rust, чтобы компилировать их для работы в интернете. Обратите внимание, что у WebAssembly на будущее есть высокоуровневая цель по поддержке языков со сборщиками мусора в модели памяти.

Как WebAssembly работает в браузерах

Сейчас во всех браузерах уже есть поддержка WebAssembly: его код исполняется в специальной виртуальной машине. Выглядит это так. Код на низкоуровневом языке компилируется с помощью мощных компиляторов (например, LLVM ) или набора инструментов таких как Emscripten и превращается в бинарный формат. Но у него есть и текстовое представление:

(module
  (func import “imports” “imported_func”) (param i32))
  (func (export “exported_func”) 
   i32.const 42 
  call $i)
)

Затем модуль JavaScript вызывает Wasm-модуль. Пример кода:

WebAssembly.instantiateStreaming(
    fetch("simple.wasm"),
    {
   	 imports: {
   		 imported_func: console.log,
   	 },
    },
).then((module) =>
    module.instance.exports.exported_func(),
);

В браузере модуль скачивается, парсится и исполняется. 

Wasm в браузере
Wasm в браузере


Кажется, что всё довольно просто. Благодаря всей этой магии открылась возможность запускать десктопные приложения в браузере или использовать код на низкоуровневых языках.

Не будем сильно углубляться в эту тему, уж очень много статей написано. Подробнее ознакомиться можно тут. 

Достоинства и недостатки WebAssembly

Достоинства

???? Стандарт. Сейчас он широко распространяется в отрасли и поддерживается многими языками, в том числе и со сборкой мусора (Garbage Collection). Совсем недавно поддержка стандарта появилась в Google Chrome и Mozilla Firefox. Также Wasm можно использовать не только в браузере, но и на серверах, используя специальный интерфейс WASI.

???? Возможность использовать библиотеки, написанные на низкоуровневых языках. Индустрия системных языков программирования накопила большой опыт в написании сложных алгоритмов с преобразованиями данных с оптимальной скоростью. И, о чудо, теперь этот опыт можно использовать при написании приложений в браузере.

???? Доступ к DOM и Browser API. WebAssembly напрямую не может обращаться к DOM или браузерному API. Все вызовы API внутри Wasm происходят через JavaScript (компиляторами добавляется специальная прослойка для исполнения JavaScript-кода). Значит, мы можем использовать почти все возможности современных браузеров с помощью специальных полифилов.

???? Быстрота и эффективность. Благодаря тому, что Wasm это бинарный формат, длительность его парсинга и компиляции заметно сокращается. Это позволяет ему исполняться в браузере на скорости, близкой к нативной (но тут есть некоторые нюансы, в основном зависящие от размера файла). И можно кешировать распарсенный и скомпилированный Wasm-модуль, используя специальный заголовок Content-Type: application/wasm.

⛓️ Типизация. В Wasm есть система типов. Это помогает корректно выполнять код. Но несмотря на всю привлекательность типизации, она может существенно замедлить работу, потому что каждый тип нужно конвертировать в понятный Wasm. Подробнее о типах в WebAssembly.

???? Изоляция. По умолчанию Wasm-модуль запускается в изолированной браузерной среде, и всё, что он может делать, это читать выделенную ему область памяти. Но изоляция не означает безопасность. Для безопасного исполнения лучше использовать последние стандарты в индустрии, к примеру, Content Security Policy (CSP).

Недостатки

???? Исполняется в основном потоке. Так как Wasm исполняется внутри основного потока бразера, стоит бережно следить и профилировать используемый код. Основные проблемы, которые могут возникнуть: накладные расходы на копирование данных, общение между потоками, блокировка потока и всё, что относится к работе с основным потоком в JavaScript. Мы тут опускаем взаимодействие с любыми типами Worker'ов.

???? Большие размеры файлов. Портируемость в браузер кода, написанного на системном языке программирования, требует добавления своего рода полифилов. Большая часть проблем такого рода решается оптимизацией сборки или прогоном с усиленной оптимизаций через компилятор. Выше я уже говорил, что файлы можно кешировать.

⌛️ Время на изучение технологии. Wasm — молодая, развивающая технология, которая только частично связана с использованием JS. Для написания Wasm-модулей нужно будет погрузиться в новый язык программирования, найти или написать плагины для используемых сборщиков или бандлеров. И, что немаловажно, обучиться грамотно отлаживать и профилировать новую для вас технологию.

Особенности (не смог отнести к достоинствам или недостаткам)

???? JS необходим для старта Wasm-модуля. Это связано с тем, что Wasm призван не заменить JS, а стать ещё одним инструментом в руках веб-разработчика. 

???? Сложно отлаживать. Сейчас можно это делать с помощью devtools. Но возможностей меньше того, к чему привык JavaScript-разработчик. В то же время уже сейчас можно отлаживать исходники файлов, написанных на низкоуровневых языках.

Пишем web-приложение c использованием WebAssembly

Кого-то удивит, но на WebAssembly можно писать и полноценные фронтенд-приложения. Вот список ингредиентов для приготовления проекта:

  1. Yew. Фреймворк для написания веб-приложений на WebAssembly, использующий язык Rust.

  2. React. Одна из популярных библиотек для создания пользовательских интерфейсов.

  3. Yarn . Менеджер пакетов, который одновременно выполняет функции менеджера проекта.

  4. Webpack. Сборщик приложений.

  5. Typescript. Надстройка над JavaScript c добавлением типизации.

  6. Prettier. Форматер кода.

  7. Eslint. Статический Анализатор кода, помогающий придерживаться единого стиля кода и находить некоторые типы ошибки.

Что за проект мы будем создавать? Небольшое приложение с микрофронтендной архитектурой с использованием Yew и React.

Для начала установим Yarn. Нам потребуется версия 3.6.3. После успешной установки настроим своё рабочее пространство

Затем инициализируем конфигурацию проекта. Скачиваем репозиторий:

git clone https://github.com/easymikey/wasm-example/tree/init-project

…и переключаемся на ветку init-project. Создадим необходимые директории:

mkdir packages && mkdir packages/shell && mkdir packages/react-counter && mkdir packages/yew-counter

Разработка входной точки

Начнем с подготовки окружения:

cd packages/shell && yarn init -y && touch webpack.config.ts && tsconfig.json && mkdir public && touch public/index.html && mkdir src && touch src/App.tsx

Добавим dev-зависимости:

yarn add -D css-loader \
esbuild \
esbuild-loader \
html-webpack-plugin \
style-loader \
typescript \
typescript-plugin-css-modules \
webpack \
webpack-cli \
webpack-dev-server \

Добавим React:

yarn add react react-dom

Получим такой package.json:

{
	"name": "shell",
	"version": "0.1.0",
	"sideEffects": false,
	"scripts": {
		"serve": "webpack serve --color --open"
	},
	"dependencies": {
		"react": "^18.2.0",
		"react-dom": "^18.2.0"
	},
	"devDependencies": {
		"@types/react": "^18.2.20",
		"@types/react-dom": "^18.2.8",
		"@types/webpack": "^5.28.3",
		"@types/webpack-dev-server": "^4.7.2",
		"css-loader": "^6.8.1",
		"esbuild": "^0.19.4",
		"esbuild-loader": "^4.0.2",
		"esbuild-register": "^3.5.0",
		"html-webpack-plugin": "^5.5.3",
		"style-loader": "^3.3.3",
		"typescript": "4.9.5",
		"typescript-plugin-css-modules": "4.1.1",
		"webpack": "^5.88.2",
		"webpack-cli": "^5.1.4",
		"webpack-dev-server": "^4.15.1"
	},
	"packageManager": "yarn@3.6.3",
	"volta": {
		"node": "20.8.1"
	}
}
Следом настроим TypeScript:
{
    "extends": "../../tsconfig.base.json",
    "include": [
   	 "./src/**/*",
    ],
    "compilerOptions": {
   	 "outDir": "dist",
   	 "baseUrl": ".",
   	 "paths": {
         // Дадим возможность IDE найти наши модули
   		 "react_counter/*": [ "../react-counter/dist/@mf-types/*" ],
   		 "yew_counter/*": [ "../yew-counter/core/pkg/index.d.ts" ]
   	 },
   	 "plugins": [ { "name": "typescript-plugin-css-modules" } ],
    },
    "exclude": [
   	 "node_modules",
   	 "./dist/**/*"
    ],
    "references": [ ]
}

Настроим сборку:
import path from 'path';
import webpack from 'webpack';
import 'webpack-dev-server';
import HtmlWebpackPlugin from 'html-webpack-plugin';

const config: webpack.Configuration = {
    entry: './src/index.tsx',
    mode: 'development',
    devServer: {
   	 static: {
   		 directory: path.join(__dirname, 'dist'),
   	 },
   	 headers: {
   		 'Access-Control-Allow-Origin': '*',
   	 },
   	 port: 3001,
    },
    resolve: {
   	 extensions: ['.js', '.ts', '.tsx', '.css'],
    },
    module: {
   	 rules: [
   		 // Use esbuild to compile JavaScript & TypeScript
   		 {
   			 // Match `.js`, `.jsx`, `.ts` or `.tsx` files
   			 test: /\.[jt]sx?$/,
   			 exclude: /yarn/,
   			 loader: 'esbuild-loader',
   			 options: {
   				 // JavaScript version to compile to
   				 target: 'es2015',
   			 },
   		 },
   		 {
   			 test: /\.module.css$/,
   			 use: [
   				 'style-loader',
   				 {
   					 loader: 'css-loader',
   					 options: {
   						 modules: {
   							 localIdentName:
   								 '[name]__[local]--[hash:base64:16]',
   						 },
   					 },
   				 },
   			 ],
   		 },
   	 ],
    },
    plugins: [
   	 new HtmlWebpackPlugin({
   		 template: './public/index.html',
   	 }),
    ],
};

export default config;

Если хочется детально изучить настройки, то почитайте документацию используемых плагинов.

Инициализируем входной HTML-файл в папке public:
<html>
  <head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1" />
	<title>Yew + React </title>
  </head>
  <style>
	body {
  	margin: 0;
  	padding: 0;
	}
  </style>
  <body>
	<div id="react-counter"></div>
  </body>
</html>

И можно приступать к разработке. 

Откроем файл src/App.tsx и напишем оболочку нашего приложения, отображающее счётчик с состоянием по умолчанию, равному нулю. Добавим колбеки для обновления состояния onIncrement и onDecrement.

import {FC, useState} from 'react';
// Стили можно посмотреть https://github.com/easymikey/wasm-example/blob/main/packages/shell/src/App.module.css
import styles from './App.module.css';

const App: FC = () => {
    const [counter, setCounter] = useState(0);

    const onIncrement = () => setCounter((c) => c + 1);

    const onDecrement = () => setCounter((c) => c - 1);

    return (
   	 <div className={styles.root}>
   		 <div className={styles.layout}>
   			 <div className={styles.counter}>{counter}</div>
   		 </div>
   	 </div>
    );
};

export default App;

Затем создадим файлы src/index.tsx и src/bootstrap.tsx со следующим содержанием:

// bootstrap.tsx
import {createRoot} from 'react-dom/client';

import App from './App';

createRoot(document.getElementById('react-counter')!).render(<App />);

index.tsx
import('./bootstrap');

Теперь можно запускать наше приложения из корня командой yarn serve.Можно увидеть текущее состояние приложения, открыв браузер на http://localhost:3001

Начальная версия приложения.
Начальная версия приложения.

Отлично, с входной точкой закончили, время начать разрабатывать модуль для уменьшения и увеличения счётчика на React

Разработка React-cчетчика

Как и в первом случае начнем с настройки окружения.

cd packages/react-counter && yarn init -y && touch webpack.config.ts && tsconfig.json && mkdir src && touch src/ReactConter.tsx && src/index.tsx

Добавим dev-зависимости:

yarn add -D css-loader \
esbuild \
esbuild-loader \
html-webpack-plugin \
style-loader \
typescript \
typescript-plugin-css-modules \
webpack \
webpack-cli \
webpack-dev-server \
@module-federation/typescript \

И добавим React:

yarn add react react-dom

Итоговый package.json:

{
	"name": "react-counter",
	"version": "0.1.0",
	"sideEffects": false,
	"scripts": {
		"serve": "webpack serve --color"
	},
	"dependencies": {
		"react": "^18.2.0",
		"react-dom": "^18.2.0"
	},
	"devDependencies": {
		"@module-federation/typescript": "^3.0.1",
		"@types/react": "^18.2.21",
		"@types/webpack": "^5.28.3",
		"@types/webpack-dev-server": "^4.7.2",
		"css-loader": "^6.8.1",
		"esbuild": "^0.19.4",
		"esbuild-loader": "^4.0.2",
		"esbuild-register": "^3.5.0",
		"html-webpack-plugin": "^5.5.3",
		"style-loader": "^3.3.3",
		"typescript": "4.9.5",
		"typescript-plugin-css-modules": "4.1.1",
		"webpack": "^5.88.2",
		"webpack-cli": "^5.1.4",
		"webpack-dev-server": "^4.15.1"
	},
	"volta": {
		"node": "20.8.1"
	}
}
Настроим TypeScript.
{
    "extends": "../../tsconfig.base.json",
    "include": [
   	 "src",
    ],
    "compilerOptions": {
   	 "outDir": "dist",
   	 "baseUrl": ".",
   	 "plugins": [ { "name": "typescript-plugin-css-modules" } ],
    },
    "exclude": [
   	 "node_modules",
   	 "dist/**/*"
    ],
    "references": [ ]
}

Настроим сборку.
import path from 'path';
import webpack from 'webpack';
import 'webpack-dev-server';
import {FederatedTypesPlugin} from '@module-federation/typescript';
import {ModuleFederationPluginOptions} from '@module-federation/typescript/src/types';

const federationConfig: ModuleFederationPluginOptions = {
	name: 'react_counter',
	filename: 'remoteEntry.js',
	exposes: {
		'./ReactCounter': './src/ReactCounter.tsx',
	},
	shared: {
		react: {singleton: true},
		'react-dom': {singleton: true},
	},
};
const config: webpack.Configuration = {
	entry: './src/index.tsx',
	mode: 'development',
	devServer: {
		static: {
			directory: path.join(__dirname, 'dist'),
		},
		hot: true,
		port: 3002,
		headers: {
			'Access-Control-Allow-Origin': '*',
		},
	},
	output: {
		publicPath: 'auto',
	},
	resolve: {
		extensions: ['.js', '.ts', '.tsx', '.css'],
	},
	module: {
		rules: [
			// Use esbuild to compile JavaScript & TypeScript
			{
				// Match `.js`, `.jsx`, `.ts` or `.tsx` files
				test: /\.[jt]sx?$/,
				exclude: /yarn/,
				loader: 'esbuild-loader',
				options: {
					// JavaScript version to compile to
					target: 'es2015',
				},
			},
			{
				test: /\.module.css$/,
				use: [
					'style-loader',
					{
						loader: 'css-loader',
						options: {
							modules: {
								localIdentName:
									'[name]__[local]--[hash:base64:16]',
							},
						},
					},
				],
			},
		],
	},
	plugins: [
		new FederatedTypesPlugin({
			federationConfig,
		}),
		new webpack.container.ModuleFederationPlugin(federationConfig),
	],
};

export default config;

Приступаем к написанию нашего микрофронтенда на React.

Откроем файл src/ReactCounter.tsx и добавим возможность обновлять счётчик. Это очень простой компонент, думаю вы писали сотни или даже тысячи таких. Код можно увидеть ниже.

import {FC} from 'react';
// Стили можно посмотреть https://github.com/easymikey/wasm-example/blob/main/packages/react-counter/src/ReactCounter.module.css
import styles from './ReactCounter.module.css';

const ReactCounter: FC<{
    onIncrement: () => void;
    onDecrement: () => void;
}> = ({onDecrement, onIncrement}) => {
    return (
     	 <div className={styles.layout}>
     		 <button className={styles.button} onClick={onIncrement}>
     			 +
     		 </button>
     		 <button className={styles.button} onClick={onDecrement}>
     			 -
     		 </button>
     	 </div>
    );
};

export default ReactCounter;

Теперь нам нужно добавить новоиспеченный микрофонтенд.

Переходим к файлу packages/shell/webpack.config.ts и обновляем его.

...

const config: webpack.Configuration = {
	...
	plugins: [
		new webpack.container.ModuleFederationPlugin({
			name: 'shell',
			remotes: {
				// Used modules
				react_counter:
					'react_counter@http://localhost:3002/remoteEntry.js',
			},
			shared: {
				react: {singleton: true},
				'react-dom': {singleton: true},
			},
		}),
		new HtmlWebpackPlugin({
			template: './public/index.html',
		}),
	],
    ...
};

export default config;

Теперь у нас есть возможность загружать модуль счётчик на React. Давайте это используем и добавим обработку в нашу оболочку. Код изменится следующим образом:

import {FC, Suspense, lazy, useState} from 'react';
import styles from './App.module.css';

const ReactCounter = lazy(() => import('react_counter/ReactCounter'));

const App: FC = () => {
    const [counter, setCounter] = useState(0);

    const onIncrement = () => setCounter((c) => c + 1);

    const onDecrement = () => setCounter((c) => c —  - 1);

    return (
     	 <div className={styles.root}>
     		 <div className={styles.layout}>
     			 <Suspense fallback="Loading ReactCounter">
     				 <ReactCounter
     					 onIncrement={onIncrement}
     					 onDecrement={onDecrement}
     				 />
     			 </Suspense>
     			 <div className={styles.counter}>{counter}</div>
     		 </div>
     	 </div>
    );
};

export default App;

Снова запустим проект из корня командой yarn serve. Проект изменился, теперь он выглядит так:

Версия приложения с добавлением ReactCounter.
Версия приложения с добавлением ReactCounter.

Отличной, второй кусочек нашего приложения готов. Теперь мы можем добавить модуль на Wasm. 

Разработка Yew-счётчика

Как и в первых двух случаях начнем с подготовки окружения.

cd packages/yew-counter && yarn init -y && touch webpack.config.ts

Для разработки Wasm-модуля нам потребуется установить Rust и wasm-pack.
Сначала установим Rust, следуя официальной документации. Затем установим wasm-pack снова следуя официальной документации.

Установим dev-зависимости:

yarn add -D webpack \
webpack-cli \
webpack-dev-server \
esbuild \
esbuild-register \
@wasm-tool/wasm-pack-plugin \

Итоговый package.json:

{
	"name": "yew-counter",
	"version": "0.1.0",
	"sideEffects": false,
	"scripts": {
		"serve": "webpack serve --color"
	},
	"devDependencies": {
		"@types/webpack": "^5.28.3",
		"@types/webpack-dev-server": "^4.7.2",
		"@wasm-tool/wasm-pack-plugin": "^1.7.0",
		"esbuild": "^0.19.4",
		"esbuild-register": "^3.5.0",
		"webpack": "^5.88.2",
		"webpack-cli": "^5.1.4",
		"webpack-dev-server": "^4.15.1"
	},
	"packageManager": "yarn@3.6.3",
	"volta": {
		"node": "20.8.1"
	}
}
Настроим сборку.
import path from 'path';
import webpack from 'webpack';
import 'webpack-dev-server';
import WasmPackPlugin from '@wasm-tool/wasm-pack-plugin';

const config: webpack.Configuration = {
	entry: './core/pkg/index.js',
	output: {
		path: path.resolve(__dirname, 'dist'),
		filename: '[name].js',
	},
	mode: 'development',
	experiments: {asyncWebAssembly: true},
	devServer: {
		static: {
			directory: path.join(__dirname, 'dist'),
		},
		headers: {
			'Access-Control-Allow-Origin': '*',
		},
		port: 3003,
	},
	resolve: {
		extensions: ['.js', '.ts'],
	},
	plugins: [
		new WasmPackPlugin({
			crateDirectory: path.resolve(__dirname, 'core'),
			outDir: path.join(__dirname, 'core/pkg'),
		}),
		new webpack.container.ModuleFederationPlugin({
			name: 'yew_counter',
			filename: 'remoteEntry.js',
			exposes: {
				'./yew': './core/pkg',
			},
		}),
	],
};

export default config;

Инициализируем Wasm-репозиторий c Yew:

wasm-pack new core --template https://github.com/yewstack/yew-wasm-pack-minimal

У нас появляется создатся папкаcore со всеми необходимыми нам файлами.

Для начала обновим файл Cargo.toml. Это аналог package.json и в нём сосредоточны все наши зависимости.

[package]
name = "core"
version = "0.1.0"
edition = "2021"
readme = "README.md"

[lib]
crate-type = ["cdylib"]


# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
yew = { version = "0.21", features = ["csr"] }
serde-wasm-bindgen = "0.6.1"
wasm-bindgen = "0.2.78"
web-sys = "0.3.64"
gloo = "0.10.0"
js-sys = "0.3.64"
stylist = { version = "0.13.0", features = ["yew_integration"] }

Начнем с файла src/app.rs и переименуем его в src/yew_counter.rs.  

Начнём реализовывать счётчик. Для начала импортируем необходимые зависимости.

// Добавляем возможность использовать css
use stylist::css;
// Добавляем возможность стилизации компонентов
use stylist::yew::styled_component;
// Своего рода полифил для wasm
use wasm_bindgen::prelude::*;
// Инициализация фреймворка 
use yew::prelude::*;

Затем опишем типы для нашего компонента.  Вы скорее всего заметите, что это напоминает описание интерфейсов в TypeScript, за исключением использования декоратора derive. Хотя кого я обманываю, большинство из нас так или иначе пробовали использовать декораторы в TypeScript. 

#[derive(Properties, Clone, PartialEq)]
pub struct Props {
	pub on_increment: js_sys::Function,
	pub on_decrement: js_sys::Function,
}
// Если это перенести на Typescript
export interface Props {
    onIncrement: (...args: any[]) => any,
    onDecrement: (...args: any[]) => any,
};

Следует отметить, что функции и типы данных не равны в Rust и JavaScript/TypeScript. В частности потому, что это разные языки.  Если немного уточнить, то для перевода типов нужно сначала замапить типы из JS/TS в Wasm, а только потому уже в Rust. По этой причине мы видим тут тип any.  Поэтому любые функции, передаваемые в модуль на Rust мы будем вызывать не таким образом как все привыкли.

С типами разобрались, давайте начнем писать компонент счётчика. И он удивительным образом похож на React.

#[styled_component]
pub fn YewCounter(props: &Props) -> Html {
	html! {
    	<div>
        	<button>{"+"}</button>
        	<button>{"-"}</button>
    	</div>
	}
}

Стилизуем наш компонент.  Получилось что-то очень похожее на styled-components, за исключением того, что библиотека на Rust только генерирует стили без обертки в компонент через шаблонные строки. Обратим особое внимание на функцию clone. Если простыми словами, то это концепция в Rust, которая явным образом позволяет создавать копию переменной. 

#[styled_component]
pub fn YewCounter(props: &Props) -> Html {
    let on_increment = {
        let on_increment = props.on_increment.clone();
        Callback::from(move |_| {
            let _ = on_increment.call0(&JsValue::NULL);
        })
    };
    let on_decrement = {
        let on_decrement = props.on_decrement.clone();
        Callback::from(move |_| {
            let _ = on_decrement.call0(&JsValue::NULL);
        })
    };

    let layout = css!(
        r#"
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 16px;"#
    );

    let button = css!(
        r#"
		height: 65px;
		width: 65px;
		padding: 0;
		border: none;
		background-color: #aee18b;
		color: #e16541;
		font-size: 40px;
		text-align: center;
		box-shadow:
			rgb(50 50 93 / 25%) 0px 2px 5px -1px,
			rgb(0 0 0 / 30%) 0px 1px 3px -1px;
		cursor: pointer;

		& + & {
			margin-left: 8px;
		}"#
    );

    html! {
        <div class={layout}>
            <button class={button.clone()} onclick={on_increment}>{"+"}</button>
            <button class={button} onclick={on_decrement}>{"-"}</button>
        </div>
    }
}

У нас нет никаких обработчиков, сейчас есть просто статический модуль, который загружает кнопки. Добавим немного магии в YewCounter:


#[styled_component]
pub fn YewCounter(props: &Props) -> Html {
    ...
	let on_increment = {
    	let on_increment = props.on_increment.clone();
    	    Callback::from(move |_| {
        	     let _ = on_increment.call0(&JsValue::NULL);
    	    })
	};
	let on_decrement = {
    	let on_decrement = props.on_decrement.clone();
    	    Callback::from(move |_| {
        	    let _ = on_decrement.call0(&JsValue::NULL);
       	})
	};
    ...
}

Согласитесь же, снова очень похоже на React? Да есть некоторые отличия, но всё же похоже.

Давайте немного разберём создание функции. В Rust нет функций с переменным количеством параметров, поэтому для связи с JavaScript используется обертка над функцией call с передачей контекста и аргументов.


// on_decrement.call0(&JsValue::NULL) В Rust === on_decrement() в JavaScript


let on_decrement = {
    let on_decrement = props.on_decrement.clone();
        // Тут мы используем анонимную функцию и ключевое слово move.
        // Подробнее про move https://doc.rust-lang.org/book/ch13-01-closures.html?highlight=move#capturing-references-or-moving-ownership
        Callback::from(move |_| {
            // Присваиваем к неиспользуемой переменной, чтобы вызвать панику. Подробнее про unwrap https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html?highlight=unwrap#shortcuts-for-panic-on-error-unwrap-and-expect
            let _ = on_decrement.call0(&JsValue::NULL);
    	})
};

Компонент нашего приложения мы написали, теперь нужно добавить для него точку монтирования, и это будет lib.rs.

// Указываем имя модуля. Подробнее про модули в Rust. https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html
mod yew_counter;

// Добавляем возможность использовать Document API
use gloo::utils::document;
// И снова добавляем полифил для Wasm
use wasm_bindgen::prelude::*;
// Импортируем компонент и тип как JS
use yew_counter::{YewCounter, Props};

// Указываем, входную точку модуля
#[wasm_bindgen]
pub fn run_app(
	root_id: String,
	on_increment: js_sys::Function,
	on_decrement: js_sys::Function,
// Небольшой след алгебраических типов данных
) -> Result<(), JsValue> {
     // Добавляем проверку, что элемент с нужным id существует.
	let root = match document().get_element_by_id(&root_id) {
    	    Some(v) => v,
       	None => return Ok(()),
	};
    // Указываем, куда отрисовывать элемент и какие пропсы для это нужны
	yew::Renderer::<App>::with_root_and_props(
    	root,
    	Props {
        	on_increment,
        	on_decrement,
    	},
	)
	.render();
    // Указываем, что инициализация модуля произошла успешно
	Ok(())
}

Очень похоже на вызов функции ReactDOM.createRoot, хоть и более многословно.

Дальше нам нужно добавить возможность вызвать наш новоиспеченный модуль. Обновим поле plugins в shell/webpack.config.ts:

...

const config: webpack.Configuration = {
	...
	plugins: [
		new webpack.container.ModuleFederationPlugin({
			name: 'shell',
			remotes: {
				// Used modules
				react_counter:
					'react_counter@http://localhost:3002/remoteEntry.js',
                yew_counter: 'yew_counter@http://localhost:3003/remoteEntry.js',
			},
			shared: {
				react: {singleton: true},
				'react-dom': {singleton: true},
			},
		}),
		new HtmlWebpackPlugin({
			template: './public/index.html',
		}),
	],
    ...
};

export default config;

Добавим ленивую инициализацию модуля YewCounter в shell/src/App.tsx и возможность обрабатывать ошибку загрузку модуля:

Код
import {FC, Suspense, lazy, useEffect, useState} from 'react';
import styles from './App.module.css';

/**
 * Общие пропсы для любого счётчика
 */
type CounterProps = {
    onIncrement: () => void;
    onDecrement: () => void;
};
// Обработчик ошибки при импорте модуля
const onRejectedComponentHandler = (
    error: unknown,
): {
    default: FC<CounterProps>;
} => {
    console.error('Component Failed Loading:', error);

    return {
   	 default: () => <div>Failed to Load</div>,
    };
};

// Добавим возможность обрабатывать ошибку импорта модуля
const ReactCounter = lazy(() =>
    import('react_counter/ReactCounter').catch(onRejectedComponentHandler),
);

const YEW_COUNTER = 'yew-counter';

// Добавим возможность ленивой загрузки YewCounter и обработку ошибки
const YewCounter = lazy(() =>
    // импортируем модуль и инициализируем модуль
    import('yew_counter/yew')
   	 .then((module) => {
   		 const YewCounter: FC<CounterProps> = ({
   			 onDecrement,
   			 onIncrement,
   		 }) => {
   			 useEffect(() => {
                 // Используя доступное из lib.rs API монтируем компонент
   				 module.run_app(YEW_COUNTER, onIncrement, onDecrement);
   			 }, []);

   			 return <div id={YEW_COUNTER} />;
   		 };
   		 return {default: YewCounter};
   	 })
   	 .catch(onRejectedComponentHandler),
);


const App: FC = () => {
    const [counter, setCounter] = useState(0);

    const onIncrement = () => setCounter((c) => c + 1);

    const onDecrement = () => setCounter((c) => c - 1);

    return (
     	 <div className={styles.root}>
     		 <div className={styles.layout}>
     			 <Suspense fallback="Loading ReactCounter">
     				 <ReactCounter
     					 onIncrement={onIncrement}
     					 onDecrement={onDecrement}
     				 />
     			 </Suspense>

     			 <div className={styles.counter}>{counter}</div>

     			 <Suspense fallback="Loading YewCounter">
     				 <YewCounter
     					 onIncrement={onIncrement}
     					 onDecrement={onDecrement}
     				 />
     			 </Suspense>
     		 </div>
     	 </div>
    );
};

export default App;

Снова запускаем из корня yarn serve

Версия приложения с ReactCounter и YewCounter.
Версия приложения с ReactCounter и YewCounter.

Ихууу. Теперь мы можем менять состояние своего приложения и через React, и через Yew. Мы попробовали новую технологию, и, кажется, у нас получилось построить простое, но в то же время необычное приложения c использование Wasm. Полная версия кода.

Заключение

Как я уже говорил, в последнее время WebAssembly стремительно развивается и позволяет творить настоящую магию. Мы с вами разработали небольшое web-приложение, используя WebAssembly. Надеюсь, этот опыт поможет вам в дальнейшем.

«Всё, что должно быть сказано, уже сказано. Но поскольку этого никто не услышал, можно всё что угодно сказать ещё раз.» — Андре Жид

Комментарии (73)


  1. Kodzo
    23.11.2023 12:29

    Какой размер приложения после публикации, сколько получит браузер?


    1. easymikey Автор
      23.11.2023 12:29
      +1

      Тут нужно понимать, что это непродашнреади решение. Тут нет заморочек с кэшированием модулей, сжатием файлов и сокрашения размера бандра (Особенно для wasm-модуля).

      Размеры бандлов:

      • shell = вендоры (~1Mb uncompressed) + код (~55Kb uncompressed)

      • react_counter = вендоры (~140Kb uncompessed) + код (~70Kb uncompressed)

      • yew_counter = wasm (~2.4Mb uncompressed = 600Kb compressed) + обвязка (130Кb uncompressed)


  1. Alesh
    23.11.2023 12:29

    А как это по скорости, выигрыш есть хоть какой-то, если сложный ui интерфейс?


    1. easymikey Автор
      23.11.2023 12:29

      Что имеется ввиду под сложным кейсом (для всех он разный) ?

      Могу привести личный пример. Я неболшой дробдаун, по скорости особых просадок незаметил, единственный момент может вырасти время первой загрузки модуля, даже если он ассинхронный. Но это все решается кэшированием и прогоном через компилятор в более строгом режиме.

      Нужно понимать, я использовал такоей пример не для того, чтобы показать какой WebAssembly быстрый. А скорее, что он на нем можно решать не только задачи связанные со сложными алгоритмами, но и верстать UI. К тому же, Rust-имеет более надежную систему типов чем TypeScript, что может помочь в обнаружении ошибок.


      1. Alesh
        23.11.2023 12:29

        По моему достаточно четко сформулировал вопрос.

        Могу детализировать, например это таблицы котировок ставок на сайте букмейкера, которые обновляются раз в секунду. Согласитесь, это не дропдаун замерять на производительноcть :)


        1. easymikey Автор
          23.11.2023 12:29

          Спасибо за уточнение:)

          Приведенный вами комплект очень сложный и написать его производительную версию бывает не просто, используя React.

          Что на счет Yew. Фронтенд на Yew обещает быть эффективным, но в данном примере утверждать не могу Могу предложить посмотреть на что-то отдаленно похожее в yew-examples.

          Я в проде еще не использовал фронтенд на Yew или любом другом фрецмворке/библиотеке на WebAssembly поэтому такого детального опыта у меня нет. Посоветовал спросить у pkirill, он более подробно может сказать о производительности WebAssembly в таких сложных сценариях :)


  1. pkirill
    23.11.2023 12:29

    Код на низкоуровневом языке компилируется с помощью мощных компиляторов (например, Emscripten) и превращается в бинарный формат

    Emscripten это же не компилятор, компилятор там llvm. Emscripten это набор библиотек напоминающих stdlib + обвязка на JS упрощающая.


    1. easymikey Автор
      23.11.2023 12:29
      -1

      Полностью с тобой согласен. Ты более подробно раскрыл:)


      1. pkirill
        23.11.2023 12:29

        Я пишу фронт на java (sudu-editor), и там у меня тоже есть примеры интеропа с Wasm, но я там не использовал ни обвязок ни библиотек emscripten. В целом это опционально. А без компилятора никак


        1. easymikey Автор
          23.11.2023 12:29

          Круто, что ты заметил такой глубокий нюанс:)

          После твоего комментария так и чешутся руки поправить текст

          На счет emscripten и его библиотек, и опционального использования сильно не углублялся. Спасибо что подсветил.

          На сколько я помню в Yew тоже можно от него оказаться. Но у меня были сложности с встраиванием его как микрофронтенд. Поэтому решил не заморачиваться.


        1. themen2
          23.11.2023 12:29

          Можно писать фронт на java?


          1. pkirill
            23.11.2023 12:29
            +1

            Я пишу, вот мой доклад

            https://www.youtube.com/watch?v=QHbYXDobo3k


            1. themen2
              23.11.2023 12:29

              Спасибо. Ща заценим. Если будут вопросы, отпишусь.

              Если это работает, то зачем тогда jetbrains продвигает KMM (kotlin multiplatform) ?


              1. easymikey Автор
                23.11.2023 12:29

                kotlin multiplatform - это только про ui.

                WebAssembly намного шире. Возможно использовать библиотеку для криптографии или обработку видео. И можно это писать на разных языках. Плюс это стандарт. Он поддерживается и развивается несколькими браузерами, а котлин зависит только от jetbrains.


                1. themen2
                  23.11.2023 12:29

                  Не только. Как раз в первых версиях предлагалось писать только data слой кроссплатформенный, а ui - нативный каждый под свою платформу.

                  Теперь вроде бы можно на Compose (jetpack compose) писать Ui и для iOS.

                  Есть open source проекта на wasm, покрывающий мобилки+десктоп+веб ?


                  1. easymikey Автор
                    23.11.2023 12:29

                    А зачем?)


                    1. easymikey Автор
                      23.11.2023 12:29

                      Но кстати про отдельный слой не знал, видимо потому что не пишу на Java или Kotlin:)

                      В любом случае все кейсы покрыть нет возможности.

                      Tairu может покрыть все платформы.

                      electronjs может покрыть десктоп.

                      Если пишешь на react, то с некоторыми особенностями можно тоже все платформы покрыть.

                      Можно даже сделать проще написать pwa приложение и оно будет работать на всех платформах.

                      Вариантов сделать одно и тоже сейчас очень много. Но лучше использовать тот, который не зависит от одного вендора.


                    1. themen2
                      23.11.2023 12:29

                      Чтобы посмотреть на production решение и почитать код. Понять насколько легко или сложно вести такой проект


                      1. easymikey Автор
                        23.11.2023 12:29

                        Имеется ввиду проект, который собирается под несколько платформ?


                      1. easymikey Автор
                        23.11.2023 12:29

                        Тут зависит от того не чем его писать. Я так понимаю на Java?


              1. pkirill
                23.11.2023 12:29
                +1

                Я могу рассказать зачем. Я в JB работал 10 лет. Но это очень долго. Например затем чтобы нельзя было использовать java. Kotlin не совместим с java: категорически нельзя использовать java в KT native или в KT JS. Понятно чтобы продвигать KT. Автор TeaVM тоже там работал :) но как узнал "политику" партии ушёл. Т.к. у него было желание сделать именно компилятор из байт кода, но это не совместимо с политикой KT.


                1. themen2
                  23.11.2023 12:29

                  А какова эта политика? Сразу транслировать в ассемблер платформы? Это специально, чтобы нельзя было совмещать с java и сделать такой вендор Лок?

                  Просто я как разработчик устал уже от всех этих новых модных штук(часто не доделанных, где чтобы сделать хелловорлд надо попотеть)


                  1. pkirill
                    23.11.2023 12:29
                    +1

                    эта политика чтобы больше покупали IDEA


                  1. easymikey Автор
                    23.11.2023 12:29

                    А вот в чем дело!

                    Тогда хотелось бы узнать, а что использовали из модных штук?:)

                    Хочется узнать об опыте)


                  1. pkirill
                    23.11.2023 12:29
                    +1

                    Это специально, чтобы нельзя было совмещать с java и сделать такой вендор Лок?

                    Именно так


          1. pkirill
            23.11.2023 12:29

            Я на самом деле копаю ещё глубже, я делаю такой фронт чтоб работал и на десктопе и на айфоне, везде. Просто писать фронт под DOM сильно проще.


            1. themen2
              23.11.2023 12:29

              Можете скинуть пример , может из вашей IDE. Посмотреть


              1. pkirill
                23.11.2023 12:29

                так у меня в докладе есть пример

                и мой новый проект, и старый

                https://github.com/SuduIDE/sudu-editor


                1. themen2
                  23.11.2023 12:29

                  Может чтобы не сильно копать, ссылку на какой нибудь типичный код в проекте скините?????


                  1. pkirill
                    23.11.2023 12:29

                    Да там он там весь типичный :)


    1. codecity
      23.11.2023 12:29
      +1

      компилятор там llvm

      Не холивара ради а точности понимания для. llvm - вроде как платформа для создания компиляторов, а не сам компилятор. Вот у меня смотрю - компилятор clang на MacOS, возможно это опция и у вас другой.


      1. pkirill
        23.11.2023 12:29
        +1

        Да, там тоже clang, но другой. :)

        Это clang от llvm.org, а у вас от Apple.


        1. codecity
          23.11.2023 12:29

          У меня 3 clang, получаетя:

          1. /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang

          2. /usr/local/Cellar/llvm/17.0.5/bin

          3. /Users/user1/emsdk/upstream/bin

          Третий, как я понимаю, как раз от emscripten.


          1. pkirill
            23.11.2023 12:29

            2 и 3 это одно и тоже

            clang в emscripten это просто релиз от llvm.org, тот что второй тоже умеет собирать Wasm таргеты


          1. pkirill
            23.11.2023 12:29
            +1

            В том смысле что для того чтобы собирать WASM таргеты именно clang входящий в emscripten не является обязательным условием, стандартный clang от llvm.org поддерживает WASM из коробки. emscripten - это именно набор библиотек и генератор обвязки JS, напирмер в нём реализован "void * operator new(unsigned);" и.т.д. Но и генереная обвязка JS тоже не есть обязательное условие, я например, на java декларирую API WASM модулей.

            Например вот https://github.com/SuduIDE/sudu-editor/blob/master/wasm-test/src/main/java/org/sudu/experiments/demo/wasm/WasmTest.java#L19


  1. pkirill
    23.11.2023 12:29
    +1

    Недостатки

    ???? Исполняется в основном потоке

    Webassembly прекрасно исполняется и не только в основном потоке, а так же на любых других (Worker) потоках.


    1. easymikey Автор
      23.11.2023 12:29

      Да, я тут не стал добавлять про использование Worker’ах. Я имел ввиду то, что он конкурирует с исполняется в одном потеке с JavaScript, если ничего с ним не делать.


      1. pkirill
        23.11.2023 12:29

        Ну понятно что вызов синхронный, как и обратный вызов JS из Wasm. Все в одном стеке


        1. easymikey Автор
          23.11.2023 12:29

          Да, я тут именно это и имел ввиду. Скорее опирался на новые веяния фронтеда. Если кто-то пишет про Worker’ы сразу указывает, что он у отдельном потоке исполняется и вызываться с JS с помощью механизма сообщений.


  1. odisseylm
    23.11.2023 12:29
    +2

    Извините, никак не могу понять? Какой смысл использовать Rust поверх среды со сборщиком мусора, если главная идея создания Rust - это эффективная и безопасная работа с памятью, благодаря эффективному использования стека (по сравнению с java/Javascript, алоцируя объекты/структуры прямо на стеке) и контролю жизненного цикла с помощью компилятора (по сравнению с C++)

    Или wasm rust всё-таки может работать с памятью как обычно, но внешние JS объекты не пытается контролировать?


    1. easymikey Автор
      23.11.2023 12:29
      +2

      Давайте разбираться:)

      Если мы говорим об эффективновности кода, то за нее отвечает среда запуска WebAssembly (в данном случае это браузер) и компилятор, с помощью которого код был скомпилирован в WebAssembly).

      Если мы говорим о жизненном цикле (выделени и освобожении памяти) при написании кода, то тут ничего не меняется, если вы пишите на Rust.

      Если мы говорим об его контроле памяти во время то тут в дело вступает WebAssembly. Благодаря устройству ее WebAssembly может эффективно читать и писать только в выделенную ему память. Это позволяет единственному web-приложению использовать множество
      независимых библиотек (использующих WebAssembly) которые могут иметь
      отдельные и полностью изолированные друг от друга диапазоны памяти. Подробнее тут.

      Если мы говорим об управлению стеком. То особенность формата WebAssembly как раз в том, что этот формат стековый. Внутри рантайма, который запускает и исполняет WebAssembly стек свой и управление и выделение памятью происходит отдельно для JS кода со сборкой мусора и для WebAssembly.


    1. pkirill
      23.11.2023 12:29
      +2

      Смыл использовать код на С, С++, и Rust огромный. Например переиспользование библиотек


    1. 1dNDN
      23.11.2023 12:29
      +1

      Я пишу клиенту много кода на c#. Клиент захотел еще себе веб-страничку маленькую сделать и я переиспользовал уже написанный код в веб-страничке, используя Blazor, который в wasm собирается. Бонусом мне не приходится выходить из привычной среды и не приходится использовать JS


    1. domix32
      23.11.2023 12:29
      +1

      По-умолчанию JS-код JITится под капотом во всё тот же wasm и делает всякие оптимизации а ля вызов типизированных вариантов функции. Но, с некоторой вероятностью может случиться деоптимизация и код будет интерпретироваться как обычно. Чтобы не ждать результатов JIT и иметь некоторый стабильный уровень производительности вы можете сразу сделать оптимизированную версию. Rust в этом случае просто как один из самых приятных вариантов написания кода - строгая типизация, алгебраические типы, одна из передовых инструментариев и вот это вот всё. Есть варианты писать на более специализированных AssemblyScript или Moonbit, например.

      С точки зрения Rust управление памятью остаётся каким и было изначально - была у вас статичная инициализация всего и вся - в wasm так и будет; были арены - будут и в wasm. GC в раст особо никому не нужен был, но если вы его сделаете, то и в васм он тоже будет, если повезёт. В остальном wasm ведёт примерно также как и нативный код.

      Интероперабельность с JS происходит через импорты и копирование строк просто потому, что wasm ничего не знает про API JS. Несмотря на это производительность местами получается более стабильной нежели на чистом JS. в работе есть некоторое количество вариантов типизированного ABI для wasm, что по идее сократит несколько накладные расходы, но они пока ещё в процессе.


  1. alex-open-plc
    23.11.2023 12:29

    Утверждается, что скорость выполнения близка к нативным приложениям... Где можно посмотреть сравнение, например с плюсами? По моему пониманию минимум в 2 раза медленнее, т.к. все равно нужно транслировать бинарный формат в инструкции процессора?


    1. pkirill
      23.11.2023 12:29
      +1

      Скорость очень хорошая

      все равно нужно транслировать бинарный формат в инструкции процессора

      Это, конечно, надо, беэ этого сложно. Но надо отдать должное что WASM именно так изначально проектировался чтобы эта трансляция была очень очень быстрой, в отличие от трансляции JS.

      И в трансляция идёт тоьлко 1 раз на загрузке модуля, а потом модуль исполняется уже продолжительное время и никакой трансляции там уже нет.


    1. easymikey Автор
      23.11.2023 12:29
      +1

      1. alex-open-plc
        23.11.2023 12:29

        PSPDFkit приводит "интересное" сравнение производительности JS и WASM...
        Цитирую:
        Примечательно, что в Edge и Safari из-за отсутствия некоторых важных оптимизаций тест WebAssembly выполнялся дольше, чем аналог на JavaScript. Производительность WebAssembly и JavaScript в Chrome отличается незначительно. Наибольшая разница в скорости выполнения тестов WebAssembly и JavaScript зафиксирована в Firefox. При тестировании выполнялись различные процедуры обработки PDF-файлов и замерялось как производительность непосредственного выполнения операций, так и суммарное время с учётом загрузки и компиляции псевдокода WASM.

        Т.е. сильно зависит от браузера, точнее его VM.

        Таки не так всё красиво с WASM...
        Про "нативную" скорость лучше помолчать.


        1. easymikey Автор
          23.11.2023 12:29

          Все зависит от ... :)

          Для JS поведение браузеров тоже может разнится в некторых специфических случая. Нативная скорость плучается не всегда. Поэтому и указано близкую к нативной. Безусловно есть свои особенности и у исполения WebAssembly.

          Тут идет речь скорее про то, что VM старается приблизить программу на нативном языке в исполняемый модуль WebAssembly. А не про то, что она всегда будет нативной и быстро исполнятся.

          Максимальная производительность WebAssembly равна максимальной производительности
          JavaScript, но написать оптимизированный по памяти код иногда проще на
          WebAssembly

          В статье есть важное дополнение. Я бы посоветовал вам почитать статью про использование WebAssembly и сравнение скоростей . :)


  1. mvv-rus
    23.11.2023 12:29
    +2

    Доступ к DOM и Browser API. WebAssembly напрямую не может обращаться к DOM или браузерному API. Все вызовы API внутри Wasm происходят через JavaScript (компиляторами добавляется специальная прослойка для исполнения JavaScript-кода).

    Это да, но зачем писать это в достоинствах? Вот, наоборот, доступ к DOM напрямую, избегая связанных с JS накладных расходов - это было бы достоинством. А так это выглядит примерно как лет 30 назад вылядела бы фраза про Windows: "Программы на Pascal и C не могут обращаться напрямую к WinAPI. Все вызовы WinAPI внутри Pascal/C присходят через среду выполнения Visual Basic (компиляторами добавляется специальная прослойка для исполнения кода для этой среды)".

    IMHO правильное наименование этому решению - компромис: между вохможностью доступа к DOM вообще и безопасностью/трудоемкостью реализции интерфейса доступа.

    PS А вообще я уже видел в жизни попытки реализовать работу в браузере программ на других языках, компилируемых в JavaVM bytecode (Java Applets) и .NET IL (Silverlight) - не то, чтобы они закончились совсем уж провалом, но, в целом, не взлетели. Но шуму вокруг было не меньше.


    1. easymikey Автор
      23.11.2023 12:29

      В достоинства я это добавил потому, что есть возможность обращаться к DOM и браузерному API. К примеру, если мы бы брали Worker’ы, то они не могут работать с DOM, а браузерное API у него доступно не все. Да с хаками, но можно это главный посыл был. А про связь с JS для общего понимания, чтобы читатели, не знавшие об этом сразу понимали текущую картину.

      IMHO правильное наименование этому решению - компромис: между вохможностью доступа к DOM вообще и безопасностью/трудоемкостью реализции интерфейса доступа.

      Да, безопастность одно из главных причин. Я думаю, что именно поэтому сейчас есть возможность доступа только через JS. Плюс для запуска все равно нужен JS. Поэтому без накладных расходов никак.

      PS А вообще я уже видел в жизни попытки реализовать работу в браузере программ на других языках, компилируемых в JavaVM bytecode (Java Applets) и .NET IL (Silverlight) - не то, чтобы они закончились совсем уж провалом, но, в целом, не взлетели. Но шуму вокруг было не меньше.

      Очень интересное замечание. Есть много причин почему они не взлетели и почему может не взлететь WebAssembly. Вы будто правило расшифровали цитату, которую я оставил в конце:)


      1. pkirill
        23.11.2023 12:29

        почему может не взлететь WebAssembly

        С моей точки зрения WebAssembly уже взлетел и его отлично применяют. А Java Applets и Silverlight не взлетели из за огромного числа дыр в безопастности, которые в WebAssembly изначально при проектировании были тщательно проработаны.

        Ну а на замену Java Applets пришли отличные AOT компиляторы компилирующие приложения в JS: https://habr.com/en/articles/240999/ (а с тех пор это всё очень сильно развивается, почти 10 лет прошло)

        Еще есть отличная статья про WebAssembly от автора TeaVM: https://habr.com/en/articles/757182/


        1. easymikey Автор
          23.11.2023 12:29

          Я в этой технологии новичок и не могу знать все нюансы на данной момент. На как и в любой технологии все зависит от многого количества факторов. Да, Wasm сейчас развивается в семимильными шагами учитывая, что он разрабатывается как стандарт (перичеслять все плюсы еще раз не будут). Но тут вопрос скорее в дальнейшем развитии и стратегии. Если они будут добавлятья новые крутые фичи, возможно в ближайшее время не нужно будет использовать JS для запуска или писать кучу обвязок. Тогда его популярность значительно вырастет. Но нельзя не отметить что на данном этаме она достачно высока (список поддерживаемых языков достачно большой).

          На счет статьи спасибо:)


          1. pkirill
            23.11.2023 12:29

            Много обвязок можно и не писать, если есть 5 мин вот посмотрите на мой пример. По моему там их почти нет :)


            1. easymikey Автор
              23.11.2023 12:29

              Не открывается(


              1. pkirill
                23.11.2023 12:29

                Извините, телефон чудит

                https://github.com/SuduIDE/sudu-editor/tree/master/wasm-test/src/main/java/org/sudu/experiments/demo/wasm


                1. easymikey Автор
                  23.11.2023 12:29

                  Обвязок вроде немного. Но кажется, что есть скрытые хитрости в org.sudu.experiments:)

                  Где найти все что лежит в org.sudu.experiments ?


                  1. pkirill
                    23.11.2023 12:29

                    Поверь, никаких хитростей, всё в репе, всё там, всё open source


                    1. easymikey Автор
                      23.11.2023 12:29

                      Я правильно понимаю, что все это обвязка для org.teavm ?


                      1. pkirill
                        23.11.2023 12:29

                        Ну да это же java код для веба. Но на JS этот самый пример не будет больше, скорее даже сильно меньше


                      1. easymikey Автор
                        23.11.2023 12:29

                        Я не очень разбираюсь в синтаксисе Java. Поэтому исходники этой либы изучать не полез. Подскажи, во что он перегоняет Java? В строки или в более сложные структуры?


                      1. pkirill
                        23.11.2023 12:29

                        В JS. Так же как и TS compiler перегоняет TS в JS


                      1. easymikey Автор
                        23.11.2023 12:29

                        Понял, ну или как babel????


                      1. easymikey Автор
                        23.11.2023 12:29

                        То есть по сути вся обвязка превращается в JS. А есть пример архитектуры? Очень интересно как связывается с модулем на wasm конкретно в Java


                      1. pkirill
                        23.11.2023 12:29

                        Вот эта репа - самый отличный пример. Там очень понятный readme как собрать. Ещё немного примеров в репе самого teavm. Посмотрите мой доклад, там с пояснениями и картинками

                        https://youtu.be/QHbYXDobo3k?si=81ZNJleJ4nNuGI1u


                      1. easymikey Автор
                        23.11.2023 12:29

                        Спасибо, сохранил для просмотра:)


          1. domix32
            23.11.2023 12:29
            +1

            Но тут вопрос скорее в дальнейшем развитии и стратегии.

            Прям вот сейчас там около десятка пропозалов - гц, интерфейс типы, мультитрединг и синхронизация, SIMD не так давно появился в основных браузерах без флагов, WASI описывается/стандартизуется.


            1. easymikey Автор
              23.11.2023 12:29

              Можешь ссылочку закинут?)


              1. domix32
                23.11.2023 12:29

                Есть вот такой FAQ в котором есть нужные ссылки по теме. Они маленько устарели и актуальные где смотреть не подскажу.

                Есть список пропозалов.

                P.S. Оказывается в хроме уже появился GC.


                1. easymikey Автор
                  23.11.2023 12:29

                  Сейчас он широко распространяется в отрасли и поддерживается многими языками, в том числе и со сборкой мусора (Garbage Collection). Совсем недавно поддержка стандарта появилась в Google Chrome и Mozilla Firefox.

                  Да это я уже указал в статье :)


                1. easymikey Автор
                  23.11.2023 12:29

                  Остальные поизучаю, спасибо)


        1. mvv-rus
          23.11.2023 12:29
          +1

          А Java Applets и Silverlight не взлетели из за огромного числа дыр в безопастности, которые в WebAssembly изначально при проектировании были тщательно проработаны.

          Утверждение мне кажется сомнительным, поскольку безопасность (сама по себе, вне контекста рекламной раскрутки) ценится на рынке невысоко ("безопасность народу не нужна").
          Скорее, IMHO неуспех предыдущих попыток (куда, кстати, можно ещё добавить и Flash) был вызван тем, что они не слишком много добавляли ценности сайтам интернета, создаваяпри этом определенные неудобства, связанными с необходимостью дополнительной установки компонентов.
          По крайней мере, этой добавляемой ценности не хватало, чтобы преодолеть сопротивление конкурентов того производителя, который их продвигал.

          А вот вокруг WebAssembly сложился консенсус основных поставщиков технологий, поэтому его ожидаемая судьба лучше, да.
          Но все-таки IMHO прямой доступ к отображаемому (DOM) позволил бы предоставлять ещё больше ценности.


          1. pkirill
            23.11.2023 12:29

            С моей колокольни было бы круто иметь доступ к WebGL например. И там по сути даже не нужны JSObjects, там по факту везде int типы (см Angle)

            Ну а доступ к дому очень сложен, там есть доступ к JS объектам, а их лучше держать подальше от wasm

            На а количество дыр и лазеек в Java Applets было огромное. Сам работал в Sun Microsystems :)

            Хорошо что это изгнали из веба