Estrutura de front-end do Yew - Rust & WebAssembly

Yew é um análogo de React and Elm, escrito inteiramente em Rust e compilado em um WebAssembly honesto. No artigo, Denis Kolodin, desenvolvedor do Yew, fala sobre como você pode criar uma estrutura sem um coletor de lixo, garantir efetivamente a imutabilidade, sem a necessidade de copiar o estado devido às regras de propriedade dos dados do Rust e quais são os recursos ao converter o Rust para o WebAssembly.



A publicação foi preparada com base no relatório de Denis na conferência HolyJS 2018 Piter . Sob o recorte - transcrição de vídeo e texto do relatório.


Denis Kolodin trabalha para o Bitfury Group, uma empresa que desenvolve várias soluções de blockchain. Há mais de dois anos, ele codifica o Rust, uma linguagem de programação da Mozilla Research. Durante esse período, Denis conseguiu estudar completamente essa linguagem e usá-la para desenvolver vários aplicativos de sistema, um back-end. Agora, em conexão com o advento do padrão WebAssembly, comecei a olhar para o front-end.

Agenda


Hoje vamos aprender sobre o que é Yew (o nome da estrutura lê o mesmo que a palavra em inglês "você" - você; "yew" é um teixo traduzido do inglês).

Vamos falar um pouco sobre aspectos arquitetônicos, sobre quais idéias a estrutura é construída, sobre as possibilidades incorporadas nela, bem como sobre os recursos que o Rust também nos oferece em comparação com outros idiomas.

No final, mostrarei como começar a usar o Yew e o WebAssembly hoje.

O que é o teixo?


Antes de tudo, este é o WebAssembly, ou seja, bytecode executável que funciona em navegadores. É necessário para executar algoritmos complexos no lado do usuário, por exemplo, criptografia, codificação / decodificação. É mais fácil implementar isso nas linguagens do sistema do que aparafusar muletas.

O WebAssembly é um padrão claramente descrito, compreendido e suportado por todos os navegadores modernos. Permite usar várias linguagens de programação. E isso é interessante principalmente porque você pode reutilizar o código criado pela comunidade em outros idiomas.

Se desejar, você pode escrever completamente um aplicativo no WebAssembly, e o Yew pode fazer isso, mas é importante não esquecer que, mesmo nesse caso, o JavaScript permanece no navegador. É necessário preparar o WebAssembly - pegue o módulo (WASM), inclua o ambiente e execute-o. I.e. JavaScript é indispensável. Portanto, vale a pena considerar o WebAssembly como uma extensão e não uma alternativa revolucionária ao JS.

Como é o desenvolvimento




Você tem uma fonte, há um compilador. Você traduz tudo isso em um formato binário e o executa em um navegador. Se o navegador for antigo, sem o suporte do WebAssembly, o emscripten será necessário. Este é, aproximadamente, um emulador do WebAssembly para um navegador.

Yew - pronto para usar framework wasm


Vamos para o Yew. Eu desenvolvi essa estrutura no final do ano passado. Então, escrevi algum tipo de aplicativo de criptomoeda no Elm e enfrentei o fato de que, devido a restrições de idioma, não posso criar uma estrutura recursiva. E naquele momento pensei: em Rust, meu problema seria resolvido com muita facilidade. E como em 99% do tempo eu escrevo no Rust e adoro essa linguagem precisamente por seus recursos, decidi experimentar - compilar o aplicativo com a mesma função de atualização no Rust.

O primeiro esboço levou várias horas, eu tive que descobrir como compilar o WebAssembly. Eu o lancei e percebi que em apenas algumas horas havia estabelecido o núcleo, o que é muito fácil de desenvolver. Levei apenas alguns dias para trazer tudo para o mecanismo de estrutura mínimo.

Publiquei em código aberto, mas não esperava que fosse popular. No entanto, hoje ele colecionou mais de 4 mil estrelas no GitHub. Você pode ver o projeto aqui . Existem muitos exemplos.

A estrutura está completamente escrita em Rust. O Yew suporta compilação diretamente no WebAssembly (destino wasm32-unknown-unknown) sem emscripten. Se necessário, você pode trabalhar com o emscripten.

Arquitetura


Agora, algumas palavras sobre como a estrutura difere das abordagens tradicionais existentes no mundo JavaScript.

Primeiro, mostrarei quais restrições de idioma encontrei no Elm. Tome o caso quando houver um modelo e houver uma mensagem que permita transformar esse modelo.

type alias Model =    { value : Int    }  type Msg    = Increment    | Decrement 

 case msg of   Increment ->     { value = model.value + 1 }   Decrement ->     { value = model.value - 1 } 

No Elm, simplesmente criamos um novo modelo e o exibimos na tela. A versão anterior do modelo permanece inalterada. Por que estou focando nisso? Porque no Yew, o modelo é mutável, e essa é uma das perguntas mais comuns. A seguir, explicarei por que isso é feito.

Inicialmente, segui o caminho clássico quando o modelo foi recriado. Mas, à medida que a estrutura se desenvolveu, vi que não faz sentido armazenar a versão anterior do modelo. O Rust permite rastrear a vida útil de todos os dados, modificados ou não. E assim posso mudar o modelo com segurança, sabendo que Rust controla a ausência de conflito.

 struct Model {   value: i64, } enum Msg {   Increment,   Decrement, } 

 match msg {   Msg::Increment => {       self.value += 1;   }   Msg::Decrement => {       self.value -= 1;   } } 

Este é o primeiro momento. O segundo ponto: por que precisamos da versão antiga do modelo? No mesmo olmo, dificilmente existe um problema de algum tipo de acesso competitivo. O modelo antigo é necessário apenas para entender quando renderizar. A consciência desse momento me permitiu me livrar completamente do imutável e não manter a versão antiga.



Veja a opção quando tivermos a função de update e dois campos - value e name . Há um valor que é salvo quando inserimos dados no campo de input . O modelo está mudando.



É importante que o value não value envolvido na renderização. E assim podemos mudar o quanto quisermos. Mas não precisamos influenciar a árvore DOM e não precisamos iniciar essas alterações.

Isso me levou à ideia de que apenas o desenvolvedor pode saber o momento certo em que a renderização realmente precisa ser iniciada. Para iniciar, comecei a usar o sinalizador - apenas um valor booleano - ShouldRender , que sinaliza que o modelo mudou e precisamos iniciar a renderização. Ao mesmo tempo, não há sobrecarga para comparações constantes, não há consumo de memória - os aplicativos escritos em Yew são mais eficazes.

No exemplo acima, não havia alocação de memória, exceto a mensagem que foi gerada e enviada. O modelo manteve seu estado, e isso foi refletido na renderização apenas com a ajuda de uma bandeira.

As possibilidades


Escrever uma estrutura que funcione no WebAssembly não é uma tarefa fácil. Temos JavaScript, mas ele deve criar algum tipo de ambiente que precisa ser interagido, e isso é uma enorme quantidade de trabalho. A versão inicial desses pacotes tinha algo parecido com isto:



Tirei uma demo de outro projeto. Existem muitos projetos que seguem esse caminho, mas rapidamente levam a um beco sem saída. Afinal, a estrutura é um desenvolvimento bastante grande e você precisa escrever muito código de encaixe. Comecei a usar bibliotecas em Rust, chamadas caixas, em particular a Stdweb .

JS Integrado


Com a ajuda das macros do Rust, você pode expandir o idioma - podemos incorporar partes de JavaScript no código do Rust, esse é um recurso muito útil do idioma.

 let handle = js! {   var callback = @{callback};   var action = function() {       callback();   };   var delay = @{ms};   return {       interval_id: setInterval(action, delay),       callback: callback,   }; }; 

O uso de macros e o Stdweb me permitiu escrever com rapidez e eficiência todos os links necessários.

Modelos jsx


No começo, eu segui o caminho Elm e comecei a usar modelos implementados 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...")       ])   ]) } 

Eu nunca fui um defensor do React. Mas quando comecei a escrever minha estrutura, percebi que o JSX no React é uma coisa muito legal. Aqui está uma apresentação muito conveniente de modelos de código.

Como resultado, peguei uma macro no Rust e implementei diretamente dentro do Rust a capacidade de escrever a marcação HTML que gera imediatamente elementos da árvore virtual.

 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 dizer que modelos do tipo JSX são modelos de código puro, mas com esteróides. Eles são apresentados em um formato conveniente. Observe também que aqui insiro uma expressão Rust diretamente no botão (a expressão Rust pode ser inserida dentro desses modelos). Isso permite que você integre muito de perto.

Componentes bastante estruturados


Então comecei a desenvolver modelos e percebi a possibilidade de usar componentes. Este é o primeiro problema que foi feito no repositório. Eu implementei componentes que podem ser usados ​​no código do modelo. Você simplesmente declara uma estrutura honesta no Rust e escreve algumas propriedades para ele. E essas propriedades podem ser definidas diretamente no modelo.



