DevTips: come compilare le tue risorse in modo efficiente

Pubblicato: 2022-08-25

Uno degli obiettivi che avevamo quest'anno era il refactoring dei nostri due plugin di punta (Nelio Content e Nelio A/B Testing) in TypeScript e React Hooks. Ebbene, siamo passati solo mezzo anno e possiamo già dire che questo traguardo è stato un successo totale Tuttavia, devo ammettere: la strada è stata un po' più complicata del previsto… soprattutto se consideriamo che, dopo l'introduzione di TypeScript, il nostro i tempi di creazione del plugin sono passati da pochi secondi a oltre due minuti! Qualcosa non andava e non sapevamo cosa.

Bene, nel post di oggi vorrei raccontarvi un po' di quell'esperienza e di cosa abbiamo fatto per risolverla. Dopotutto, sappiamo tutti che TypeScript rallenterà sempre un po' il processo di compilazione (il controllo del tipo ha un prezzo), ma non dovrebbe essere così tanto! Bene, avviso spoiler: il problema non è mai stato TypeScript... era la mia configurazione. TypeScript lo rendeva solo "ovvio". Allora cominciamo, vero?

Progetto Giocattolo

Per aiutarti a capire il problema che abbiamo riscontrato alcune settimane fa e come l'abbiamo risolto, il meglio che possiamo fare è creare un esempio molto semplice da seguire. Costruiamo un semplice plugin per WordPress che utilizza TypeScript e come una configurazione errata può comportare tempi di compilazione estremamente lenti. Se hai bisogno di aiuto per iniziare, dai un'occhiata a questo post sugli ambienti di sviluppo di WordPress.

Creazione plugin

La prima cosa che dovresti fare è creare una nuova cartella con il nome del tuo plugin (ad esempio, nelio ) nella directory /wp-content/plugins di WordPress. Quindi aggiungi il file principale ( nelio.php ) con il seguente contenuto:

 <?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; }

Se hai fatto bene, vedrai che ora puoi attivare il plugin in WordPress:

Screenshot del nostro plug-in giocattolo nell'elenco dei plug-in
Screenshot del nostro plug-in giocattolo nell'elenco dei plug-in.

Certo, il plugin non fa ancora niente... ma almeno si vede

Dattiloscritto

Aggiungiamo del codice TypeScript! La prima cosa che faremo è inizializzare npm nella nostra cartella dei plugin. Esegui questo:

 npm init

e segui le istruzioni sullo schermo. Quindi, installa le dipendenze:

 npm add -D @wordpress/scripts @wordpress/i18n

e modifica il file package.json per aggiungere gli script di build richiesti da @wordpress/scripts :

 { ... "scripts": { "build": "wp-scripts build", "start": "wp-scripts start", }, ... }

Una volta che npm è pronto, personalizziamo TypeScript aggiungendo un file tsconfig.json :

 { "compilerOptions": { "target": "es5", "module": "esnext", "moduleResolution": "node", "outDir": "build", "lib": [ "es7", "dom" ] }, "exclude": [ "node_modules" ] }

Infine, scriviamo del codice TS. Vogliamo che sia molto semplice ma "abbastanza vicino" a quello che avevamo in Nelio A/B Testing e Nelio Content, quindi crea una cartella src nel nostro progetto con un paio di file TypeScript all'interno: index.ts e utils/index.ts .

Da un lato, supponiamo che utils/index.ts sia un pacchetto di utilità. Cioè, contiene alcune funzioni di cui potrebbero aver bisogno altri file nel nostro progetto. Ad esempio, diciamo che fornisce le classiche funzioni min e 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;

Diamo invece un'occhiata al file principale della nostra app: index.ts . Ai fini dei nostri test, tutto ciò che desideriamo è un semplice script che utilizzi il nostro pacchetto di utilità e una dipendenza da WordPress. Qualcosa come questo:

 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 Impostazioni predefinite

Se dovessimo creare il progetto in questo momento usando npm run build , tutto funzionerebbe immediatamente. E questo è semplicemente perché @wordpress/scripts (cioè lo strumento sottostante che stiamo usando per costruire il nostro progetto) è stato progettato per funzionare con una base di codice come quella del nostro esempio. Cioè, se abbiamo un file index.ts nella cartella src , genererà un file index.js nella cartella build insieme a un file di dipendenza index.asset.php :

 > ls build index.asset.php index.js

Perché due file? Bene, uno è il file JavaScript compilato (duh) e l'altro è un file di dipendenze con alcune informazioni utili sul nostro script. In particolare, ci dice su quali librerie JavaScript, tra quelle incluse in WordPress, fa affidamento. Ad esempio, il nostro index.ts si basa sul pacchetto @wordpress/i18n per internazionalizzare le stringhe, e questa è una libreria inclusa in WordPress quindi... sì, wp-i18n apparirà in index.asset.php :

 build/index.asset.php <?php return array( 'dependencies' => array( 'wp-i18n' ), 'version' => 'c6131c7f24df4fa803b7', );

