DevTips – Cum să vă compilați eficient activele
Publicat: 2022-08-25Unul dintre obiectivele pe care le-am avut pentru acest an a fost să refactorăm cele două plugin-uri emblematice (Nelio Content și Nelio A/B Testing) la TypeScript și React Hooks. Ei bine, am trecut doar jumătate de an și putem deja spune că acest obiectiv a fost un succes total. Totuși, trebuie să recunosc: drumul a fost puțin mai complicat decât ne așteptam… mai ales dacă ne gândim că, după introducerea TypeScript, Timpul de construire a pluginului a trecut de la câteva secunde la peste două minute! Ceva nu era în regulă și nu știam ce.
Ei bine, în postarea de astăzi aș vrea să vă povestesc puțin despre acea experiență și despre ce am făcut pentru a o remedia. La urma urmei, știm cu toții că TypeScript va încetini întotdeauna puțin procesul de construire (verificarea tipului are un preț), dar nu ar trebui să fie atât de mult! Ei bine, alertă spoiler: problema nu a fost niciodată TypeScript... a fost configurația mea. TypeScript a făcut-o doar „evident”. Deci, să începem, nu?
Proiect jucărie
Pentru a vă ajuta să înțelegeți problema cu care am confruntat cu câteva săptămâni în urmă și cum am remediat-o, cel mai bine putem face este să creăm un exemplu foarte simplu pe care să îl urmați. Să construim un plugin WordPress simplu care să folosească TypeScript și cum o configurare greșită poate duce la timpi de compilare extrem de lenți. Dacă aveți nevoie de ajutor pentru a începe, consultați această postare despre mediile de dezvoltare WordPress.
Creare plugin
Primul lucru pe care ar trebui să-l faceți este să creați un folder nou cu numele pluginului dvs. (de exemplu, nelio
) în directorul WordPress /wp-content/plugins
. Apoi adăugați fișierul principal ( nelio.php
) cu următorul conținut:
<?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; }
Dacă ați procedat corect, veți vedea că acum puteți activa pluginul în WordPress:

