Vue, Storybook, TypeScript - démarrer un nouveau projet avec les meilleures pratiques à l'esprit


(initialement publié sur Medium )


J'aime écrire du code React. Cela peut être une étrange introduction à une histoire sur Vue, mais vous devez comprendre mon expérience pour comprendre pourquoi je parle ici de Vue.


J'aime écrire du code React et je déteste le lire. JSX est une bonne idée pour assembler les pièces rapidement, Material-UI est une solution incroyable pour démarrer l'interface utilisateur de votre prochaine startup, le calcul CSS à partir des constantes JS vous permet d'être très flexible. Pourtant, lire vos anciens JSX est horrible - même avec des pratiques de révision de code scrupuleuses, vous pourriez ne pas vous gratter la tête une fois que vous essayez de comprendre l'imbrication complexe des composants.


J'ai entendu beaucoup de choses à propos de Vue - le nouveau gamin du quartier - et j'ai finalement décidé de me mouiller les pieds; apportant tous mes bagages mentaux de React et Polymer (et Angular, mais ne parlons pas de ça).


Vue ressemble beaucoup à Polymer, d'autant plus que les auteurs le nomment comme l'une des sources d' inspiration . La structure des fichiers *.vue semblait être la meilleure partie de Polymer et j'ai plongé directement. Quelques jours plus tard, j'ai rampé hors du marais du développement dactylographié, piloté par l'interface utilisateur et de nombreuses meilleures pratiques et je suis prêt à partager ce que j'avais trouvé.


C'est parti!


Nous utiliserons npx pour exécuter les commandes. Si vous n'avez pas encore de npx, voici comment l'obtenir: npm install -g npx . Npx est un épargnant de vie lorsque vous traitez avec des packages npm cli et que vous ne voulez pas npm install -g dizaines d'applications. Vous aurez également besoin de Yarn si vous ne l'avez pas - npm install -g yarn devrait vous mettre à jour.


 $ 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 a un joli assistant pour vous guider; pour ce tutoriel, nous utiliserons le mode manuel et activerons toutes les fonctionnalités . Trop fort? Peut-être, mais nous voulons voir comment Vue fonctionne avec tout ce qu'il peut fournir hors de la boîte. Examinons tout de même les options et les raisons de savoir comment et pourquoi.


Nous devons activer Babel et TypeScript . TS sera la langue principale de choix et Babel prendra en charge la gestion du code externe qui nécessite un transpiling. Vous pourriez faire valoir que TypeScript peut également transpiler du code JS, et c'est effectivement le cas, mais dans mes expériences (en particulier en ce qui concerne les tests unitaires et Vuetify), j'ai pensé qu'il était préférable de conserver TS pour *.ts et d'utiliser Babel pour tout le reste.


Les pré-processeurs CSS seront utiles pour Vuetify; bien qu'il soit livré avec du CSS pré-minifié, vous souhaiterez peut-être inclure les fichiers de style d'origine pour travailler avec les styles. Linter / Formatter est une exigence évidente pour tout nouveau projet (vous devez respecter un style de code unique et vous pourrez me remercier dans un an lorsque vous lirez votre ancien code). Nous activons les tests unitaires et les tests E2E - bien que vous ne souhaitiez peut-être pas effectuer les cas de test e2e complets, il est utile de savoir comment les corriger après avoir terminé avec Vuetify.


La prise en charge de l'application Web progressive (PWA) , le routeur et Vuex ne sont pas strictement requis pour ce didacticiel et nous ne les utiliserons pas, mais leur activation vous simplifiera la vie dans un vrai projet.


Utiliser la syntaxe des composants de style classe? Oui Les classes rendent le code légèrement plus volumineux mais plus lisible et plus facile à raisonner; ils facilitent également votre vie TypeScript.


Utiliser Babel aux côtés de TypeScript pour les polyfills détectés automatiquement? Oui Nous voulons à la fois Babel et TS pour le cas que nous examinerons plus tard.


Utiliser le mode historique pour le routeur? Oui (mais YMMV). Nous n'écrirons aucun backend pour servir cela en production, mais c'est généralement une bonne idée d'utiliser l'API historique.


Choisissez un pré-processeur CSS (PostCSS, Autoprefixer et les modules CSS sont pris en charge par défaut): nous n'utiliserons que des modules CSS dans ce tutoriel, vous êtes donc libre de choisir sass / less / stylus en fonction de vos préférences.


Choisissez une configuration de linter / formateur: TSlint est un choix évident car nous voulons utiliser TypeScript autant que possible.


Choisissez des fonctionnalités supplémentaires pour les peluches: activez les deux ( a ). Le peluchage est bon.


