Initialiser un projet React sous TypeScript avec Webpack

Cet article à pour but d’aider à bien démarrer un nouveau projet React en utilisant TypeScript comme langage de programmation, le tout géré avec Webpack.

Webpack est un module bundler. Son rôle est d’importer tout type de fichier via des loaders permettant la transformation de chaque syntaxe vers des fichiers packagés lisibles depuis les navigateurs supportant ES5 (ECMAScript Edition 5). Webpack offre également une API pour gérer le HMR (Hot Module Reload) qui permet de relancer un module « à chaud » sans avoir à actualiser la page (via F5 par exemple). Webpack est très complexe à configurer donc nous ne survolerons ici qu’une partie des fonctionnalités. Je vous recommande cependant d’aller consulter la documentation en ligne en marge de cet article pour mieux appréhender cet outil.

Initialisation du projet avec npm

Avant d’aller plus loin, vous devez tout d’abord installer npm. npm est un outil de partage de module JavaScript. Il est fourni avec l’installation de Node.js.

Je vous invite également à installer en parallèle l’outil de commande Git Bash. Via l’intégration Windows, vous aurez ainsi la possibilité d’exécuter des commandes npm depuis vos projets plus facilement.

Une fois tous ces outils installés, depuis le dossier devant contenir votre projet, lancez un bash git (bouton droit > Git Bash Here), puis tapez la commande suivante:

npm init

Vous serez amené à répondre à différentes questions (libre à vous de sélectionner ou non les valeurs par défaut) qui vont générer à terme un fichier package.json à la racine de votre projet.

{
  "name": "your-project-name",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Nous allons maintenant procéder à l’installation des dépendances de production et de développement (React, Webpack et Typescript) pour notre projet React.js.

Exécutez l’ensemble des commandes ci-dessous :

npm install --save-dev webpack webpack-cli webpack-dev-server chalk html-webpack-plugin
npm install --save-dev typescript awesome-typescript-loader source-map-loader

Les paramètres –save et –save-dev indiquent respectivement que les dépendances de production et de développement sont installées localement au projet et non globalement à votre machine. Il est préférable de le faire de cette manière car cela vous permettra de maintenir des versions de dépendances différentes entre vos projets sans subir d’éventuels « breaking changes ».

awesome-typescript-loader est un loader permettant la transpilation de TypeScript avec Webpack (vous pouvez éventuellement utiliser ts-loader à la place), tandis que source-map-loader permet l’extraction des source maps des fichiers existants. Cela permet de faciliter le debug des fichiers packagés via un outil de développement web (Chrome DevTool par exemple). Ce dernier est bien sûr déconseillé en mode de production.

Votre fichier package.json doit maintenant ressembler à ça et un dossier node_modules (ainsi qu’un fichier package-lock.json) a dû apparaître à la racine de votre projet. Celui-ci contient toutes les dépendances de votre projet :

{
  "name": "your-project-name",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@types/react": "^16.3.14",
    "@types/react-dom": "^16.0.5",
    "react": "^16.4.0",
    "react-dom": "^16.4.0"
  },
  "devDependencies": {
    "awesome-typescript-loader": "^5.0.0",
    "chalk": "^2.4.1",
    "html-webpack-plugin": "^3.2.0",
    "source-map-loader": "^0.2.3",
    "typescript": "^2.8.3",
    "webpack": "^4.8.3",
    "webpack-cli": "^2.1.4",
    "webpack-dev-server": "^3.1.4"
  }
}

Les numéros de version des dépendances sont susceptibles de changer en fonction des mises à jour effectuées entre la publication de cet article et le moment où vous le lirez.

Ajout du fichier tsconfig.json

Le fichier tsconfig.json définit le nœud racine de votre application ainsi que les options de compilation requises par votre projet utilisant TypeScript. Vous pouvez obtenir de plus amples informations sur le paramétrage du fichier depuis le lien suivant.

Créez à la racine de votre projet un fichier tsconfig.json et copiez le contenu suivant dans ce fichier :