Sigur, pluginul nu face nimic încă... dar cel puțin apare
TypeScript
Să adăugăm niște cod TypeScript! Primul lucru pe care îl vom face este să inițializam npm în folderul nostru cu pluginuri. Rulați asta:
npm init
și urmați instrucțiunile de pe ecran. Apoi, instalați dependențele:
npm add -D @wordpress/scripts @wordpress/i18n
și editați fișierul package.json
pentru a adăuga scripturile de compilare cerute de @wordpress/scripts
:
{ ... "scripts": { "build": "wp-scripts build", "start": "wp-scripts start", }, ... }
Odată ce npm este gata, să personalizăm TypeScript adăugând un fișier tsconfig.json
:
{ "compilerOptions": { "target": "es5", "module": "esnext", "moduleResolution": "node", "outDir": "build", "lib": [ "es7", "dom" ] }, "exclude": [ "node_modules" ] }
În cele din urmă, să scriem un cod TS. Vrem ca acest lucru să fie foarte simplu, dar „suficient de aproape” de ceea ce aveam în Nelio A/B Testing și Nelio Content, așa că creați un folder src
în proiectul nostru cu câteva fișiere TypeScript în interior: index.ts
și utils/index.ts
.
Pe de o parte, să presupunem că utils/index.ts
este un pachet de utilitate. Adică, conține câteva funcții de care ar putea avea nevoie și alte fișiere din proiectul nostru. De exemplu, să presupunem că oferă funcțiile clasice min
și 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;
Pe de altă parte, să aruncăm o privire la fișierul principal al aplicației noastre: index.ts
. În scopurile noastre de testare, tot ce ne dorim este un script simplu care să folosească pachetul nostru de utilitate și o dependență de WordPress. Ceva de genul:
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 Setări implicite
Dacă ar fi să construim proiectul chiar acum folosind npm run build
, totul ar funcționa de la cutie. Și asta pur și simplu pentru că @wordpress/scripts
(adică instrumentul de bază pe care îl folosim pentru construirea proiectului nostru) a fost proiectat să funcționeze cu o bază de cod ca cea din exemplul nostru. Adică, dacă avem un fișier index.ts
în folderul src
, acesta va build
un fișier index.js
în folderul de compilare împreună cu un fișier de dependență index.asset.php
:
> ls build index.asset.php index.js
De ce două dosare? Ei bine, unul este fișierul JavaScript compilat (duh), iar celălalt este un fișier de dependențe cu câteva informații utile despre scriptul nostru. În special, ne spune pe ce biblioteci JavaScript, dintre cele incluse în WordPress, se bazează. De exemplu, index.ts
nostru se bazează pe pachetul @wordpress/i18n
pentru a internaționaliza șirurile și aceasta este o bibliotecă inclusă în WordPress, așa că... da, wp-i18n
va apărea în index.asset.php
:
build/index.asset.php <?php return array( 'dependencies' => array( 'wp-i18n' ), 'version' => 'c6131c7f24df4fa803b7', );
Din păcate, configurația implicită nu este perfectă, dacă mă întrebați pe mine. Iata de ce.
Dacă introducem o eroare în codul dvs. (de exemplu, să numim funcția min
cu un string
arg în loc de un number
):
const m = min( `${ a }`, b );
acest lucru ar trebui să declanșeze o eroare. Dar nu este. Compilează fără probleme.
Verificări de tip în timpul compilării cu TypeScript
Pentru a rezolva „limitarea” menționată mai sus, trebuie doar să ne creăm propriul fișier de configurare webpack și să îi spunem să folosească tsc
(compilatorul TypeScript) ori de câte ori întâlnește codul TS. Cu alte cuvinte, avem nevoie de următorul fișier 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', }, };
După cum puteți vedea, începe prin a încărca configurația implicită a pachetului web inclusă în pachetul @wordpress/scripts
și apoi extinde defaultConfig
adăugând un ts-loader
la toate fișierele .ts
. Ușor de gălăgie!
Și acum, iată:
> 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
compilarea proiectului nostru duce la o eroare. Ura! Sigur, este puțin mai lent, dar cel puțin avem câteva verificări de securitate înainte de a încărca ceva în producție.
Punerea în coadă a scripturilor
Ei bine, acum că știți că există o problemă în codul dvs., remediați-o și compilați din nou pluginul. A funcționat totul? Rece! Pentru că acum este timpul să punem în coadă scriptul și dependențele sale de PHP, astfel încât să îl putem încerca în browserul nostru.
Deschideți nelio.php
și adăugați următorul fragment:
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'] ); } );
Apoi, accesați tabloul de bord WordPress (orice pagină va face truc) și aruncați o privire la consola JavaScript a browserului dvs. Ar trebui să vedeți următorul text:
Min between 2 and 3 is 2
Grozav!
Dar dependențele MELE?
Să vorbim pentru o secundă despre gestionarea dependenței în JavaScript/webpack/WordPress. @wordpress/scripts
este configurat astfel încât, în mod implicit, dacă proiectul dvs. folosește o dependență care este ambalată în nucleul WordPress, aceasta va fi listată ca atare în fișierul .asset.php
. Acest lucru, de exemplu, explică de ce @wordpress/i18n
a fost listat în fișierul de dependențe al scriptului nostru.
Dar cum rămâne cu dependențele de „alte” pachete? Ce s-a întâmplat cu pachetul nostru utils
? Pe scurt: în mod implicit, pachetul web compilează și îmbină toate dependențele în scriptul de ieșire. Uită-te la fișierul JS generat (compilează-l cu npm run start
pentru a dezactiva minificarea):