Choisissez une solution de test unitaire: ce didacticiel se concentre sur Jest, vous devez donc le sélectionner.


Choisissez une solution de test E2E: ce tutoriel se concentre sur Cypress.


Où préférez-vous placer la configuration pour Babel, PostCSS, ESLint, etc.? Ne pensez-vous pas que c'est un peu étrange que tout le monde essaie d'encore plus de choses dans package.json ? Le fichier est à peine lisible tel qu'il est maintenant. Utilisez des fichiers de configuration dédiés - ils sont beaucoup plus faciles à utiliser et votre historique git sera plus joli.


Il est temps de vérifier la configuration, exécuter le yarn serve :


Il ne devrait y avoir aucune erreur dans la console et la navigation vers http: // localhost: 8080 / vous accueillera avec:


Vérification du travail des tests unitaires, exécuter le yarn test:unit :


Et travail de test e2e ( yarn test:e2e --headless ):


Super! Passons à l'interface utilisateur.


Le dilemme matériel


Il existe quelques bibliothèques d'interfaces matérielles pour Vue à un niveau de flexibilité et de polissage différent. Il existe sûrement des dizaines d'autres bibliothèques de composants, vous êtes donc libre d'utiliser Bootstrap Vue si vous en avez envie. Ce didacticiel se concentre sur Vuetify pour plusieurs raisons:


  • c'est la bibliothèque de matériaux la plus étoilée sur GitHub;
  • c'était une douleur royale de le faire fonctionner, c'est donc une excellente démonstration de tous les cas de bord dans lesquels vous pouvez trébucher.

Convaincu? Procédez ensuite à l'installation: vue add vuetify . Sélectionnez l'option Configurer (avancé) .


Utilisez un modèle prédéfini? Vuetify remplacera App.vue et HelloWorld.vue par défaut. Répondez oui à ceci car c'est un nouveau projet.


Utiliser un thème personnalisé? Oui Vous en aurez besoin tôt ou tard, alors configurons-le. Pour les mêmes raisons, répondez oui à Utiliser des propriétés personnalisées (variables CSS)? .


Sélectionnez la police de l'icône: icônes de matériau (mais je vais vous montrer comment le corriger pour Font Awesome plus tard également). Utiliser des polices comme dépendance? Non. Nous obtiendrons les polices de CDN.


Utilisez des composants à la carte? Oui Cela semble être le moyen le plus simple d'utiliser Vuetify.


Il y a un tas de changements, mais le plus important lorsque vous exécutez le yarn serve maintenant, vous verrez une image différente:


(vous recevrez également une douzaine d'avertissements de votre linter).


Vérifions les tests unitaires ...


Faire fonctionner Vuetify avec les tests unitaires et e2e


Vérifions ./tests/unit/example.spec.ts . Le test vérifie que msg affiche « nouveau message » mais le modèle fourni avec Vuetify ne prend plus en charge cet accessoire. Dans une situation réelle, vous supprimeriez à la fois le composant HelloWorld et son test, mais ici, nous mettons à jour le message pour rechercher quelque chose qui se trouve dans le composant:


 const msg = 'Welcome to Vuetify'; 

Maintenant, le test réussit (vérifiez avec le yarn test:unit ) mais il y a encore une bonne douzaine d'avertissements similaires à


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

La façon dont Vuetify fonctionne, il ajoute ./src/plugins/vuetify.ts qui configure Vuetify dans le cadre de l'application. Ce fichier provient de ./src/main.ts . Malheureusement, main.ts est ignoré lorsque vous exécutez des tests unitaires.


Tout d'abord, vuetify.ts les erreurs et les avertissements dans le vuetify.ts généré.


Ouvrez votre ./tsconfig.json et ajoutez vuetify à la section compilerOptions.types :


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

Cela indique au compilateur TypeScript d'où obtenir les types Vuetify et l'erreur dans ./src/plugins/vuetify.ts disparaît. Corrigeons quelques avertissements de style pour le nettoyer:


 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', }); 

Nous devons maintenant charger Vuetify dans le cadre de nos tests unitaires. Créez un nouveau fichier sur ./tests/jest-setup.js avec le contenu suivant:


 import '@/plugins/vuetify'; 

et mettez à jour ./jest.config.js pour le charger:


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


Les tests échouent toujours mais de manière plutôt cryptique. Que s'est-il passé?


vuetify/lib est une source brute non traitée de Vuetify qui inclut des éléments comme les modules ES. Jest exécute les transformations uniquement pour votre code source par défaut, ce qui signifie qu'il ignore tout dans node_modules . Plus encore, étant donné que nous avons dit à Vue d'utiliser TypeScript, la plaisanterie n'est pas configurée pour transpiler JS.


Pour résoudre ce problème, nous devons apporter deux modifications à ./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) ], } 

