DevTips – Comment compiler efficacement vos ressources
Publié: 2022-08-25L'un des objectifs que nous avions pour cette année était de refactoriser nos deux plugins phares (Nelio Content et Nelio A/B Testing) en TypeScript et React Hooks. Eh bien, nous venons de passer la moitié de l'année et nous pouvons déjà dire que cet objectif a été un succès total. Cependant, je dois admettre que la route a été un peu plus compliquée que prévu… surtout si l'on considère qu'après l'introduction de TypeScript, notre les temps de construction des plugins sont passés de quelques secondes à plus de deux minutes ! Quelque chose n'allait pas et nous ne savions pas quoi.
Eh bien, dans le post d'aujourd'hui, j'aimerais vous parler un peu de cette expérience et de ce que nous avons fait pour y remédier. Après tout, nous savons tous que TypeScript ralentira toujours un peu le processus de construction (la vérification de type a un prix), mais cela ne devrait pas être si important ! Eh bien, alerte spoiler : le problème n'a jamais été TypeScript… c'était ma config. TypeScript n'a fait que le rendre "évident". Alors commençons, d'accord ?
Projet de jouet
Pour vous aider à comprendre le problème que nous avons rencontré il y a quelques semaines et comment nous l'avons résolu, le mieux que nous puissions faire est de créer un exemple très simple à suivre. Construisons un plugin WordPress simple qui utilise TypeScript et comment une mauvaise configuration peut entraîner des temps de compilation extrêmement lents. Si vous avez besoin d'aide pour démarrer, consultez cet article sur les environnements de développement WordPress.
Création de plugins
La première chose à faire est de créer un nouveau dossier avec le nom de votre plugin (par exemple, nelio
) dans le répertoire /wp-content/plugins
de votre WordPress. Ajoutez ensuite le fichier principal ( nelio.php
) avec le contenu suivant :
<?php /** * Plugin Name: Nelio * Description: This is a test plugin. * Version: 0.0.1 * * Author: Nelio Software * Author URI: https://neliosoftware.com * License: GPL-2.0+ * License URI: http://www.gnu.org/licenses/gpl-2.0.txt * * Text Domain: nelio */ if ( ! defined( 'ABSPATH' ) ) { exit; }
Si vous l'avez bien fait, vous verrez que vous pouvez maintenant activer le plugin dans WordPress :

