DevTips – Cara Mengkompilasi Aset Anda Secara Efisien

Diterbitkan: 2022-08-25

Salah satu tujuan yang kami miliki untuk tahun ini adalah untuk memperbaiki dua plugin andalan kami (Konten Nelio dan Pengujian A/B Nelio) ke TypeScript dan React Hooks. Yah, kita baru saja melewati setengah tahun dan kita sudah dapat mengatakan bahwa tujuan ini telah sukses total Namun, harus saya akui: jalannya sedikit lebih rumit dari yang diharapkan… terutama jika kita mempertimbangkan bahwa, setelah memperkenalkan TypeScript, waktu pembuatan plugin berubah dari beberapa detik menjadi lebih dari dua menit! Ada yang salah dan kami tidak tahu apa.

Nah, dalam posting hari ini saya ingin menceritakan sedikit tentang pengalaman itu dan apa yang kami lakukan untuk memperbaikinya. Lagi pula, kita semua tahu TypeScript akan selalu memperlambat proses pembuatan sedikit (pemeriksaan jenis ada harganya), tetapi seharusnya tidak terlalu banyak! Nah, peringatan spoiler: masalahnya tidak pernah TypeScript ... itu adalah konfigurasi saya. TypeScript hanya membuatnya "jelas." Jadi mari kita mulai, ya?

Proyek mainan

Untuk membantu Anda memahami masalah yang kami alami beberapa minggu yang lalu dan cara kami memperbaikinya, hal terbaik yang dapat kami lakukan adalah membuat contoh yang sangat sederhana untuk Anda ikuti. Mari kita buat plugin WordPress sederhana yang menggunakan TypeScript dan bagaimana kesalahan konfigurasi dapat mengakibatkan waktu kompilasi yang sangat lambat. Jika Anda memerlukan bantuan untuk memulai, lihat posting ini di lingkungan pengembangan WordPress.

Pembuatan Plugin

Hal pertama yang harus Anda lakukan adalah membuat folder baru dengan nama plugin Anda (misalnya, nelio ) di /wp-content/plugins WordPress Anda. Kemudian tambahkan file utama ( nelio.php ) dengan konten berikut:

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

Jika Anda melakukannya dengan benar, Anda akan melihat bahwa Anda sekarang dapat mengaktifkan plugin di WordPress:

Tangkapan layar plugin mainan kami di daftar plugin
Tangkapan layar plugin mainan kami di daftar plugin.

Tentu, plugin belum melakukan apa-apa ... tapi setidaknya itu muncul

TypeScript

Mari tambahkan beberapa kode TypeScript! Hal pertama yang akan kita lakukan adalah menginisialisasi npm di folder plugin kita. Jalankan ini:

 npm init

dan ikuti petunjuk di layar. Kemudian, instal dependensi:

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

dan edit file package.json untuk menambahkan skrip build yang diperlukan oleh @wordpress/scripts :

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

Setelah npm siap, mari sesuaikan TypeScript dengan menambahkan file tsconfig.json :

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

Akhirnya, mari kita menulis beberapa kode TS. Kami ingin ini menjadi sangat sederhana tetapi "cukup dekat" dengan apa yang kami miliki di Nelio A/B Testing dan Nelio Content, jadi buat folder src di proyek kami dengan beberapa file TypeScript di dalamnya: index.ts dan utils/index.ts .

Di satu sisi, mari kita asumsikan utils/index.ts adalah paket utilitas. Artinya, ini berisi beberapa fungsi yang mungkin dibutuhkan file lain dalam proyek kita. Misalnya, katakanlah ia menyediakan fungsi min dan max klasik:

 export const min = ( a: number, b: number ): number => a < b ? a : b; export const max = ( a: number, b: number ): number => a > b ? a : b;

Di sisi lain, mari kita lihat file utama aplikasi kita: index.ts . Untuk tujuan pengujian kami, yang kami inginkan hanyalah skrip sederhana yang menggunakan paket utilitas kami dan ketergantungan WordPress. Sesuatu seperti ini:

 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 Pengaturan Default

Jika kita membangun proyek sekarang menggunakan npm run build , semuanya akan berjalan di luar kotak. Dan itu hanya karena @wordpress/scripts (yaitu alat dasar yang kami gunakan untuk membangun proyek kami) telah dirancang untuk bekerja dengan basis kode seperti contoh kami. Artinya, jika kita memiliki file index.ts di folder src , itu akan menghasilkan file index.js di folder build bersama dengan file dependensi index.asset.php :

 > ls build index.asset.php index.js