Sfortunatamente, la configurazione predefinita non è perfetta, se me lo chiedi. Ecco perché.

Se introduciamo un bug nel tuo codice (ad esempio, chiamiamo la funzione min con una string arg invece di un number ):

 const m = min( `${ a }`, b );

questo dovrebbe generare un errore. Ma non è così. Si compila senza problemi.

Controlli di tipo durante la compilazione con TypeScript

Per risolvere la suddetta "limitazione", dobbiamo solo creare il nostro file di configurazione webpack e dirgli di usare tsc (il compilatore TypeScript) ogni volta che incontra codice TS. In altre parole, abbiamo bisogno del seguente file webpack.config.json :

 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', }, };

Come puoi vedere, inizia caricando la configurazione predefinita del webpack inclusa nel pacchetto @wordpress/scripts e quindi estende defaultConfig aggiungendo un ts-loader a tutti i file .ts . Vai tranquillo!

E ora, ecco:

 > 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 compilazione del nostro progetto genera un errore. Evviva! Certo, è un po' più lento, ma almeno abbiamo alcuni controlli di sicurezza prima di caricare qualsiasi cosa in produzione.

Accodamento degli script

Bene, ora che sai che c'è un problema nel tuo codice, risolvilo e compila di nuovo il plugin. Ha funzionato tutto? Freddo! Perché ora è il momento di accodare lo script e le sue dipendenze su PHP in modo che possiamo provarlo nel nostro browser.

Apri nelio.php e aggiungi il seguente snippet:

 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'] ); } );

Quindi, vai alla dashboard di WordPress (qualsiasi pagina farà il trucco) e dai un'occhiata alla console JavaScript del tuo browser. Dovresti vedere il seguente testo:

 Min between 2 and 3 is 2

Simpatico!

E le mie dipendenze?

Parliamo per un secondo della gestione delle dipendenze in JavaScript/webpack/WordPress. @wordpress/scripts è configurato in modo tale che, per impostazione predefinita, se il tuo progetto utilizza una dipendenza contenuta nel core di WordPress, sarà elencato come tale nel file .asset.php . Questo, ad esempio, spiega perché @wordpress/i18n è stato elencato nel file delle dipendenze del nostro script.

Ma che dire delle dipendenze da "altri" pacchetti? Che cosa è successo al nostro pacchetto di utils ? Per farla breve: per impostazione predefinita webpack compila e unisce tutte le dipendenze nello script di output Basta guardare il file JS generato (compilalo con npm run start per disabilitare la minimizzazione):

 ... 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; }; }), ...

Vedere? Il nostro codice utils è proprio lì, incorporato nel nostro script di output.

