Jelajahi kemampuan berbagi runtime JavaScript Module Federation, manfaatnya untuk membangun aplikasi global yang skalabel, dapat dipelihara, dan kolaboratif, serta strategi implementasi praktis.
JavaScript Module Federation: Membuka Kekuatan Berbagi Runtime untuk Aplikasi Global
Dalam lanskap digital yang berkembang pesat saat ini, membangun aplikasi web yang skalabel, dapat dipelihara, dan kolaboratif adalah hal yang terpenting. Seiring dengan pertumbuhan tim pengembang dan semakin kompleksnya aplikasi, kebutuhan akan pembagian kode yang efisien dan pemisahan (decoupling) menjadi semakin krusial. JavaScript Module Federation, sebuah fitur terobosan yang diperkenalkan dengan Webpack 5, menawarkan solusi yang kuat dengan memungkinkan pembagian kode saat runtime di antara aplikasi yang di-deploy secara independen. Postingan blog ini akan membahas konsep inti dari Module Federation, dengan fokus pada kemampuan berbagi runtime-nya, dan menjelajahi bagaimana hal ini dapat merevolusi cara tim global membangun dan mengelola aplikasi web mereka.
Lanskap Pengembangan Web yang Berkembang dan Kebutuhan untuk Berbagi
Secara historis, pengembangan web sering kali melibatkan aplikasi monolitik di mana semua kode berada dalam satu basis kode. Meskipun pendekatan ini cocok untuk proyek-proyek kecil, ia dengan cepat menjadi sulit dikelola seiring dengan skala aplikasi yang membesar. Ketergantungan menjadi saling terkait, waktu build meningkat, dan men-deploy pembaruan bisa menjadi tugas yang kompleks dan berisiko. Munculnya microservices dalam pengembangan backend membuka jalan bagi pola arsitektur serupa di frontend, yang mengarah pada kemunculan microfrontend.
Microfrontend bertujuan untuk memecah aplikasi frontend yang besar dan kompleks menjadi unit-unit yang lebih kecil, independen, dan dapat di-deploy. Hal ini memungkinkan tim yang berbeda untuk mengerjakan bagian aplikasi yang berbeda secara otonom, yang mengarah pada siklus pengembangan yang lebih cepat dan otonomi tim yang lebih baik. Namun, tantangan signifikan dalam arsitektur microfrontend selalu berupa pembagian kode yang efisien. Menduplikasi pustaka umum, komponen UI, atau fungsi utilitas di berbagai microfrontend menyebabkan:
- Peningkatan Ukuran Bundle: Setiap aplikasi membawa salinan dependensi bersama sendiri, yang membengkakkan ukuran unduhan keseluruhan bagi pengguna.
- Dependensi yang Tidak Konsisten: Microfrontend yang berbeda mungkin akhirnya menggunakan versi yang berbeda dari pustaka yang sama, yang menyebabkan masalah kompatibilitas dan perilaku yang tidak terduga.
- Beban Pemeliharaan: Memperbarui kode bersama memerlukan perubahan di beberapa aplikasi, meningkatkan risiko kesalahan dan memperlambat proses deployment.
- Sumber Daya yang Terbuang: Mengunduh kode yang sama berkali-kali tidak efisien baik untuk klien maupun server.
Module Federation secara langsung mengatasi tantangan-tantangan ini dengan memperkenalkan mekanisme untuk benar-benar berbagi kode saat runtime.
Apa itu JavaScript Module Federation?
JavaScript Module Federation, yang utamanya diimplementasikan melalui Webpack 5, adalah fitur alat build yang memungkinkan aplikasi JavaScript untuk memuat kode secara dinamis dari aplikasi lain saat runtime. Ini memungkinkan pembuatan beberapa aplikasi independen yang dapat berbagi kode dan dependensi tanpa memerlukan monorepo atau proses integrasi saat build yang kompleks.
Ide intinya adalah untuk mendefinisikan "remotes" (aplikasi yang mengekspos modul) dan "hosts" (aplikasi yang mengonsumsi modul dari remotes). Remotes dan hosts ini dapat di-deploy secara independen, menawarkan fleksibilitas yang signifikan dalam mengelola aplikasi yang kompleks dan memungkinkan beragam pola arsitektur.
Memahami Berbagi Runtime: Inti dari Module Federation
Berbagi runtime adalah landasan kekuatan Module Federation. Berbeda dengan teknik pemisahan kode (code-splitting) tradisional atau manajemen dependensi bersama yang sering terjadi pada saat build, Module Federation memungkinkan aplikasi untuk menemukan dan memuat modul dari aplikasi lain langsung di browser pengguna. Ini berarti bahwa pustaka umum, pustaka komponen UI bersama, atau bahkan modul fitur dapat dimuat sekali oleh satu aplikasi dan kemudian tersedia untuk aplikasi lain yang membutuhkannya.
Mari kita uraikan cara kerjanya:
Konsep Kunci:
- Exposes: Sebuah aplikasi dapat 'mengekspos' modul-modul tertentu (misalnya, komponen React, fungsi utilitas) yang dapat dikonsumsi oleh aplikasi lain. Modul-modul yang diekspos ini dibundel ke dalam sebuah container yang dapat dimuat dari jarak jauh.
- Remotes: Sebuah aplikasi yang mengekspos modul dianggap sebagai 'remote'. Ia mengekspos modulnya melalui konfigurasi bersama.
- Consumes: Sebuah aplikasi yang perlu menggunakan modul dari remote adalah 'consumer' atau 'host'. Ia mengkonfigurasi dirinya sendiri untuk mengetahui di mana menemukan aplikasi remote dan modul mana yang harus dimuat.
- Shared Modules: Module Federation memungkinkan pendefinisian modul bersama. Ketika beberapa aplikasi mengonsumsi modul bersama yang sama, hanya satu instans dari modul tersebut yang dimuat dan dibagikan di antara mereka. Ini adalah aspek kritis untuk mengoptimalkan ukuran bundle dan mencegah konflik dependensi.
Mekanismenya:
Ketika aplikasi host membutuhkan modul dari remote, ia memintanya dari container remote. Container remote kemudian secara dinamis memuat modul yang diminta. Jika modul tersebut sudah dimuat oleh aplikasi lain yang mengonsumsi, modul itu akan dibagikan. Pemuatan dan pembagian dinamis ini terjadi dengan mulus saat runtime, menyediakan cara yang sangat efisien untuk mengelola dependensi.
Manfaat Module Federation untuk Pengembangan Global
Keuntungan mengadopsi Module Federation untuk membangun aplikasi global sangat besar dan luas jangkauannya:
1. Peningkatan Skalabilitas dan Kemudahan Pemeliharaan:
Dengan memecah aplikasi besar menjadi microfrontend yang lebih kecil dan independen, Module Federation secara inheren mendorong skalabilitas. Tim dapat bekerja pada microfrontend individual tanpa mempengaruhi yang lain, memungkinkan pengembangan paralel dan deployment independen. Ini mengurangi beban kognitif yang terkait dengan pengelolaan basis kode yang besar dan membuat aplikasi lebih mudah dipelihara dari waktu ke waktu.
2. Ukuran Bundle dan Kinerja yang Dioptimalkan:
Manfaat paling signifikan dari berbagi runtime adalah pengurangan ukuran unduhan secara keseluruhan. Alih-alih setiap aplikasi menduplikasi pustaka umum (seperti React, Lodash, atau pustaka komponen UI kustom), dependensi ini dimuat sekali dan dibagikan. Hal ini mengarah pada:
- Waktu Muat Awal yang Lebih Cepat: Pengguna mengunduh lebih sedikit kode, menghasilkan rendering awal aplikasi yang lebih cepat.
- Navigasi Berikutnya yang Lebih Baik: Saat bernavigasi antara microfrontend yang berbagi dependensi, modul yang sudah dimuat akan digunakan kembali, menghasilkan pengalaman pengguna yang lebih responsif.
- Beban Server yang Berkurang: Lebih sedikit data yang ditransfer dari server, yang berpotensi menurunkan biaya hosting.
Bayangkan sebuah platform e-commerce global dengan bagian-bagian terpisah untuk daftar produk, akun pengguna, dan checkout. Jika setiap bagian adalah microfrontend terpisah, tetapi semuanya bergantung pada pustaka komponen UI umum untuk tombol, formulir, dan navigasi, Module Federation memastikan bahwa pustaka ini hanya dimuat sekali, tidak peduli bagian mana yang pertama kali dikunjungi pengguna.
3. Peningkatan Otonomi dan Kolaborasi Tim:
Module Federation memberdayakan tim yang berbeda, yang mungkin berlokasi di berbagai wilayah global, untuk bekerja secara independen pada microfrontend masing-masing. Mereka dapat memilih tumpukan teknologi mereka sendiri (dalam batas wajar, untuk menjaga tingkat konsistensi) dan jadwal deployment. Otonomi ini mendorong inovasi yang lebih cepat dan mengurangi beban komunikasi. Namun, kemampuan untuk berbagi kode umum juga mendorong kolaborasi, karena tim dapat berkontribusi dan mendapat manfaat dari pustaka dan komponen bersama.
Bayangkan sebuah lembaga keuangan global dengan tim terpisah di Eropa, Asia, dan Amerika Utara yang bertanggung jawab atas penawaran produk yang berbeda. Module Federation memungkinkan setiap tim untuk mengembangkan fitur spesifik mereka sambil memanfaatkan layanan otentikasi umum atau pustaka charting bersama yang dikembangkan oleh tim pusat. Ini mendorong penggunaan kembali dan memastikan konsistensi di berbagai lini produk.
4. Agnostik Teknologi (dengan catatan):
Meskipun Module Federation dibangun di atas Webpack, ia memungkinkan tingkat agnostisisme teknologi antara microfrontend yang berbeda. Satu microfrontend bisa dibangun dengan React, yang lain dengan Vue.js, dan yang lain dengan Angular, selama mereka menyetujui cara mengekspos dan mengonsumsi modul. Namun, untuk berbagi runtime yang sesungguhnya dari framework kompleks seperti React atau Vue, perhatian khusus perlu diberikan pada bagaimana framework ini diinisialisasi dan dibagikan untuk menghindari beberapa instans dimuat dan menyebabkan konflik.
Sebuah perusahaan SaaS global mungkin memiliki platform inti yang dikembangkan dengan satu framework dan kemudian membuat modul fitur khusus yang dikembangkan oleh tim regional yang berbeda menggunakan framework lain. Module Federation dapat memfasilitasi integrasi bagian-bagian yang berbeda ini, asalkan dependensi bersama dikelola dengan hati-hati.
5. Manajemen Versi yang Lebih Mudah:
Ketika dependensi bersama perlu diperbarui, hanya remote yang mengeksposnya yang perlu diperbarui dan di-deploy ulang. Semua aplikasi yang mengonsumsi akan secara otomatis mengambil versi baru pada saat pemuatan berikutnya. Ini menyederhanakan proses pengelolaan dan pembaruan kode bersama di seluruh lanskap aplikasi.
Mengimplementasikan Module Federation: Contoh Praktis dan Strategi
Mari kita lihat bagaimana mengatur dan memanfaatkan Module Federation dalam praktik. Kita akan menggunakan konfigurasi Webpack yang disederhanakan untuk mengilustrasikan konsep-konsep inti.
Skenario: Berbagi Pustaka Komponen UI
Misalkan kita memiliki dua aplikasi: 'Katalog Produk' (remote) dan 'Dasbor Pengguna' (host). Keduanya perlu menggunakan komponen 'Button' bersama dari pustaka 'Shared UI' yang didedikasikan.
1. Pustaka 'Shared UI' (Remote):
Aplikasi ini akan mengekspos komponen 'Button'-nya.
webpack.config.js
untuk 'Shared UI' (Remote):
const { ModuleFederationPlugin } = require('webpack');
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'remoteEntry.js',
path: path.resolve(__dirname, 'dist'),
publicPath: 'http://localhost:3001/dist/', // URL tempat remote akan disajikan
},
plugins: [
new ModuleFederationPlugin({
name: 'sharedUI',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button.js', // Mengekspos komponen Button
},
shared: {
// Mendefinisikan dependensi bersama
react: {
singleton: true, // Memastikan hanya satu instans React yang dimuat
},
'react-dom': {
singleton: true,
},
},
}),
],
// ... konfigurasi webpack lainnya (babel, devServer, dll.)
};
src/components/Button.js
:
import React from 'react';
const Button = ({ onClick, children }) => (
);
export default Button;
Dalam pengaturan ini, 'Shared UI' mengekspos komponen Button
-nya dan mendeklarasikan react
dan react-dom
sebagai dependensi bersama dengan singleton: true
untuk memastikan mereka diperlakukan sebagai instans tunggal di semua aplikasi yang mengonsumsi.
2. Aplikasi 'Katalog Produk' (Remote):
Aplikasi ini juga perlu mengonsumsi komponen Button
bersama dan berbagi dependensinya sendiri.
webpack.config.js
untuk 'Katalog Produk' (Remote):
const { ModuleFederationPlugin } = require('webpack');
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'remoteEntry.js',
path: path.resolve(__dirname, 'dist'),
publicPath: 'http://localhost:3002/dist/', // URL tempat remote ini akan disajikan
},
plugins: [
new ModuleFederationPlugin({
name: 'productCatalog',
filename: 'remoteEntry.js',
exposes: {
'./ProductList': './src/components/ProductList.js', // Mengekspos ProductList
},
remotes: {
// Mendefinisikan remote yang perlu dikonsumsi
sharedUI: 'sharedUI@http://localhost:3001/dist/remoteEntry.js',
},
shared: {
// Dependensi bersama dengan versi yang sama dan singleton: true
react: {
singleton: true,
},
'react-dom': {
singleton: true,
},
},
}),
],
// ... konfigurasi webpack lainnya
};
'Katalog Produk' sekarang mengekspos komponen ProductList
-nya dan mendeklarasikan remote-nya sendiri, secara spesifik menunjuk ke aplikasi 'Shared UI'. Ia juga mendeklarasikan dependensi bersama yang sama.
3. Aplikasi 'Dasbor Pengguna' (Host):
Aplikasi ini akan mengonsumsi komponen Button
dari 'Shared UI' dan ProductList
dari 'Katalog Produk'.
webpack.config.js
untuk 'Dasbor Pengguna' (Host):
const { ModuleFederationPlugin } = require('webpack');
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
publicPath: 'http://localhost:3000/dist/', // URL tempat bundle aplikasi ini disajikan
},
plugins: [
new ModuleFederationPlugin({
name: 'userDashboard',
remotes: {
// Mendefinisikan remote yang dibutuhkan aplikasi host ini
sharedUI: 'sharedUI@http://localhost:3001/dist/remoteEntry.js',
productCatalog: 'productCatalog@http://localhost:3002/dist/remoteEntry.js',
},
shared: {
// Dependensi bersama yang harus cocok dengan remote
react: {
singleton: true,
import: 'react', // Menentukan nama modul untuk import
},
'react-dom': {
singleton: true,
import: 'react-dom',
},
},
}),
],
// ... konfigurasi webpack lainnya
};
src/index.js
untuk 'Dasbor Pengguna':
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
// Mengimpor komponen Button bersama secara dinamis
const RemoteButton = React.lazy(() => import('sharedUI/Button'));
// Mengimpor komponen ProductList secara dinamis
const RemoteProductList = React.lazy(() => import('productCatalog/ProductList'));
const App = () => {
const handleClick = () => {
alert('Tombol diklik dari UI bersama!');
};
return (
Dasbor Pengguna
Memuat Tombol... }>
Klik Saya
Produk
Memuat Produk...