Mengapa dua file? Nah, yang satu adalah file JavaScript yang dikompilasi (duh) dan yang lainnya adalah file dependensi dengan beberapa informasi berguna tentang skrip kita. Secara khusus, ini memberi tahu kita pustaka JavaScript mana, dari yang termasuk dalam WordPress, yang diandalkannya. Misalnya, index.ts kami bergantung pada paket @wordpress/i18n untuk menginternasionalkan string, dan itu adalah perpustakaan yang disertakan di WordPress jadi… yup, wp-i18n akan muncul di index.asset.php :

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

Sayangnya, konfigurasi default tidak sempurna, jika Anda bertanya kepada saya. Inilah alasannya.

Jika kami memperkenalkan bug dalam kode Anda (mis., panggil fungsi min dengan string arg alih-alih number ):

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

ini harus memicu kesalahan. Tapi tidak. Ini mengkompilasi tanpa masalah.

Ketik Pemeriksaan selama Kompilasi dengan TypeScript

Untuk mengatasi “batasan” yang disebutkan di atas, kita hanya perlu membuat file konfigurasi webpack kita sendiri dan memerintahkannya untuk menggunakan tsc (kompiler TypeScript) setiap kali menemukan kode TS. Dengan kata lain, kita membutuhkan file webpack.config.json berikut:

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

Seperti yang Anda lihat, ini dimulai dengan memuat konfigurasi webpack default yang disertakan dalam paket @wordpress/scripts dan kemudian memperluas defaultConfig dengan menambahkan ts-loader ke semua file .ts . Mudah sekali!

Dan sekarang, lihatlah:

 > 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

kompilasi proyek kami menghasilkan kesalahan. Hore! Tentu, ini sedikit lebih lambat, tetapi setidaknya kami memiliki beberapa pemeriksaan keamanan sebelum mengunggah apa pun ke produksi.

Mengantrekan Script

Nah, sekarang Anda tahu ada masalah dalam kode Anda, perbaiki dan kompilasi plugin lagi. Apakah itu semua berhasil? Dingin! Karena sekarang saatnya enqueue script dan dependensinya pada PHP agar bisa kita coba di browser kita.

Buka nelio.php dan tambahkan cuplikan berikut:

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

Selanjutnya, buka dasbor WordPress (halaman mana pun akan melakukan triknya) dan lihat konsol JavaScript browser Anda. Anda akan melihat teks berikut:

 Min between 2 and 3 is 2

Bagus!

Bagaimana Dengan Dependensi SAYA?

Mari kita bicara sebentar tentang manajemen ketergantungan di JavaScript/webpack/WordPress. @wordpress/scripts dikonfigurasi sedemikian rupa sehingga, secara default, jika proyek Anda menggunakan ketergantungan yang dikemas dalam inti WordPress, itu akan terdaftar seperti itu di file .asset.php . Ini, misalnya, menjelaskan mengapa @wordpress/i18n terdaftar di file dependensi skrip kami.

Tetapi bagaimana dengan dependensi ke paket "lainnya"? Apa yang terjadi dengan paket utils kami? Singkat cerita: secara default webpack mengkompilasi dan menggabungkan semua dependensi ke dalam skrip keluaran Lihat saja file JS yang dihasilkan (kompilasi dengan npm run start untuk menonaktifkan minifikasi):

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

Melihat? Kode utils kami ada di sana, tertanam dalam skrip keluaran kami.

Bagaimana dengan @wordpress/i18n ? Yah, itu hanya referensi sederhana ke variabel global:

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

Seperti yang saya katakan, @wordpress/scripts hadir dengan sebuah plugin, Dependency Extraction Webpack Plugin , yang "mengecualikan" dependensi tertentu dari proses kompilasi dan menghasilkan kode dengan asumsi mereka akan tersedia dalam lingkup global. Dalam contoh kita, misalnya, kita dapat melihat bahwa @wordpress/i18n ada di wp.i18n . Itu sebabnya, saat mengantrekan skrip kita, kita juga perlu mengantrekan dependensinya.

Konfigurasi Kustom untuk Menghasilkan Dua Skrip Terpisah

Dengan semua ini dalam pikiran, katakanlah kita ingin mencapai hal yang sama dengan paket utils kita. Artinya, kami tidak ingin kontennya disematkan di index.js , melainkan harus dikompilasi ke dalam file .js sendiri dan muncul sebagai ketergantungan pada index.asset.php . Bagaimana kita melakukannya?

Pertama, kita harus mengganti nama pernyataan import di index.js sehingga tampak seperti paket nyata. Dengan kata lain, daripada mengimpor skrip menggunakan jalur relatif ( ./utils ), alangkah baiknya jika kita bisa menggunakan nama seperti @nelio/utils . Untuk melakukan ini, yang harus Anda lakukan adalah mengedit file package.json proyek untuk menambahkan dependensi baru dalam dependencies :

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

