Masukkan Password
86 Bekerja dengan Modul ES6
Lecture ini membahas ES6 Modules, yaitu cara modern TypeScript/JavaScript untuk membagi kode ke banyak file menggunakan export dan import.
Kalau lecture sebelumnya membahas namespace, sekarang dosen menunjukkan mengapa ES6 Modules lebih aman dan lebih modern.
1. Gambaran besar lecture
Sebelumnya kita punya struktur seperti ini:
src/
├── app.ts
├── models/
│ └── user.ts
└── user-utils/
└── user-action.ts
Misalnya:
user.ts
↓
user-action.ts
↓
app.ts
Artinya:
user.tsmenyediakanUseruser-action.tsmenggunakanUserdan menyediakancreateUser()sertagetUsers()app.tsmenggunakancreateUser()dangetUsers()
Pada lecture sebelumnya, hubungan antar-file dilakukan dengan:
/// <reference path="../models/user.ts" />
dan menggunakan:
namespace App {
...
}
Sekarang semuanya akan diganti dengan ES6 Modules.
2. Masalah namespace yang ingin diselesaikan
Dosen memberikan contoh masalah.
Misalnya app.ts menggunakan:
createUser("John", 28, "male");
Padahal createUser() sebenarnya didefinisikan di:
user-action.ts
Dengan namespace, TypeScript bisa mengetahui hubungan tersebut jika kita menambahkan reference:
/// <reference path="./user-utils/user-action.ts" />
Tetapi dosen mencoba menghapus reference tersebut.
Hasilnya menarik:
TypeScript tidak memberikan compilation error.
Tetapi ketika program dijalankan:
createUser is not a function
Jadi ada perbedaan penting:
TypeScript compilation
↓
berhasil
↓
JavaScript dijalankan
↓
ERROR
Ini disebut runtime error.
3. Kenapa runtime error ini kurang bagus?
Idealnya, kesalahan seperti ini diketahui sebelum program dijalankan.
Kita ingin:
Salah import
↓
TypeScript
↓
❌ Compilation error
bukan:
Salah dependency
↓
TypeScript
↓
✅ Compile
↓
Browser
↓
💥 Runtime error
ES6 Modules membantu kita mendapatkan pengecekan dependency yang lebih jelas.
4. Apa itu ES6 Module?
ES6 Module adalah fitur standar JavaScript modern untuk membagi kode ke dalam file-file yang terpisah.
Dua keyword terpenting:
export
dan
import
Secara sederhana:
export
Artinya:
“Kode ini boleh digunakan oleh file lain.”
import
Artinya:
“Saya ingin menggunakan kode dari file lain.”
Analogi sederhananya:
Bayangkan user.ts adalah sebuah toko.
user.ts
┌─────────────────┐
│ User │
│ createSomething │
│ helper │
└─────────────────┘
Kalau User diberi:
export
berarti:
“Barang ini boleh dibeli oleh file lain.”
Kemudian file lain melakukan:
import
artinya:
“Saya mau mengambil barang tersebut.”
5. Langkah pertama: hapus namespace
Sebelumnya user.ts kira-kira:
namespace App {
export class User {
constructor(
public name: string,
public age: number,
public gender: string
) {}
}
}
Sekarang namespace dihapus:
export class User {
constructor(
public name: string,
public age: number,
public gender: string
) {}
}
Perhatikan sesuatu yang penting:
export class User
bukan:
class User
Kenapa?
Karena User ingin digunakan oleh file lain.
6. Kenapa export diperlukan?
Misalnya kita punya:
class User {
...
}
Tanpa export, User hanya menjadi bagian dari module/file tersebut.
File lain tidak bisa mengatakan:
import { User } from "../models/user.js";
Karena User tidak diekspor.
Jadi:
export class User
berarti:
“Saya menyediakan
Useruntuk digunakan dari luar file ini.”
7. Sekarang masuk ke user-action.ts
Sebelumnya ada namespace dan reference:
/// <reference path="../models/user.ts" />
namespace App {
...
}
Sekarang keduanya dihapus.
Kemudian kita ingin menggunakan:
User
dari:
models/user.ts
Maka kita perlu melakukan:
import { User } from "../models/user.js";
Mari kita bedah satu-satu.
8. Memahami syntax import
Kode:
import { User } from "../models/user.js";
terdiri dari beberapa bagian.
Bagian 1
import
Artinya:
Saya ingin mengambil sesuatu dari module lain.
Bagian 2
{ User }
Artinya:
Saya ingin mengambil
User.
Bagian 3
from
Artinya:
Ambil dari file berikut.
Bagian 4
"../models/user.js"
Artinya:
File sumbernya berada di folder
models, satu level di atas.
Jadi:
user-action.ts
│
│ ../
↓
src/
├── models/
│ └── user.ts
│
└── user-utils/
└── user-action.ts
Dari user-action.ts, kita naik satu folder:
../
kemudian masuk:
models/
sehingga:
../models/user.js
9. Hal penting: kenapa user.js, bukan user.ts?
Ini salah satu bagian paling penting dari lecture.
Kita menulis:
import { User } from "../models/user.js";
bukan:
import { User } from "../models/user.ts";
Kenapa?
Karena TypeScript akan dikompilasi menjadi JavaScript.
Misalnya:
src/
├── models/
│ └── user.ts
setelah compile menjadi:
dist/
├── models/
│ └── user.js
Browser tidak menjalankan TypeScript secara langsung.
Browser menjalankan:
JavaScript
Jadi dependency yang akan dicari browser adalah:
user.js
10. Ini konsep yang harus benar-benar dipahami
Ada dua dunia:
Saat development
Kita menulis:
.ts
Contoh:
user.ts
user-action.ts
app.ts
Setelah compilation
TypeScript menghasilkan:
.js
Contoh:
user.js
user-action.js
app.js
Maka browser bekerja dengan:
JavaScript
bukan:
TypeScript
Gambaran sederhananya:
DEVELOPMENT
│
▼
TypeScript files
(.ts)
│
│ tsc
▼
JavaScript files
(.js)
│
▼
Browser
11. Sekarang app.ts
Kita juga menghapus:
/// <reference ... />
dan:
namespace App {
...
}
Kemudian app.ts ingin menggunakan:
createUser()
dan:
getUsers()
yang berada di:
user-action.ts
Maka kita melakukan:
import { createUser, getUsers } from "./user-utils/user-action.js";
Sekarang app.ts secara eksplisit mengatakan:
“Saya membutuhkan
createUserdangetUsersdari fileuser-action.js.”
12. export dan import harus berpasangan
Ini konsep paling penting.
Misalnya di user-action.ts:
export function createUser(...) {
...
}
export function getUsers() {
...
}
Kemudian di app.ts:
import { createUser, getUsers }
from "./user-utils/user-action.js";
Hubungannya:
user-action.ts
┌─────────────────────────┐
│ export createUser │
│ export getUsers │
│ │
│ userList │
└────────────┬────────────┘
│
│ import
▼
app.ts
13. Kenapa userList tidak di-export?
Di user-action.ts ada:
const userList: User[] = [];
Dosen mengatakan dia tidak ingin menggunakan userList secara langsung dari file lain.
Maka:
const userList: User[] = [];
tidak perlu:
export const userList: User[] = [];
Sebaliknya:
export function createUser(...) {
...
}
export function getUsers() {
...
}
Yang diekspor hanya sesuatu yang memang ingin digunakan file lain.
Ini sebenarnya bagus karena membuat encapsulation.
14. Apa itu encapsulation dalam konteks ini?
Misalnya:
const userList: User[] = [];
adalah data internal.
Kita tidak ingin semua file bisa melakukan:
userList.push(...)
userList.pop(...)
userList = ...
Kita hanya memberikan akses melalui function:
createUser()
dan:
getUsers()
Jadi:
user-action.ts
┌─────────────────────┐
│ │
│ userList │
│ (internal) │
│ │ │
│ ▼ │
│ createUser() │
│ getUsers() │
│ │ │
└───────┼─────────────┘
│
export
│
▼
app.ts
app.ts tidak perlu tahu bagaimana userList disimpan.
15. Sekarang masalah berikutnya: tsconfig.json
Karena kita sudah tidak menggunakan namespace, konfigurasi TypeScript juga perlu diubah.
Sebelumnya kita menggunakan:
"outFile": "./dist/bundle.js"
Ini cocok dengan pendekatan namespace yang dibahas sebelumnya.
Sekarang outFile tidak diperlukan.
Jadi dihapus/comment:
// "outFile": "./dist/bundle.js"
16. Mengapa outFile tidak diperlukan?
Sebelumnya kita ingin menghasilkan satu file:
bundle.js
Misalnya:
src/
├── app.ts
├── user.ts
└── user-action.ts
menjadi:
dist/
└── bundle.js
Tetapi sekarang kita menggunakan ES6 Modules.
Kita justru ingin mempertahankan file-file tersebut:
dist/
├── app.js
├── models/
│ └── user.js
└── user-utils/
└── user-action.js
Jadi struktur folder tetap dipertahankan.
17. module: "es2015"
Dosen juga mengubah konfigurasi module menjadi:
"module": "es2015"
Artinya TypeScript diminta menghasilkan JavaScript yang menggunakan ES Modules.
Dengan kata lain, TypeScript tidak mengubah konsep:
import
export
menjadi sistem module lain.
Hasil JavaScript tetap menggunakan:
import
export
18. target: "es2015"
Dosen juga mengatur:
"target": "es2015"
atau bisa juga menggunakan:
ES6
Karena ES2015 = ES6.
Jadi:
ES2015
=
ES6
Untuk lecture ini:
{
"compilerOptions": {
"module": "es2015",
"target": "es2015"
}
}
19. Hasil compilation
Setelah compile, struktur:
src/
├── app.ts
├── models/
│ └── user.ts
└── user-utils/
└── user-action.ts
menjadi:
dist/
├── app.js
├── models/
│ └── user.js
└── user-utils/
└── user-action.js
Perhatikan:
src/models/user.ts
↓
dist/models/user.js
dan:
src/user-utils/user-action.ts
↓
dist/user-utils/user-action.js
Struktur folder tetap sama.
20. Lalu HTML harus menunjuk ke mana?
Sebelumnya HTML menunjuk:
<script src="bundle.js"></script>
Sekarang tidak ada bundle.js.
Karena app.ts adalah entry point aplikasi, hasil compile-nya adalah:
app.js
Maka HTML:
<script src="app.js"></script>
Tetapi masih ada satu masalah.
21. Error: Cannot use import statement outside a module
Kalau kita hanya menulis:
<script src="app.js"></script>
browser bisa memberikan:
Cannot use import statement outside of a module
Kenapa?
Karena browser melihat:
import { createUser } from "./user-utils/user-action.js";
tetapi script tersebut dianggap sebagai script JavaScript biasa.
Browser perlu diberi tahu:
“Script ini adalah ES Module.”
Caranya:
<script type="module" src="app.js"></script>
22. Perhatikan type="module"
Ini sangat penting.
JavaScript biasa
<script src="app.js"></script>
ES Module
<script type="module" src="app.js"></script>
type="module" memberi tahu browser bahwa file tersebut menggunakan sistem ES Modules.
23. Bagaimana browser sekarang memahami dependency?
Ini bagian lecture yang sangat menarik.
Misalnya index.html:
<script type="module" src="app.js"></script>
Browser pertama kali mengambil:
app.js
Tetapi browser melihat di dalamnya:
import { createUser, getUsers }
from "./user-utils/user-action.js";
Browser berpikir:
“Oh,
app.jsmembutuhkanuser-action.js.”
Kemudian browser mengambil:
user-action.js
Tetapi ternyata user-action.js juga memiliki:
import { User }
from "../models/user.js";
Browser kemudian mengambil:
user.js
Jadi dependency chain-nya:
index.html
│
▼
app.js
│
│ import
▼
user-action.js
│
│ import
▼
user.js
Kita tidak perlu memasukkan ketiga file tersebut ke HTML satu per satu.
Cukup:
<script type="module" src="app.js"></script>
24. Bandingkan dengan cara lama
Cara namespace
Kita perlu memikirkan bagaimana file-file tersebut dimuat dan menggunakan:
/// <reference path="..." />
serta bisa menggunakan outFile untuk menggabungkannya.
Misalnya:
HTML
│
▼
bundle.js
│
├── user.js
├── user-action.js
└── app.js
Cara ES Modules
Browser memahami dependency secara langsung:
HTML
│
▼
app.js
│
▼
user-action.js
│
▼
user.js
Karena hubungan tersebut ditulis secara eksplisit menggunakan:
import
25. Kenapa ES Modules lebih aman?
Dosen menunjukkan hal penting.
Misalnya app.ts punya:
import {
createUser,
getUsers
} from "./user-utils/user-action.js";
Kemudian kita hapus import tersebut tetapi tetap menggunakan:
createUser(...)
TypeScript akan memberikan error.
Ini bagus.
Karena TypeScript berkata kira-kira:
“
createUsertidak tersedia di file ini.”
Jadi kesalahan bisa diketahui ketika compile.
Bukan menunggu browser menjalankan aplikasi.
26. Ini perbedaan penting dengan namespace
Secara sederhana:
| Namespace | ES6 Module |
|---|---|
namespace App {} | import / export |
/// <reference> | import |
| Pengelompokan namespace | Module per file |
Bisa memakai outFile | Umumnya file tetap terpisah |
| Dependency kurang eksplisit | Dependency sangat eksplisit |
| Pendekatan lama | Pendekatan modern |
| TypeScript-specific | Standar JavaScript |
Jadi dosen sebenarnya sedang mengatakan:
Namespace bisa digunakan, tetapi ES6 Modules memberikan mekanisme module yang lebih modern dan eksplisit.
27. Jangan salah memahami export
Ada satu hal yang sering membingungkan pemula.
Misalnya:
export class User {}
export bukan berarti:
“Masukkan class ini ke JavaScript.”
Bukan.
export berarti:
“Class ini tersedia sebagai bagian dari public API module ini sehingga module lain dapat meng-import-nya.”
Misalnya:
export class User {}
lalu:
import { User } from "./user.js";
28. Jangan salah memahami import
import juga bukan berarti:
“Copy-paste seluruh isi file.”
Secara konsep lebih tepat:
“Module ini membutuhkan sesuatu yang diekspor oleh module lain.”
Misalnya:
import { User } from "../models/user.js";
berarti:
user-action.ts
│
│ membutuhkan
▼
User
│
│ berasal dari
▼
models/user.js
29. ./ dan ../ tetap penting
Karena sekarang kita menggunakan path untuk import, kamu harus memahami ini.
./
Berarti:
folder saat ini
Contoh:
import { X } from "./helper.js";
Artinya:
file sekarang
│
└── helper.js
../
Berarti:
naik satu folder
Contoh:
import { User } from "../models/user.js";
Kalau kita berada di:
user-utils/
user-action.js
maka:
../
membawa kita ke:
src/
kemudian:
models/user.js
30. Struktur akhirnya
Setelah semua perubahan, kira-kira seperti ini:
src/
│
├── app.ts
│
├── models/
│ └── user.ts
│
└── user-utils/
└── user-action.ts
user.ts
export class User {
constructor(
public name: string,
public age: number,
public gender: string
) {}
}
user-action.ts
import { User } from "../models/user.js";
const userList: User[] = [];
export function createUser(
name: string,
age: number,
gender: string
) {
const user = new User(name, age, gender);
userList.push(user);
return user;
}
export function getUsers() {
return userList;
}
app.ts
import {
createUser,
getUsers
} from "./user-utils/user-action.js";
const U1 = createUser("John", 28, "male");
const U2 = createUser("Mary", 30, "female");
console.log(U1);
console.log(U2);
console.log(getUsers());
31. Alur keseluruhan
Sekarang coba lihat keseluruhan prosesnya:
TypeScript
│
┌─────────┼─────────┐
▼ ▼ ▼
user.ts user-action.ts app.ts
│ │ │
│ │ │
│ └─────┐ │
│ │ │
└───────────────┘ │
│
import/export │
│
compile │
↓ │
JavaScript files │
│ │
▼ ▼
dist/
│
┌─────────┼──────────┐
▼ ▼ ▼
user.js user-action.js app.js
│
▼
Browser
│
▼
app.js loaded
│
import detected
▼
user-action.js
│
import detected
▼
user.js
32. Network tab yang ditunjukkan dosen
Dosen kemudian membuka Network tab di browser.
Tujuannya untuk membuktikan bahwa browser benar-benar mengambil file-file dependency tersebut.
Misalnya browser melakukan request:
index.html
↓
style.css
↓
app.js
↓
user-action.js
↓
user.js
Kita hanya menuliskan di HTML:
<script type="module" src="app.js"></script>
Tetapi browser menemukan dependency melalui:
import ...
dan mengambil file yang diperlukan.
33. Kenapa browser bisa melakukan itu?
Karena ES Modules adalah fitur standar JavaScript modern.
Browser modern memahami:
import { something } from "./something.js";
dan:
export ...
Jadi dependency graph dapat diketahui dari kode itu sendiri.
Contohnya:
app.js
│
├── import → user-action.js
│ │
│ └── import → user.js
│
└── ...
Browser dapat mengikuti hubungan tersebut.
34. Dependency graph
Sebenarnya konsep ini bisa kita sebut sebagai dependency graph.
Contohnya:
app.js
│
▼
user-action.js
│
▼
user.js
Artinya:
app.js
bergantung pada:
user-action.js
dan:
user-action.js
bergantung pada:
user.js
Jadi:
user.js
↑
│
user-action.js
↑
│
app.js
Semakin besar aplikasi, konsep dependency seperti ini menjadi sangat berguna.
35. Analogi sederhana
Bayangkan aplikasi seperti restoran.
user.ts
Adalah dapur yang menyediakan:
User
Dengan:
export class User {}
Dapur berkata:
“Saya menyediakan User.”
user-action.ts
Memesan User:
import { User } from "../models/user.js";
Artinya:
“Saya membutuhkan User dari dapur.”
Kemudian user-action.ts menyediakan:
createUser
getUsers
dengan:
export function createUser() {}
export function getUsers() {}
app.ts
Kemudian memesan:
import {
createUser,
getUsers
} from "./user-utils/user-action.js";
Artinya:
“Saya membutuhkan dua fungsi tersebut.”
Jadi setiap file secara eksplisit mengatakan:
Saya menyediakan apa?
↓
export
Saya membutuhkan apa?
↓
import
Ini jauh lebih jelas daripada berharap semua kode berada dalam satu namespace global.
36. Kenapa module lebih bagus untuk project besar?
Bayangkan project kecil:
5 file
Namespace mungkin masih terasa nyaman.
Tetapi project besar bisa memiliki:
100+
500+
1000+
file.
Kalau semuanya berbagi namespace global, bisa terjadi:
App
├── User
├── Product
├── Order
├── Payment
├── Cart
├── ...
└── ...
dan dependency antar bagian menjadi semakin sulit dilacak.
Dengan module, setiap file mengatakan secara eksplisit:
import { User } from "...";
atau:
export class Product {}
Sehingga dependency lebih mudah dibaca.
37. Kesalahan paling umum pemula
❌ 1. Lupa export
class User {}
kemudian:
import { User } from "./user.js";
Tidak akan bekerja dengan benar karena User tidak diekspor.
Harus:
export class User {}
❌ 2. Lupa import
Misalnya:
export function createUser() {}
di file A.
Kemudian file B langsung:
createUser();
Padahal belum:
import { createUser } from "./a.js";
Maka module B tidak memiliki nama tersebut.
❌ 3. Salah path
Misalnya struktur:
src/
├── models/
│ └── user.ts
└── user-utils/
└── user-action.ts
Dari user-action.ts ke user.ts:
../models/user.js
bukan:
./models/user.js
karena models bukan berada di dalam user-utils.
❌ 4. Salah extension
Dalam konteks lecture ini, import ditulis:
import { User } from "../models/user.js";
bukan:
import { User } from "../models/user.ts";
karena yang akhirnya dimuat browser adalah JavaScript hasil compilation.
❌ 5. Lupa type="module"
Kalau HTML:
<script src="app.js"></script>
bisa muncul:
Cannot use import statement outside of a module
Gunakan:
<script type="module" src="app.js"></script>
38. Namespace vs ES6 Modules — inti perbedaannya
Kalau ingin mengingat lecture ini dengan sangat sederhana:
Namespace
"Semua kode ini saya kelompokkan ke App."
Contoh:
namespace App {
export class User {}
}
ES6 Module
"File ini menyediakan User.
File lain secara eksplisit meminta User."
Contoh:
// user.ts
export class User {}
dan:
// user-action.ts
import { User } from "../models/user.js";
39. Satu kalimat untuk masing-masing keyword
Kalau sedang belajar untuk ujian/interview, ingat:
export= membuat sesuatu tersedia untuk module lain.
import= mengambil sesuatu yang diekspor oleh module lain.
type="module"= memberi tahu browser bahwa JavaScript tersebut menggunakan ES Modules.
module: "es2015"= meminta TypeScript menghasilkan ES Module JavaScript.
target: "es2015"= menentukan versi JavaScript yang menjadi target hasil compilation.
40. Hal terpenting dari lecture ini
Kalau saya ringkas seluruh lecture menjadi satu alur:
Namespace
│
│ punya beberapa keterbatasan
▼
ES6 Modules
│
├── export
│ ↓
│ menyediakan code
│
├── import
│ ↓
│ menggunakan code dari file lain
│
├── module: es2015
│ ↓
│ TypeScript menghasilkan ES Modules
│
├── target: es2015
│ ↓
│ target JavaScript modern
│
├── hapus outFile
│ ↓
│ tidak perlu bundle.js
│
└── <script type="module">
↓
browser memahami import/export
Dan struktur akhirnya:
index.html
│
▼
<script type="module" src="app.js">
│
▼
app.js
│
│ import
▼
user-action.js
│
│ import
▼
user.js
Jadi inti besar lecture ini adalah: dengan ES6 Modules, kita tidak lagi hanya mengelompokkan kode menggunakan namespace, tetapi setiap file secara eksplisit menyatakan apa yang dia sediakan (export) dan apa yang dia butuhkan (import). Browser modern kemudian bisa mengikuti dependency tersebut secara otomatis.