1 September 2021

Modul, Pengenalan

Saat aplikasi kita berkembang menjadi lebih besar, kita ingin membaginya menjadi banyak file, yang disebut modul. Sebuah modul bisa berisi kelas atau fungsi library untuk tujuan spesifik.

Untuk waktu yang lama, Javascript ada tanpa sintaks modul tingkat-bahasa. Hal ini tidak menjadi masalah, karena awalnya kode skrip lebih kecil dan simpel, jadi modul tidak diperlukan.

Tetapi akhirnya kode skrip menjadi lebih kompleks, jadi komunitas membuat berbagai cara untuk mengatur kode menjadi modul, library khusus untuk memuat modul sesuai permintaan.

Disebut beberapa dengan nama (Untuk alasan sejarah):

  • AMD – satu dari sistem modul yang paling kuno, awalnya diimplementasikan oleh require.js.
  • CommonJS – Sistem modul yang dibuat untuk server Node.js.
  • UMD – ada satu lagi sistem modul, disarankan untuk menjadi modul universal, karena cocok dengan AMD dan CommonJS.

Sekarang semua ini perlahan menjadi bagian dari sejarah, tetapi kita masih bisa menemukannya di kode skrip lama.

Sistem modul tingkat-bahasa muncul di tahun 2015, berevolusi secara bertahap sejak saat itu, dan sekarang di dukung oleh semua browser utama dan Node.js. Jadi sekarang kita akan mulai mempelajari modul Javascript modern.

Apa itu modul?

Modul hanya sebuah file. Satu kode skrip adalah satu modul. Sangat simpel bukan.

Modul bisa memuat satu sama lain dan menggunakan pengarah khusus export dan import untuk fungsi pertukaran, memanggil fungsi dari satu modul ke modul lainnya.

  • export kata kunci variabel label dan fungsi yang bisa diakses diluar modul saat ini.
  • import memperbolehkan impor fungsi dari modul lain. Misalnya, jika kita memiliki file sayHi.js ekspor fungsi:
// 📁 sayHi.js
export function sayHi(user) {
  alert(`Hello, ${user}!`);
}

…Lalu file lain bisa di impor dan menggunakannya:

// 📁 main.js
import {sayHi} from './sayHi.js';

alert(sayHi); // function...
sayHi('John'); // Hello, John!

import akan diarahkan untuk memuat modul dari path sayHi.js yang relatif dengan file saat ini, dan menetapkan fungsi yang diekspor sayHi pada variabel yang sesuai.

Mari kita jalankan contoh ini di browser.

Karena modul didukung oleh kata kunci dan fitur khusus, kita harus memberi tahu browser bahwa kode skrip harus diperlakukan sebagai modul, dengan menggunakan attribut <script type="module">.

Seperti ini:

Hasil
say.js
index.html
export function sayHi(user) {
  return `Hello, ${user}!`;
}
<!doctype html>
<script type="module">
  import {sayHi} from './say.js';

  document.body.innerHTML = sayHi('John');
</script>

Browser otomatis mengambil dan mengevaluasi modul impor (dan impornya jika diperlukan), dan kemudian menjalankan kode skrip.

Modul hanya bekerja melalui HTTP(s), dan tidak di file lokal

Inti dari fitur modul

Apa perbedaan modul, dibandingkan dengan kode skrip “biasa”?

Terdapat inti dari fitur, valid untuk browser dan javascript di sisi server.

Selalu menggunakan “use strict”

Modul selalu menggunakan use strict secara default. Misalnya untuk menetapkan variabel yang tidak dideklarasikan akan menjadi error.

<script type="module">
  a = 5; // error
</script>

Batasan level-modul

Setiap modul memiliki batasan level-tinggi sendiri. Dengan kata lain, variabel level-tinggi dan fungsi dari modul tidak bisa dilihat di kode skrip lainnya.

Contoh dibawah ini, dua kode skrip di impor, dan hello.js mencoba untuk menggunakan variabel user yang dideklarasikan di user.js, maka akan gagal:

Hasil
hello.js
user.js
index.html
alert(user); // no such variable (each module has independent variables)
let user = "John";
<!doctype html>
<script type="module" src="user.js"></script>
<script type="module" src="hello.js"></script>

Modul diharapkan untuk export yang ingin bisa diakses dari luar import yang dibutuhkan.

Jadi kita harus impor user.js pada hello.js untuk mendapatkan fungsi yang dibutuhkan dari user.js daripada mengandalkan variabel global.

Ini variasi yang benar:

Hasil
hello.js
user.js
index.html
import {user} from './user.js';

