Yew es un análogo de React and Elm, escrito completamente en Rust y compilado en un WebAssembly honesto. En el artículo, Denis Kolodin, desarrollador de Yew, habla sobre cómo puede crear un marco sin un recolector de basura, garantizar de manera efectiva inmutable, sin la necesidad de copiar el estado debido a las reglas de propiedad de los datos de Rust, y cuáles son las características al traducir Rust a WebAssembly.
La publicación se preparó sobre la base del informe de Denis en la conferencia HolyJS 2018 Piter . Debajo del corte: transcripción de video y texto del informe.Denis Kolodin trabaja para Bitfury Group, una compañía que desarrolla varias soluciones de blockchain. Durante más de dos años, ha estado codificando en Rust, un lenguaje de programación de Mozilla Research. Durante este tiempo, Denis logró estudiar a fondo este lenguaje y usarlo para desarrollar varias aplicaciones del sistema, un backend. Ahora, en relación con el advenimiento del estándar WebAssembly, comencé a mirar hacia el front-end.Agenda
Hoy aprenderemos sobre qué es Yew (el nombre del marco se lee igual que la palabra inglesa "you" - you; "yew" es un árbol de tejo traducido del inglés).
Hablemos un poco sobre los aspectos arquitectónicos, sobre las ideas sobre las que se construye el marco, sobre las posibilidades que se integran en él, así como sobre las características que Rust también nos brinda en comparación con otros lenguajes.
Al final, le mostraré cómo comenzar a usar Yew y WebAssembly hoy.
¿Qué es el tejo?
En primer lugar, esto es WebAssembly, es decir Código de bytes ejecutable que funciona en los navegadores. Es necesario para ejecutar algoritmos complejos en el lado del usuario, por ejemplo, criptografía, codificación / decodificación. Es más fácil implementar esto en los lenguajes del sistema que atornillar muletas.
WebAssembly es un estándar que está claramente descrito, comprendido y respaldado por todos los navegadores modernos. Le permite usar varios lenguajes de programación. Y esto es interesante principalmente porque puede reutilizar el código creado por la comunidad en otros idiomas.
Si lo desea, puede escribir completamente una aplicación en WebAssembly, y Yew le permite hacer esto, pero es importante no olvidar que incluso en este caso, JavaScript permanece en el navegador. Es necesario preparar WebAssembly: tome el módulo (WASM), agréguele el entorno y ejecútelo. Es decir JavaScript es indispensable. Por lo tanto, vale la pena considerar WebAssembly como una extensión en lugar de una alternativa revolucionaria a JS.
Cómo se ve el desarrollo

