Vue, Storybook, TypeScript - iniciando um novo projeto com as melhores práticas em mente


(publicado originalmente no Medium )


Eu gosto de escrever o código React. Essa pode ser uma introdução estranha a uma história sobre o Vue, mas você precisa entender meus antecedentes para entender por que estou aqui discutindo o Vue.


Eu gosto de escrever código React e odeio lê-lo. O JSX é uma ótima idéia para montar as peças rapidamente, o Material-UI é uma solução incrível para inicializar a interface do usuário da sua próxima inicialização, a computação CSS a partir de constantes JS permite que você seja muito flexível. No entanto, a leitura de seus JSXs antigos é horrível - mesmo com práticas escrupulosas de revisão de código, você pode coçar a cabeça nem uma vez ao tentar entender o complexo agrupamento dos componentes.


Ouvi muitas coisas sobre Vue - o garoto não tão novo no quarteirão - e finalmente decidi me molhar; trazendo toda a minha bagagem mental de React e Polymer (e Angular, mas não vamos falar sobre isso).


O Vue é muito parecido com o Polymer, mais ainda pelos autores o nomeiam como uma das fontes de inspiração . A estrutura dos arquivos *.vue parecia as melhores partes do Polymer e eu mergulhei direto. Poucos dias depois, saí do pântano do desenvolvimento de texto datilografado, orientado à interface do usuário e inúmeras práticas recomendadas e estou pronto para compartilhar o que encontrei.


Vamos lá!


Usaremos npx para executar os comandos. Se você ainda não possui o npx, veja como obtê-lo: npm install -g npx . Npx é um salva-vidas quando você lida com os pacotes npm cli e não deseja npm install -g dezenas de aplicativos. Você também precisará do Yarn se não o tiver - o npm install -g yarn deve npm install -g yarn lo.


 $ npx vue create not-a-todo-app Vue CLI v3.3.0 ? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, TS, PWA, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E ? Use class-style component syntax? Yes ? Use Babel alongside TypeScript for auto-detected polyfills? Yes ? Use history mode for router? (Requires proper server setup for index fallback in production) Yes ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS ? Pick a linter / formatter config: TSLint ? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save ? Pick a unit testing solution: Jest ? Pick a E2E testing solution: Cypress ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? No 

Vue Cli tem um bom assistente para guiá-lo; neste tutorial, usaremos o modo manual e ativaremos todos os recursos . Excesso? Talvez, mas queremos ver como o Vue funciona com tudo o que pode oferecer imediatamente. Ainda assim, vamos analisar as opções e a razão de como e por quê.


Precisamos de Babel e TypeScript habilitados. O TS será o idioma principal de escolha e o Babel suportará o processamento de código externo que requer transpilação. Você pode argumentar que o TypeScript também pode transpilar o código JS e, de fato, é esse o caso, mas em meus experimentos (especialmente relacionados ao teste de unidade e ao Vuetify), achei muito melhor manter TS para *.ts e usar Babel para todo o resto.


Os pré-processadores CSS serão úteis para o Vuetify; Embora seja fornecido com CSS pré-minificado, convém incluir os arquivos de styl originais para trabalhar com estilos. Linter / Formatter é um requisito óbvio para qualquer novo projeto (você deve aderir a um estilo de código único e pode me agradecer em um ano quando ler seu código antigo). Ativamos o teste de unidade e o teste E2E - embora você não queira fazer os casos de teste completos do e2e, é útil saber como corrigi-los depois de concluirmos o Vuetify.


O suporte para o Progressive Web App (PWA) , o roteador e o Vuex não são estritamente necessários para este tutorial e não os usaremos, mas habilitá-los simplificará sua vida em um projeto real.


Usar sintaxe de componente no estilo de classe? Sim As classes tornam o código um pouco mais volumoso, mas mais legível e fácil de raciocinar; eles também facilitam sua vida TypeScript.


Usar o Babel ao lado do TypeScript para polyfills detectados automaticamente? Sim Queremos Babel e TS para o caso que examinaremos mais adiante.


Usar o modo histórico para o roteador? Sim (mas YMMV). Não escreveremos nenhum back-end para servir isso na produção, mas geralmente é uma boa ideia usar a API de histórico.


Escolha um pré-processador CSS (PostCSS, Autoprefixer e CSS Modules são suportados por padrão): usaremos apenas módulos CSS neste tutorial, para que você possa escolher sass / less / stylus com base em suas preferências.


Escolha uma configuração de linter / formatador: TSlint é uma escolha óbvia, pois queremos usar o TypeScript o máximo possível.


