Publié par

Il y a 4 semaines -

Temps de lecture 11 minutes

Booster votre application Vue.js avec TypeScript

Progressivement TypeScript devient la norme dans les projets Web. D’ailleurs, ces derniers temps plusieurs projets open source ont fait le choix de l’adopter pour leur base de code comme Slack et Lyft. Loin d’être les seuls, les développeurs de Jest et Yarn, de chez Facebook, ont également entrepris de remplacer Flow par TypeScript. Cela démontre un tournant significatif, en effet Flow est un concurrent direct de TypeScript qui a pourtant été conçu par Facebook.

Même décision prise par Evan You et son équipe pour la base de code de la version 3.0 de Vue.js. Il est possible de trouver bon nombre d’annonces similaires sur Twitter.

On pourrait aussi évoquer Angular qui depuis sa réécriture en 2016 utilise par défaut TypeScript.

Bref, c’est un langage qui a le vent en poupe et nous allons voir dans cet article comment l’utiliser dans une application Vue.js.

TypeScript kézako ?

Ce langage est de plus en plus utilisé, mais pourquoi ? Quels sont ses avantages ?

TypeScript est un langage de programmation développé par Microsoft et sorti en 2012. Il s’agit d’un sur-ensemble de JavaScript.

Il ajoute un système de typage statique puissant, grâce auquel il est possible de :

  • typer n’importe quelle variable, propriété d’une classe ou paramètre d’une fonction ;
  • utiliser des interfaces ;
  • utiliser les génériques sur des classes ;
  • créer des types union ;
  • créer des enums ;
  • l’assignation de valeur null et undefined doivent être explicité dans le type de l’élément.

Ceci permet d’avoir un code qui est plus expressif, plus sûr car les erreurs de type sont vérifiées à la compilation. En effet, les sources écrites en TypeScript sont transpilées en JavaScript pour être exécutées à l’aide de la commande tsc. De ce fait, les programmes TypeScript sont 100% compatibles avec les différents moteurs JavaScript existants. Au-delà de la détection des erreurs au plus tôt, les IDE modernes comme WebStorm ou VSCode peuvent s’appuyer sur la définition des types de notre application pour proposer une auto-complétion efficace.

A noter que les fichiers TypeScript possèdent l’extension .ts pour les différencier des fichiers JavaScript.

Pour aller plus loin n’hésitez pas à consulter la documentation officielle qui est très bien faite, à commencer par le guide présentant TypeScript aux développeurs JavaScript.

Utilisation de bibliothèques

Beaucoup de bibliothèques utilisées encore aujourd’hui sont écrites en JavaScript et même s’il est possible de les utiliser sans problèmes dans un projet TypeScript, il serait préférable d’avoir la définition de leurs APIs publiques. Tout simplement pour profiter des mêmes avantages que pour notre code applicatif et ainsi éviter les erreurs d’utilisation de ces bibliothèques.

Heureusement pour nous, un grand nombre de définitions est disponible sur NPM sous forme de dépendances dont le nom commence généralement par @types, par exemple pour Jest avec le package @types/jest. La plupart de ces définitions proviennent du projet https://github.com/DefinitelyTyped/DefinitelyTyped, qui en possède aujourd’hui pratiquement 6000.

TypeScript et Vue.js

Tout comme React avec les fichiers .tsx, Vue.js possède un support de TypeScript complet incluant les principaux types de la bibliothèque mais aussi les types des plugins officiels vue-router et vuex.

En plus de la syntaxe classique en plain object, ce support permet aussi de définir les composants sous forme de classe.

Syntaxe plain object

Elle repose sur la syntaxe de base avec les types en plus, pour l’activer il suffit de passer le composant à la fonction Vue.extend.

Voici un exemple :

export default Vue.extend({
  props: {
    brand: String,
    year: Number
  },
  created (): void {
    // Le composant a été instancié : il est possible par exemple de faire un appel d'API pour récupérer les données du véhicule
  },
  watch: { 
    year (): void { 
      console.log('Year changed ' + newValue);
    } 
  },
  computed: {
    description (): string {
      return `${this.brand} since ${this.year}`;
    }
  },
})

Syntaxe sous forme de classe

Vue.js propose aussi d’écrire ses composants sous forme de classe en utilisant des décorateurs, assez proche de ce qui se fait en Angular. Concrètement, la classe représentant le composant doit être décorée par @Component. Il existe un décorateur pour chaque fonctionnalité d’un composant.

  • Les propriétés d’entrée ou props correspondent à des propriétés de la classe décorées par @Prop.
  • Pour observer les changements d’une propriété – par exemple my-watched-property – il faut décorer une méthode avec @Watch('my-watched-property').
  • Pour définir une propriété interne du composant – que l’on déclare habituellement avec la fonction data – il suffit de déclarer une simple propriété de classe sans décorateur.
  • Les computed properties deviennent de simple getter en utilisant l’opérateur get.
  • Les hooks du cycle de vie se traduisent par des méthodes nommées created, mounted ou encore destroyed.

