DevTips – Cara Mengkompilasi Aset Anda Secara Efisien
Diterbitkan: 2022-08-25Salah 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:

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.