Bien sûr, le plugin ne fait rien pour le moment… mais au moins il apparaît
Manuscrit
Ajoutons du code TypeScript ! La première chose que nous ferons est d'initialiser npm dans notre dossier de plugins. Lance ça:
npm init
et suivez les instructions à l'écran. Ensuite, installez les dépendances :
npm add -D @wordpress/scripts @wordpress/i18n
et modifiez le fichier package.json
pour ajouter les scripts de construction requis par @wordpress/scripts
:
{ ... "scripts": { "build": "wp-scripts build", "start": "wp-scripts start", }, ... }
Une fois que npm est prêt, personnalisons TypeScript en ajoutant un fichier tsconfig.json
:
{ "compilerOptions": { "target": "es5", "module": "esnext", "moduleResolution": "node", "outDir": "build", "lib": [ "es7", "dom" ] }, "exclude": [ "node_modules" ] }
Enfin, écrivons du code TS. Nous voulons que ce soit très simple mais "suffisamment proche" de ce que nous avions dans Nelio A/B Testing et Nelio Content, alors créez un dossier src
dans notre projet avec quelques fichiers TypeScript à l'intérieur : index.ts
et utils/index.ts
.
D'une part, supposons que utils/index.ts
est un package utilitaire. Autrement dit, il contient quelques fonctions dont d'autres fichiers de notre projet pourraient avoir besoin. Par exemple, disons qu'il fournit les fonctions classiques min
et max
:
export const min = ( a: number, b: number ): number => a < b ? a : b; export const max = ( a: number, b: number ): number => a > b ? a : b;
D'autre part, regardons le fichier principal de notre application : index.ts
. Pour nos besoins de test, tout ce que nous voulons, c'est un script simple qui utilise notre package utilitaire et une dépendance WordPress. Quelque chose comme ça:
import { __, sprintf } from '@wordpress/i18n'; import { min } from './utils'; const a = 2; const b = 3; const m = min( a, b ); console.log( sprintf( /* translators: 1 -> num, 2 -> num, 3 -> num */ __( 'Min between %1$s and %2$s is %3$s', 'nelio' ), a, b, m ) );
@wordpress/scripts Paramètres par défaut
Si nous devions construire le projet maintenant en utilisant npm run build
, tout fonctionnerait immédiatement. Et c'est simplement parce que @wordpress/scripts
(c'est-à-dire l'outil sous-jacent que nous utilisons pour construire notre projet) a été conçu pour fonctionner avec une base de code comme celle de notre exemple. Autrement dit, si nous avons un fichier index.ts
dans le dossier src
, il générera un fichier index.js
dans le dossier build
avec un fichier de dépendance index.asset.php
:
> ls build index.asset.php index.js
Pourquoi deux fichiers ? Eh bien, l'un est le fichier JavaScript compilé (duh) et l'autre est un fichier de dépendances contenant des informations utiles sur notre script. En particulier, il nous indique sur quelles bibliothèques JavaScript, parmi celles incluses dans WordPress, il s'appuie. Par exemple, notre index.ts
s'appuie sur le package @wordpress/i18n
pour internationaliser les chaînes, et c'est une bibliothèque incluse dans WordPress donc… oui, wp-i18n
apparaîtra dans index.asset.php
:
build/index.asset.php <?php return array( 'dependencies' => array( 'wp-i18n' ), 'version' => 'c6131c7f24df4fa803b7', );
Malheureusement, la configuration par défaut n'est pas parfaite, si vous me demandez. Voici pourquoi.
Si nous introduisons un bogue dans votre code (par exemple, appelons la fonction min
avec une string
arg au lieu d'un number
):
const m = min( `${ a }`, b );
cela devrait déclencher une erreur. Mais ce n'est pas le cas. Il compile sans problème.
Vérifications de type lors de la compilation avec TypeScript
Pour résoudre la "limitation" susmentionnée, il nous suffit de créer notre propre fichier de configuration Webpack et de lui dire d'utiliser tsc
(le compilateur TypeScript) chaque fois qu'il rencontre du code TS. En d'autres termes, nous avons besoin du fichier webpack.config.json
suivant :
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); const config = { ...defaultConfig, module: { ...defaultConfig.module, rules: [ ...defaultConfig.module.rules, { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/, }, ], }, }; module.exports = { ...config, entry: './src/index', output: { path: __dirname + '/build', filename: 'index.js', }, };
Comme vous pouvez le voir, il commence par charger la configuration webpack par défaut incluse dans le package @wordpress/scripts
, puis étend le defaultConfig
en ajoutant un ts-loader
à tous les fichiers .ts
. Très facile!
Et maintenant, voici :
> npm run build ... ERROR in ...src/index.ts TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. webpack 5.74.0 compiled with 1 error in 4470 ms
la compilation de notre projet entraîne une erreur. Hourra! Bien sûr, c'est un peu plus lent, mais au moins nous avons quelques contrôles de sécurité avant de télécharger quoi que ce soit en production.
Mise en file d'attente des scripts
Eh bien, maintenant que vous savez qu'il y a un problème dans votre code, corrigez-le et compilez à nouveau le plugin. Est-ce que tout a fonctionné ? Cool! Parce qu'il est maintenant temps de mettre en file d'attente le script et ses dépendances sur PHP afin que nous puissions l'essayer dans notre navigateur.
Ouvrez nelio.php
et ajoutez l'extrait suivant :
add_action( 'admin_enqueue_scripts', function() { $path = untrailingslashit( plugin_dir_path( __FILE__ ) ); $url = untrailingslashit( plugin_dir_url( __FILE__ ) ); $asset = require( $path . '/build/index.asset.php' ); wp_enqueue_script( 'nelio', $url . '/build/index.js', $asset['dependencies'], $asset['version'] ); } );
Ensuite, rendez-vous sur le tableau de bord WordPress (n'importe quelle page fera l'affaire) et jetez un œil à la console JavaScript de votre navigateur. Vous devriez voir le texte suivant :
Min between 2 and 3 is 2
Agréable!
Qu'en est-il de MES dépendances ?
Parlons un instant de la gestion des dépendances en JavaScript/webpack/WordPress. @wordpress/scripts
est configuré de telle sorte que, par défaut, si votre projet utilise une dépendance qui est empaquetée dans le noyau de WordPress, elle sera répertoriée comme telle dans le fichier .asset.php
. Ceci, par exemple, explique pourquoi @wordpress/i18n
a été répertorié dans le fichier de dépendances de notre script.
Mais qu'en est-il des dépendances aux "autres" packages ? Qu'est-il arrivé à notre package d' utils
? Pour faire court : par défaut, Webpack compile et fusionne toutes les dépendances dans le script de sortie. Il suffit de regarder le fichier JS généré (compilez-le avec npm run start
pour désactiver la minification) :