Pour illustrer reprenons l’exemple précédent :

import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class Car extends Vue {
  @Prop() private brand?: string;
  @Prop() private year?: number;

  created(): void {
    // Le composant a été instancié : il est possible par exemple de faire un appel d'API pour récupérer les données du véhicule
  }

  @Watch('year')
  onYearChanged(newValue: number) {
    console.log('Year changed ' + newValue);
  }

  get description() {
    return `${this.brand} since ${this.year}`;
  }
}

Maintenant, comment faire pour déclarer la dépendance vers un autre composant ? Il faut savoir que le décorateur @Component accepte un paramètre qui est un objet similaire à un composant Vue.js.

Ainsi il est possible d’utiliser la syntaxe old-fashioned way pour définir ce dont nous avons besoin, comme la dépendance vers d’autres composants et c’est – il me semble – la seule façon de faire.

import Basket from "./Basket.vue";
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';

@Component({
  components: {
    Basket
  }
})
export default class Car extends Vue {
}

A noter que ce décorateur est disponible dans le paquet officiel https://github.com/vuejs/vue-class-component, alors que les autres décorateurs font parties du paquet non officiel https://github.com/kaorun343/vue-property-decorator.

Cette syntaxe rend, de mon point de vue, l’écriture du composant plus claire car les différentes sections du composant sont bien séparées.

Comparatif

Ci-dessous un tableau comparant les deux syntaxes :

Plain object component Class based component
export default {
@Component
export default class HelloWorld extends Vue {
Propriété interne (data)
  data() {
    return {
      firstName: 'Alice',
      lastName: 'Bob',
    }
  },
  firstName: string = 'Alice';
  lastName: string = 'Bob';



Les propriétés externes (prop)
  props: ['msg', 'age'],
  
  @Prop() msg?: string;
  @Prop() age?: number;
Les computed properties
  computed: {
    fullName() {
      return `${this.firstName} ${this.lastName}`;
    },
  },
  get fullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }
  
  
Les hooks du cycle de vie
  created() {
  },
  mounted() {
  },
  
  created() {
  }

  mounted() {
  }
Les méthodes
  methods: {
    compute1(param1, param2) {
      // ...
    },
    compute2(param1, param2) {
      // ...
    },
  },
  compute1(param1: string, param1: number) {
    // ...
  }

  compute2(param1: string, param1: number) {
    // ...
  }
  
Les watchers
  watch: {
    firstName(value, oldValue) {
      console.log('firstName changed to', value);
    }
  }
}
  @Watch('firstName', {deep: true})
  firstNameChanged(value: string, oldValue: string) {
    console.log('firstName changed to', value);
  }
}

Comment l’utiliser dans mon projet ?

From scratch

Tout d’abord il faut installer Vue CLI, si ce n’est pas déjà fait.

npm install -g @vue/cli

L’initialisation d’un nouveau projet se fait avec la commande create qui offre la possibilité de choisir un certain nombre d’options de configuration.

vue create my-app

Initialiser une application Vue.js avec TypeScript et les composants sous forme de classe avec le CLIComme illustré ci-dessus, pour configurer TypeScript il faut choisir l’option “Manually select features” ce qui permet d’activer les fonctionnalités suivantes :

  • TypeScript ;
  • Use class-style component : uniquement nécessaire pour les composants sous forme de classe évidement ;
  • ESLint, et non TSLint car déprécié début 2019.

Sur un projet existant initié avec Vue CLI

La solution ci-dessous fonctionne pour les projets ayant été initialisés par vue-cli 3.0 et utilisant donc Vue CLI service.

Cependant je vous conseille tout de même d’utiliser la dernière version de Vue CLI.

npm install -g @vue/cli

Puis il suffit de lancer la commande suivante à la racine de votre projet :

vue add @vue/typescript

Et c’est tout.

En effet, cette commande pose les mêmes questions que lors de la création d’un projet from scratch et va :

  • ajouter les dépendances ;
  • ajouter les fichiers de configuration ;
  • et même convertir, si vous le souhaitez, les fichiers JavaScript en TypeScript.

Behind the magic

Concrètement, les devDependencies suivantes ont été ajoutées :

npm -D i @types/vue @vue/cli-plugin-typescript @vue/eslint-config-typescript typescript