{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "commonjs",
    "target": "es5",
    "lib": ["es6", "dom"],
    "jsx": "react"
  },
  "exclude": ["node_modules", "**/*.spec.ts"],
  "include": ["./src/**/*"]
}
  • outDir indique au compilateur la destination des fichiers transpilés.
  • target définit le niveau de transpilation que l’on souhaite (ici en ECMAScript 5), permettant le niveau désiré de compatibilité avec les anciens navigateurs.
  • lib permet de fournir une liste de librairies que l’on souhaite pouvoir utiliser et injecter dans la compilation.
  • jsx indique le support de React depuis les fichiers tsx.

Hello World!

Nous allons maintenant développer un petit projet TypeScript se basant sur la librairie ReactJS. Autant profiter des avantages de TypeScript en utilisant les principes de la programmation orientée objet dans ce petit exemple. Dans un premier temps vous allez créer l’ensemble des fichiers pour que l’arborescence de votre projet ressemble à ça :

Project Tree
Arborescence du projet « Hello World »

Passons ensuite au code contenu dans les différents fichiers.

Nous allons implémenter une interface simple définissant les propriétés attendues par le composant Hello défini un peu plus loin.

export interface IHelloProps {
    name: string;
}

Ci-dessous le composant Hello qui utilise la librairie ReactJS pour restituer une balise HTML. Notez l’import en chemin relatif de l’interface IHelloProps que nous avons précédemment rédigée. Dans le cas contraire TypeScript tentera de rechercher l’interface depuis le dossier des node_modules.

import * as React from 'react';
import { IHelloProps } from '../interfaces/IHelloProps';
 
export class Hello extends React.Component<IHelloProps, {}> {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

Rédigeons maintenant le contenu du fichier index.tsx. Celui-ci se charge d’intégrer le contenu de notre code dans la vue depuis un élément HTML identifié comme root. Pour plus d’explications sur l’utilisation du langage ReactJS, je vous invite à suivre le tutoriel contenu sur le site officiel.

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Hello } from './components/hello';
 
ReactDOM.render(<Hello name="World" />, document.getElementById('root'));

Enfin, nous avons notre vue HTML qui nous permettra d’afficher notre composant auprès des utilisateurs.

<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <title>React with TypeScript, bundled with Webpack</title>
</head>
 
<body>
    <div id="root"></div>
</body>
 
</html>

A un détail près, vous devriez vous demander comment la vue est-elle capable d’afficher le composant alors qu’aucune référence n’est définie ? Je pourrais effectivement ajouter des balises <script> référençant les différents modules, mais nous allons utiliser les outils mis à notre disposition par Webpack pour les générer automatiquement.

Ajout du fichier webpack.config.js

Ajoutez à la racine du projet un fichier webpack.config.js et copier le script suivant dedans :

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
 
const DIST_DIR = path.resolve(__dirname, 'dist');
const SRC_DIR = path.resolve(__dirname, 'src');
const config = {
  mode: 'development',
 
  // File entry of the project.
  entry: {
    app: SRC_DIR + '/app/index.tsx'
  },
 
  // Define output directory.
  output: {
    path: DIST_DIR,
    filename: '[name].bundle.js'
  },
 
  // Enable sourcemaps for debugging webpack's output.
  devtool: 'inline-source-map',
 
  // Manage loaders rules.
  module: {
    rules: [
      // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
      {
        test: /\.tsx?$/,
        include: SRC_DIR,
        exclude: /(node_modules|bower_components)/,
        loader: 'awesome-typescript-loader'
      },
      // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
      {
        test: /\.js$/,
        include: SRC_DIR,
        exclude: /(node_modules|bower_components)/,
        loader: 'source-map-loader',
        enforce: 'pre'
      }
    ]
  },
 
  resolve: {
    // Add selected extension files as resolvable extensions.
    extensions: ['.ts', '.tsx', '.js', '.json', '.jsx']
  },
 
  plugins: [
    new HtmlWebpackPlugin({
      hash: true,
      title: 'React with TypeScript, bundled with Webpack',
      template: SRC_DIR + '/index.html',
      filename: DIST_DIR + '/index.html'
    }),
    new webpack.NamedModulesPlugin(),
    new webpack.HotModuleReplacementPlugin()
  ],
 
  // Mount web server with HMR.
  devServer: {
    historyApiFallback: true,
    contentBase: DIST_DIR,
    compress: true,
    port: 9000,
    hot: true,
    inline: true
  }
};
 
