TL; DR
- Este artigo não discute as estruturas JS da lista TOP-3.
- Ao desenvolver em uma estrutura JS não-TOP-3, é necessário resolver uma ordem de magnitude mais problemas técnicos do que o esperado no início do desenvolvimento
- A história é baseada em eventos reais.
A história começou com um miniprojeto, desenvolvido originalmente com base nas bibliotecas backbone.js e marionette.js. Essas, é claro, são as grandes bibliotecas que iniciaram a história do desenvolvimento de aplicativos de página única. Mas já naquela época eles eram mais propensos a ter valor histórico do que prático. Mencionarei apenas o fato de que para exibir uma tabela simples foi necessário criar: 1) um módulo com uma descrição do modelo, 2) um módulo com uma descrição da coleção, 3) um módulo com a definição de um modelo de vista, 4) um módulo com a definição de uma coleção de vistas, 4) um modelo de linha de tabela, 5) modelo de tabela; 6) módulo controlador. Tendo cerca de 10 entidades em um aplicativo pequeno - no estágio inicial, você já tinha mais de cinquenta módulos pequenos. E este é apenas o começo. Mas agora não é sobre isso.
Em algum momento, após seis meses do aplicativo, ele ainda não apareceu nos resultados da pesquisa. A adição de
prerender.io ao projeto (que usava o mecanismo phantom.js) ajudou, mas não da maneira esperada. As nuvens começaram a se acumular sobre o aplicativo e acima de mim, depois do qual percebi que precisava fazer algo com muita rapidez e eficiência, de preferência hoje. O objetivo que estabeleci foi o seguinte: alternar para a renderização do servidor. Com backbone.js e marionettejs, isso é quase impossível. De qualquer forma, o projeto rendr no backbone.js, desenvolvido sob a direção de Spike Brehm (o autor da ideia de aplicativos isomórficos / universais), coletando 58 colaboradores e 4.184 curtidas no github.com, foi interrompido em 2015 e claramente não se destinava a uma blitz de um dia . Comecei a procurar uma alternativa. Excluí a estrutura do JS TOP-3 da consideração imediatamente, porque não tinha tempo para o seu desenvolvimento. Após uma breve pesquisa, encontrei o framework JS em crescimento rápido riot.js
github.com/riot/riot , que atualmente possui 13.704 curtidas no github.com e, como eu esperava, poderia muito bem ter atingido o primeiro posição (que, no entanto, não aconteceu).
E sim, encerrei a pergunta (embora não no mesmo dia, mas nos próximos dois dias do dia) transferindo o aplicativo para a renderização do servidor usando riot.js. E no mesmo dia em que isso aconteceu, o site foi movido para as dez primeiras páginas dos resultados de pesquisa. E dentro de uma semana eu fui para a primeira página, de onde ela não sai há vários anos.
Isso encerra a história de sucesso e começa a história de derrotas. O próximo projeto foi muito mais complicado. É verdade que havia um ponto positivo em que 99% das telas de aplicativos estavam na conta pessoal do usuário, portanto não havia necessidade de renderização do servidor. Inspirado na primeira experiência bem-sucedida do uso do riot.js, comecei a promover a idéia de consolidar o sucesso e aplicar o riot.js no front-end. Por fim, pareceu-me que, finalmente, foi encontrada uma solução que combinava simplicidade e funcionalidade, conforme prometido pela documentação do riot.js. Quão errado eu estava!
O primeiro problema que encontrei quando era necessário fornecer ao designer de layout HTML todas as ferramentas de desenvolvimento necessárias. Em particular, precisávamos de plug-ins para o editor de código, um mecanismo no qual seria possível colocar os componentes e observar imediatamente o resultado, inclusive com sobrecarga a quente de componentes (recarga a quente). Tudo isso tinha que ser dado na forma pronta para operação industrial em um futuro próximo, e tudo isso não era. Como resultado, o layout do aplicativo começou em um dos mecanismos de modelo tradicionais, o que levou ao estágio ingrato de converter documentos HTML em componentes riot.js.
No entanto, o principal problema que surgiu nesta fase nem sequer está relacionado à tradução do layout do formato do modelo para os componentes riot.js. De repente, o riot.js fornece mensagens completamente pouco informativas sobre erros de compilação de modelos, bem como erros de tempo de execução (isso é verdade para todas as versões do riot.js até a versão 4.0, que foi completamente redesenhada). Não havia informações não apenas sobre a linha na qual o erro ocorreu, mas também sobre o nome do arquivo ou componente em que ocorreu o erro. Foi possível procurar um erro por muitas horas seguidas e ainda assim não foi encontrado. E então tive que reverter todas as alterações para o último estado de trabalho.
A seguir, houve um problema com o roteamento. O roteamento no riot.js vem quase que
imediatamente ,
github.com/riot/route - pelo menos do mesmo desenvolvedor. Isso permitiu que ele esperasse uma operação sem problemas. Mas, em algum momento, notei que algumas páginas estão imprevisivelmente sobrecarregadas. Ou seja, uma vez que a transição para uma nova rota pode ocorrer no modo de aplicativo de página única, e outra vez a mesma transição sobrecarregou o documento HTML inteiro, como quando se trabalha com um aplicativo Web clássico. Nesse caso, é claro, o estado interno foi perdido se ainda não tivesse sido salvo no servidor. (No momento, o desenvolvimento desta biblioteca está parado e não é usado com o riot.js 4.0.)
O único componente do sistema que funcionou conforme o esperado foi o gerente de estado mínimo semelhante ao fluxo
github.com/jimsparkman/RiotControl . É verdade que, para trabalhar com esse componente, era necessário nomear e cancelar ouvintes de mudanças de estado com muito mais frequência do que gostaríamos.
A ideia inicial deste artigo foi a seguinte: mostrar por exemplo nossa própria experiência com a estrutura riot.js, as tarefas que o desenvolvedor teria que resolver, que decidiu (decidiu) desenvolver um aplicativo em uma estrutura JS que não está na lista TOP-3. No entanto, no processo de preparação, decidi atualizar algumas páginas da documentação do riot.js em minha memória e soube que uma nova versão do riot.js 4.0 foi lançada, que foi completamente redesenhada (do zero), redesenhada, que pode ser encontrada no artigo do desenvolvedor do riot. js em medium.com:
medium.com/@gianluca.guarini/every-revolution-begins-with-a-riot-js-first-6c6a4b090ee . Neste artigo, aprendi que todos os principais problemas que me preocupavam e sobre os quais eu iria falar neste artigo foram eliminados. Especificamente, no riot.js versão 4.0:
- o compilador é completamente reescrito (ou melhor, foi escrito primeiro porque o mecanismo costumava trabalhar com expressões regulares antes) - isso afetava, em particular, o conteúdo das informações das mensagens de erro
- além da renderização do servidor, o hydrate foi adicionado ao cliente - isso finalmente nos permitiu começar a escrever aplicativos universais sem renderização dupla (a primeira vez no servidor e no cliente devido à falta da função hydrate () nas versões mais antigas)
- plugin adicionado para componentes de sobrecarga quentes github.com/riot/hot-reload
- e muitas outras mudanças úteis
Isso não é publicidade, apenas o plano de fundo. De fato, minhas conclusões serão muito ambíguas em termos de recomendações de uso, e o artigo em geral não é dedicado a uma estrutura ou biblioteca específica, mas às tarefas que precisam ser resolvidas durante o processo de desenvolvimento.
Infelizmente, o trabalho realizado pelos desenvolvedores do riot.js ainda não foi avaliado adequadamente pela comunidade. Por exemplo, a biblioteca de renderização do servidor
github.com/riot/ssr nos seis meses que se passaram desde o início de seu desenvolvimento coletou três co-colaboradores e três curtidas no github.com (nem todas as curtidas são feitas pelos contribuidores, embora uma seja a mesma que a do colaborador).
Portanto, no decorrer da peça, ele mudou a direção do artigo e, em vez de memórias, tentou percorrer todo o caminho novamente, tendo um pouco mais de conhecimento e experiência, versões mais avançadas de bibliotecas e tempo livre ilimitado.
Então aqui vamos nós. Por exemplo, uma implementação do aplicativo
github.com/gothinkster/realworld foi feita. Este projeto foi discutido mais de uma vez em Habré. Para aqueles que não estão familiarizados com ele, descreverei brevemente sua ideia. Os desenvolvedores deste projeto, com a ajuda de diferentes linguagens de programação e estruturas (ou sem elas), resolvem o mesmo problema: desenvolvimento de um mecanismo de blog que se parece com uma versão simplificada do medium.com em funcionalidade. Esse é um compromisso entre a complexidade de aplicativos reais que temos que desenvolver diariamente e todo.app, que nem sempre nos permite apreciar realmente o trabalho com a biblioteca ou estrutura. Este projeto é respeitado pelos desenvolvedores. Para confirmar o que foi dito acima, posso dizer que há apenas uma implementação do Rich Harris (principal desenvolvedor do sveltejs)
github.com/sveltejs/realworld .
Ambiente de desenvolvimento
Claro que você está pronto para a batalha, mas pense nos desenvolvedores ao seu redor. Nesse caso, concentre-se na pergunta em que ambiente de desenvolvimento seus colegas trabalham. Se não houver plug-ins para os principais ambientes de desenvolvimento e editores de código de programa para a estrutura com a qual você trabalhará, é improvável que você tenha suporte. Por exemplo, eu uso o editor Atom para desenvolvimento. Para ele, existe um plug-in anti-motim
github.com/riot/syntax-highlight/tree/legacy , que não foi atualizado nos últimos três anos. E no mesmo repositório há um plugin para sublime
github.com/riot/syntax-highlight - ele é atual e suporta a versão atual do riot.js 4.0.
No entanto, o componente riot.js é um fragmento válido de um documento HTML no qual o código JS está contido no corpo do elemento de script. Portanto, tudo funcionará se você adicionar um tipo de documento html à extensão * .riot. Obviamente, essa é uma decisão forçada, pois, caso contrário, seria simplesmente impossível continuar mais aqui.
Temos destaque de sintaxe em um editor de texto e agora precisamos de funcionalidades mais avançadas, o que estamos acostumados a obter do eslint. No nosso caso, o código JS do componente está contido no corpo do elemento de script, eu esperava encontrar e encontrar um plug-in para extrair o código JS do documento HTML -
github.com/BenoitZugmeyer/eslint-plugin-html . Depois disso, minha configuração eslint começou a ficar assim:
{ "parser": "babel-eslint", "plugins": [ "html" ], "settings": { "html/html-extensions": [".html", ".riot"] }, "env": { "browser": true, "node": true, "es6": true }, "extends": "standard", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" }, "parserOptions": { "ecmaVersion": 2018, "sourceType": "module" }, "rules": { } }
A presença de plug-ins para destaque de sintaxe e eslint provavelmente não é a primeira coisa que um desenvolvedor começa a pensar ao escolher uma estrutura JS. Enquanto isso, sem essas ferramentas, você pode encontrar oposição de colegas e seu êxodo em massa por "boas" razões do projeto. Embora a única e verdadeira razão válida seja que eles não se sentem à vontade trabalhando sem um arsenal completo de desenvolvedores. No caso do riot.js, o problema foi resolvido pelo método Columbus. No sentido de que não há realmente plugins para o riot.js, mas devido às peculiaridades da sintaxe do modelo riot.js, que parece um fragmento de um documento HTML comum, cobrimos 99% da funcionalidade necessária usando ferramentas para trabalhar com um documento HTML.
Além dos meios para escrever e validar o código que acabamos de examinar, as ferramentas do desenvolvedor incluem ferramentas para montagem e depuração rápidas do projeto, recarga quente de componentes e módulos em um navegador da Web ao fazer alterações no projeto. Vamos considerar essa parte na próxima seção.
Montagem do projeto
Conseguimos nos acostumar com a maioria dos recursos necessários ao criar o projeto e até parar de pensar no que poderia ser diferente. Mas poderia ser de outra maneira. E, se você escolheu uma nova estrutura JS, é aconselhável primeiro garantir que tudo funcione conforme o esperado. Por exemplo, como eu já mencionei, o maior problema ao desenvolver versões mais antigas do riot.js foi a falta de mensagens de erro de compilação e informações de tempo de execução sobre o componente em que esse erro ocorreu. Também é importante a velocidade de compilação. Como regra, para acelerar a velocidade de compilação, em uma estrutura construída corretamente, apenas a parte alterada é recompilada, como resultado, o tempo de reação às alterações no texto do componente é mínimo. Bem, é muito bom se uma recarga quente de componentes for suportada sem uma recarga completa da página no navegador da web.
Portanto, tentarei listar a lista de verificação, em que você deve prestar atenção especial ao analisar as ferramentas de criação do projeto:
1. A presença de um modo de desenvolvimento e um aplicativo de trabalho
No modo de desenvolvedor:
2. Mensagens informativas sobre erros de compilação do projeto (nome do arquivo de origem, número da linha no arquivo de origem, descrição do erro)
3. Mensagens informativas sobre erros de tempo de execução (nome do arquivo de origem, número da linha no arquivo de origem, descrição do erro)
4. Remontagem rápida de módulos modificados
5. Sobrecarga a quente de componentes no navegador
No modo de operação:
6. A presença de versão no nome do arquivo (por exemplo, 4a8ee185040ac59496a2.main.js)
7. O layout de pequenos módulos em um ou mais módulos (pedaços)
8. Divisão de código em partes usando importação dinâmica
No riot.js versão 4.0, o módulo
github.com/riot/webpack-loader apareceu, o que corresponde totalmente à lista de verificação fornecida. Não listarei todos os recursos da configuração da montagem. A única coisa que gostaria de chamar sua atenção é que, no projeto em questão, utilizo módulos para o express.js: webpack-dev-middleware e webpack-hot-middleware, que permitem trabalhar imediatamente em um servidor totalmente funcional, a partir do momento da digitação. Isso, em particular, permite o desenvolvimento de aplicativos Web universais / isomórficos. Chamo a atenção para o fato de que o módulo de sobrecarga a quente do módulo é válido apenas para um navegador da web. Ao mesmo tempo, o componente renderizado no lado do servidor permanece inalterado. Portanto, é necessário escutar suas alterações e, no momento certo, excluir todo o código armazenado em cache pelo servidor e carregar o código dos módulos alterados. Como fazer isso por um longo tempo, fornecerei um link para a implementação:
github.com/apapacy/realworld-riotjs-effector-universal-hot/blob/master/src/dev_server.jsEncaminhamento
Parafraseando Leo Tolstoy um pouco, podemos dizer que todos os mecanismos das estruturas JS são semelhantes entre si, enquanto todos os roteiros anexados a eles funcionam à sua maneira. Costumo encontrar a classificação condicional de roteadores em dois tipos: declarativa e imperativa. Agora tentarei descobrir como essa classificação se justifica.
Vamos fazer uma pequena excursão pela história. No início da Internet, URLs / URIs correspondiam ao nome do arquivo hospedado no servidor. Viramos várias páginas da história de uma só vez e aprenderemos sobre o advento da Arquitetura do Modelo 2 (MVC). Nesta arquitetura, um controlador frontal aparece, que executa a função de roteamento. Perguntei-me quem primeiro decidiu do controlador frontal selecionar a função de roteamento em um bloco separado, que envia uma solicitação a um dos muitos controladores e ainda não encontrou uma resposta. Parece que eles começaram a fazer tudo de uma vez.
Ou seja, o roteamento determinou a ação que deve ser executada no servidor e (transitivamente pelo controlador) a exibição que será gerada pelo controlador. Ao transferir o roteamento para o lado do cliente (navegador da web), uma idéia da função de roteamento no aplicativo já estava formada. E aqueles que se concentraram principalmente no fato de que o roteamento determina a ação, desenvolveram o roteamento imperativo e aqueles que prestaram atenção em que, no final, o roteamento determina a visão que deve ser mostrada ao usuário, desenvolveram o roteamento declarativo.
Ou seja, ao transferir de um servidor para um cliente para roteamento, eles “travaram” duas funções que eram características do roteamento do servidor (selecionando uma ação e selecionando uma exibição). Além disso, surgiram novas tarefas - é a navegação em um aplicativo de página única sem recarregar completamente o documento HTML, trabalhando com o histórico de visitas e muito mais. Para ilustrar, fornecerei trechos da documentação do roteador de uma estrutura mega popular:
... facilita a criação de aplicativos SPA. Inclui os seguintes recursos
- Rotas / exibições aninhadas
- Configuração modular do roteador
- Acesso a parâmetros de rota, consulta, curingas
- Veja a animação de transição com base no Vue.js
- Controle de navegação conveniente
- Aposição automática da classe CSS ativa para links
- Modos de histórico HTML5 ou hash, com comutação automática no IE9
- Comportamento de rolagem personalizado
Nesta opção, o roteamento está claramente sobrecarregado com a funcionalidade e precisa repensar suas tarefas no lado do cliente. Comecei a procurar uma solução adequada para minha tarefa. Como critério principal, considerei esse roteamento:
- deve funcionar da mesma forma no lado do cliente da web e no servidor da web para aplicativos da web universais / isomórficos;
- deve funcionar com qualquer estrutura (incluindo a escolhida) ou sem ela.
E eu encontrei essa biblioteca, aqui é
github.com/kriasoft/universal-router . Se descrevermos brevemente a idéia desta biblioteca, ela configura rotas que pegam uma cadeia de URLs na entrada e chamam uma função assíncrona na saída, que passa a URL analisada como um parâmetro real. Honestamente, eu queria perguntar: isso é tudo? E como então todos devem trabalhar com isso? E então encontrei um artigo em medium.com
medium.com/@ippei.tanaka/universal-router-history-react-97ec79464573 , no qual uma opção bastante boa foi proposta, com a exceção de talvez reescrever o método de histórico push (), que não Preciso e que apaguei do meu código. Como resultado, a operação do roteador no lado do cliente é definida da seguinte maneira:
const routes = new UniversalRouter([ { path: '/sign-in', action: () => ({ page: 'login', data: { action: 'sign-in' } }) }, { path: '/sign-up', action: () => ({ page: 'login', data: { action: 'sign-up' } }) }, { path: '/', action: (req) => ({ page: 'home', data: { req, action: 'home' } }) }, { path: '/page/:page', action: (req) => ({ page: 'home', data: { req, action: 'home' } }) }, { path: '/feed', action: (req) => ({ page: 'home', data: { req, action: 'feed' } }) }, { path: '/feed/page/:page', action: (req) => ({ page: 'home', data: { req, action: 'feed' } }) }, ... { path: '(.*)', action: () => ({ page: 'notFound', data: { action: 'not-found' } }) } ]) const root = getRootComponent() const history = createBrowserHistory() const render = async (location) => { const route = await router.resolve(location) const component = await import(`./riot/pages/${route.page}.riot`) riot.register(route.page, component.default || component) root.update(route, root) } history.listen(render)
Agora qualquer chamada para history.push () iniciará o roteamento. Para navegar dentro do aplicativo, você também precisa criar um componente que agrupe o elemento HTML padrão a (âncora), sem esquecer de cancelar seu comportamento padrão:
<navigation-link href={ props.href } onclick={ action }> <slot/> <script> import history from '../history' export default { action (e) { e.preventDefault() history.push(this.props.href) if (this.props.onclick) { this.props.onclick.call(this, e) } e.stopPropagation() } } </script> </navigation-link>
Gerenciamento de estado do aplicativo
Inicialmente, incluí a biblioteca mobx no projeto. Tudo funcionou como esperado. Exceto que não correspondia exatamente à tarefa - o estudo que estabeleci no início do artigo. Então mudei para
github.com/zerobias/effector . Este é um projeto muito poderoso. Ele fornece 100% da funcionalidade redux (apenas sem grandes despesas gerais) e 100% da funcionalidade mobx (embora neste caso seja necessário codificar um pouco mais, mas ainda menos se comparado ao mobx sem decoradores)
A descrição da loja é mais ou menos assim:
import { createStore, createEvent } from 'effector' import { request } from '../agent' import { parseError } from '../utils' export default class ProfileStore { get store () { return this.profileStore.getState() } constructor () { this.success = createEvent() this.error = createEvent() this.updateError = createEvent() this.init = createEvent() this.profileStore = createStore(null) .on(this.init, (state, store) => ({ ...store })) .on(this.success, (state, data) => ({ data })) .on(this.error, (state, error) => ({ error })) .on(this.updateError, (state, error) => ({ ...state, error })) } getProfile ({ req, author }) { return request(req, { method: 'get', url: `/profiles/${decodeURIComponent(author)}` }).then( response => this.success(response.data.profile), error => this.error(parseError(error)) ) } follow ({ author, method }) { return request(undefined, { method, url: `/profiles/${author}/follow` }).then( response => this.success(response.data.profile), error => this.error(parseError(error)) ) } }
Essa biblioteca usa redutores completos (eles são chamados na documentação do effector.js), que muitas pessoas não possuem no mobx, mas com muito menos esforço de codificação do que o redux. Mas o principal não é nem isso. Tendo recebido 100% da funcionalidade de redux e mobx, usei apenas um décimo da funcionalidade inerente ao effector.js. A partir do qual podemos concluir que seu uso em projetos complexos pode enriquecer significativamente os fundos dos desenvolvedores.
Teste
TodoConclusões
Então, o trabalho está concluído. O resultado é apresentado no repositório
github.com/apapacy/realworld-riotjs-effector-universal-hot e neste artigo sobre Habré.
Site de demonstração no momento
realworld-riot-effector-universal-hot-pnujtmugam.now.shE no final, compartilharei minhas impressões sobre o desenvolvimento. O desenvolvimento do riot.js versão 4.0 é bastante conveniente. Muitas construções são mais fáceis de escrever do que no React. Demorou exatamente duas semanas para se desenvolver sem fanatismo nas horas posteriores e nos finais de semana. Mas ... Um pequeno, mas ... O milagre não aconteceu novamente. A renderização do servidor no React é 20 a 30 vezes mais rápida. Empresas ganham novamente. No entanto, duas bibliotecas interessantes de roteamento e gerenciador de estado foram testadas.
apapacy@gmail.com
17 de junho de 2019