Pour utiliser la syntaxe orientée classe pour les composants, il faut aussi ajouter les dépendances vue-class-component et vue-property-decorator dans la section dependencies cette fois-ci :

npm -S i vue-class-component vue-property-decorator

Configuration

Des fichiers de configuration notamment pour TypeScript sont ajoutés au projet :

  • tsconfig.json
  • src/shims-tsx.d.ts
  • src/shims-vue.d.ts

Il est aussi nécessaire d’indiquer à ESLint de prendre en compte les règles TypeScript en ajoutant à sa configuration @vue/typescript dans la section extends, par défaut elle se trouve dans le fichier .eslintrc.js.

module.exports = {
  root: true,
  env: {
    node: true
  },
  'extends': [
    'plugin:vue/essential',
    'eslint:recommended',
    '@vue/typescript' // à ajouter
  ],
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
  },
  parserOptions: {
    parser: '@typescript-eslint/parser'
  }
}

Conversion du code

L’extension des fichiers .js doit être changée en .ts pour que cela fonctionne.

Les composants qui ont été écrits avec la syntaxe classique, et ceux écrits en TypeScript peuvent cohabiter sans problème. Dans les fichiers “vue”, pour indiquer qu’un composant est écrit en TypeScript, il est nécessaire d’ajouter l’attribut lang avec la valeur ts au bloc script :

<script lang="ts">
import { Prop } from 'vue-property-decorator';
import Vue from 'vue';
import Component from 'vue-class-component';

@Component
export default class HelloWorld extends Vue {
 @Prop()
 msg?: string;

 secretVisible = false;

 created() {
   this.secretVisible = true;
 }
}
</script>

A noter qu’il est impératif de préciser l’extension .vue lorsque de l’import d’un composant dans un fichier .ts ou dans un autre composant écrit en TypeScript (lang="ts"), sinon le composant n’est pas résolu. Pour plus d’informations je vous invite à regarder cet issue.

Vous trouverez sur ce dépôt un exemple de projet basique qui a été migré en TypeScript. Ce commit montre quelles modifications ont été nécessaires pour réaliser cette migration.

Mon projet utilise une ancienne version de Vue.js

Pour les projets utilisant une version plus ancienne de Vue.js (< 2.5), la solution ci-dessus ne fonctionnera pas. En effet, il est nécessaire d’avoir un projet utilisant Vue CLI service pour mettre en place cette architecture.

Pas de panique, cependant, ce n’est pas perdu mais cela implique de migrer votre projet vers une version récente de Vue.js. Heureusement, l’un des points fort de ce framework est d’être rétro-compatible, vous ne devriez donc pas rencontrer trop d’écueils. Vous trouverez un exemple de migration sur cet autre dépôt en regardant ce commit.

Erreurs classiques que vous pourriez rencontrer

Dans un composant en TypeScript (lang="ts"), ne pas oublier d’ajouter l’extension .vue lors de l’import d’un composant contenu dans un fichier vue comme indiqué dans le paragraphe Conversion du code.

Lors du bootstrap de l’application Vue.js dans le fichier main.ts : utiliser la propriété render à la place de template car le compilateur de template n’est plus embarqué au runtime pour les applications utilisant Vue CLI service pour des raisons de performances. Ce qui vous permettra d’éviter l’erreur [Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available.

new Vue({
  render: h => h(App),
}).$mount('#app');

Conclusion

Comme nous l’avons vu, le coût du passage à TypeScript est très faible, alors qu’il apporte énormément d’avantages en contrepartie :

  • une meilleure robustesse de l’application car on détectera les erreurs au plus tôt ;
  • une meilleure lisibilité grâce aux types qui constituent, par ailleurs, une documentation améliorée ;
  • une meilleure intégration aux outils permettant un refactoring plus précis et performant ;
  • un meilleur confort pour le développeur grâce à une auto-complétion plus intelligente.

Tout ceci explique pourquoi tant de projets se tournent vers ce langage et délaissent petit à petit le bon vieux JavaScript.

Ainsi, aucune raison de ne pas sauter le pas et de migrer son application écrite en Vue.js, d’autant qu’il est possible de le faire de manière incrémentale.

De plus, la version 3.0 de Vue.js, dont la Bêta est sortie fin Avril, apporte un meilleur support de TypeScript. En effet, le code source de Vue a été entièrement réécrit en TypeScript. Il sera alors possible d’utiliser les vrais types utilisés par Vue.js en interne, plutôt qu’une définition écrite à côté. Spoiler alert, cette version apporte de meilleures performances, jusqu’à deux fois plus rapide, un runtime plus léger et reste rétro-compatible. Mais surtout elle apporte de nouvelles fonctionnalités comme l’API Composition qui s’inspire des Hooks de React.

Publié par

Commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.