Che dire di @wordpress/i18n ? Bene, è solo un semplice riferimento a una variabile globale:

 ... var __webpack_modules__ = ({ "./src/utils/index.ts": ..., "@wordpress/i18n": ((module)) => { module.exports = window["wp"]["i18n"]; }) ...

Come dicevo, @wordpress/scripts viene fornito con un plug-in, Dependency Extraction Webpack Plugin , che "esclude" alcune dipendenze dal processo di compilazione e genera codice supponendo che saranno disponibili nell'ambito globale. Nel nostro esempio, ad esempio, possiamo vedere che @wordpress/i18n è in wp.i18n . Ecco perché, quando accodiamo il nostro script, dobbiamo anche accodare le sue dipendenze.

Configurazione personalizzata per generare due script separati

Con tutto questo in mente, diciamo che vogliamo ottenere la stessa cosa con il nostro pacchetto utils . Cioè, non vogliamo che il suo contenuto sia incorporato in index.js , ma piuttosto dovrebbe essere compilato nel proprio file .js e apparire come una dipendenza da index.asset.php . Come lo facciamo?

Innanzitutto, dovremmo rinominare l'istruzione import in index.js in modo che sembri un vero pacchetto. In altre parole, invece di importare lo script usando un percorso relativo ( ./utils ), sarebbe bello se potessimo usare un nome come @nelio/utils . Per fare ciò, tutto ciò che devi fare è modificare il file package.json del progetto per aggiungere una nuova dipendenza nelle dependencies :

 { ... "dependencies": { "@nelio/utils": "./src/utils" }, "devDependencies": { "@wordpress/i18n": ..., "@wordpress/scripts": ... }, ... }

eseguire npm install per creare un collegamento simbolico in node_modules che punta a questo "nuovo" pacchetto, e infine eseguire npm init in src/utils in modo che, dal punto di vista di npm, @nelio/utils sia un pacchetto valido.

Quindi, per compilare @nelio/utils nel proprio script, dobbiamo modificare la nostra configurazione webpack.config.js e definire due esportazioni:

  • quello che avevamo già ( ./src/index.ts )
  • un'altra esportazione per compilare ./src/utils in un file diverso, esponendone le esportazioni in una variabile globale denominata, ad esempio, nelio.utils .

In altre parole, vogliamo questo:

 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', }, }, ];

Compila di nuovo il codice e dai un'occhiata alla cartella ./build : vedrai che ora abbiamo tutti due script. Dai un'occhiata a ./build/utils.js e vedrai come definisce nelio.utils , come previsto:

 ... 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__; ...

Purtroppo siamo solo a metà. Se dai un'occhiata anche a ./build/index.js , vedrai che src/utils è ancora incorporato in esso ... non dovrebbe essere una "dipendenza esterna" e utilizzare la variabile globale che abbiamo appena definito?

Configurazione personalizzata per creare dipendenze esterne

Per trasformare @nelio/utils in una vera e propria dipendenza esterna, dobbiamo personalizzare ulteriormente il nostro pacchetto web e sfruttare il plug-in di estrazione delle dipendenze menzionato in precedenza. Riapri semplicemente il file webpack.config.js e modifica la variabile di config come segue:

 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', } ), ], };

in modo che tutti i riferimenti a @nelio/utils siano tradotti come nelio.utils nel codice e sia presente una dipendenza dal gestore di script nelio-utils . Se diamo un'occhiata alle dipendenze di entrambi gli script, vediamo quanto segue:

 build/index.asset.php <?php return array( 'dependencies' => array('nelio-utils', 'wp-i18n')... ?> build/utils.asset.php <?php return array( 'dependencies' => array()... ?>

e se guardiamo in ./build/index.js confermiamo che effettivamente la dipendenza @nelio/utils è ora esterna:

 ... var __webpack_modules__ = ({ "@nelio/utils": ((module)) => { module.exports = window["nelio"]["utils"]; }), "@wordpress/i18n": ((module)) => { module.exports = window["wp"]["i18n"]; }) ...

C'è un ultimo problema che dobbiamo affrontare, però. Vai al tuo browser, aggiorna la pagina del dashboard e guarda la console. Non si vede niente, giusto? Come mai? Bene, nelio ora dipende da nelio-utils , ma questo script non è registrato in WordPress... quindi le sue dipendenze non possono essere soddisfatte in questo momento. Per risolvere questo problema, modifica nelio.php e registra il nuovo 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'] ); } );

Come accelerare il processo di costruzione

Se eseguiamo il processo di compilazione più volte e in media il tempo necessario per il completamento, vediamo che la build viene eseguita in circa 10 secondi:

 > 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.

che potrebbe non sembrare molto, ma questo è un semplice progetto giocattolo e, come dicevo, progetti reali come Nelio Content o Nelio A/B Testing richiedono minuti per essere compilati.

Perché è "così lento" e cosa possiamo fare per accelerarlo? Per quanto ne so, il problema risiedeva nella configurazione del nostro pacchetto web. Più esportazioni hai nel modulo.exports del tuo module.exports , più lento diventa il tempo di compilazione. Tuttavia, una singola esportazione è molto, molto più veloce.

Ridimensioniamo leggermente il nostro progetto per utilizzare una singola esportazione. Prima di tutto, crea un file export.ts in src/utils con il seguente contenuto:

 export * as utils from './index';

Quindi, modifica il tuo webpack.config.js in modo che abbia un'unica esportazione con due voci:

 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', }, }, };

Infine, ricostruisci il progetto:

 > 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.

Ci sono voluti solo 6 secondi, che è quasi la metà del tempo prima! Abbastanza pulito, eh?

Riepilogo

TypeScript ti aiuterà a migliorare la qualità del codice , perché ti permette di controllare in fase di compilazione che i tipi siano corretti e non ci siano incongruenze. Ma come ogni cosa nella vita, i vantaggi dell'utilizzo di TypeScript hanno un prezzo: la compilazione del codice diventa un po' più lenta.

Nel post di oggi abbiamo visto che, a seconda della configurazione del tuo webpack, la compilazione del tuo progetto può essere molto più veloce (o più lenta). Il punto debole richiede una singola esportazione... ed è di questo che abbiamo parlato oggi.

Spero che il post ti sia piaciuto. Se è così, per favore condividilo. Se conosci altri modi per ottimizzare il webpack, per favore dimmelo nella sezione commenti qui sotto. Buona giornata!

Immagine in primo piano di Saffu su Unsplash.