Tienes una fuente, hay un compilador. Traduces todo esto a un formato binario y lo ejecutas en un navegador. Si el navegador es antiguo, sin soporte de WebAssembly, se requiere emscripten. Esto es, más o menos, un emulador de WebAssembly para un navegador.
Tejo - listo para usar marco de wasm
Pasemos a Yew. Desarrollé este marco a fines del año pasado. Luego escribí algún tipo de aplicación de criptomonedas en Elm y me enfrenté al hecho de que debido a restricciones de idioma no puedo crear una estructura recursiva. Y en ese momento pensé: en Rust, mi problema se resolvería muy fácilmente. Y dado que el 99% del tiempo escribo en Rust y me encanta este lenguaje precisamente por sus características, decidí experimentar: compilar la aplicación con la misma función de actualización en Rust.
El primer boceto me llevó varias horas, tuve que descubrir cómo compilar WebAssembly. Lo lancé y me di cuenta de que en solo unas pocas horas había establecido el núcleo, que es muy fácil de desarrollar. Me llevó solo unos días llevar todo al motor de marco mínimo.
Lo publiqué en código abierto, pero no esperaba que fuera popular. Sin embargo, hoy ha reunido más de 4 mil estrellas en GitHub. Puedes ver el proyecto
aquí . Hay muchos ejemplos
El marco está completamente escrito en Rust. Yew admite la compilación directa a WebAssembly (objetivo wasm32-unknown-unknown) sin emscripten. Si es necesario, puede trabajar con emscripten.
Arquitectura
Ahora unas pocas palabras sobre cómo el marco difiere de los enfoques tradicionales que existen en el mundo de JavaScript.
Primero, te mostraré las restricciones de idioma que encontré en Elm. Tome el caso cuando hay un modelo y hay un mensaje que le permite transformar este modelo.
type alias Model = { value : Int } type Msg = Increment | Decrement
case msg of Increment -> { value = model.value + 1 } Decrement -> { value = model.value - 1 }
En Elm, simplemente creamos un nuevo modelo y lo mostramos en la pantalla. La versión anterior del modelo permanece sin cambios. ¿Por qué me estoy centrando en esto? Porque en Yew, el modelo es mutable, y esta es una de las preguntas más comunes. A continuación, explicaré por qué se hace esto.
Inicialmente, seguí el camino clásico cuando se recreó el modelo. Pero a medida que se desarrolló el marco, vi que no tiene sentido almacenar la versión anterior del modelo. Rust le permite rastrear la vida útil de todos los datos, ya sean modificados o no. Y así puedo cambiar el modelo de manera segura, sabiendo que Rust controla la ausencia de conflicto.
struct Model { value: i64, } enum Msg { Increment, Decrement, }
match msg { Msg::Increment => { self.value += 1; } Msg::Decrement => { self.value -= 1; } }
Este es el primer momento. El segundo punto: ¿por qué necesitamos la versión anterior del modelo? En el mismo olmo, apenas hay un problema de algún tipo de acceso competitivo. El modelo anterior solo es necesario para entender cuándo renderizar. La conciencia de este momento me permitió deshacerme por completo de la inmutable y no mantener la versión anterior.

Mire la opción cuando tengamos la función de
update
y dos campos:
value
y
name
. Hay un valor que se guarda cuando ingresamos datos en el campo de
input
. El modelo está cambiando.

Es importante que el
value
no
value
involucrado en la representación. Y así podemos cambiarlo tanto como queramos. Pero no necesitamos influir en el árbol DOM y no necesitamos iniciar estos cambios.
Esto me llevó a la idea de que solo el desarrollador puede conocer el momento correcto cuando realmente se necesita iniciar el renderizado. Para iniciar, comencé a usar la bandera, solo un valor booleano,
ShouldRender
, que indica que el modelo ha cambiado y tenemos que comenzar a renderizar. Al mismo tiempo, no hay gastos generales para las comparaciones constantes, no hay consumo de memoria: las aplicaciones escritas en Yew son más efectivas.
En el ejemplo anterior, no había ninguna asignación de memoria, excepto el mensaje que se generó y envió. El modelo retuvo su estado, y esto se reflejó en el renderizado solo con la ayuda de una bandera.
Las posibilidades
Escribir un marco que funcione en WebAssembly no es tarea fácil. Tenemos JavaScript, pero debería crear algún tipo de entorno con el que deba interactuar, y esto es una gran cantidad de trabajo. La versión inicial de estos paquetes se parecía a esto:

