DevTips – Comment compiler efficacement vos ressources

Publié: 2022-08-25

L'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 :

Capture d'écran de notre plugin jouet dans la liste des plugins
Capture d'écran de notre plugin jouet dans la liste des plugins.

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.