document.body.innerHTML = user; // John
export let user = "John";
<!doctype html>
<script type="module" src="hello.js"></script>

Pada browser, batasan level-tinggi independen juga ada untuk setiap <script type="module">:

<script type="module">
  // The variable is only visible in this module script(variabel hanya terlihat di modul skrip ini)
  let user = "John";
</script>

<script type="module">
  alert(user); // Error: user is not defined(Error: user tidak definisikan)
</script>

Jika kita benar-benar perlu untuk membuat variabel global tingkat window, kita akan menetapkan secara eksplisit pada window dan mengaksesnya sebagai window.user. Tetapi itu adalah pengecualian yang membutukan alasan bagus.

Kode modul dievaluasi saat pertama kali saat di impor

Jika modul yang sama di impor di banyak tempat yang lain, kode yang akan dijalankan hanya yang pertama saja, lalu ekspor akan di berikan pada semua modul impor.

Hal ini memiliki konsekuensi yang penting. Mari kita lihat dengan contoh:

Pertama, jika kode modul dijalankan akan memberikan efek samping, seperti menampilkan pesan, maka mengimpornya berkali-kali hanya akan memicu pesan satu kali saja:

// 📁 alert.js
alert("Module is evaluated!");
// Import the same module from different files(impor modul yang sama dari file yang berbeda)

// 📁 1.js
import `./alert.js`; // Module is evaluated!(modul dievaluasi)

// 📁 2.js
import `./alert.js`; // (shows nothing)(menampilkan kosong)

Pada praktek, modul level-tinggi banyak di gunakan untuk inisialisasi, membuat struktur data internal, dan jika kita ingin membuat sesuatu bisa digunakan kembali – ekspor saja.

Sekarang, contoh yang lebih lanjut.

Katakanlah, modul mengekspor sebuah objek:

// 📁 admin.js
export let admin = {
  name: "John"
};

Jika modul ini diimpor dari banyak file, modul hanya akan dievalusi pertama kali saja, lalu objek admin dibuat, dan akan diteruskan kepada semua file impor lebih lanjut lainnya.

Semua file impor akan mendapatkan persis satu hanya objek admin:

// 📁 1.js
import {admin} from './admin.js';
admin.name = "Pete";

// 📁 2.js
import {admin} from './admin.js';
alert(admin.name); // Pete

// Both 1.js and 2.js imported the same object(1.js dan 2.js mengimpor objek yang sama)
// Changes made in 1.js are visible in 2.js(perubahan di buat di 1.js terlihat di 2.js)

Jadi, mari kita ulangi – modul hanya dijalankan satu kali, ekspor akan dihasilkan, dan kemudian dibagikan diantara impor, jadi jika sesuatu merubah objek admin, modul lainnya bisa melihat hal tersebut.

Perilaku seperti itu mengizinkan kita untuk bisa mengkonfigurasi modul di impor pertama. Kita bisa mengatur properti satu kali, dan file impor lebih lanjut sudah siap.

Misalnya, modul admin.js mungkin menyediakan fungsi tertentu, tetapi ingin mendapatkan kredensial pada objek admin dari luar:

// 📁 admin.js
export let config = { };

export function sayHi() {
  alert(`Ready to serve, ${config.user}!`);
}

Pada init.js, kode skrip pertama dari aplikasi, kita mengatur admin.name. Kemudian semua orang akan melihatnya, termasuk pemanggilan yang dibuat di dalam admin.js itu sendiri:

// 📁 init.js
import {config} from './admin.js';
config.user = "Pete";

…Sekarang modul admin.js telah dikonfigurasi.

Importir lebih lanjut dapat memanggilnya, dan itu menunjukkan dengan benar pengguna saat ini:

// 📁 another.js
import {sayHi} from './admin.js';

sayHi(); // Ready to serve, Pete!

import.meta

Objek import.meta berisi informasi tentang modul saat ini.

Kontennya tergantung dengan lingkungannya. Pada browser, berisi url dari kode skrip, atau url halaman web saat ini di dalam HTML:

<script type="module">
  alert(import.meta.url); // script URL
  // for an inline script - the URL of the current HTML-page
</script>

Dalam modul, “this” tidak didefinisikan

Ini semacam fitur yang kecil , tetapi untuk lebih lengkap kita harus menyebutkannya.

Dalam modul, level-tinggi this tidak didefinisikan.

Dibedakan dengan kode skrip tidak modul, dimana this adalah objek global:

<script>
  alert(this); // window
</script>

<script type="module">
  alert(this); // undefined
</script>