module.exports = config;

Nous utilisons ici plusieurs fonctionnalités de Webpack.

La première est que Webpack se charge de transpiler le code TypeScript via les loaders définis dans la zone module, puis de le packager vers un fichier bundle.js déclaré dans la zone output et qui sera ajouté à notre vue automatiquement grâce au plugin HtmlWebpackPlugin. Le plugin utilise la vue HTML comme template pour y ajouter la référence au fichier bundle.js lors de la compilation. On peut également l’utiliser pour ajouter autre chose en déclarant des variables (title par exemple) et en modifiant la balise head de la vue HTML comme suit :

<head>
    <meta charset="UTF-8">
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>

La deuxième fonctionnalité est l’utilisation d’un serveur web (devServer) qui se chargera de monter pour nous la vue dans un navigateur depuis l’adresse localhost:9000, mais aussi de recompiler et d’actualiser tout changement effectué dans le code source dans la page web.

Vous verrez souvent des exemples qui utilisent des chemins relatifs dans le fichier de configuration pour définir les dossiers d’entrées ou de sorties du projet, mais dans les faits et pour plus de certitude il est toujours préférable de déclarer des chemins absolus.

Nous allons maintenant automatiser notre déploiement en ajoutant des scripts npm depuis le fichier package.json. Modifiez le fichier de la manière suivante :

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "npm run build",
    "build": "webpack -d && webpack-dev-server"
  }

Vous pouvez à présent via une invite de commande ouverte depuis la racine du projet exécuter la commande suivante :

npm start

C’est un raccourci qui exécutera le script défini sous la commande build. Vous auriez pu directement utiliser la commande suivante à la place : npm run build. Plus d’info sur les commandes de scripts npm ici.

Si tout s’est bien passé vous devriez à présent avoir d’ouvert dans votre navigateur par défaut une page web pointant sur l’adresse localhost:9000 et affichant le message « Hello World ».

Conclusion

Vous avez vu dans cet article comment installer un projet web TypeScript « from scratch ». Nous y avons associé la librairie ReactJS, et avons utilisé Webpack pour compiler et générer les packages de déploiement. Avec ce tutoriel vous avez acquis les bases pour réaliser n’importe quel site à l’aide des nouvelles technologies du web. A vous maintenant d’explorer chaque composant pour en tirer le meilleur partie.

Tips

Si vous avez jeté un œil dans le code distribué généré par Webpack dans le dossier dist, vous avez peut-être remarqué la taille conséquente du fichier bundle.js généré. Ceci est dû au fait que nous y ajoutons également les librairies ReactJS. Heureusement, il est tout à fait possible de configurer Webpack pour qu’il ne prenne pas en compte certaines librairies ou frameworks statiques (Angular par exemple) afin d’alléger la compilation du bundle. Pour cela modifiez le fichier webpack.config.js en y ajoutant la configuration suivante :

externals: {
    "react": "React",
    "react-dom": "ReactDOM"
}

Vous allez indiquer à Webpack que les dépendances react & react-dom utilisant les variables d’import React et ReactDOM ne doivent pas être comprises dans le bundle. Il vous faut par contre modifier votre vue HTML pour y référencer les deux modules. Il existe plusieurs méthodes. Vous pouvez télécharger les librairies et les copier localement sur votre machine. Vous pouvez aussi utiliser un lien vers un CDN (Content Delivery Network) possédant ces deux librairies. Nous allons opter pour cette deuxième solution (voir le lien suivant pour plus d’informations sur le CDN). Procédez comme suit :

<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <title><%= htmlWebpackPlugin.options.title %></title>
</head>
 
<body>
    <div id="root"></div>
    <!-- Dependencies -->
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
</body>
 
</html>

Relancez la compilation via la commande et vérifiez le contenu du dossier dist :

npm start

Vous verrez que votre fichier bundle.js passe de 1.7Mo à … 32Ko. En plus du gain de poids, l’idée d’utiliser des modules depuis des liens CDN permet d’exploiter au mieux le cache de votre navigateur.

Resources

Laisser un commentaire

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

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

Initialiser un projet React sous TypeScript avec Webpack

par Cyrille Perrot Temps de lecture: 9 min
0