Dans (1), nous demandons à Jest de transformer tout fichier *.js ou *.jsx avec Babel (et Babel est préconfiguré pour nous par Vue Cli), mais qu'est-ce que (2) ? transformIgnorePatterns spécifie les chemins que Jest ignorera lors du transpilage de code et, comme je l'ai noté précédemment, la valeur par défaut inclut node_modules . Ici, nous remplaçons la valeur par défaut par un regex cryptique node_modules/(?!(vuetify)/) qui signifie «ignorer tout chemin commençant par node_modules/ moins qu'il ne soit suivi par vuetify »:


Remarquez comment les deux premiers chemins ont une correspondance, mais pas le troisième. Cette astuce sera utile lorsque nous ajouterons Storybook, mais pour l'instant.


Relancer les tests ...


Les éléments personnalisés inconnus sont de retour; mais au moins il compile et s'exécute avec succès. Vuetify est transposé mais nous devons encore enregistrer les composants manuellement. Il existe quelques options sur la façon de le faire ( consultez leurs documents pour d'autres options ); ce que nous allons faire ici, c'est importer les composants requis dans la portée globale de Vue. Ouvrez à nouveau ./src/plugins/vuetify.ts et mettez-le à jour en:


 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', }); 

Enfin, les tests réussissent:


Les tests E2E échoueront également ( yarn test:e2e --headless ), mais cela est dû au fait que ./tests/e2e/specs/test.js recherche une chaîne qui n'est plus là. Les tests E2E font tourner votre application réelle dans un vrai navigateur, donc il n'y a pas de code à corriger - Vuetify est tout défini dans votre application. Corrigez le test.js pour rechercher le nouvel en-tête:


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

et il redeviendra vert.


Récapitulons. Nous avons ajouté Vuetify, corrigé les tests unitaires et e2e pour gérer un nouveau modèle et mis à jour Jest pour transpiler le code source de Vuetify et le charger. Notre application est fonctionnelle et nous pouvons utiliser différents composants matériels. Passons aux histoires!


Livres d'histoires


Les livres de contes sont une idée géniale: vous écrivez vos cas de test du point de vue du concepteur: de petits composants à l'application complète. Vous pouvez raisonner avec le flux de données, vous assurer que tout ressemble exactement à ce que votre concepteur d'interface utilisateur a présenté dans Photoshop, tester vos composants de manière isolée. Ajoutons le support du livre d'histoires!


Il y a un plugin Vue Storybook mais j'ai trouvé que sb init donne un modèle par défaut plus agréable, nous allons donc l'utiliser à la place. Exécutez npx -p @storybook/cli sb init et après quelques minutes, vous devriez obtenir une invite pour exécuter le yarn storybook . Faisons-le:


Ajoutons une nouvelle histoire! Créez ./src/components/LoveButton.stories.ts avec le contenu suivant:


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

(notez que vous pouvez utiliser LoveButton.stories.js ici si vous voulez être laxiste en tapant vos histoires).


TypeScript vous avertira des types manquants que vous pouvez corriger avec yarn add -D @types/storybook__vue .


Créez maintenant ./src/components/LoveButton.vue avec le contenu suivant:


 <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> 

Le ./stories d'histoires se penchera sur ./stories pour vos histoires par défaut, mais il est souvent plus pratique de garder les histoires plus proches de vos composants (comme nous l'avons fait). Pour dire au livre de contes où chercher ceux-ci, mettez à jour votre ./.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); 

Maintenant, exécutez yarn storybook nouveau le yarn storybook :


Pas trop excitant. La console regorge d'avertissements:


Mais nous savons de quoi il s'agit maintenant. Storybook est un autre contexte «racine» avec son propre point d'entrée; il n'utilise pas main.ts et en tant que tel ne charge pas Vuetify, nous devons donc lui dire de le faire. Mettre à jour ./.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); 

Nous chargeons à nouveau notre configuration existante, ce qui garantit que Storybook utilise le même thème que la vraie application. Malheureusement, le yarn storybook échouera maintenant:


Storybook ne sait pas que nous utilisons TypeScript, il ne peut donc pas charger le fichier vuetify.ts . Pour résoudre ce problème, nous devons mettre à jour la propre configuration du webpack de Storybook. Créez ./.storybook/webpack.config.js avec le contenu suivant:


 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; }; 

Cela charge la configuration par défaut, ajoute ts-loader pour les fichiers TypeScript et ajoute également la prise en charge de less et styl (que Vuetify utilise).