... 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; }; }), ...
Vedea? Codul nostru utils
este chiar acolo, încorporat în scriptul nostru de ieșire.
Ce zici de @wordpress/i18n
? Ei bine, este doar o simplă referință la o variabilă globală:
... var __webpack_modules__ = ({ "./src/utils/index.ts": ..., "@wordpress/i18n": ((module)) => { module.exports = window["wp"]["i18n"]; }) ...
După cum spuneam, @wordpress/scripts
vine cu un plugin, Dependency Extraction Webpack Plugin , care „exclude” anumite dependențe din procesul de compilare și generează cod, presupunând că vor fi disponibile în domeniul global. În exemplul nostru, de exemplu, putem vedea că @wordpress/i18n
este în wp.i18n
. De aceea, atunci când punem în coadă scriptul, trebuie să punem în coadă dependențele acestuia.
Configurare personalizată pentru a genera două scripturi separate
Având toate acestea în minte, să presupunem că vrem să realizăm același lucru cu pachetul nostru utils
. Adică, nu dorim ca conținutul său să fie încorporat în index.js
, ci mai degrabă ar trebui să fie compilat în propriul fișier .js
și să apară ca o dependență de index.asset.php
. Cum facem asta?
În primul rând, ar trebui să redenumim instrucțiunea de import
în index.js
, astfel încât să pară un pachet real. Cu alte cuvinte, în loc să importam scriptul folosind o cale relativă ( ./utils
), ar fi bine dacă am putea folosi un nume ca @nelio/utils
. Pentru a face acest lucru, tot ce trebuie să faceți este să editați fișierul package.json
al proiectului pentru a adăuga o nouă dependență în dependencies
:
{ ... "dependencies": { "@nelio/utils": "./src/utils" }, "devDependencies": { "@wordpress/i18n": ..., "@wordpress/scripts": ... }, ... }
rulați npm install
pentru a crea un link simbolic în node_modules
care indică acest pachet „nou” și, în final, rulați npm init
în src/utils
, astfel încât, din punctul de vedere al lui npm, @nelio/utils
fie un pachet valid.
Apoi, pentru a compila @nelio/utils
în propriul script, trebuie să ne edităm configurația webpack.config.js
și să definim două exporturi:
- cel pe care îl aveam deja (
./src/index.ts
) - un alt export pentru a compila
./src/utils
într-un fișier diferit, expunând exporturile sale într-o variabilă globală numită, de exemplu,nelio.utils
.
Cu alte cuvinte, vrem asta:
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ți din nou codul și aruncați o privire la folderul ./build
- veți vedea că acum toți avem două scripturi. Aruncă o privire la ./build/utils.js
și vei vedea cum definește nelio.utils
, așa cum era de așteptat:
... 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__; ...
Din păcate, suntem doar la jumătatea drumului. Dacă aruncați o privire și pe ./build/index.js
, veți vedea că src/utils
este încă încorporat în el... nu ar trebui să fie o „dependență externă” și să folosiți variabila globală pe care tocmai am definit-o?
Configurare personalizată pentru a crea dependențe externe
Pentru a transforma @nelio/utils
într-o dependență externă reală, trebuie să ne personalizăm în continuare pachetul web și să profităm de pluginul de extragere a dependențelor pe care l-am menționat mai devreme. Pur și simplu redeschideți fișierul webpack.config.js
și modificați variabila de config
după cum urmează:
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', } ), ], };
astfel încât toate referințele la @nelio/utils
să fie traduse ca nelio.utils
în cod și există o dependență de handler-ul de script nelio-utils
. Dacă ne uităm la dependențele ambelor scripturi, vedem următoarele:
build/index.asset.php <?php return array( 'dependencies' => array('nelio-utils', 'wp-i18n')... ?> build/utils.asset.php <?php return array( 'dependencies' => array()... ?>
iar dacă ne uităm în ./build/index.js
confirmăm că într-adevăr dependența @nelio/utils
este acum externă:
... var __webpack_modules__ = ({ "@nelio/utils": ((module)) => { module.exports = window["nelio"]["utils"]; }), "@wordpress/i18n": ((module)) => { module.exports = window["wp"]["i18n"]; }) ...
Totuși, există o ultimă problemă pe care trebuie să o rezolvăm. Accesați browserul dvs., reîmprospătați pagina tabloului de bord și priviți consola. Nu apare nimic, nu? De ce? Ei bine, nelio
depinde acum de nelio-utils
, dar acest script nu este înregistrat în WordPress... așa că dependențele sale nu pot fi îndeplinite acum. Pentru a remedia acest lucru, editați nelio.php
și înregistrați noul 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'] ); } );
Cum să accelerezi procesul de construire
Dacă rulăm procesul de construire de mai multe ori și medim cât durează finalizarea, vedem că construirea rulează în aproximativ 10 secunde:
> 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.
care poate nu pare mult, dar acesta este un simplu proiect de jucărie și, așa cum spuneam, proiecte reale precum Nelio Content sau Nelio A/B Testing au nevoie de câteva minute pentru a fi compilate.
De ce este „atât de lent” și ce putem face pentru a o accelera? Din câte mi-am dat seama, problema a stat în configurația pachetului nostru web. Cu cât aveți mai multe exporturi în module.exports ale module.exports
-ului, cu atât timpul de compilare devine mai lent. Cu toate acestea, un singur export este mult, mult mai rapid.
Să refactorăm ușor proiectul nostru pentru a folosi un singur export. În primul rând, creați un fișier export.ts
în src/utils
cu următorul conținut:
export * as utils from './index';
Apoi, editați webpack.config.js
astfel încât să aibă un singur export cu două intrări:
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', }, }, };
În cele din urmă, construiți din nou proiectul:
> 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.
A durat doar 6 secunde, adică aproape jumătate din timp! Destul de îngrijit, nu?
rezumat
TypeScript vă va ajuta să îmbunătățiți calitatea codului , deoarece vă permite să verificați în timpul compilării dacă tipurile sunt corecte și că nu există inconsecvențe. Dar, ca orice în viață, avantajele utilizării TypeScript au un preț: compilarea codului devine puțin mai lentă.
În postarea de astăzi am văzut că, în funcție de configurația pachetului web, compilarea proiectului poate fi mult mai rapidă (sau mai lentă). Punctul dulce necesită un singur export... și despre asta am vorbit astăzi.
Sper că v-a plăcut postarea. Dacă da, vă rugăm să o distribuiți. Dacă știți și alte modalități de optimizare a pachetului web, vă rugăm să-mi spuneți în secțiunea de comentarii de mai jos. Să aveţi o zi bună!
Imagine prezentată de Saffu pe Unsplash.