Tomé una demostración de otro proyecto. Hay muchos proyectos que van por este camino, pero rápidamente lleva a un callejón sin salida. Después de todo, el framework es un desarrollo bastante grande y tienes que escribir mucho código de acoplamiento. Comencé a usar bibliotecas en Rust que se llaman cajas, en particular, la
Stdweb
.
JS integrado
Con la ayuda de las macros Rust, puede expandir el idioma: podemos incrustar fragmentos de JavaScript en el código Rust, esta es una característica muy útil del lenguaje.
let handle = js! { var callback = @{callback}; var action = function() { callback(); }; var delay = @{ms}; return { interval_id: setInterval(action, delay), callback: callback, }; };
El uso de macros y Stdweb me permitió escribir rápida y eficientemente todos los enlaces necesarios.
Plantillas Jsx
Al principio, seguí el camino de Elm y comencé a usar plantillas implementadas usando código.
fn view(&self) -> Html<Context, Self> { nav("nav", ("menu"), vec![ button("button", (), ("onclick", || Msg::Clicked)), tag("section", ("ontop"), vec![ p("My text...") ]) ]) }
Nunca he sido partidario de React. Pero cuando comencé a escribir mi marco, me di cuenta de que JSX en React es algo genial. Aquí hay una presentación muy conveniente de plantillas de código.
Como resultado, tomé una macro en Rust e implementé directamente dentro de Rust la capacidad de escribir un marcado HTML que genera inmediatamente elementos de árbol virtuales.
impl Renderable<Context, Model> for Model { fn view(&self) -> Html<Context, Self> { html! { <div> <nav class="menu",> <button onclick=|_| Msg::Increment,>{ "Increment" }</button> <button onclick=|_| Msg::Decrement,>{ "Decrement" }</button> </nav> <p>{ self.value }</p> <p>{ Local::now() }</p> </div> } } }
Podemos decir que las plantillas tipo JSX son plantillas de código puro, pero con esteroides. Se presentan en un formato conveniente. También tenga en cuenta que aquí inserto una expresión de Rust directamente en el botón (la expresión de Rust se puede insertar dentro de estas plantillas). Esto le permite integrarse muy de cerca.
Componentes bastante estructurados
Luego comencé a desarrollar plantillas y me di cuenta de la posibilidad de usar componentes. Este es el primer problema que se ha realizado en el repositorio. He implementado componentes que se pueden usar en el código de plantilla. Simplemente declara una estructura honesta en Rust y escribe algunas propiedades para ella. Y estas propiedades se pueden configurar directamente desde la plantilla.

Una vez más, noto lo importante de que estas plantillas son código Rust sinceramente generado. Por lo tanto, cualquier error aquí será notado por el compilador. Es decir no puede equivocarse, como suele ser el caso en el desarrollo de JavaScript.
Áreas tipificadas
Otra característica interesante es que cuando un componente se coloca dentro de otro componente, puede ver el tipo de mensaje del padre.

El compilador une rígidamente estos tipos y no le dará la oportunidad de cometer un error. Al procesar eventos, los mensajes que el componente espera o puede enviar deben coincidir completamente con el padre.
Otras características
Transferí una implementación de Rust directamente al marco que le permite usar convenientemente varios formatos de serialización / deserialización (proporcionándole envoltorios adicionales). A continuación se muestra un ejemplo: vamos al almacenamiento local y, restaurando los datos, especificamos un determinado contenedor, lo que esperamos aquí es json.
Msg::Store => { context.local_storage.store(KEY, Json(&model.clients)); } Msg::Restore => { if let Json(Ok(clients)) = context.local_storage.restore(KEY) { model.clients = clients; } }
Puede tener cualquier formato, incluso binario. En consecuencia, la serialización y la deserialización se vuelven transparentes y convenientes.
La idea de otra oportunidad que implementé provino de los usuarios del marco. Pidieron hacer fragmentos. Y aquí me encontré con algo interesante. Al ver en JavaScript la capacidad de insertar fragmentos en el árbol DOM, primero decidí que sería muy fácil implementar dicha función en mi marco. Pero probé esta opción, y resultó que no funciona. Tenía que resolverlo, caminar sobre este árbol, ver qué había cambiado allí, etc.
El marco Yew usa un árbol DOM virtual, todo existe inicialmente en él. De hecho, cuando hay algunos cambios en la plantilla, se convierten en parches que ya cambian el árbol DOM representado.
html! { <> <tr><td>{ "Row" }</td></tr> <tr><td>{ "Row" }</td></tr> <tr><td>{ "Row" }</td></tr> </> }
Beneficios adicionales
Rust proporciona muchas más funciones poderosas diferentes, solo hablaré sobre las más importantes.
Servicios: interacción con el mundo exterior.
La primera oportunidad de la que quiero hablar es de servicios. Puede describir la funcionalidad necesaria en forma de algún servicio, publicarla como una caja y reutilizarla.
En Rust, la capacidad de crear bibliotecas, su integración, acoplamiento y pegado está muy bien implementada. De hecho, puede crear varias API para interactuar con su servicio, incluidas las de JavaScript. Al mismo tiempo, el marco puede interactuar con el mundo exterior, a pesar de que funciona dentro del tiempo de ejecución de WebAssembly.
Ejemplos de servicios:
- TimeOutService;
- IntervalService;
- FetchService;
- WebSocketService;
- Servicios personalizados ...
Servicios de óxido y cajas:
crates.io .
Contexto: requisitos estatales
Otra cosa que implementé en el marco no es completamente tradicional, es el contexto. React tiene una API de contexto, pero usé Context en un sentido diferente. El marco Yew consta de los componentes que usted hace, y el Contexto es un estado global. Los componentes pueden no tener en cuenta este estado global, pero pueden hacer algunas demandas, de modo que la entidad global cumple con algunos criterios.
Digamos que nuestro componente abstracto requiere la capacidad de cargar algo en S3.