Mais uma vez, noto o importante: esses modelos são honestamente gerados pelo código Rust. Portanto, qualquer erro aqui será percebido pelo compilador. I.e. você não pode estar enganado, como costuma acontecer no desenvolvimento do JavaScript.

Áreas digitadas


Outro recurso interessante é que, quando um componente é colocado dentro de outro componente, ele pode ver o tipo de mensagem do pai.



O compilador vincula rigidamente esses tipos e não oferece a oportunidade de cometer um erro. Ao processar eventos, as mensagens que o componente espera ou pode enviar devem corresponder totalmente ao pai.

Outros recursos


Transferi uma implementação do Rust diretamente para a estrutura que permite usar convenientemente vários formatos de serialização / desserialização (fornecendo wrappers adicionais). Abaixo está um exemplo: vamos ao armazenamento local e, restaurando os dados, especificamos um determinado invólucro - o que esperamos aqui é 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;   } } 

Pode ser qualquer formato, incluindo binário. Por conseguinte, a serialização e a desserialização tornam-se transparentes e convenientes.

A ideia de outra oportunidade que implementei veio dos usuários da estrutura. Eles pediram para fazer fragmentos. E aqui me deparei com uma coisa interessante. Vendo em JavaScript a capacidade de inserir fragmentos na árvore DOM, primeiro decidi que seria muito fácil implementar essa função em minha estrutura. Mas eu tentei essa opção, e acabou que não funciona. Eu tive que descobrir, andar nesta árvore, ver o que havia mudado ali, etc.

A estrutura do Yew usa uma árvore DOM virtual, tudo existe inicialmente nela. De fato, quando há algumas alterações no modelo, elas se transformam em patches que já alteram a árvore DOM renderizada.

 html! {   <>       <tr><td>{ "Row" }</td></tr>       <tr><td>{ "Row" }</td></tr>       <tr><td>{ "Row" }</td></tr>   </> } 

Benefícios adicionais


Ferrugem fornece muitos recursos poderosos mais diferentes, vou falar apenas sobre o mais importante.

Serviços: interação com o mundo exterior


A primeira oportunidade sobre a qual quero falar é sobre serviços. Você pode descrever a funcionalidade necessária na forma de algum serviço, publicá-la como uma caixa e reutilizá-la.

No Rust, a capacidade de criar bibliotecas, sua integração, encaixe e colagem é muito bem implementada. De fato, você pode criar várias APIs para interagir com seu serviço, incluindo JavaScript. Ao mesmo tempo, a estrutura pode interagir com o mundo externo, apesar de funcionar dentro do tempo de execução do WebAssembly.

Exemplos de serviços:

  • TimeOutService;
  • IntervalService;
  • FetchService;
  • WebSocketService;
  • Serviços personalizados ...

Serviços e caixas de ferrugem : crates.io .

Contexto: requisitos de estado


Outra coisa que eu implementei no framework não é totalmente tradicional, é o contexto. O React possui uma API de contexto, mas eu usei o Contexto em um sentido diferente. A estrutura Yew consiste nos componentes que você cria e o Contexto é um estado global. Os componentes podem não levar em consideração esse estado global, mas podem fazer algumas exigências - para que a entidade global atenda a alguns critérios.

Digamos que nosso componente abstrato exija a capacidade de fazer upload de algo para o S3.



Pode ser visto abaixo que ele usa esse upload, ou seja, envia dados para o S3. Esse componente pode ser disposto na forma de um rack. O usuário que baixa esse componente e o adiciona dentro do modelo ao seu aplicativo encontrará um erro - o compilador perguntará a ele onde está o suporte do S3. O usuário terá que implementar esse suporte. Depois disso, o componente começa automaticamente a viver uma vida plena.

Onde é necessário? Imagine: você está criando um componente com criptografia inteligente. Ele tem requisitos para que o contexto ao redor permita que ele entre em algum lugar. Tudo o que você precisa fazer é adicionar um formulário de autorização ao modelo e, no seu contexto, implementar a conexão com o seu serviço. I.e. serão literalmente três linhas de código. Depois disso, o componente começa a funcionar.

Imagine que temos dezenas de componentes diferentes. E todos eles têm o mesmo requisito. Isso permite implementar algum tipo de funcionalidade uma vez para reviver todos os componentes e acessar os dados necessários. Fora de contexto. E o compilador não permitirá que você cometa um erro: se você não tiver uma interface que exija um componente, nada funcionará.

Portanto, você pode criar facilmente botões muito exigentes que solicitarão alguma API ou outros recursos. Graças ao Rust e ao sistema dessas interfaces (eles são chamados de característica no Rust), torna-se possível declarar os requisitos dos componentes.