... var __webpack_modules__ = ({ "./src/utils/index.ts": ((...) => ... var min = function (a, b) { return a < b ? a : b; }; var max = function (a, b) { return a > b ? a : b; }; }), ...
Voir? Notre code utils
est là, intégré dans notre script de sortie.
Qu'en est-il de @wordpress/i18n
? Eh bien, c'est juste une simple référence à une variable globale :
... var __webpack_modules__ = ({ "./src/utils/index.ts": ..., "@wordpress/i18n": ((module)) => { module.exports = window["wp"]["i18n"]; }) ...
Comme je le disais, @wordpress/scripts
est livré avec un plugin, Dependency Extraction Webpack Plugin , qui "exclut" certaines dépendances du processus de compilation et génère du code en supposant qu'elles seront disponibles dans la portée globale. Dans notre exemple, par exemple, nous pouvons voir que @wordpress/i18n
est dans wp.i18n
. C'est pourquoi, lors de la mise en file d'attente de notre script, nous devons également mettre en file d'attente ses dépendances.
Configuration personnalisée pour générer deux scripts distincts
Avec tout cela à l'esprit, disons que nous voulons réaliser la même chose avec notre package utils
. Autrement dit, nous ne voulons pas que son contenu soit intégré dans index.js
, mais plutôt qu'il soit compilé dans son propre fichier .js
et apparaisse comme une dépendance sur index.asset.php
. Comment fait-on cela?
Tout d'abord, nous devons renommer l'instruction d' import
dans index.js
afin qu'elle ressemble à un vrai package. En d'autres termes, au lieu d'importer le script en utilisant un chemin relatif ( ./utils
), ce serait bien si nous pouvions utiliser un nom comme @nelio/utils
. Pour cela, il suffit d'éditer le fichier package.json
du projet pour ajouter une nouvelle dépendance dans dependencies
:
{ ... "dependencies": { "@nelio/utils": "./src/utils" }, "devDependencies": { "@wordpress/i18n": ..., "@wordpress/scripts": ... }, ... }
exécutez npm install
pour créer un lien symbolique dans node_modules
pointant vers ce « nouveau » package, et enfin exécutez npm init
dans src/utils
afin que, du point de vue de npm, @nelio/utils
soit un package valide.
Ensuite, pour compiler @nelio/utils
dans son propre script, nous devons éditer notre configuration webpack.config.js
et définir deux exports :
- celui que nous avions déjà (
./src/index.ts
) - une autre exportation pour compiler
./src/utils
dans un fichier différent, exposant ses exportations dans une variable globale nommée, par exemple,nelio.utils
.
En d'autres termes, nous voulons ceci :
module.exports = [ { ...config, entry: './src/index', output: { path: __dirname + '/build', filename: 'index.js', }, }, { ...config, entry: './src/utils', output: { path: __dirname + '/build', filename: 'utils.js', library: [ 'nelio', 'utils' ], libraryTarget: 'window', }, }, ];
Compilez à nouveau le code et jetez un œil au dossier ./build
— vous verrez que nous avons maintenant tous deux scripts. Jetez un œil à ./build/utils.js
et vous verrez comment il définit nelio.utils
, comme prévu :
... var min = function (a, b) { return a < b ? a : b; }; var max = function (a, b) { return a > b ? a : b; }; (window.nelio = window.nelio || {}).utils = __webpack_exports__; ...
... var min = function (a, b) { return a < b ? a : b; }; var max = function (a, b) { return a > b ? a : b; }; (window.nelio = window.nelio || {}).utils = __webpack_exports__; ...
Malheureusement, nous n'en sommes qu'à la moitié. Si vous regardez également ./build/index.js
, vous verrez que src/utils
y est toujours intégré… ne devrait-il pas s'agir d'une « dépendance externe » et utiliser la variable globale que nous venons de définir ?
Configuration personnalisée pour créer des dépendances externes
Pour transformer @nelio/utils
en une véritable dépendance externe, nous devons personnaliser davantage notre webpack et tirer parti du plugin d'extraction de dépendances que nous avons mentionné précédemment. Rouvrez simplement le fichier webpack.config.js
et modifiez la variable de config
comme suit :
const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' ); const config = { ...defaultConfig, module: { ...defaultConfig.module, rules: [ ...defaultConfig.module.rules, { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/, }, ], }, plugins: [ ...defaultConfig.plugins.filter( ( p ) => p.constructor.name !== 'DependencyExtractionWebpackPlugin' ), new DependencyExtractionWebpackPlugin( { requestToExternal: ( request ) => '@nelio/utils' === request ? [ 'nelio', 'utils' ] : undefined, requestToHandle: ( request ) => '@nelio/utils' === request ? 'nelio-utils' : undefined, outputFormat: 'php', } ), ], };
afin que toutes les références à @nelio/utils
soient traduites en nelio.utils
dans le code, et qu'il existe une dépendance au gestionnaire de script nelio-utils
. Si nous examinons les dépendances des deux scripts, nous voyons ce qui suit :
build/index.asset.php <?php return array( 'dependencies' => array('nelio-utils', 'wp-i18n')... ?> build/utils.asset.php <?php return array( 'dependencies' => array()... ?>
et si on regarde dans ./build/index.js
on confirme qu'effectivement la @nelio/utils
est désormais externe :
... var __webpack_modules__ = ({ "@nelio/utils": ((module)) => { module.exports = window["nelio"]["utils"]; }), "@wordpress/i18n": ((module)) => { module.exports = window["wp"]["i18n"]; }) ...
Il y a cependant un dernier problème auquel nous devons nous attaquer. Accédez à votre navigateur, actualisez la page du tableau de bord et regardez la console. Rien ne s'affiche, n'est-ce pas ? Pourquoi? Eh bien, nelio
dépend maintenant de nelio-utils
, mais ce script n'est pas enregistré dans WordPress… donc ses dépendances ne peuvent pas être satisfaites pour le moment. Pour résoudre ce problème, modifiez nelio.php
et enregistrez le nouveau script :
add_action( 'admin_enqueue_scripts', function() { $path = untrailingslashit( plugin_dir_path( __FILE__ ) ); $url = untrailingslashit( plugin_dir_url( __FILE__ ) ); $asset = require( $path . '/build/utils.asset.php' ); wp_register_script( 'nelio-utils', $url . '/build/utils.js', $asset['dependencies'], $asset['version'] ); } );
Comment accélérer le processus de construction
Si nous exécutons le processus de génération plusieurs fois et calculons la durée moyenne de son exécution, nous constatons que la génération s'exécute en 10 secondes environ :
> yarn run build ... ./src/index.ts + 2 modules ... webpack 5.74.0 compiled successfully in 5703 ms ... ./src/utils/index.ts ... webpack 5.74.0 compiled successfully in 5726 m Done in 10.22s.
ce qui peut sembler peu, mais il s'agit d'un simple projet de jouet et, comme je le disais, de vrais projets comme Nelio Content ou Nelio A/B Testing nécessitent quelques minutes pour être compilés.
Pourquoi est-ce « si lent » et que pouvons-nous faire pour l'accélérer ? Autant que je sache, le problème résidait dans notre configuration webpack. Plus vous avez d'exportations dans le module.exports de votre module.exports
, plus le temps de compilation devient lent. Cependant, une seule exportation est beaucoup plus rapide.
Refactorisons légèrement notre projet pour utiliser une seule exportation. Tout d'abord, créez un fichier export.ts
dans src/utils
avec le contenu suivant :
export * as utils from './index';
Ensuite, modifiez votre webpack.config.js
afin qu'il ait une seule exportation avec deux entrées :
module.exports = { ...config, entry: { index: './src/index', utils: './src/utils/export', }, output: { path: __dirname + '/dist', filename: 'js/[name].js', library: { name: 'nelio', type: 'assign-properties', }, }, };
Enfin, reconstruisez le projet :
> yarn run build ... built modules 522 bytes [built] ./src/index.ts + 2 modules ... ./src/utils/export.ts + 1 modules ... webpack 5.74.0 compiled successfully in 4339 ms Done in 6.02s.
Cela n'a pris que 6 secondes, soit presque la moitié du temps qu'avant ! Plutôt chouette, hein ?
Sommaire
TypeScript vous aidera à améliorer la qualité du code , car il vous permet de vérifier au moment de la compilation que les types sont corrects et qu'il n'y a pas d'incohérences. Mais comme tout dans la vie, les avantages de l'utilisation de TypeScript ont un prix : la compilation du code devient un peu plus lente.
Dans l'article d'aujourd'hui, nous avons vu que, selon la configuration de votre webpack, la compilation de votre projet peut être beaucoup plus rapide (ou plus lente). Le sweet spot nécessite une seule exportation… et c'est ce dont nous avons parlé aujourd'hui.
J'espère que vous avez aimé le poste. Si oui, merci de le partager. Si vous connaissez d'autres moyens d'optimiser Webpack, veuillez me le dire dans la section des commentaires ci-dessous. Passe une bonne journée!
Image sélectionnée par Saffu sur Unsplash.