Les avertissements sont toujours là, car nous devons enregistrer les composants que nous avons utilisés. Utilisons cette fois des composants locaux pour que vous puissiez voir la différence (dans une vraie application de production, il est cependant beaucoup plus simple de tous les enregistrer dans vuetify.ts ). Mettez à jour ./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> 

Le livre d'histoires se rafraîchit lors de l'enregistrement:


Un peu mieux. Qu'est-ce qui manque? Le programme d'installation de Vuetify a ajouté les polices css directement dans ./public/index.html mais Storybook n'utilise pas ce fichier, nous devons donc ajouter la police Material Icons manquante. Créez ./.storybook/preview-head.hmtl avec ce qui suit (copie à partir 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"> 

(il existe d'autres façons de faire de même, par exemple en utilisant CSS @import ).


Vous devez redémarrer votre yarn storybook de yarn storybook pour qu'il se re-rende correctement:


Beaucoup mieux mais toujours en dessous: la police de texte est incorrecte car Vuetify s'attend à ce que tous ses composants soient imbriqués dans v-app qui applique les styles de page. Nous ne pouvons certainement pas ajouter v-app à notre bouton, alors décorons plutôt l'histoire. Mettez à jour vos ./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"/>`, })); 

Vous devez enregistrer VApp et VContent dans la portée globale, mettre à jour vos ./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', }); 

Enfin, le résultat est spectaculaire:


Ajout de tests de livre de contes


Enfin, assurons-nous que nos histoires sont couvertes par des tests unitaires. Ajoutez les dépendances requises: yarn add -D @storybook/addon-storyshots jest-vue-preprocessor babel-plugin-require-context-hook and create ./test/unit/storybook.spec.js :


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

La configuration du require.context de require.context utilise require.context pour collecter toutes les sources; cette fonction est fournie par webpack et nous devons utiliser babel-plugin-require-context-hook pour la remplacer dans Jest. Modifiez votre ./babel.config.js :


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

Ici, nous ajoutons le plugin require-context-hook si babel s'exécute pour des tests unitaires.


Enfin, nous devons autoriser Jest à transpiler les fichiers *.vue du livre d'histoires. Vous vous souvenez de cette expression régulière dans ./jest.config.js ? Revoyons-le maintenant:


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

Notez que nous ne pouvons pas simplement y ajouter une deuxième ligne. N'oubliez pas qu'il s'agit d'un modèle ignoré, donc si le premier modèle ignore tout sauf Vuetify, les fichiers du livre de contes sont déjà ignorés au moment où Jest arrive à la deuxième expression régulière.


Les nouveaux tests fonctionnent comme prévu:


Ce test exécutera toutes vos histoires et les vérifiera par rapport aux instantanés locaux dans ./tests/unit/__snapshots__/ . Pour le voir en action, vous pouvez supprimer <v-icon>favorite</v-icon> de votre composant bouton et relancer le test pour le voir échouer:


yarn test:unit -u mettra à jour votre instantané pour la nouvelle disposition des boutons.


Récapitulatif


Dans ce didacticiel, nous avons appris à créer une nouvelle application Vue avec TypeScript activé; comment ajouter la bibliothèque Vuetify avec les composants Material UI. Nous nous sommes assurés que nos tests unitaires et e2e fonctionnent comme prévu. Enfin, nous avons ajouté la prise en charge des livres d'histoires, créé un exemple d'histoire et fait en sorte que les modifications de l'interface utilisateur soient couvertes par nos tests unitaires.


Pensées de clôture


JS est un monde en mouvement, les choses changent constamment, de nouveaux modèles émergent, les anciens sont oubliés. Ce tutoriel peut être obsolète en seulement quelques mois, voici donc quelques conseils utiles.


Connaissez votre outillage. Il est possible de copier-coller des lignes à partir du débordement de la pile jusqu'à ce que votre code fonctionne, mais vous devez rechercher pourquoi le changement l'a fait fonctionner plus tard. Lisez les documents et assurez-vous de bien comprendre en quoi consiste exactement le changement.


Si vous avez quelque chose à travailler, même partiellement, faites un commit. Même si c'est un travail en cours, vous aurez quelque chose à revenir au cas où vos modifications ultérieures casseraient quelque chose.


Expérimentez! Si quelque chose ne fonctionne pas comme vous le pensez et les documents disent le contraire, essayez! Le monde frontal est principalement open-source, alors fouillez les sources tierces et voyez si vous pouvez bricoler vos instruments de l'intérieur pour ajouter une journalisation de débogage.

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


All Articles