Se puede ver a continuación que usa esta carga, es decir, envía datos a S3. Tal componente se puede colocar en forma de estante. El usuario que descargue este componente y lo agregue dentro de la plantilla a su aplicación encontrará un error: el compilador le preguntará dónde está el soporte S3. El usuario tendrá que implementar este soporte. Después de eso, el componente automáticamente comienza a vivir una vida plena.
¿Dónde se necesita? Imagínese: está creando un componente con criptografía inteligente. Tiene requisitos para que el contexto que lo rodea le permita iniciar sesión en algún lugar. Todo lo que necesita hacer es agregar un formulario de autorización en la plantilla y en su contexto implementar la conexión con su servicio. Es decir Será literalmente tres líneas de código. Después de eso, el componente comienza a funcionar.
Imagine que tenemos docenas de componentes diferentes. Y todos tienen el mismo requisito. Esto le permite implementar algún tipo de funcionalidad una vez para revivir todos los componentes y extraer los datos que se necesitan. Justo fuera de contexto. Y el compilador no le permitirá cometer un error: si no tiene una interfaz que requiera un componente, nada funcionará.
Por lo tanto, puede crear fácilmente botones muy delicados que solicitarán algunas API u otras características. Gracias a Rust y al sistema de estas interfaces (se denominan rasgos en Rust), es posible declarar los requisitos de los componentes.
El compilador no te permitirá cometer un error.
Imagine que estamos creando un componente con algunas propiedades, una de las cuales es la capacidad de devolver la llamada. Y, por ejemplo, configuramos la propiedad y omitimos una letra en su nombre.

Intentando compilar, Rust responde rápidamente. Él dice que nos equivocamos y que no existe tal propiedad:

Como puede ver, Rust usa directamente esta plantilla y puede representar todos los errores dentro de la macro. Él le dice cómo debería llamarse realmente la propiedad. Si pasó el compilador, no tendrá errores tontos de tiempo de ejecución como errores tipográficos.
Ahora imagine que tenemos un botón que le pide a nuestro contexto global que pueda conectarse a S3. Y cree un contexto que no implemente el soporte S3. Veamos que pasa.

El compilador informa que hemos insertado un botón, pero esta interfaz no está implementada para el contexto.

Solo queda ir al editor, agregar un enlace a Amazon en el contexto, y todo comenzará. Puede crear servicios listos para usar con algún tipo de API, luego simplemente agregar al contexto, sustituir un enlace y el componente cobra vida de inmediato. Esto le permite hacer cosas muy interesantes: agregar componentes, crear un contexto, rellenarlo con servicios. Y todo esto funciona de forma completamente automática; se necesitan esfuerzos mínimos para unir todo.
¿Cómo comenzar a usar Yew?
¿Dónde comenzar si desea intentar compilar una aplicación WebAssembly? ¿Y cómo se puede hacer esto usando el marco Yew?
Compilación de óxido a desperdicio
Primero, necesitas instalar el compilador. Hay una herramienta de oxidación para esto:
curl https://sh.rustup.rs -sSf | sh
Además, es posible que necesite emscripten. ¿Para qué puede ser útil? La mayoría de las bibliotecas escritas para lenguajes de programación del sistema, especialmente para Rust (originalmente un sistema), están desarrolladas para Linux, Windows y otros sistemas operativos completos. Obviamente, el navegador no tiene muchas características.
Por ejemplo, la generación de números aleatorios en un navegador no se realiza de la misma manera que en Linux. emscripten es útil si desea utilizar bibliotecas que requieren una API del sistema.
Las bibliotecas y toda la infraestructura están cambiando silenciosamente a WebAssembly honesto, y ya no se requiere emscripten (usa capacidades basadas en JavaScript para generar números aleatorios y otras cosas), pero si necesita construir algo que no es compatible con el navegador, no puede hacerlo sin emscripten .
También recomiendo usar cargo-web:
cargo install cargo-web
Es posible compilar WebAssembly sin utilidades adicionales. Pero Cargo-web es una herramienta genial que proporciona varias cosas que son útiles para los desarrolladores de JavaScript. En particular, supervisará los archivos: si realiza algún cambio, comenzará a compilarse inmediatamente (el compilador no proporciona tales funciones). En este caso, Cargo-web le permitirá acelerar el desarrollo. Hay diferentes sistemas de construcción para Rust, pero la carga es el 99.9% de todos los proyectos.
Se crea un nuevo proyecto de la siguiente manera:
cargo new --bin my-project
[package]
name = "my-project"
version = "0.1.0"
[dependencies]
yew = "0.3.0"
Entonces solo comienza el proyecto:
cargo web start --target wasm32-unknown-unknown
Di un ejemplo de WebAssembly honesto. Si necesita compilar bajo emscripten (el compilador de óxido puede conectar emscripten), puede insertar la palabra
emscripten
en el último elemento
unknown
, lo que le permite usar más cajas. No olvide que emscripten es un kit adicional bastante grande para su archivo. Por lo tanto, es mejor escribir código honesto de WebAssembly.
Restricciones existentes
Cualquiera que tenga experiencia en codificación en lenguajes de programación del sistema puede sentirse frustrado por las limitaciones existentes en el marco. No todas las bibliotecas se pueden usar en WebAssembly. Por ejemplo, en un entorno JavaScript no hay hilos. WebAssembly en principio no declara esto y, por supuesto, puede usarlo en un entorno multiproceso (esta es una pregunta abierta), pero JavaScript sigue siendo un entorno de subproceso único. Sí, hay trabajadores, pero esto es aislamiento, por lo que no habrá flujos allí.
Parece que puedes vivir sin flujos. Pero si desea utilizar bibliotecas basadas en subprocesos, por ejemplo, desea agregar algún tipo de tiempo de ejecución, esto podría no despegar.
Además, no hay una API del sistema, excepto la que transferirá de JavaScript a WebAssembly. Por lo tanto, muchas bibliotecas no serán portadas. No puede escribir y leer archivos directamente, no se pueden abrir sockets y no puede escribir en la red. Si desea crear un socket web, por ejemplo, debe arrastrarlo desde JavaScript.
Otro inconveniente es que existe el depurador WASM, pero nadie lo ha visto. Todavía está en un estado tan crudo que es poco probable que sea útil para usted. Entonces depurar WebAssembly es una pregunta difícil.
Cuando se usa Rust, casi todos los problemas de tiempo de ejecución estarán asociados con errores en la lógica empresarial, serán fáciles de solucionar. Pero muy raramente aparecen errores de bajo nivel, por ejemplo, una de las bibliotecas hace el acoplamiento incorrecto, y esta ya es una pregunta difícil. Por ejemplo, en este momento hay un problema: si compilo el marco con emscripten y hay una celda de memoria variable, cuya posesión se quita, se regala, emscripten se está desmoronando en algún lugar en el medio (y ni siquiera estoy seguro de si es emscripten). Sepa, si se encuentra con un problema en algún lugar del middleware a un nivel bajo, solucionarlo será difícil en este momento.
El futuro del marco
¿Cómo se desarrollará Yew más? Veo su propósito principal en la creación de componentes monolíticos. Tendrá un archivo compilado de WebAssembly y simplemente péguelo en la aplicación. Por ejemplo, puede proporcionar capacidades criptográficas, renderización o edición.
Integración JS
La integración con JavaScript se fortalecerá. JavaScript ha escrito una gran cantidad de geniales bibliotecas que son fáciles de usar. Y hay ejemplos en el repositorio donde muestro cómo puede usar la biblioteca JavaScript existente directamente desde el marco Yew.
CSS escrito
Como se usa Rust, es obvio que puede agregar CSS tipeado que se puede generar con la misma macro que en el ejemplo de un motor de plantillas similar a JSX. En este caso, el compilador verificará, por ejemplo, si ha asignado algún otro atributo en lugar de color. Esto te ahorrará toneladas de tiempo.
Componentes listos
También busco crear componentes listos para usar. En el marco, puede crear grietas que proporcionarán, por ejemplo, un conjunto de algunos botones o elementos que se conectarán como una biblioteca, se agregarán a las plantillas y se usarán.
Mejora del rendimiento en casos privados.
El rendimiento es un tema muy delicado y complejo. ¿WebAssembly es más rápido que JavaScript? No tengo pruebas que confirmen una respuesta positiva o negativa. Parece que y de acuerdo con algunas pruebas muy simples que realicé, WebAssembly es muy rápido. Y tengo plena confianza en que su rendimiento será mayor que el de JavaScript, solo porque es un código de bytes de bajo nivel donde no se requiere asignación de memoria y hay muchos otros momentos que requieren recursos.
Más colaboradores
Me gustaría atraer a más contribuyentes. Las puertas para participar en el marco siempre están abiertas. Todos los que quieran actualizar algo, comprender el núcleo y transformar las herramientas con las que trabajan una gran cantidad de desarrolladores pueden conectarse fácilmente y ofrecer sus propias ediciones.
Al proyecto ya han asistido muchos contribuyentes. Pero no hay contribuyentes principales en este momento, porque para esto necesita comprender el vector de desarrollo del marco, pero aún no se ha formulado claramente. Pero hay una columna vertebral, muchachos muy versados en Yew: unas 30 personas. Si también desea agregar algo al marco, siempre envíe una solicitud de extracción.
La documentación
Un punto obligatorio en mis planes es la creación de una gran cantidad de documentación sobre cómo escribir aplicaciones en Yew. Obviamente, el enfoque de desarrollo en este caso es diferente de lo que vimos en React and Elm.
A veces, los chicos me muestran casos interesantes de cómo usar el marco. Aún así, crear un marco no es lo mismo que escribir profesionalmente en él. Todavía se están formando prácticas para usar el marco.
Pruébelo, instale Rust, amplíe sus capacidades como desarrollador. Dominar WebAssembly será útil para cada uno de nosotros, porque la creación de aplicaciones muy complejas es el momento que hemos estado esperando durante mucho tiempo. En otras palabras, WebAssembly no se trata solo de un navegador web, sino que generalmente es un tiempo de ejecución que definitivamente se está desarrollando y se desarrollará aún más activamente.
Si le gustó el informe, preste atención: del 24 al 25 de noviembre, se llevará a cabo un nuevo HolyJS en Moscú, y también habrá muchas cosas interesantes allí. — , ( ).