Escolha recursos adicionais de fiapos: ative os dois ( a ). Linting é bom.


Escolha uma solução de teste de unidade: este tutorial se concentra no Jest, portanto você deve selecioná-lo.


Escolha uma solução de teste E2E: este tutorial se concentra no Cypress.


Onde você prefere colocar a configuração para Babel, PostCSS, ESLint, etc.? Você não acha que é meio estranho que todos tentem colocar ainda mais coisas no package.json ? O arquivo mal pode ser lido do jeito que está agora. Use arquivos de configuração dedicados - eles são muito mais fáceis de trabalhar e seu histórico do git será mais bonito.


Hora de verificar a configuração, execute o yarn serve :


Não deve haver erros no console e a navegação para http: // localhost: 8080 / o receberá com:


Verificando os testes de unidade, execute o yarn test:unit :


E trabalho de teste e2e ( yarn test:e2e --headless ):


Ótimo! Passando para a interface do usuário.


O dilema material


Existem algumas bibliotecas de UI de material para o Vue com um nível diferente de flexibilidade e aprimoramento. Certamente, existem dezenas de outras bibliotecas de componentes, portanto você pode usar o Bootstrap Vue, se quiser. Este tutorial se concentra no Vuetify por vários motivos:


  • é a biblioteca de materiais mais estrelada no GitHub;
  • foi uma pena real fazê-lo funcionar, por isso é uma ótima demonstração de todos os casos extremos nos quais você pode tropeçar.

Convencido? Prossiga com a instalação e: vue add vuetify . Selecione a opção Configurar (avançado) .


Usar um modelo pré-fabricado? O Vuetify substituirá App.vue padrão e HelloWorld.vue. Responda sim a isso, pois é um novo projeto.


Usar tema personalizado? Sim Você precisará de um mais cedo ou mais tarde, então vamos configurá-lo. Pelas mesmas razões, responda sim para Usar propriedades personalizadas (variáveis ​​CSS)? .


Selecione a fonte do ícone: Ícones dos materiais (mas também mostrarei como corrigi-lo no Font Awesome mais tarde). Usar fontes como uma dependência? Não. Obteremos as fontes da CDN.


Use componentes à la carte? Sim Essa parece ser a maneira mais fácil de usar o Vuetify.


Há várias mudanças, mas o mais importante é que, quando você executa o yarn serve agora verá uma imagem diferente:


(você também receberá uma dúzia de avisos no seu linter).


Vamos verificar os testes de unidade ...


Fazendo o Vuetify funcionar com testes de unidade e e2e


Vamos verificar ./tests/unit/example.spec.ts . O teste verifica se a mensagem exibe " nova mensagem ", mas o modelo que acompanha o Vuetify não suporta mais esse suporte. Em uma situação do mundo real, você removeria o componente HelloWorld e seu teste, mas aqui atualizamos a mensagem para procurar algo que esteja no componente:


 const msg = 'Welcome to Vuetify'; 

Agora o teste passa (verifique com o yarn test:unit ), mas ainda há uma boa dúzia de avisos semelhantes a


 [Vue warn]: Unknown custom element: <v-layout> - did you register the component correctly? For recursive components, make sure to provide the "name" option. 

Da maneira como o Vuetify funciona, ele adiciona ./src/plugins/vuetify.ts que configura o Vuetify como parte do aplicativo. Este arquivo é origens de ./src/main.ts . Infelizmente o main.ts é ignorado quando você executa testes de unidade.


Primeiro, vamos corrigir os erros e avisos no vuetify.ts gerado.


Abra seu ./tsconfig.json e adicione vuetify à seção compilerOptions.types :


  "types": [ "webpack-env", "vuetify", "jest" ], 

Isso informa ao compilador TypeScript de onde obter os tipos Vuetify e o erro em ./src/plugins/vuetify.ts desaparece. Vamos corrigir alguns avisos de estilo para limpá-lo:


 import Vue from 'vue'; import Vuetify from 'vuetify/lib'; import 'vuetify/src/stylus/app.styl'; Vue.use(Vuetify, { theme: { primary: '#ee44aa', secondary: '#424242', accent: '#82B1FF', error: '#FF5252', info: '#2196F3', success: '#4CAF50', warning: '#FFC107', }, customProperties: true, iconfont: 'md', }); 

Agora precisamos carregar o Vuetify no contexto de nossos testes de unidade. Crie um novo arquivo em ./tests/jest-setup.js com o seguinte conteúdo:


 import '@/plugins/vuetify'; 

e atualize ./jest.config.js para carregá-lo:


 module.exports = { ... setupFiles: ['./tests/jest-setup.js'], } 


Os testes ainda falham, mas de uma maneira um tanto enigmática. O que aconteceu?


vuetify/lib é uma fonte bruta não processada do Vuetify, que inclui itens como módulos ES. O Jest executa transformações apenas para o seu código-fonte por padrão, o que significa que ele ignora tudo em node_modules . Mais ainda, já que dissemos ao Vue para usar o TypeScript, o gracejo não está configurado para transpilar JS.


Para corrigir isso, precisamos fazer duas alterações em ./jest.config.js :


 module.exports = { ... transform: { '^.+\\.vue$': 'vue-jest', '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', '^.+\\.tsx?$': 'ts-jest', '^.+\\.jsx?$': 'babel-jest', // <-- (1) }, transformIgnorePatterns: [ 'node_modules/(?!(vuetify)/)', // <-- (2) ], } 

Em (1) dizemos ao Jest para transformar qualquer arquivo *.js ou *.jsx com Babel (e Babel é pré-configurado para nós por Vue Cli), mas o que é (2) ? transformIgnorePatterns especifica os caminhos que o Jest ignorará ao transpilar código e, como observei anteriormente, o padrão inclui node_modules . Aqui, substituímos o padrão por um regex enigmático node_modules/(?!(vuetify)/) que significa "ignore qualquer caminho que comece com node_modules/ menos que seja seguido por vuetify ":


Observe como os dois primeiros caminhos têm uma correspondência, mas o terceiro não. Esse truque será útil quando adicionarmos o Storybook, mas por enquanto.


Executando os testes novamente ...


Os elementos personalizados desconhecidos estão de volta; mas pelo menos compila e executa com sucesso. O Vuetify é transpilado, mas ainda precisamos registrar os componentes manualmente. Existem algumas opções sobre como fazer isso ( verifique os documentos para outras opções ); o que faremos aqui é importar os componentes necessários para o escopo global do Vue. Abra ./src/plugins/vuetify.ts novamente e atualize-o para:


 import Vue from 'vue'; import Vuetify, { VFlex, VLayout, VContainer, VImg } from 'vuetify/lib'; import 'vuetify/src/stylus/app.styl'; Vue.use(Vuetify, { components: { VFlex, VLayout, VContainer, VImg }, theme: { primary: '#ee44aa', secondary: '#424242', accent: '#82B1FF', error: '#FF5252', info: '#2196F3', success: '#4CAF50', warning: '#FFC107', }, customProperties: true, iconfont: 'md', }); 

Finalmente, os testes passam:


Os testes do E2E também falharão ( yarn test:e2e --headless ), mas é devido a ./tests/e2e/specs/test.js à procura de uma string que não existe mais. Os testes E2E ativam seu aplicativo real em um navegador real, para que não haja código a ser corrigido - o Vuetify está definido no seu aplicativo. Corrija o test.js para procurar o novo cabeçalho:


 cy.contains('h1', 'Welcome to Vuetify') 

e ficará verde novamente.


Vamos recapitular. Adicionamos o Vuetify, corrigimos os testes de unidade e e2e para lidar com um novo modelo e atualizamos o Jest para transpilar o código-fonte do Vuetify e carregá-lo. Nossa aplicação é funcional e podemos usar vários componentes materiais. Passando para as histórias!


Livros de histórias


Os livros de histórias são uma idéia brilhante: você escreve seus casos de teste da perspectiva do designer: pequenos componentes para o aplicativo completo. Você pode argumentar com o fluxo de dados, garantir que tudo pareça exatamente como o seu designer de interface do usuário expôs no Photoshop, testar seus componentes isoladamente. Vamos adicionar suporte ao livro de histórias!


Existe um plugin de livro de histórias do Vue, mas achei que o sb init fornece um modelo padrão melhor, então vamos usá-lo. Execute npx -p @storybook/cli sb init e, após alguns minutos, você deverá receber um prompt para executar o yarn storybook . Vamos fazer isso:


Vamos adicionar uma nova história! Crie ./src/components/LoveButton.stories.ts com o seguinte conteúdo:


 import { storiesOf } from '@storybook/vue'; import LoveButton from './LoveButton.vue'; storiesOf('LoveButton', module) .add('default', () => ({ components: { LoveButton }, template: `<love-button love="vue"/>`, })); 

(observe que você pode usar o LoveButton.stories.js aqui se quiser ser menos LoveButton.stories.js ao digitar suas histórias).


O TypeScript avisa sobre os tipos ausentes que você pode corrigir com o yarn add -D @types/storybook__vue .


Agora crie ./src/components/LoveButton.vue com o seguinte conteúdo:


 <template> <v-btn color="red"> <v-icon>favorite</v-icon> {{love}} </v-btn> </template> <script lang="ts"> import Vue from 'vue'; export default Vue.extend({ props: ['love'], }); </script> 

Por padrão, o Storybook analisará ./stories para suas histórias, mas muitas vezes é mais prático manter as histórias mais próximas de seus componentes (como fizemos). Para contar ao livro de histórias onde procurar por eles, atualize seu ./.storybook/config.js :


 import { configure } from '@storybook/vue'; const req = require.context('../src', true, /.stories.(j|t)s$/); function loadStories() { req.keys().forEach(filename => req(filename)); } configure(loadStories, module); 

Agora, execute o yarn storybook novamente:


Não é muito emocionante. O console está cheio de avisos:


No entanto, sabemos o que é isso agora. O livro de histórias é outro contexto "raiz" com seu próprio ponto de entrada; ele não usa main.ts e, como tal, não carrega o Vuetify, por isso precisamos dizer a ele para fazer isso. Atualize ./.storybook/config.js :


 import { configure } from '@storybook/vue'; import '../src/plugins/vuetify'; // <-- add this const req = require.context('../src', true, /.stories.(j|t)s$/); function loadStories() { req.keys().forEach(filename => req(filename)); } configure(loadStories, module); 

Estamos carregando nossa configuração existente novamente, o que garante que o Storybook use o mesmo tema do aplicativo real. Infelizmente, o yarn storybook falhará agora:


O Storybook não sabe que usamos o TypeScript, por isso não pode carregar o arquivo vuetify.ts . Para corrigir isso, precisamos atualizar a própria configuração do webpack do Storybook. Crie ./.storybook/webpack.config.js com o seguinte conteúdo:


 const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); module.exports = (baseConfig, env, defaultConfig) => { defaultConfig.resolve.extensions.push('.ts', '.tsx', '.vue', '.css', '.less', '.scss', '.sass', '.html') defaultConfig.module.rules.push({ test: /\.ts$/, exclude: /node_modules/, use: [ { loader: 'ts-loader', options: { appendTsSuffixTo: [/\.vue$/], transpileOnly: true }, } ], }); defaultConfig.module.rules.push({ test: /\.less$/, loaders: [ 'style-loader', 'css-loader', 'less-loader' ] }); defaultConfig.module.rules.push({ test: /\.styl$/, loader: 'style-loader!css-loader!stylus-loader' }); defaultConfig.plugins.push(new ForkTsCheckerWebpackPlugin()) return defaultConfig; }; 

Isso carrega a configuração padrão, adiciona ts-loader para arquivos TypeScript e também adiciona suporte para less e styl (que o Vuetify usa).


Os avisos ainda estão lá, porque precisamos registrar os componentes que usamos. Agora, vamos usar componentes locais para que você possa ver a diferença (em um aplicativo de produção real, é muito mais simples registrá-los no vuetify.ts ). Atualize ./src/components/LoveButton.vue :


 <template> <v-btn color="red"> <v-icon>favorite</v-icon> {{love}} </v-btn> </template> <script lang="ts"> import Vue from 'vue'; import { VBtn, VIcon } from 'vuetify/lib'; // <-- add this export default Vue.extend({ components: { VBtn, VIcon }, // <-- and this props: ['love'], }); </script> 

O livro de histórias é atualizado ao salvar:


Marginalmente melhor. O que está faltando? O instalador do Vuetify adicionou as fontes css diretamente em ./public/index.html mas o Storybook não usa esse arquivo, por isso precisamos adicionar a fonte ausente dos ícones dos materiais. Crie ./.storybook/preview-head.hmtl com o seguinte (copiando de ./public/index.html ):


 <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons"> 

(existem outras maneiras de fazer o mesmo, por exemplo, usando CSS @import ).


Você precisa reiniciar o seu yarn storybook de yarn storybook para que ele seja re-renderizado corretamente:


Muito melhor, mas ainda menor: a fonte de texto está incorreta porque o Vuetify espera que todos os seus componentes sejam aninhados no v-app que aplica os estilos de página. Certamente não podemos adicionar o v-app ao nosso botão, então vamos decorar a história. Atualize seu ./src/components/LoveButton.stories.ts :


 import { storiesOf } from '@storybook/vue'; import { VApp, VContent } from 'vuetify/lib'; // <-- add the import import LoveButton from './LoveButton.vue'; // add the decorator const appDecorator = () => { return { components: { VApp, VContent }, template: ` <v-app> <div style="background-color: rgb(134, 212, 226); padding: 20px; width: 100%; height: 100%;"> <v-content> <story/> </v-content> </div> </v-app> `, }; }; storiesOf('LoveButton', module) .addDecorator(appDecorator) // <-- decorate the stories .add('default', () => ({ components: { LoveButton }, template: `<love-button love="vue"/>`, })); 

Você deve registrar o VApp e o VContent no escopo global, atualizar seu ./src/plugins/vuetify.ts :


 import Vue from 'vue'; import Vuetify, { VFlex, VLayout, VContainer, VImg, VApp, VContent } from 'vuetify/lib'; import 'vuetify/src/stylus/app.styl'; Vue.use(Vuetify, { components: { VFlex, VLayout, VContainer, VImg, VApp, VContent }, theme: { primary: '#ee44aa', secondary: '#424242', accent: '#82B1FF', error: '#FF5252', info: '#2196F3', success: '#4CAF50', warning: '#FFC107', }, customProperties: true, iconfont: 'md', }); 

Finalmente, o resultado é espetacular:


Adicionando teste de livro de histórias


Finalmente, vamos garantir que nossas histórias sejam cobertas por testes de unidade. Adicione as dependências necessárias: yarn add -D @storybook/addon-storyshots jest-vue-preprocessor babel-plugin-require-context-hook e crie ./test/unit/storybook.spec.js :


 import registerRequireContextHook from 'babel-plugin-require-context-hook/register'; import initStoryshots from '@storybook/addon-storyshots'; registerRequireContextHook(); initStoryshots(); 

A configuração do livro de histórias usa o require.context para coletar todas as fontes; essa função é fornecida pelo webpack e precisamos usar o babel-plugin-require-context-hook para substituí-lo no Jest. Modifique seu ./babel.config.js :


 module.exports = api => ({ presets: ['@vue/app'], ...(api.env('test') && { plugins: ['require-context-hook'] }), }); 

Aqui, adicionamos o plug require-context-hook in require-context-hook se o babel for executado para testes de unidade.


Finalmente, precisamos permitir que o Jest transpile os arquivos *.vue do livro de histórias. Lembra-se do regex lookahead em ./jest.config.js ? Vamos revisitá-lo agora:


 module.exports = { ... transformIgnorePatterns: [ 'node_modules/(?!(vuetify/|@storybook/.*\\.vue$))', ], } 

Observe que não podemos simplesmente adicionar uma segunda linha lá. Lembre-se de que é um padrão de ignorância; portanto, se o primeiro padrão ignora tudo, menos o Vuetify, os arquivos do livro de histórias já são ignorados quando o Jest chega ao segundo regex.


Os novos testes funcionam conforme o esperado:


Este teste executará todas as suas histórias e as verificará nos snapshots locais em ./tests/unit/__snapshots__/ . Para vê-lo em ação, você pode remover <v-icon>favorite</v-icon> do componente de botão e executar novamente o teste para ver se ele falha:


yarn test:unit -u atualizará seu instantâneo para o novo layout de botão.


Recapitular


Neste tutorial, aprendemos como criar um novo aplicativo Vue com o TypeScript ativado; como adicionar a biblioteca Vuetify aos componentes da interface do usuário do material. Garantimos que nossos testes de unidade e e2e funcionem conforme o esperado. Finalmente, adicionamos suporte para livros de histórias, criamos uma amostra de história e garantimos que as alterações na interface do usuário sejam cobertas por nossos testes de unidade.


Pensamentos finais


JS é um mundo em movimento, as coisas mudam constantemente, novos padrões surgem, velhos são esquecidos. Este tutorial pode ficar obsoleto em apenas alguns meses, então, aqui estão algumas dicas úteis.


Conheça o seu ferramental. Não há problema em copiar e colar linhas do estouro de pilha até que seu código funcione, mas você deve pesquisar por que a alteração o fez funcionar posteriormente. Leia os documentos e entenda o que exatamente a mudança faz.


Se você tem algo para trabalhar, mesmo que parcialmente - faça um commit. Mesmo que seja um trabalho em andamento, você terá algo a reverter, caso suas alterações adicionais quebrem alguma coisa.


Experimente! Se algo não funcionar da maneira que você pensa e os documentos dizem o contrário, experimente! O mundo do front-end é principalmente de código aberto, então procure as fontes de terceiros e veja se você pode mexer com seus instrumentos por dentro para adicionar o log de depuração.

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


All Articles