Débuter un projet avec VueJS, Typescript et Webpack

VueJS est un framework JavaScript qui a actuellement le vent en poupe. La communauté s'agrandit de jour en jour et les tutos / articles de qualité ne manquent pas, même en Français. Cependant, lorsque l'on souhaite combiner VueJS avec d'autres technos sympas telles que TypeScript et Webpack, les ressources se font plus rares et l'initialisation d'un projet semble plutôt complexe.

Le but de cet article est d'apprendre à configurer pas à pas un environnement combinant ces 3 trois technos et de comprendre comment profiter au maximum de la puissance de Typescript et de la souplesse de VueJS.

 

1. Initialisation / Configuration

Après avoir lancé le traditionnel npm init, nous allons débuter notre projet en installant VueJS, TypeScript, Webpack et quelques dépendances utiles que nous allons utiliser par la suite :

npm i vue
npm i -D typescript webpack ts-loader css-loader vue-loader vue-template-compiler

 Nous allons maintenant ajouter un fichier tsconfig.json à notre projet et configurer le compilateur Typescript de manière suivante :

{
	"compilerOptions": {
		"outDir": "./dist/",
		"sourceMap": true,
		"strict": true,
		"noImplicitReturns": true,		
        "experimentalDecorators": true,
        "allowSyntheticDefaultImports": true,
        "noImplicitAny": false,
		"module": "es2015",
		"moduleResolution": "node",
		"target": "es5",
		"lib": [
            "dom",
            "es5",
            "es2015.promise"
        ]
	},
	"include": [
		"./src/**/*"
	]
}

Ensuite, nous allons créer un fichier webpack.config.js afin de configurer Webpack pour lui indiquer les loaders à utiliser pour nos différents fichiers (.ts, .vue, etc...) :

var path = require('path')
var webpack = require('webpack')

module.exports = {
  entry: './src/index.ts',
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/dist/',
    filename: 'build.js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            'scss': 'vue-style-loader!css-loader!sass-loader',
            'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
          }
        }
      },
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
        options: {
          appendTsSuffixTo: [/\.vue$/],
        }
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]?[hash]'
        }
      }
    ]
  },
  resolve: {
    extensions: ['.ts', '.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  },
  devServer: {
    historyApiFallback: true,
    noInfo: true
  },
  performance: {
    hints: false
  },
  devtool: '#eval-source-map'
}

En plus de cela, nous allons créer un répertoire /src/ et y ajouter un fichier vue-shim.d.ts pour que Typescript reconnaisse et traite correctement les fichiers ".vue" :

declare module "*.vue" {
  import Vue from 'vue'
  export default Vue
}

Nous allons terminer la configuration de notre projet en créant un dernier fichier index.ts dans /src/.

Nous pouvons maintenant compiler notre application en lançant un :

webpack --watch

Un fichier build.js a été généré dans /dist/. Nous allons y faire appel dans un fichier index.html que nous allons créer à la racine du projet :

<!doctype html>
<html lang="fr">

<head>
	<title>VueJS, Typescript et Webpack</title>
</head>

<body>
	<div id="app">
	</div>

	<script type="text/javascript" src="dist/build.js"></script>
</body>

</html>

 

2. Création d'un composant

Nous allons maintenant créer notre premier composant VueJS / Typescript. Pour cela nous allons ajouter un fichier Hello.vue dans le répertoire /src/components/ de notre projet. Ce composant aura pour rôle de souhaiter la bienvenue à l'utilisateur de notre application et devra prendre en paramètre 2 propriétés : Le nom de l'utilisateur et le nombre de "!" à afficher. L'utilisateur aura également la possibilité d'ajouter et d'enlever ces "!" qui seront au nombre de 3 par défaut.

<template>
    <div>
        <h1>Hello {{name}} {{exclamationMarks}}</h1>
        <button @click="decrement">-</button>
        <button @click="increment">+</button>
    </div>
</template>

<script lang="ts">
	import Vue from "vue";

	export default Vue.extend({
		props: {
			'name': String, 
			'nbExclamationMarks': {
				type: Number,
				default: 3
			}
		},
		data() {
			return {
				enthusiasm: this.nbExclamationMarks,
			}
		},
		methods: {
			increment() {
				this.enthusiasm++;
			},
			decrement() {
				if (this.enthusiasm > 1) {
					this.enthusiasm--;
				}
			},
		},
		computed: {
			exclamationMarks(): string {
				return Array(this.enthusiasm + 1).join('!');
			}
		}
	});
</script>

Le fichier que nous venons de créer contient 2 parties : une partie template (dans <template>) et une partie Typescript (dans <script>). L'attribut lang="ts" sur le <script> est nécessaire pour indiquer à Webpack qu'il faut utiliser le loader Typescript.

Pour rappel :

  • "props" contient les propriétés que doit recevoir le composant. Attention : Le typage des propriétés en VueJS est celui de Javascript (String, Number, Object...). Il ne doit pas être confondu avec le typage de Typescript.
  • "data" contient les propriétés accessibles au niveau du script et du template
  • "methods" contient les méthodes accessibles au niveau du script et du template
  • "computed" contient les méthodes "helpers" s'apparentant à des "get"
  • Le mot-clé "this" est en réalité un alias qui pointe sur les éléments déclarés dans "props" ou dans "data"

Il faut maintenant importer notre super composant et le déclarer dans une instance de VueJS dans le fichier index.ts :

import Vue from "vue";
import HelloComponent from "./components/Hello.vue";

let v = new Vue({
    el: "#app",
    template: `<hello-component name="Infinite Square" />`,
    components: {
        HelloComponent
    }
});

Nous utilisons l'élément <div id="app"> comme racine de notre application VueJS. Notre composant est importé via la syntaxe ES6 et est déclaré dans "components". Nous l'utilisons dans le template de l'instance en lui passant le paramètre "name" (l'autre paramètre étant optionnel).