jalankan npm install untuk membuat symlink di node_modules yang menunjuk ke paket "baru" ini, dan terakhir jalankan npm init di src/utils sehingga, dari sudut pandang npm, @nelio/utils adalah paket yang valid.

Kemudian, untuk mengkompilasi @nelio/utils ke dalam skripnya sendiri, kita perlu mengedit konfigurasi webpack.config.js dan menentukan dua ekspor:

  • yang sudah kita miliki ( ./src/index.ts )
  • ekspor lain untuk dikompilasi ./src/utils ke file yang berbeda, memperlihatkan ekspornya dalam variabel global bernama, misalnya, nelio.utils .

Dengan kata lain, kami menginginkan ini:

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

Kompilasi kode lagi dan lihat folder ./build — Anda akan melihat bahwa sekarang kita semua memiliki dua skrip. Lihatlah ./build/utils.js dan Anda akan melihat bagaimana ia mendefinisikan nelio.utils , seperti yang diharapkan:

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

Sayangnya, kita baru setengah jalan. Jika Anda juga melihat ./build/index.js , Anda akan melihat bahwa src/utils masih tertanam di dalamnya… bukankah seharusnya itu menjadi “ketergantungan eksternal” dan menggunakan variabel global yang baru saja kita definisikan?

Konfigurasi Kustom untuk Membuat Dependensi Eksternal

Untuk mengubah @nelio/utils menjadi dependensi eksternal yang sebenarnya, kita perlu menyesuaikan lebih lanjut webpack kita dan memanfaatkan plugin ekstraksi dependensi yang telah disebutkan sebelumnya. Cukup buka kembali file webpack.config.js dan ubah variabel config sebagai berikut:

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

sehingga semua referensi ke @nelio/utils diterjemahkan sebagai nelio.utils dalam kode, dan ada ketergantungan pada script handler nelio-utils . Jika kita melihat dependensi dari kedua skrip, kita melihat yang berikut:

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

dan jika kita melihat di ./build/index.js kita mengonfirmasi bahwa memang @nelio/utils sekarang bersifat eksternal:

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

Ada satu masalah terakhir yang perlu kita atasi. Buka browser Anda, segarkan halaman dasbor, dan lihat konsol. Tidak ada yang muncul, kan? Mengapa? Nah, nelio sekarang tergantung pada nelio-utils , tetapi skrip ini tidak terdaftar di WordPress… jadi dependensinya tidak dapat dipenuhi sekarang. Untuk memperbaikinya, edit nelio.php dan daftarkan skrip baru:

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

Cara Mempercepat Proses Pembuatan

Jika kita menjalankan proses pembangunan beberapa kali dan rata-rata berapa lama waktu yang dibutuhkan untuk menyelesaikannya, kita melihat bahwa pembangunan berjalan dalam waktu sekitar 10 detik:

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

yang mungkin tidak tampak banyak tetapi ini adalah proyek mainan sederhana dan, seperti yang saya katakan, proyek nyata seperti Konten Nelio atau Pengujian A/B Nelio perlu beberapa menit untuk dikompilasi.

Mengapa "sangat lambat" dan apa yang bisa kita lakukan untuk mempercepatnya? Sejauh yang saya tahu, masalahnya terletak pada konfigurasi webpack kami. Semakin banyak ekspor yang Anda miliki di module.exports module.exports Anda, semakin lambat waktu kompilasinya. Namun, satu ekspor jauh lebih cepat.

Mari kita refactor proyek kita sedikit untuk menggunakan ekspor tunggal. Pertama-tama, buat file export.ts di src/utils dengan konten berikut:

 export * as utils from './index';

Kemudian, edit webpack.config.js Anda sehingga memiliki ekspor tunggal dengan dua entri:

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

Akhirnya, bangun proyek lagi:

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

Hanya butuh 6 detik, yang hampir separuh waktu dulu! Cukup rapi, ya?

Ringkasan

TypeScript akan membantu Anda meningkatkan kualitas kode , karena memungkinkan Anda memeriksa pada waktu kompilasi apakah jenisnya benar dan tidak ada inkonsistensi. Tetapi seperti segala sesuatu dalam hidup, keuntungan menggunakan TypeScript ada harganya: kompilasi kode menjadi sedikit lebih lambat.

Dalam posting hari ini kita telah melihat bahwa, tergantung pada konfigurasi webpack Anda, kompilasi proyek Anda bisa jauh lebih cepat (atau lebih lambat). Sweet spot membutuhkan satu ekspor… dan itulah yang kita bicarakan hari ini.

Saya harap Anda menyukai posnya. Jika demikian, silakan bagikan. Jika Anda tahu cara lain untuk mengoptimalkan webpack, beri tahu saya di bagian komentar di bawah. Semoga harimu menyenangkan!

Gambar unggulan oleh Saffu di Unsplash.