O compilador não permitirá que você cometa um erro


Imagine que estamos criando um componente com algumas propriedades, uma das quais é a capacidade de definir retorno de chamada. E, por exemplo, definimos a propriedade e perdemos uma letra em seu nome.



Tentando compilar, Rust responde rapidamente. Ele diz que estávamos enganados e não existe essa propriedade:



Como você pode ver, o Rust usa diretamente esse modelo e pode renderizar todos os erros dentro da macro. Ele diz como a propriedade deve realmente ser chamada. Se você passou no compilador, não terá erros de execução parvos, como erros de digitação.

Agora imagine, temos um botão que solicita que nosso contexto global seja capaz de se conectar ao S3. E crie um contexto que não implemente o suporte S3. Vamos ver o que acontece.



O compilador relata que inserimos um botão, mas essa interface não está implementada para o contexto.



Resta apenas entrar no editor, adicionar um link para a Amazon no contexto e tudo começará. Você pode criar serviços prontos com algum tipo de API e simplesmente adicionar ao contexto, substituir um link para ele e o componente ganha vida imediatamente. Isso permite que você faça coisas muito legais: adicione componentes, crie um contexto, encha-o de serviços. E tudo isso funciona de forma totalmente automática; são necessários esforços mínimos para unir tudo.

Como começar a usar o Yew?


Por onde começar, se você quiser tentar compilar um aplicativo WebAssembly? E como isso pode ser feito usando a estrutura Yew?

Compilação de ferrugem para wasm


Primeiro, você precisa instalar o compilador. Existe uma ferramenta de ferrugem para isso:

curl https://sh.rustup.rs -sSf | sh

Além disso, você pode precisar do emscripten. Para que pode ser útil? A maioria das bibliotecas criadas para linguagens de programação do sistema, especialmente para o Rust (originalmente um sistema), são desenvolvidas para Linux, Windows e outros sistemas operacionais completos. Obviamente, o navegador não possui muitos recursos.

Por exemplo, a geração aleatória de números em um navegador não é feita da mesma maneira que no Linux. O emscripten é útil se você deseja usar bibliotecas que requerem uma API do sistema.

As bibliotecas e toda a infraestrutura estão mudando silenciosamente para WebAssembly honesto, e o emscripten não é mais necessário (você usa recursos baseados em JavaScript para gerar números aleatórios e outras coisas), mas se precisar criar algo que não é suportado no navegador, não é possível sem o emscripten .

Eu também recomendo usar a web de carga:

cargo install cargo-web

É possível compilar o WebAssembly sem utilitários adicionais. Mas o cargo-web é uma ferramenta interessante que fornece várias coisas úteis para desenvolvedores de JavaScript. Em particular, ele monitorará os arquivos: se você fizer alterações, ele começará a compilar imediatamente (o compilador não fornece essas funções). Nesse caso, o Cargo-web permitirá acelerar o desenvolvimento. Existem diferentes sistemas de construção para o Rust, mas a carga representa 99,9% de todos os projetos.

Um novo projeto é criado da seguinte maneira:

cargo new --bin my-project

[package]
name = "my-project"
version = "0.1.0"

[dependencies]
yew = "0.3.0"

Em seguida, basta iniciar o projeto:

cargo web start --target wasm32-unknown-unknown

Dei um exemplo de WebAssembly honesto. Se você precisar compilar em emscripten (o compilador rust pode conectar o próprio emscripten), poderá inserir a palavra emscripten no último elemento unknown , o que permite usar mais caixas. Não esqueça que o emscripten é um kit adicional bastante grande para o seu arquivo. Portanto, é melhor escrever um código WebAssembly honesto.

Restrições existentes


Qualquer pessoa com experiência em codificação em linguagens de programação do sistema pode ficar frustrada com as limitações existentes na estrutura. Nem todas as bibliotecas podem ser usadas no WebAssembly. Por exemplo, em um ambiente JavaScript, não há threads. O WebAssembly, em princípio, não declara isso e, é claro, você pode usá-lo em um ambiente com vários threads (essa é uma pergunta em aberto), mas o JavaScript ainda é um ambiente de thread único. Sim, existem trabalhadores, mas isso é isolamento, então não haverá fluxos por lá.

Parece que você pode viver sem fluxos. Mas se você deseja usar bibliotecas baseadas em encadeamento, por exemplo, deseja adicionar algum tipo de tempo de execução, isso pode não decolar.