Si le "watch" de Webpack tourne toujours, vous devriez avoir un fichier build.js correctement compilé et le résultat devrait être visible en ouvrant le fichier index.html dans votre navigateur :

2017-11-14_161218.png

3. Transformation en Classe et utilisation des décorateurs

La structure de la partie script du fichier Hello.vue n'est pas sans nous rappeler celle d'une classe. Il y a des propriétés, des méthodes, des getters... Et s'il était possible de transformer tout cela en vraie Classe TypeScript ? Le réponse est oui grace au package node "vue-property-decorator" que nous avons préalablement installé. 

Pour débuter notre transformation, nous allons remplacer l'import du module vue par :

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

En plus d'importer Vue, le module "vue-property-decorator" nous permet d'utiliser les décorateurs @Component et @Prop. Nous allons les utiliser pour créer notre Classe, toujours dans la partie <script> du fichier Hello.vue :

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

@Component
export default class HelloComponent extends Vue {
	@Prop({ type: String }) name: string;
	@Prop({ type: Number, default: 3 }) nbExclamationMarks: number;
	
	enthusiasm: number = this.nbExclamationMarks;
	
	increment() {
		this.enthusiasm++;
	}

	decrement() {
		if (this.enthusiasm > 1) {
			this.enthusiasm--;
		}
	}

	get exclamationMarks(): string {
		return Array(this.enthusiasm + 1).join('!');
	}
}

Plus de "props", "data", "methods" ou de "computed" : Tout est maintenant plus clair avec la syntaxe Typescript !

Nous utilisons dorénavant @Component pour identifier la classe comme un composant ainsi que @Prop pour les propriétés (avec la conservation du "typage JS" utilisé par VueJS pour son binding). Les autres variables sont implicitement considérées comme des "datas" au même titre que les méthodes qui sont considérées comme des "methods". Les getters sont eux rangés dans "computed".

Le module "vue-property-decorator" propose également d'autres décorateurs tels que @Model, @Watch et @Inject tous aussi utiles que nous verrons dans un futur proche.

 

Et si je ne souhaite pas utiliser les fichiers ".vue" ?

Vous trouvez peut-être que mélanger les langages n'est pas naturel ou bien rend le code moins lisible ? Et bien, il est possible de se passer des fichiers ".vue" et de dispatcher leur code en 2 fichiers.

Nous allons donc créer un fichier Hello.component.html :

<div>
	<h1>Hello {{name}} {{exclamationMarks}}</h1>
	<button @click="decrement">-</button>
	<button @click="increment">+</button>
</div>

Et un fichier Hello.component.ts :

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

@Component({
	template: require('./Hello.component.html')
})
export default class HelloComponent extends Vue {
	@Prop({ type: String }) name: string;
	@Prop({ type: Number, default: 3 }) nbExclamationMarks: number;
	
	enthusiasm: number = this.nbExclamationMarks;
	
	increment() {
		this.enthusiasm++;
	}

	decrement() {
		if (this.enthusiasm > 1) {
			this.enthusiasm--;
		}
	}

	get exclamationMarks(): string {
		return Array(this.enthusiasm + 1).join('!');
	}
}

Notez l'évolution du décorateur @Component qui fait maintenant référence au fichier HTML précédemment créé.

Nous devons maintenant modifier l'import du composant dans le fichier index.ts à la racine :

import HelloComponent from "./components/Hello.component";

A ce stade votre build webpack devrait vous renvoyer une erreur. Nous devons lui donner les moyens de charger des fichiers html dans nos scripts. Pour cela installons un nouveau loader 

npm i -D html-loader

Et appelons ce loader au niveau des "rules" dans le fichier webpack.config.js

{
	test: /\.html$/,
	loader: 'html-loader',
	options: {
		minimize: true
	}
}

Il se peut que Typescript vous remonte des erreurs sur l'import du fichier HTML au niveau du composant. Pour y palier, vous aurez peut-être besoin d'installer un @typing et de déclarer les ".html" comme des modules :

npm i -D @types/node

 

declare module "*.html" {
    const content: string;
    export default content;
}

Relancez Webpack et tout devrait fonctionner correctement !

 

Nous avons maintenant la possibilité de démarrer sereinement un projet en utilisant le meilleur de VueJS et de Typescript ! Bon coding !

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus