Potongan kode dalam materi ini:
- Export dan Import dalam Node.js:
https://repl.it/@dicodingacademy/163-02-export-import-nodejs?lite=true - Export dan Import banyak Nilai dalam Node.js:
https://repl.it/@dicodingacademy/163-02-multiple-export-nodejs?lite=true - Export dan Import dalam ES6:
https://repl.it/@dicodingacademy/163-02-exporting-importing-single-value-es6?lite=true - Export dan import banyak Nilai dalam ES6:
https://repl.it/@dicodingacademy/163-02-exporting-importing-multiple-value-es6?lite=true
Jika aplikasi kita akan terus berkembang, tentu kita tidak bisa menuliskan seluruh kode hanya pada satu berkas JavaScript.
Ketika kita membaginya menjadi beberapa berkas JavaScript, di situlah kita perlu membuat sebuah modul JavaScript. Apa tujuannya? Tak lain untuk menghubungkan berkas JavaScript yang terpisah agar dapat saling digunakan satu sama lain.
Awalnya, tidak ada cara untuk melakukan modular menggunakan sintaks pada JavaScript.
Awalnya JavaScript terlahir tanpa adanya fitur standar import dan export (Fitur tersebut dapat diterapkan namun perlu menggunakan library tambahan). Karena tidak terdapat fitur standar untuk import dan export, untuk menggunakan banyak berkas JavaScript di browser kita lakukan dengan menggunakan tag <script> pada berkas HTML.
- <script src="src/script/data/clubs.js"></script>
- <script src="src/script/data/data-source.js"></script>
- <script src="src/script/view/main.js"></script>
- <script src="app.js"></script>
Menggunakan tag <script> memang simpel, namun urutan dari penulisan tag tersebut merupakan hal yang vital. Karena app.js tidak akan bekerja jika dituliskan di atas main.js, karena app.js membutuhkan kode main.js tersedia terlebih dahulu.
Belum lagi berkas yang kita buat akan semakin banyak dan kita perlu menuliskan tag <script> nya kembali dengan urutan yang benar. Hal klasik seperti ini sudah seharusnya diubah dengan pendekatan yang lebih modern.
Dengan menggunakan module, kita dapat melakukan import maupun export variabel, class, object, array, atau apapun itu. JavaScript module bersifat reusable sehingga dapat digunakan pada banyak aplikasi yang kita buat.
Pada materi kali ini, kita akan belajar bagaimana cara implementasi module pada Node.js dan ES6
Node.js Export and Import Modules
Sebelumnya sudah dijelaskan bahwa module bekerja dengan cara exporting atau importing nilai baik itu variabel, fungsi, array, objek ataupun class agar dapat digunakan pada berkas JavaScript lain. Dalam satu berkas JavaScript terdiri dari satu module, dan di dalamnya kita dapat melakukan export lebih dari satu nilai.
Dalam environment Node.js, untuk melakukan import dan export module kita gunakan module.exports. Setiap berkas JavaScript yang berjalan pada Node, memiliki objek module lokal yang memiliki properti exports. Properti tersebut digunakan untuk mendefinisikan nilai apa yang akan diekspor dari berkas tersebut.
Kode di bawah ini merupakan contoh bagaimana cara melakukan export nilai dengan menggunakan module.exports.
- const coffeeStock = {
- arabica: 100,
- robusta: 150,
- liberica: 200
- }
- module.exports = coffeeStock;
Berkas state.js
Kode module.exports = coffeeStock membuat object coffeeStock diterapkan sebagai nilai dari module.exports. Di mana nanti nilai properti exports ini akan digunakan (import) pada berkas JavaScript lain.
Jika mencoba melihat nilai module yang ada pada berkas state.js dengan menambahkan kode console.log(module) di akhir berkasnya. Maka kita akan melihat objek coffeeStock menjadi nilai dari properti exports.
- Module {
- id: '.',
- exports: { arabica: 100, robusta: 150, liberica: 200 },
- parent: null,
- filename: '/home/runner/163-02-export-import/state.js',
- loaded: false,
- children: [],
- paths:
- [ '/home/runner/163-02-export-import/node_modules',
- '/home/runner/node_modules',
- '/home/node_modules',
- '/node_modules' ] }
Lalu bagaimana caranya untuk melakukan import atau menggunakan objek yang sudah di-export pada berkas lain? Caranya adalah dengan menggunakan method require().
- const coffeeStock = require('./state.js');
- console.log(coffeeStock);
- /* output
- { arabica: 100, robusta: 150, liberica: 200 }
- */
- const coffeeStock = {
- arabica: 100,
- robusta: 150,
- liberica: 200
- }
- module.exports = coffeeStock;
Dalam inisialisasi variabel coffeeStock (nama variabel bebas kita tentu), kita gunakan method require() dengan memberikan parameter lokasi dari berkas state.js.
Dengan begitu variabel coffeeStock akan memiliki nilai module.exports yang sama pada berkas state.js. Setelah mendapatkan nilainya, kita bebas menggunakannya layaknya variabel lokal pada biasanya.
- const coffeeStock = require('./state.js');
- const makeCoffee = (type, miligrams) => {
- if(coffeeStock[type] >= miligrams) {
- console.log("Kopi berhasil dibuat!")
- } else {
- console.log("Biji kopi habis!")
- }
- }
- makeCoffee("robusta", 80);
- /* output:
- Kopi berhasil dibuat!
- */
- const coffeeStock = {
- arabica: 100,
- robusta: 150,
- liberica: 200
- }
- module.exports = coffeeStock;
“Tips dalam memberikan lokasi pada method require(): Jika kita gunakan lokasi yang relatif (dapat berubah/dipindahkan), pastikan awali dengan menuliskan ./. Contohnya, berkas index.js dan state.js berada pada folder yang sama, maka kita cukup menuliskannya dengan ./state.js.
Multiple export value in Node.js
Pada pengenalan modul disebutkan dalam satu modul kita dapat mengekspor banyak nilai. Lantas bagaimana cara melakukannya?
Sebenarnya nilai yang diekspor tetaplah satu, namun jika terdapat kasus di mana kita ingin mengekspor lebih dari satu nilai, kita dapat memanfaatkan objek literals { }. Contohnya, mari kita kita tambahkan variabel isCoffeeMakerReady pada berkas state.js.
- const coffeeStock = {
- arabica: 100,
- robusta: 150,
- liberica: 200
- }
- const isCoffeeMakerReady = true;
Lantas untuk mengeksport kedua nilai (coffeeStock, isCoffeeMakerReady) kita tidak melakukannya dengan seperti ini:
- module.exports = coffeeStock;
- module.exports = isCoffeeMakerReady;
Namun dengan memanfaatkan objek literal seperti ini:
- module.exports = {coffeeStock, isCoffeeMakerReady};
Sehingga jika kita lihat nilai module pada console, nilai dari properti exports merupakan sebuah objek yang menampung nilai dari objek coffeeStock dan variabel isCoffeeMakerReady:
- Module {
- id: '.',
- exports:
- { coffeeStock: { arabica: 100, robusta: 150, liberica: 200 },
- isCoffeeMakerReady: true },
- parent: null,
- filename: '/home/runner/163-02-multiple-export-nodejs/state.js',
- loaded: false,
- children: [],
- paths:
- [ '/home/runner/163-02-multiple-export-nodejs/node_modules',
- '/home/runner/node_modules',
- '/home/node_modules',
- '/node_modules' ] }
Lalu bagaimana cara melakukan impor kedua nilai tersebut? Masih ingat dengan materi destructuring object? Pada berkas index.js, kita gunakan teknik destructuring object dalam mendapatkan kedua nilainya seperti ini:
- const {coffeeStock, isCoffeeMakerReady} = require('./state.js');
- console.log(coffeeStock);
- console.log(isCoffeeMakerReady);
- /* output
- { arabica: 100, robusta: 150, liberica: 200 }
- true
- */
- const coffeeStock = {
- arabica: 100,
- robusta: 150,
- liberica: 200
- }
- const isCoffeeMakerReady = true;
- module.exports = {coffeeStock, isCoffeeMakerReady};
Namun ingat, ketika menggunakan destructuring object, pastikan penamaan lokal variabelnya sesuai dengan properti objeknya. Jika tidak, maka variabel tersebut akan menghasilkan undefined.
ES6 Modules
Jika kita berada di luar environment Node.js, contohnya browser, kita tetap bisa melakukan impor dan ekspor module JavaScript dengan menggunakan keyword import dan export yang tersedia mulai dari ES6.
Sebelum adanya fitur ini, kita hanya menggunakan tag <script> pada HTML untuk menggunakan berkas JavaScript. Semakin banyak berkas JavaScript yang digunakan, semakin banyak pula tag <script> dituliskan. Nah, dengan menggunakan fitur export dan import, kita cukup menggunakan satu tag <script> yang merupakan berkas JavaScript utama (biasanya diberi nama main.js, app.js, atau index.js).
Pada saat ini, fitur ES6 module standarnya tidak diaktifkan pada browser. Namun kita dapat mengaktifkan fitur ini dengan mudah, yakni dengan menambahkan attribute type=”module” pada tag <script> yang kita gunakan.
- <script src="app.js" type="module"></script>
Tidak seluruh browser dapat mengaktifkan fitur tersebut. Tapi jangan khawatir, setidaknya browser yang banyak digunakan sekarang sudah mendukung fitur ES6 Modules. Berikut rincian detailnya:
- Google Chrome: Versi 61+
- Mozilla Firefox: Versi 60+
- Safari: 10.1+
- Microsoft Edge: 16+
Exporting and importing single value (Default Export)
Pada Node.js sebelumnya tidak ada perbedaan antara exporting multiple value dan single value. Semua nilai yang akan diekspor, dijadikan nilai dari properti module.exports. Pada ES6 module, jika kita hanya mengekspor satu nilai pada sebuah berkas JavaScript baik itu primitive value, function, array, object ataupun class, kita gunakan keyword export default. Contohnya seperti ini:
- const coffeeStock = {
- arabica: 100,
- robusta: 150,
- liberica: 200
- }
- export default coffeeStock;
Lalu bagaimana cara untuk mengimpor nilainya? Kita dapat melakukannya dengan menggunakan keyword import … from seperti berikut ini:
- import coffeeStock from "./state.js";
Berbeda dengan gaya Node.js, kita gunakan keyword import untuk menggantikan const, let, ataupun var dalam mendeklarasi variabel yang diimpor. Lalu di sana juga kita menggunakan from dalam menspesifikasikan lokasi berkas JavaScript-nya.
Ketika menggunakan export default, kita dapat menggunakan penamaan apa saja ketika mendeklarasikan variabel dalam mengimpor nilainya.
- // Kita dapat mengubah penamaan coffeeStock sesuai kebutuhan kita.
- import stock from "./state.js";
Hal tersebut aman untuk dilakukan karena dengan menggunakan export default, dapat dipastikan hanya satu nilai yang diekspor pada satu berkas JavaScript.
Setelah kita berhasil mendapatkan nilai yang diekspor, kita dapat menggunakan nilainya layaknya variabel lokal biasa.
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width">
- <title>repl.it</title>
- <link href="style.css" rel="stylesheet" type="text/css" />
- </head>
- <body>
- <h2>Coffee Stock</h2>
- <ul id="coffee-stock-list">
- </ul>
- <script src="app.js" type="module"></script>
- </body>
- </html>
- import coffeeStock from "./state.js";
- const displayStock = stock => {
- const coffeeStockListElement = document.querySelector("#coffee-stock-list");
- for(const type in stock) {
- const coffeeStockItemElement = document.createElement("li");
- coffeeStockItemElement.innerText = `${type}: ${stock[type]}`;
- coffeeStockListElement.appendChild(coffeeStockItemElement);
- }
- }
- displayStock(coffeeStock);
- const coffeeStock = {
- arabica: 100,
- robusta: 150,
- liberica: 200
- }
- export default coffeeStock;
Exporting and Importing Multiple Value (Named Export/Import)
Jika sebelumnya kita hanya melakukan ekspor satu nilai pada berkas JavaScript menggunakan default export, pada materi kali ini kita akan membahas bagaimana cara melakukan ekspor banyak nilai dalam satu berkas JavaScript dengan menggunakan ES6.
Named export digunakan untuk mengekspor banyak nilai dalam berkas JavaScript. Cara kerjanya mirip seperti pada Node.js. Nilai yang akan diekspor dituliskan di dalam objek literals, seperti ini:
- const coffeeStock = {
- arabica: 100,
- robusta: 150,
- liberica: 200
- }
- const isCoffeeMakerReady = true;
- export { coffeeStock, isCoffeeMakerReady };
Lalu untuk mendapatkan nilai yang diekspor menggunakan named export, kita gunakan teknik destructuring object.
- import { coffeeStock, isCoffeeMakerReady } from "./state.js";
- console.log(coffeeStock);
- console.log(isCoffeeMakerReady);
- /* output:
- { arabica: 100, robusta: 150, liberica: 200 }
- true
- */
Karena named import menggunakan teknik destructuring object untuk mendapatkan nilainya, maka pastikan penamaan variabel sesuai dengan nama variabel yang diekspor. Jika terjadi kesalahan penulisan, maka akan terjadi eror seperti berikut:
- import { stock, isCoffeeMakerReady } from "./state.js";
- /* output:
- SyntaxError: The requested module './state.js' does not provide an export named 'stock'
- */
Namun jika kita tetap ingin mengubah penamaan variabel dari named import, kita bisa melakukannya dengan menambahkan keyword as setelah penamaan variabelnya.
- import { coffeeStock as stock, isCoffeeMakerReady } from "./state.js";
- console.log(stock);
- console.log(isCoffeeMakerReady);
- /* output:
- { arabica: 100, robusta: 150, liberica: 200 }
- true
- */
Setelah kita berhasil mendapatkan nilai yang diekspor, kita dapat menggunakan nilainya layaknya variabel lokal biasa.
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width">
- <title>repl.it</title>
- <link href="style.css" rel="stylesheet" type="text/css" />
- </head>
- <body>
- <h2>Coffee Stock</h2>
- <ul id="coffee-stock-list">
- </ul>
- <button id="coffee-order-button">Pesan kopi!</button>
- <script src="app.js" type="module"></script>
- </body>
- </html>
- import { coffeeStock, isCoffeeMakerReady } from "./state.js";
- const displayStock = stock => {
- const coffeeStockListElement = document.querySelector("#coffee-stock-list");
- for (const type in stock) {
- const coffeeStockItemElement = document.createElement("li");
- coffeeStockItemElement.innerText = `${type}: ${stock[type]}`;
- coffeeStockListElement.appendChild(coffeeStockItemElement);
- }
- }
- const coffeeOrder = (type, miligrams) => {
- return new Promise((resolve, reject) => {
- if (isCoffeeMakerReady) {
- if (coffeeStock[type] >= miligrams) {
- resolve("Kopi berhasil dipesan!");
- } else {
- reject("Maaf Stock kopi habis!")
- }
- } else {
- reject("Maaf mesin sedang rusak!")
- }
- })
- }
- const coffeeOrderButtonEventHandler = async event => {
- const type = prompt("Kopi apa yang ingin dipesan?");
- const miligrams = prompt("Berapa miligrams?");
- try {
- const result = await coffeeOrder(type, miligrams);
- alert(result);
- } catch (rejectionReason) {
- alert(rejectionReason);
- }
- }
- const coffeeOrderButtonElement = document.querySelector("#coffee-order-button");
- coffeeOrderButtonElement.addEventListener("click", coffeeOrderButtonEventHandler)
- displayStock(coffeeStock);
- const coffeeStock = {
- arabica: 100,
- robusta: 150,
- liberica: 200
- }
- const isCoffeeMakerReady = true;
- export { coffeeStock, isCoffeeMakerReady };
Solution : Module
Apakah Anda sudah berhasil menerapkan module pada proyek Club Finder? Jika belum, mari kita lakukan bersama-sama.
Langkah pertama adalah hapus tag <script> selain app.js pada berkas index.html.
- <script src="src/script/data/clubs.js"></script>
- <script src="src/script/data/data-source.js"></script>
- <script src="src/script/view/main.js"></script>
- <script src="app.js"></script>
Menjadi:
- <script src="app.js"></script>
Lalu untuk mengaktifkan fitur module pada browser, tambahkan attribute type dengan nilai “module” pada tag <script>.
- <script src="app.js" type="module"></script>
Kemudian kita lakukan export nilai-nilai yang akan digunakan tiap berkas JavaScript-nya. Mulai dari berkas clubs.js.
Karena pada clubs.js kita hanya melakukan export pada satu nilai variabel, maka gunakanlah export default untuk mengekspor nilai variabel clubs.
Tambahkan kode berikut di akhir berkas clubs.js:
- export default clubs;
Selanjutnya kita export juga class DataSource yang ada pada berkas data-source.js. Tambahkan kode berikut di akhir baris kodenya:
- export default DataSource;
Nilai terakhir yang harus kita export adalah fungsi main yang berada pada berkas main.js. Tambahkan kode berikut di akhir baris kodenya:
- export default main;
Setelah kita selesai mengekspor seluruh nilai yang akan digunakan. Selanjutnya kita impor nilai-nilai yang dibutuhkan pada masing-masing berkas JavaScript.
Yang pertama, berkas data-source.js membutuhkan nilai clubs yang berasal dari module clubs.js. Untuk mendapatkan nilainya, kita perlu mengimpor nilai clubs dengan cara menambahkan kode berikut di awal baris kode data-source.js:
- import clubs from './clubs.js';
Yang kedua, class DataSource digunakan pada berkas main.js sehingga kita juga perlu mengimpor DataSource pada module data-source.js. Tambahkan kode berikut di awal baris berkas main.js:
- import DataSource from '../data/data-source.js';
Dan yang terakhir pada berkas app.js kita membutuhkan nilai main dari module main.js. Sehingga lakukan juga impor nilai main dengan cara menambahkan kode berikut di awal baris berkas app.js:
- import main from "./src/script/view/main.js";
Setelah menerapkan module, kemungkinan besar Anda akan mengalami eror:
- Access to script at 'file:///....../ClubFinder/app.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.
Eror terjadi karena ketika menggunakan import, kita harus tunduk terhadap peraturan CORS. Apa itu? Nanti akan kita bahas detail pada modul Asynchronous JavaScript Request. Namun singkatnya, kita tidak bisa lagi membuka proyek dengan membuka berkas index.html secara langsung. Namun harus menggunakan local http server (dijalankan melalui localhost).
Jika Anda terbiasa dengan Node.js dan sudah menginstalnya, Anda bisa gunakan package http-server yang tersedia pada tautan berikut: https://www.npmjs.com/package/http-server. Namun bagi Anda yang belum terbiasa menggunakannya, Anda bisa memasang extensions pada code editor yang digunakan.
- Visual Studio Code : Untuk Anda yang menggunakan VSCode sebagai text editor, Anda bisa memasang extensions bernama “Live Server” untuk menjalankan website pada localhost. Panduan cara memasang extensions pada VSCode dapat Anda pelajari pada tautan berikut: https://code.visualstudio.com/docs/editor/extension-gallery
- Atom Text Editor : Untuk Anda yang menggunakan Atom sebagai text editor, Anda bisa memasang extensions bernama “atom-live-server” untuk menjalankan website pada localhost. Panduan cara memasang extensions pada Atom dapat Anda pelajari pada tautan berikut: https://flight-manual.atom.io/using-atom/sections/atom-packages/
- Bracket : Untuk Anda yang menggunakan bracket. Anda bisa menjalankan project pada localhost dengan memanfaatkan fitur live preview yang tersedia langsung pada text editor tersebut.
Langkah dari solution ini bisa Anda temukan juga pada repository berikut: https://github.com/dicodingacademy/a163-bfwd-labs/tree/108-club-finder-module-solution