Além disso, não há API do sistema, exceto a que você transferirá do JavaScript para o WebAssembly. Portanto, muitas bibliotecas não serão portadas. Você não pode gravar e ler arquivos diretamente, os soquetes não podem ser abertos e não é possível gravar na rede. Se você deseja criar um soquete da Web, por exemplo, é necessário arrastá-lo do JavaScript.

Outra desvantagem é que o depurador WASM existe, mas ninguém o viu. Ainda está em um estado tão bruto que é improvável que seja útil para você. Portanto, depurar o WebAssembly é uma questão complicada.

Ao usar o Rust, quase todos os problemas de tempo de execução serão associados a erros na lógica de negócios, eles serão fáceis de corrigir. Mas raramente ocorrem erros de baixo nível - por exemplo, uma das bibliotecas faz o encaixe errado - e essa já é uma pergunta difícil. Por exemplo, no momento, existe um problema: se eu compilar a estrutura com o emscripten e houver uma célula de memória variável, cuja posse é retirada, é dada, o emscripten está desmoronando em algum lugar no meio (e nem tenho certeza se é emscripten). Se você se deparar com um problema em algum lugar do middleware em um nível baixo, será difícil corrigi-lo no momento.

O futuro da estrutura


Como o Yew se desenvolverá mais? Eu vejo seu principal objetivo na criação de componentes monolíticos. Você terá um arquivo WebAssembly compilado e simplesmente o colará no aplicativo. Por exemplo, ele pode fornecer recursos criptográficos, renderização ou edição.

Integração JS


A integração com JavaScript será reforçada. O JavaScript criou um grande número de bibliotecas legais que são fáceis de usar. E há exemplos no repositório onde mostro como você pode usar a biblioteca JavaScript existente diretamente da estrutura Yew.

CSS digitado


Como o Rust é usado, é óbvio que você pode adicionar CSS digitado que pode ser gerado com a mesma macro que no exemplo de um mecanismo de modelo semelhante a JSX. Nesse caso, o compilador verificará, por exemplo, se você atribuiu algum outro atributo em vez de cor. Isso economizará muito tempo.

Componentes prontos


Também busco criar componentes prontos para uso. Na estrutura, você pode criar rachaduras que fornecerão, por exemplo, um conjunto de alguns botões ou elementos que serão conectados como uma biblioteca, adicionados aos modelos e usados.

Melhoria de desempenho em casos particulares


O desempenho é uma questão muito delicada e complexa. O WebAssembly é mais rápido que o JavaScript? Não tenho provas que confirmem uma resposta positiva ou negativa. Parece que, e de acordo com alguns testes muito simples que eu conduzi, o WebAssembly é muito rápido. E tenho total confiança de que seu desempenho será maior que o do JavaScript, apenas porque é um código de byte de baixo nível em que a alocação de memória não é necessária e há muitos outros momentos que exigem recursos.

Mais colaboradores


Eu gostaria de atrair mais colaboradores. As portas para participar da estrutura estão sempre abertas. Qualquer pessoa que queira modernizar algo, entender o kernel e transformar as ferramentas com as quais trabalha um grande número de desenvolvedores, pode se conectar e oferecer suas próprias edições com facilidade.

O projeto já teve a participação de muitos colaboradores. Mas não há colaboradores principais no momento, pois para isso você precisa entender o vetor de desenvolvimento da estrutura, mas ainda não foi claramente formulado. Mas há uma espinha dorsal, caras que são muito versados ​​em Yew - cerca de 30 pessoas. Se você também quiser adicionar algo à estrutura, sempre envie uma solicitação de recebimento.

A documentação


Um ponto obrigatório em meus planos é a criação de uma grande quantidade de documentação sobre como escrever aplicativos no Yew. Obviamente, a abordagem de desenvolvimento nesse caso é diferente da que vimos em React e Elm.

Às vezes, os caras me mostram casos interessantes de como usar o framework. Ainda assim, criar uma estrutura não é o mesmo que escrever profissionalmente nela. Práticas para usar a estrutura ainda estão sendo formadas.

Experimente, instale o Rust, expanda seus recursos como desenvolvedor. O domínio do WebAssembly será útil para cada um de nós, porque a criação de aplicativos muito complexos é o momento que estamos esperando há muito tempo. Em outras palavras, o WebAssembly não é apenas um navegador da Web, mas geralmente é um tempo de execução que está definitivamente se desenvolvendo e se desenvolverá ainda mais ativamente.

Se você gostou do relatório, preste atenção: de 24 a 25 de novembro, um novo HolyJS será realizado em Moscou e também haverá muitas coisas interessantes por lá. — , ( ).

Source: https://habr.com/ru/post/pt422253/


All Articles