Kategori: Javascript

Belajar Babel Loader Javascript ES6

2020031319583407bf63e74fd402a5583504377dfd8829.png
Apa itu babel atau babel.js? Babel merupakan sebuah transpiler yang bertugas untuk mengubah sintaks JavaScript modern (ES6+) menjadi sintaks yang dapat didukung penuh oleh seluruh browser.

JavaScript merupakan bahasa pemrograman yang berkembang sangat pesat. Komunitasnya besar, dan tiap tahun selalu terdapat versi yang baru. 
Namun perkembangan yang pesat tadi ternyata membutuhkan waktu yang lama untuk diadaptasi oleh browser atau Node.js. Lalu jika kita ingin mencoba sintaks terbaru di JavaScript apakah kita perlu menunggu hingga seluruh browser berhasil mengadaptasi pembaharuan tersebut? Tentu tidak! 
Dengan babel Anda dapat menuliskan sintaks JavaScript versi terbaru tanpa khawatir memikirkan dukungan pada browser. Karena babel akan mengubah sintaks yang kita tuliskan menjadi kode yang dapat diterima browser.
Jika Anda penasaran bagaimana cara babel bekerja, babel menyediakan sebuah playground yang dapat kita manfaatkan untuk mengubah sintaks JavaScript modern (ES6+) menjadi sintaks lama. Untuk mencobanya, silakan Anda buka tautan berikut: https://babeljs.io/repl.
20200313195932ff6b88f0352e20c9c08f27fd89aff700.png
Pada playground tersebut kita juga dapat memilih preset yang kita inginkan. Secara default preset akan mengarah ES2015 (ES6).
Anda sudah tahu  sekilas mengenai babel. Nah pada webpack kita juga dapat menggunakan babel dalam bentuk loader. 
Walaupun webpack secara standarnya dapat memproses berkas JavaScript tanpa perlu bantuan loader, namun proses tersebut tidak mengubah sintaks yang kita tuliskan. 
Artinya jika kita menuliskan sintaks JavaScript modern, maka kita akan menemukannya juga pada berkas bundle.js.
2020031320001286d0cce40a7f1f8ac22836dcbab4867b.png
Walaupun saat ini Google Chrome dan Mozilla Firefox sudah mendukung penulisan sintaks ES6, namun setidaknya kita perlu sedikit peduli terhadap dukungan browser lama seperti Internet Explorer atau browser versi lama lainnya.
Untuk menggunakan babel pada webpack sebagai loader, kita perlu memasang tiga package menggunakan npm pada devDependencies
Yang pertama package @babel/core, yang kedua babel-loader, dan yang ketiga @babel/preset-env.

  1. npm install @babel/core babel-loader @babel/preset-env --save-dev


Package @babel/core merupakan package inti yang harus dipasang ketika kita hendak menggunakan babel, baik pada webpack maupun tools yang lain.
Package babel-loader merupakan package yang diperlukan untuk menggunakan babel sebagai loader pada webpack.
Yang terakhir package @babel/preset-env merupakan package preset yang akan kita gunakan untuk membantu babel-loader dalam melakukan tugasnya. 
@babel/preset-env merupakan preset cerdas yang memungkinkan kita menggunakan sintaks JavaScript terbaru tanpa menetapkan secara spesifik sintaks JavaScript versi apa yang kita gunakan.
Berkas package.json akan tampak seperti ini setelah memasang ketiga package tersebut:

  1. {

  2.   "name": "webclock",

  3.   "version": "1.0.0",

  4.   "description": "",

  5.   "main": "index.js",

  6.   "scripts": {

  7.     "build": "webpack"

  8.   },

  9.   "author": "",

  10.   "license": "ISC",

  11.   "dependencies": {

  12.     "jquery": "^3.4.1",

  13.     "moment": "^2.24.0"

  14.   },

  15.   "devDependencies": {

  16.     "@babel/core": "^7.8.4",

  17.     "@babel/preset-env": "^7.8.4",

  18.     "babel-loader": "^8.0.6",

  19.     "css-loader": "^3.4.2",

  20.     "style-loader": "^1.1.3",

  21.     "webpack": "^4.41.6",

  22.     "webpack-cli": "^3.3.11"

  23.   }

  24. }


Setelah berhasil memasang ketiga package tersebut, langkah selanjutnya kita dapat gunakan babel-loader dan preset-nya pada webpack configuration.

  1. const path = require("path");

  2.  

  3. module.exports = {

  4.     entry: "./src/index.js",

  5.     output: {

  6.         path: path.resolve(__dirname, "dist"),

  7.         filename: "bundle.js"

  8.     },

  9.     mode: "production",

  10.     module: {

  11.         rules: [

  12.             /* style and css loader */

  13.             {

  14.                 test: /.css$/,

  15.                 use: [

  16.                     {

  17.                         loader: "style-loader"

  18.                     },

  19.                     {

  20.                         loader: "css-loader"

  21.                     }

  22.                 ]

  23.             },

  24.             /* babel loader */

  25.             {

  26.                 test: /.js$/,

  27.                 exclude: "/node_modules/",

  28.                 use: [

  29.                     {

  30.                         loader: "babel-loader",

  31.                         options: {

  32.                             presets: ["@babel/preset-env"]

  33.                         }

  34.                     }

  35.                 ]

  36.             }

  37.         ]

  38.     }

  39. }


Ketika menerapkan rule untuk berkas .js, jangan lupa untuk menetapkan properti exclude dengan nilai “/node_modules/”. 
Apa artinya? Dengan menetapkan properti exclude itu berarti kita mengecualikan webpack untuk memproses berkas .js yang berada pada folder “node_modules”. 
Hal ini dapat meminimalisir proses yang tidak diperlukan, sehingga mempercepat proses build pada proyek kita.  
Lalu pada penerapan babel-loader juga kita menggunakan properti options dengan menetapkan properti presets di dalamnya. Pada properti presets kita tetapkan preset (dalam bentuk array literas) yang sudah kita pasang menggunakan npm, yaitu @babel/preset-env.
Setelah menggunakan babel loader pada webpack configuration, mari kita coba build dan buka kembali berkas bundle.js
Maka kode yang kita tuliskan dalam ES6 akan diubah dalam sintaks yang dapat diterima oleh seluruh browser.
20200313200507f7a720e39de819c62a15f1be147aa62e.png
Bahkan pada berkas bundle tersebut dipastikan sudah tidak terdapat lagi sintaks yang dituliskan menggunakan ES6.
20200313200535f035aab9b99622b9e95be3f0d98fc827.png
Namun walaupun sintaksnya sudah diubah, proyek akan tetap berjalan normal seperti biasanya.
Contoh: 
https://github.com/dicodingacademy/a163-bfwd-labs/tree/207-webclock-webpack-using-loader

Contoh Menggunakan API Dan Fetch di dalam Web Sederhana

Apakah Anda sudah berhasil menerapkan Fetch dalam menampilkan data dari API TheSportDB? Jika belum, yuk kita lakukan bersama-sama!
Pada dokumentasi API menyebutkan bahwa, untuk mendapatkan daftar klub olahraga kita dapat menggunakan target url: https://www.thesportsdb.com/api/v1/json/1/searchteams.php?t=Arsenal
2020031323502200000abb4fd3bf7637506d2872ff1cb0.png
Sebelum menuliskan langsung pada proyek dengan fetch, biasakan ketika hendak mengkonsumsi API biasakan untuk mencobanya menggunakan aplikasi Postman terlebih dahulu. Jika target url tersebut diakses melalui Postman dengan GET Request, maka akan menghasilkan response dengan struktur JSON yang tampak pada tab Body.
202003132350321a588cd4b3f0285be6fd1af52fa3591e.png
Pada response JSON yang dihasilkan menampung satu key dengan nama teams yang memiliki value berupa sebuah array. Di dalam array tersebut menampilkan banyak data terkait klub olahraga yang memiliki nama Arsenal. Kita dapat memanfaatkan key strTeam untuk mendapatkan nama klub, strTeamBadge untuk mendapatkan logo klub, dan strDescriptionEN untuk mendapatkan deskripsi singkat dalam bahasa inggris.
Lantas untuk mencari data team berdasarkan kata kunci lain kita dapat mengubah kata “Arsenal” menggunakan kata kunci yang kita inginkan, misalnya “Barcelona”. Sehingga melakukan request terhadap url: https://www.thesportsdb.com/api/v1/json/1/searchteams.php?t=Barcelona akan menghasilkan response JSON dengan informasi klub olahraga terkait “Barcelona”.
20200313235048e7906db6f58df01a750d175c26f3b342.png
Cukup mudah bukan untuk menggunakan API tersebut?
Nah, Setelah memahami cara penggunaan API-nya, sekarang mari kita mulai tuliskan fungsi fetch pada proyek Club Finder. Langkah awal buka kembali proyek Club Finder pada text editor yang Anda gunakan.
202003132351017c49f82cb83c52af812adb0b076cc23f.png
Kemudian buka berkas data-source.js pada src -> script -> data -> data-source.js. Kita refactor fungsi searchClub dengan menghapus seluruh logika yang ada di dalamnya, kemudian tuliskan fungsi fetch seperti ini:

  1. class DataSource {

  2.    static searchClub(keyword) {

  3.        return fetch(`https://www.thesportsdb.com/api/v1/json/1/searchteams.php?t=${keyword}`)

  4.    }

  5. }

  6.  

  7. export default DataSource;


Seperti yang sudah kita ketahui, fungsi fetch() akan mengembalikan promise resolve jika request berhasil dilakukan. Maka untuk menangani respon dari request yang dibuat, kita gunakan .then() yang di dalamnya berisi variabel response sebagai response object yang didapat.

  1. class DataSource {

  2.    static searchClub(keyword) {

  3.        return fetch(`https://www.thesportsdb.com/api/v1/json/1/searchteams.php?t=${keyword}`)

  4.        .then(response => {

  5.           

  6.        })

  7.    }

  8. }

  9.  

  10. export default DataSource;


Kemudian di dalam blok then tersebut, kita ubah nilai response menjadi JSON dengan memanggil method response.json().

  1. class DataSource {

  2.    static searchClub(keyword) {

  3.        return fetch(`https://www.thesportsdb.com/api/v1/json/1/searchteams.php?t=${keyword}`)

  4.        .then(response => {

  5.            return response.json();

  6.        })

  7.    }

  8. }

  9.  

  10. export default DataSource;


Karena method response.json() juga mengembalikan nilai promise, maka untuk mendapatkan nilai yang dibawa oleh resolve kita perlu menambahkan .then lainnya (chaining promise). Di dalam .then yang kedua ini, berikan parameter dengan nama responseJson (penamaan variabel tidaklah baku, namun gunakan penamaan yang menunjukkan arti dari nilai variabelnya).

  1. class DataSource {

  2.    static searchClub(keyword) {

  3.        return fetch(`https://www.thesportsdb.com/api/v1/json/1/searchteams.php?t=${keyword}`)

  4.        .then(response => {

  5.            return response.json();

  6.        })

  7.        .then(responseJson => {

  8.           

  9.        })

  10.    }

  11. }

  12.  

  13. export default DataSource;


responseJson merupakan nilai JSON yang dihasilkan dari perubahan object response dalam bentuk JSON melalui method .json() tadi.
Di dalam block .then yang kedua, kita kembalikan (return) dengan nilai promise resolve dengan membawa nilai jsonResponse.teams jika nilai array tidak null. Namun jika teams bernilai null, maka kembalikan dengan nilai promise reject dengan membawa nilai “${keyword} is not found”.

  1. class DataSource {

  2.    static searchClub(keyword) {

  3.        return fetch(`https://www.thesportsdb.com/api/v1/json/1/searchteams.php?t=${keyword}`)

  4.        .then(response => {

  5.            return response.json();

  6.        })

  7.        .then(responseJson => {

  8.            if(responseJson.teams) {

  9.                return Promise.resolve(responseJson.teams);

  10.            } else {

  11.                return Promise.reject(`${keyword} is not found`);

  12.            }

  13.        })

  14.    }

  15. }

  16.  

  17. export default DataSource;


Simpan perubahan tersebut dan jalankan aplikasi dalam mode development menggunakan perintah:

  1. npm run start-dev


Setelah proyek terbuka, lakukan pencarian dengan keyword apapun yang Anda mau, di sini kita contohkan dengan “Barcelona”.
20200313235133e90126ea84a6df29dc44b91c4f296331.png
Yah, data yang ditampilkan undefined. Mengapa bisa demikian? Ini disebabkan karena kita belum menyesuaikan key berdasarkan response yang didapat dari public API. Kita harus menggunakan key strTeam untuk mendapatkan nama klub, strTeamBadge untuk mendapatkan logo klub, dan strDescriptionEN untuk mendapatkan deskripsi singkat dalam bahasa inggris.
Ketiga key tersebut kita tetapkan pada berkas src -> script -> component -> club-item.js. Lebih tepatnya pada fungsi render.

  1. render() {

  2.        this.shadowDOM.innerHTML = `

  3.            <style>

  4.                 ……..

  5.            </style>

  6.            <img class="fan-art-club" src="${this._club.fanArt}" alt="Fan Art">

  7.            <div class="club-info">

  8.                <h2>${this._club.name}</h2>

  9.                <p>${this._club.description}</p>

  10.            </div>`;

  11.    }


Kita ubah properti this._club.fanArt menjadi this._club.strTeamBadgethis._club.name menjadi this._club.strTeam, dan this._club.description menjadi this._club.strDescriptionEN.
Sehingga fungsi render akan menjadi seperti ini:

  1. render() {

  2.        this.shadowDOM.innerHTML = `

  3.            <style>

  4.                * {

  5.                    margin: 0;

  6.                    padding: 0;

  7.                    box-sizing: border-box;

  8.                }

  9.                :host {

  10.                    display: block;

  11.                    margin-bottom: 18px;

  12.                    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);

  13.                    border-radius: 10px;

  14.                    overflow: hidden;

  15.                }

  16.               

  17.                .fan-art-club {

  18.                    width: 100%;

  19.                    max-height: 300px;

  20.                    object-fit: cover;

  21.                    object-position: center;

  22.                }

  23.               

  24.                .club-info {

  25.                    padding: 24px;

  26.                }

  27.               

  28.                .club-info > h2 {

  29.                    font-weight: lighter;

  30.                }

  31.               

  32.                .club-info > p {

  33.                    margin-top: 10px;

  34.                    overflow: hidden;

  35.                    text-overflow: ellipsis;

  36.                    display: -webkit-box;

  37.                    -webkit-box-orient: vertical;

  38.                    -webkit-line-clamp: 10; /* number of lines to show */

  39.                }

  40.            </style>

  41.            <img class="fan-art-club" src="${this._club.strTeamBadge}" alt="Fan Art">

  42.            <div class="club-info">

  43.                <h2>${this._club.strTeam}</h2>

  44.                <p>${this._club.strDescriptionEN}</p>

  45.            </div>`;

  46. }


Simpan kembali perubahan kode yang dituliskan kemudian lakukan pencarian kembali pada aplikasi Club Finder. Seharusnya sekarang aplikasi sudah bisa menampilkan data dengan baik.
202003132351502bc06712f0552fa6f26e8f8de5607033.png
Voila! Anda sudah berhasil menerapkan Fetch pada proyek Club Finder.

Contoh Ajax Menggunakan Fetch

Seperti yang sudah kita ketahui, fetch memanfaatkan promise dalam melakukan tugasnya, sehingga network request yang dibuat menggunakan fetch akan selalu berjalan asynchronous.

Penggunaan dasar dari fetch tampak seperti ini:
20200313194951a9ddbc715b419aa9f5c19cfd5cb5ffae.png
Network request dilakukan pada saat fungsi fetch() tereksekusi.

  1. fetch("https://web-server-book-dicoding.appspot.com/list")


Jika request berhasil diproses oleh server, fungsi fetch() akan mengembalikan promise resolve dan membawa response object di dalamnya. Namun nilai response yang dibawa resolve belum sebagai data JSON yang kita butuhkan, melainkan informasi mengenai response itu sendiri, seperti status codetarget urlheaders, dsb. Maka dari itu, untuk mendapatkan data JSON yang dibutuhkan, kita perlu mengubah response object ke dalam bentuk JSON dengan memanggil method .json().

  1. fetch("https://web-server-book-dicoding.appspot.com/list")

  2.  .then(response => {

  3.    return response.json();

  4.  })


Method .json() juga mengembalikan nilai Promise, sehingga kita membutuhkan chaining promise dengan menambahkan .then() untuk mendapatkan data JSON yang sesungguhnya.

  1. fetch("https://web-server-book-dicoding.appspot.com/list")

  2.  .then(response => {

  3.    return response.json();

  4.  })

  5.  .then(responseJson => {

  6.    console.log(responseJson);

  7.  })


Lalu jangan lupa juga untuk menambahkan block catch() pada akhir chaining promise untuk menangani apabila rejected promise terjadi baik karena fungsi fetch() atau json().

  1. fetch("https://web-server-book-dicoding.appspot.com/list")

  2.  .then(response => {

  3.    return response.json();

  4.  })

  5.  .then(responseJson => {

  6.    console.log(responseJson);

  7.  })

  8.  .catch(error => {

  9.    console.log(error);

  10.  });


Karena fetch memanfaatkan promise, kita bisa memanfaatkan async/await jika Anda lebih suka dengan gaya penulisan synchronous.

  1. fetch("https://web-server-book-dicoding.appspot.com/list")

  2.  .then(response => {

  3.    return response.json();

  4.  })

  5.  .then(responseJson => {

  6.    console.log(responseJson);

  7.  })

  8.  .catch(error => {

  9.    console.log(error);

  10.  });



  1. async function getBooks() {

  2.  try {

  3.    const response = await fetch("https://web-server-book-dicoding.appspot.com/list");

  4.    const responseJson = await response.json();

  5.    console.log(responseJson);

  6.  } catch (error) {

  7.    console.log(error);

  8.  }

  9. }

  10.  

  11. getBooks();


Advanced Fetch Usage

Fungsi fetch() dapat menerima dua buah parameter di dalamnya. Selain menetapkan target URL, kita juga dapat memberikan options untuk menetapkan methodheaderbody, dsb pada request yang akan dijalankan. Namun penerapan options ini bersifat pilihan. Tidak wajib.
2020031320004200d4bfb205c4a5469bb6063092d38d30.png
Penggunaan fetch() tanpa menerapkan options akan membuat GET request sederhana yang ditujukkan pada targetUrl. Hal tersebut sama seperti yang sudah kita lakukan sebelumnya. Lantas kapan kita perlu menerapkan options pada penggunaan fetch? Berikut beberapa kasus saat kita memerlukannya.

Changing Request Method

Untuk membuat POST request ataupun method request lainnya kita perlu menerapkan options dengan properti method di dalamnya.

  1. fetch("https://web-server-book-dicoding.appspot.com/add", {

  2.  method: "POST"

  3. })


Nilai dari properti method dituliskan dalam bentuk string, contohnya “POST”“PUT”“DELETE”, dsb. Nilai default dari properti ini adalah “GET”, sehingga jika kita membuat GET Request, kita tidak perlu menetapkan nilai method secara eksplisit.

Set Header Property

Untuk menambahkan request header dengan fetch kita gunakan properti headers pada options. Contohnya, untuk menambahkan properti Content-Type dengan nilai application/json pada headers kita dapat melakukannya dengan seperti ini:

  1. fetch("https://web-server-book-dicoding.appspot.com/add", {

  2.  method: "POST",

  3.  headers: {

  4.    "Content-Type": "application/json"

  5.  }

  6. })


Tentu kita juga dapat menetapkan lebih dari satu properti pada headers. Contohnya kita tambahkan lagi properti X-Auth-Token dengan nilai 12345.

  1. fetch("https://web-server-book-dicoding.appspot.com/add", {

  2.  method: "POST",

  3.  headers: {

  4.    "Content-Type": "application/json",

  5.    "X-Auth-Token": "12345"

  6.  }

  7. })


Set Data to Body Request

Untuk mengirimkan data pada body request kita gunakan properti body pada options, contohnya seperti ini:

  1. fetch("https://web-server-book-dicoding.appspot.com/add", {

  2.  method: "POST",

  3.  headers: {

  4.    "Content-Type": "application/json",

  5.    "X-Auth-Token": "12345"

  6.  },

  7.  body: JSON.stringify({

  8.    id: 10,

  9.    title: "Edensor",

  10.    author: "Andrea Hirata"

  11.  })

  12. })


Sama seperti XHR, data yang dikirimkan melalui body request perlu diubah menjadi JSON String terlebih dahulu.

  1. body: JSON.stringify({

  2.    id: 10,

  3.    title: "Edensor",

  4.    author: "Andrea Hirata"

  5. })


Contoh kode di atas dapat Anda temukan pada tautan berikut https://repl.it/@dicodingacademy/163-06-AJAX-Fetch-Advanced?lite=true
Anda bisa coba lakukan POST Request menggunakan Fetch dengan menjalankan potongan kode tersebut.

Contoh Dasar Ajax Menggunakan XMLHttpRequest

XMLHttpRequest atau XHR merupakan objek yang tersedia pada browser yang digunakan untuk membuat HTTP Requests menggunakan JavaScript. Karena namanya kita mungkin beranggapan bahwa XHR hanya mendukung format data XML, tetapi nyatanya saat ini XHR dapat digunakan oleh banyak format, bukan hanya XML.

Walaupun sekarang terdapat cara yang lebih modern yaitu menggunakan fetch, namun masih ada kok beberapa developer yang menggunakan XHR dalam melakukan HTTP Request. Alasannya karena alasan dukungan browser tua atau suatu fitur yang tidak bisa fetch lakukan seperti tracking upload progress.

Basic Usage

XMLHttpRequest dapat berjalan secara synchronous maupun asynchronous. Namun kebanyakan kasus HTTP Request sebaiknya asynchronous.
Contoh penggunaan dasar dari XMLHttpRequest adalah seperti ini:
202003132318522fefb326486e99f9449b3ea0b5d8cb31.png
Langkah awal untuk menggunakan XMLHttpRequest adalah dengan membuat instance baru dari objek XMLHttpRequest seperti ini:

  1. const xhr = new XMLHttpRequest();


Lalu pada instance dari objek tersebut (xhr) kita tentukan callback function ketika request berhasil (onload) dan ketika request gagal (onerror).

  1. xhr.onload = function () {

  2. console.log(this.responseText);

  3. }

  4. xhr.onerror = function () {

  5. console.log("Ups something error")

  6. }


Di dalam callback function tersebut, nilai dari this merupakan XHR objek. Sehingga this.responseText akan memiliki nilai response (dalam bentuk text) dari server. Ingat karena pada callback function tersebut nilai this merupakan XHR objek, kita tidak dapat menggunakan gaya arrow function dalam menuliskan callback function-nya.

  1. xhr.onload = () => {

  2.     // akan menyebabkan error, karena arrow function tidak memiliki this.

  3.     console.log(this.responseText);

  4. }


Selanjutnya kita tentukan HTTP Method dan URL yang dituju dengan menggunakan method .open()

  1. xhr.open("GET", "https://api-to-call.com/endpoint");


Nah, langkah akhir adalah mengirimkan request yang sudah kita konfigurasi di atas menggunakan method .send().

  1. xhr.send();


Sehingga seluruh kode dalam penggunaan dasar dari XMLHttpRequest dengan contoh target url berikut https://web-server-book-dicoding.appspot.com/list adalah seperti ini:

  1. const xhr = new XMLHttpRequest();

  2.  

  3. xhr.onload = function () {

  4.  console.log(this.responseText)

  5. }

  6. xhr.onerror = function () {

  7.  console.log("Ups something error")

  8. }

  9.  

  10. xhr.open("GET", "https://web-server-book-dicoding.appspot.com/list");

  11. xhr.send();


Coba Anda copy dan paste kode di atas dan jalankan pada console devTools. Jika request berhasil, Anda akan mendapatkan data dalam bentuk JSON String. Data tersebut bisa Anda olah menjadi JavaScript objek dengan mengubahnya menggunakan method JSON.parse().

  1. JSON.parse(this.responseText)


Anda juga bisa mencoba menjalankan potongan kode di atas pada repl.it melalui tautan berikut: https://repl.it/@dicodingacademy/163-06-AJAX-XHR-basic?lite=true

Header and Body Request using XHR

Sebelumnya kita sudah belajar bagaimana cara menggunakan dasar XMLHttpRequest dalam membuat sebuah GET Request. Lalu bagaimana jika kita ingin menggunakan method POST dan menetapkan properti pada Header ? Lalu bagaimana cara mengirimkan data pada body HTTP Request? Yuk kita bahas permasalahan tersebut satu per satu.

Set Header Property

Dalam menggunakan Web API tidak selamanya kita hanya menggunakan GET request saja, terutama ketika request yang kita inginkan bertujuan mengubah atau menambahkan data. Web API biasanya meminta kita untuk melakukannya dengan method POST atau PUT. Tak jarang juga Web API meminta kita untuk menetapkan properti tertentu pada request header untuk menunjukkan credential yang kita miliki. Request tersebut sudah pernah kita coba ya menggunakan Postman. Tapi bagaimana ya caranya jika menggunakan XHR?
Untuk menetapkan properti pada request header, Anda dapat gunakan method setRequestHeader() pada instance XMLHttpRequest setelah menetapkan method .open(). Method tersebut menerima dua parameter. Parameter pertama merupakan nama properti dan parameter yang kedua adalah nilai yang akan ditetapkan pada properti tersebut. Sehingga untuk menetapkan properti Content-Type dengan nilai application/json pada request kita dapat melakukannya dengan cara:

  1. const xhr = new XMLHttpRequest();

  2.  

  3. xhr.onload = function() {

  4.  console.log(this.responseText);

  5. }

  6.  

  7. xhr.onerror = function() {

  8.  console.log("Ups something error");

  9. }

  10.  

  11. xhr.open("POST", "https://web-server-book-dicoding.appspot.com/add");

  12.  

  13. // menambahkan properti pada header request

  14. xhr.setRequestHeader("Content-Type", "application/json");


Kita dapat menetapkan properti pada request header sebanyak yang kita perlukan

  1. const xhr = new XMLHttpRequest();

  2.  

  3. xhr.onload = function() {

  4.  console.log(this.responseText);

  5. }

  6.  

  7. xhr.onerror = function() {

  8.  console.log("Ups something error");

  9. }

  10.  

  11. xhr.open("POST", "https://web-server-book-dicoding.appspot.com/add");

  12.  

  13. // menambahkan properti pada header request

  14. xhr.setRequestHeader("Content-Type", "application/json");

  15. xhr.setRequestHeader("X-Auth-Token", "12345")


Cukup mudah bukan?

Set Data to Body Request

Lalu untuk menetapkan data pada body request, lakukanlah pada method .send() dari instance XMLHttpRequest. Contohnya untuk mengirimkan data JSON String pada body request, begini caranya: 

  1. const xhr = new XMLHttpRequest();

  2.  

  3. xhr.onload = function() {

  4.  console.log(this.responseText);

  5. }

  6.  

  7. xhr.onerror = function() {

  8.  console.log("Ups something error");

  9. }

  10.  

  11. xhr.open("POST", "https://web-server-book-dicoding.appspot.com/add");

  12.  

  13. // menambahkan properti pada header request

  14. xhr.setRequestHeader("Content-Type", "application/json");

  15. xhr.setRequestHeader("X-Auth-Token", "12345")

  16.  

  17. const book = {

  18.  id: 10,

  19.  title: "Edensor",

  20.  author: "Andrea Hirata"

  21. }

  22.  

  23. xhr.send(JSON.stringify(book));


Sebenarnya tidak hanya JSON format yang dapat dikirimkan melalui body, pada situs MDN menyebutkan bahwa kita dapat mengirimkan dokumen yang sudah ter-serialized sebelum dikirim, atau data lain yang terdaftar pada Fetch spec seperti BlobBufferSourceFormDataURLSeachParamReadableStream atau USVString object.
Contoh kode di atas dapat Anda temukan pada tautan berikut https://repl.it/@dicodingacademy/163-06-AJAX-XHR-Header-and-Body?lite=true. Anda bisa coba lakukan POST Request dengan menjalankan potongan kode tersebut

Hasil Akhir Cek :
https://github.com/dicodingacademy/a163-bfwd-labs/tree/302-dicoding-books-xhr

Apa Itu JSON?

Setelah kita mengetahui apa itu Web API dan cara pengujiannya menggunakan Postman, sekarang saatnya kita mempelajari suatu format yang biasa digunakan dalam transaksi data menggunakan Web API, yaitu JSON.

Jauh pada materi sebelumnya, atau jika Anda sudah mengikuti kelas Belajar Dasar Pemrograman Web,  tentunya Anda sudah mengenal dan menggunakan JSON bukan? Pada materi kali ini kita akan membahas JSON lebih detail lagi.
JSON sendiri adalah singkatan dari JavaScript Object Notation. JSON merupakan format yang sering digunakan dalam pertukaran data. Saat ini JSON banyak diandalkan karena formatnya berbasis teks dan relatif mudah dibaca.
Bukan hanya JavaScript, walaupun memiliki nama JavaScript Object Notation, format JSON ini dapat digunakan oleh hampir semua bahasa pemrograman yang ada. Jika Anda belajar fundamental dalam membangun aplikasi Android pada kelas Dicoding, baik menggunakan Kotlin ataupun Java, Anda akan berhadapan dengan JSON untuk transaksi datanya.
Lalu seperti apa sebenarnya rupa JSON ini? Struktur JSON dapat terbentuk dari 2 (dua) literal data, yakni objek dan array.

  1. {

  2.     "error": false,

  3.     "message": "success",

  4.     "books": [

  5.     {

  6.     "id": 1,

  7.     "title": "Laskar Pelangi",

  8.     "author": "Andrea Hirata"

  9.     },

  10.     {

  11.     "id": 2,

  12.     "title": "Filosofi Kopi",

  13.     "author": "Dewi Lestari"

  14.     },

  15.     {

  16.     "id": 3,

  17.     "title": "Clean Code",

  18.     "author": "Robert C Martin"

  19.     }

  20.    ]

  21. }

  22.  

  23.  


Data yang merupakan objek pada JSON selalu diawali dengan tanda { (buka kurung kurawal)  dan diakhiri dengan tanda tutup kurung kurawal } (tutup kurung kurawal). Sedangkan array pada JSON selalu diawali dengan tanda [ (buka kurung siku) dan diakhiri dengan tanda ] (tutup kurung siku).
20200313225902ae266e132fb41907aef8dbcd4201c57c.png
Struktur dari JSON juga menggunakan format key: value untuk menampilkan datanya. Contoh di atas error merupakan key dan false merupakan value. Penulisan JSON hampir identik dengan JavaScript objek. Namun key pada JSON selalu dituliskan di dalam tanda “ “ (kutip dua).

  1. { "message": "success" }


Pada JSON value kita dapat menetapkan nilai dengan berbagai tipe data, di antaranya:
  • String
  • Number
  • Object
  • Array
  • Boolean
  • Null

Using JSON in JavaScript

Setelah mengenal rupa, struktur dan penulisan JSON, selanjutnya bagaimana cara menggunakan JSON pada JavaScript? Sama seperti menggunakan objek JavaScript biasa!

  1. const data = {

  2. "error": false,

  3. "message": "success",

  4. "books": [

  5. {

  6. "id": 1,

  7. "title": "Laskar Pelangi",

  8. "author": "Andrea Hirata"

  9. },

  10. {

  11. "id": 2,

  12. "title": "Filosofi Kopi",

  13. "author": "Dewi Lestari"

  14. },

  15. {

  16. "id": 3,

  17. "title": "Clean Code",

  18. "author": "Robert C Martin"

  19. }

  20. ]

  21. };

  22.  

  23. console.log(`Error? ${data.error}`);

  24. console.log("Daftar Buku: ");

  25. data.books.forEach((book, index) => {

  26. console.log(`${index + 1}. ${book.title} (${book.author})`);

  27. })

  28.  

  29. /* output

  30. Error? false

  31. Daftar Buku:

  32. 1. Laskar Pelangi (Andrea Hirata)

  33. 2. Filosofi Kopi (Dewi Lestari)

  34. 3. Clean Code (Robert C Martin)

  35. */


Kita bisa mengakses data JSON objek menggunakan tanda titik setelah variabel yang menampungnya. Contoh cara mengakses data dengan key books seperti:

  1. data.books


Namun jika key terdiri dari karakter yang tidak dapat digunakan dalam penamaan variabel seperti white spacedashslash atau yang lainnya, datanya dapat kita askes melalui indexing seperti ini:

  1. const data = {

  2.   ....,

  3.  "book list": [

  4.   ......

  5.  ]

  6. };

  7.  

  8. data["book list"].forEach((book, index) => {

  9.  console.log(`${index + 1}. ${book.title} (${book.author})`);

  10. })

  11.  

  12. /* output

  13. Daftar Buku:

  14. 1. Laskar Pelangi (Andrea Hirata)

  15. 2. Filosofi Kopi (Dewi Lestari)

  16. 3. Clean Code (Robert C Martin)

  17. */


Seperti yang sudah kita ketahui, JSON ini biasanya digunakan untuk transaksi data ke/dari web server. Ketika transaksi data berlangsung, data tersebut selalu dalam bentuk string.
20200313230409c9fc8c2540e6f3f5d173d016dba073e3.pngContoh JSON yang dihasilkan oleh Web Server
Nah untuk mengelola data JSON dalam bentuk string pada JavaScript, kita perlu melakukan parse dengan menggunakan global object JSON. Terdapat dua method penting dalam global object JSON, yang pertama parse() dan yang kedua stringify()
Method JSON.parse() digunakan untuk mengubah JSON dalam bentuk String menjadi objek JavaScript. Contohnya seperti ini:

  1. const jsonString = `{

  2.  "error": false,

  3.  "message": "success",

  4.  "books": [

  5.    {

  6.      "id": 1,

  7.      "title": "Laskar Pelangi",

  8.      "author": "Andrea Hirata"

  9.    },

  10.    {

  11.      "id": 2,

  12.      "title": "Filosofi Kopi",

  13.      "author": "Dewi Lestari"

  14.    },

  15.    {

  16.      "id": 3,

  17.      "title": "Clean Code",

  18.      "author": "Robert C Martin"

  19.    }

  20.  ]

  21. }`;

  22.  

  23. const data = JSON.parse(jsonString);

  24.  

  25. data.books.forEach((book, index) => {

  26.  console.log(`${index + 1}. ${book.title} (${book.author})`);

  27. })

  28.  

  29. /* output

  30. Daftar Buku:

  31. 1. Laskar Pelangi (Andrea Hirata)

  32. 2. Filosofi Kopi (Dewi Lestari)

  33. 3. Clean Code (Robert C Martin)

  34. */


Lalu method JSON.stringify() memiliki fungsi sebaliknya. Yaitu mengubah JavaScript objek dalam bentuk JSON string. Contohnya seperti ini:

  1. const data = {

  2.  error: false,

  3.  message: "success",

  4.  books: [

  5.    {

  6.      "id": 1,

  7.      "title": "Laskar Pelangi",

  8.      "author": "Andrea Hirata"

  9.    },

  10.    {

  11.      "id": 2,

  12.      "title": "Filosofi Kopi",

  13.      "author": "Dewi Lestari"

  14.    },

  15.    {

  16.      "id": 3,

  17.      "title": "Clean Code",

  18.      "author": "Robert C Martin"

  19.    }

  20.  ]

  21. };

  22.  

  23. const jsonString = JSON.stringify(data);

  24. console.log(jsonString);

  25.  

  26. /* output:

  27. {"error":false,"message":"success","books":[{"id":1,"title":"Laskar Pelangi","author":"Andrea Hirata"},{"id":2,"title":"Filosofi Kopi","author":"Dewi Lestari"},{"id":3,"title":"Clean Code","author":"Robert C Martin"}]}

  28. */


Apa itu Web API?

Web APIs

Pada pengenalan modul ini kita sudah paham bagaimana AJAX dapat menampilkan informasi yang dinamis pada aplikasi kita. Namun kita belum mengetahui dari mana sumber data tersebut berasal? Bagaimana cara mengaksesnya? Untuk menjawab itu semua, mari berkenalan dengan Web API.

Web API (Application Programming Interface) merupakan interface yang disediakan oleh penyedia data agar data yang ia miliki dapat dimanfaatkan dengan mudah pada banyak aplikasi, baik itu aplikasi web, mobile, desktop ataupun lainnya.
Penyedia data yang telah membangun Web API memiliki keuntungan karena proses pengembangan aplikasi menjadi lebih efisien. Mengapa? Karena cukup dengan satu Web API, data dapat dikonsumsi pada berbagai macam platform seperti yang sudah disebutkan di atas.
2020031322462248e1afa1ef73e3da2e43e6f7bcfb5c7a.png

Pada ilustrasi di atas, Web API bekerja menggunakan salah satu pola standar yaitu REST (Representational State Transfer). Pola inilah yang saat ini banyak digunakan karena simpel dan mudah dipelajari daripada pola yang lainnya. Jenis data yang diterima atau dikirimkan pada pola REST dapat berupa format text, JSON atau XML.
Pola REST hampir sepenuhnya menggunakan HTTP dalam transaksi datanya. Sehingga untuk berkomunikasi dengan Web API ini, client (aplikasi) harus membuat HTTP request pada endpoint (URL) yang telah ditentukkan. Biasanya penyedia data juga menetapkan HTTP Request Method yang berbeda pada setiap endpoint-nya.
Terdapat banyak tipe/method dalam melakukan HTTP Request, namun terdapat 4 (empat) method penting yang biasanya digunakan pada Web API dalam melakukan transaksi data.
  • GET : Digunakan untuk mengambil informasi dari Web API.
  • POST : Digunakan untuk menambahkan data.
  • PUT : Digunakan untuk mengubah data.
  • DELETE : Digunakan untuk menghapus data.
Setelah client membuat HTTP request pada Web API, maka Web API akan mengembalikan HTTP response. Pada response terdapat status kode yang menunjukkan apakah request yang kita lakukan berhasil atau gagal. Berikut beberapa status kode yang dapat dihasilkan dari HTTP Response pada Web API.
  • 200 (OK) : Request berhasil dipenuhi oleh server (Web API).
  • 400 (Bad Request) : Server tidak mengerti request yang dikirimkan client.
  • 401 (Unauthorized) : Request membutuhkan authorization.
  • 403 (Forbidden) : Server mengerti request dari client namun menolak untuk memprosesnya karena request tersebut tidak boleh dilakukan.
  • 404 (Not Found) : Resource yang client minta, tidak ditemukan.
  • 500 (Server Error) : Server mengalami kendala untuk memproses request.
Banyak penyedia data yang menyediakan Web API secara cuma-cuma atau dapat dikonsumsi umum. Namun ada juga penyedia data yang membangun Web API yang hanya digunakan untuk keperluan internal saja. Biasanya jika kita mengakses Web API yang sifatnya private, kita akan terhalangi oleh CORS (cross-origin resource sharing

CORS

Sebelum kita mencoba langkah-langkah mengakses Web API, ada hal yang harus kita ketahui terlebih dahulu perihal keamanan. Dalam transaksi menggunakan Web API menggunakan AJAX kita dapat melihat request yang dibuat dan response yang dihasilkan melalui Network tabs pada DevTools.
20200313224809099128c193bbc451d3759397de0d6025.png
Hal tersebut disebabkan AJAX diproses pada sisi client sehingga pengguna dapat leluasa untuk melihat dan mengontrolnya. Maka dengan alasan keamanan ketika kita melakukan AJAX baik menggunakan dengan XHR atau Fetch, kita hanya diperbolehkan melakukan request dari domain yang sama.
Contoh jika kita ingin mengakses API yang bersumber dari google.com/api maka kita sebagai client perlu menjadi google.com. Jika kita berasal dari domain yang berbeda, contohnya bing.com maka kita tidak dapat menggunakan AJAX pada google.com/api. Hal ini dikenal sebagai same-origin policy.
Namun jika memang Web API tersebut diperbolehkan untuk dikonsumsi umum berarti penyedia data harus menerapkan teknologi yang dinamakan CORS (Cross-Origin Resource Sharing) pada server-nya. 
Server yang menyediakan Web API menggunakan CORS dapat digunakan secara umum dan mengelakkan kebijakan same-origin guna mengakses informasi yang ada di dalamnya

Test a Web API using Postman

Apa yang perlu Anda lakukan sebelum mencoba Web API menggunakan AJAX? Untuk mempelajari atau menguji sebuah Web API, developer menggunakan tools yang bernama Postman. Postman adalah GUI API Caller yang dapat membuat HTTP Request dengan method yang lengkap seperti GETPOSTPUTDELETE dan lainnya.
Postman merupakan tools yang sangat cocok untuk menguji sebuah Web API karena terdapat fungsi yang relatif lengkap sebagai API caller dalam melakukan HTTP Request. Pada Postman kita dapat menetapkan parameter dan mengirimkan data pada body atau header request dengan mudah, tanpa memerlukan kode.
Postman tersedia secara gratis dan dapat berjalan pada sistem operasi Windows, Linux maupun MacOS. Untuk mendapatkan aplikasi Postman, kita bisa mengunduhnya melalui https://www.postman.com.
202003132250278ee3046b28571ea08248c18a8e7417bf.png
Setelah mengunduhnya, silakan Anda pasang aplikasi postman pada komputer. Saat  selesai Anda bisa langsung membukanya.
2020031322505168bdc63b88a0a8016d0fa0f2e4aa6520.png
Ketika pertama kali membuka aplikasi Postman, kita perlu login atau registrasi untuk memiliki akun. Setelah berhasil login, berikut tampilan pada halaman utamanya:
20200313225118a91df05b0a17ef9fc809985aa27a71b6.png
Untuk melakukan sebuah HTTP Request kita dapat memilih menu “create a request” yang tersedia pada halaman tersebut.
20200313225142ca222609d25b934d654c583a6ad2bdae.png
Kemudian untuk mengirimkan request kita tentukan dulu endpoint (request URL) mana yang akan kita tuju pada kolom yang sudah tersedia. Kita akan mencoba melakukan request pada Web API dengan endpoint berikut: https://web-server-book-dicoding.appspot.com/list
202003132252316324bd0c0171af9c247588f079a45e08.png
Setelah mengisikan request URLnya coba kita tekan tombol “Send”. Maka respon yang akan didapatkan adalah daftar buku dalam bentuk JSON.
202003132252561d2ed5fd42e7c2b84d25fe56b9c32f5a.png
Jika penyedia data menyediakan Web API secara terbuka atau dapat diakses  umum, biasanya ia akan membuat sebuah dokumentasi cara penggunaan API. Di sana kita dapat menemukan informasi endpoint mana saja yang dapat kita akses, method apa saja yang diperlukan, ataupun format atau struktur seperti apa yang perlu diterapkan untuk mengirim data. Dokumentasi tersebut sangatlah penting karena melalui itulah seorang developer jadi tahu bagaimana ia bisa memanfaatkan Web API tersebut.
Dokumentasi dari Web API yang kita gunakan pada contoh di atas dapat kita lihat pada tautan berikut https://web-server-book-dicoding.appspot.com/.
20200313225338bb74016a898e8187cf6da8c639a2b667.png
Dari dokumentasi tersebut kita menjadi tahu ternyata kita dapat menambahkan data buku baru dengan mengakses endpoint /add. Di sana juga kita bisa melihat method, headers, serta body apa yang perlu kita terapkan dalam melakukan request tersebut. Sehingga kita dapat mencobanya pada pada Postman.
202003132254066304010fc29b90c06fd0f8993840fefe.png
Anda juga bisa mencoba fungsi lain seperti menghapus atau mengubah data buku melalui Web API tersebut. Jika Anda masih belum terbiasa dengan Postman, terdapat artikel yang cukup bagus sebagai panduan menggunakan Postman untuk Pemula. https://www.guru99.com/postman-tutorial.html

Apa Itu Webpack?

What is Webpack?

Pada dasarnya webpack merupakan module bundler untuk aplikasi JavaScript modern. Ketika webpack dijalankan pada proyek kita, di belakang layar webpack akan mengobservasi module apa saja yang kita gunakan dan membuat modul-modul tersebut dibungkus menjadi satu berkas (atau lebih).
20200313163923ddf1c4f937107ffe80d299dd072edcb2.gifIlustrasi diambil dari https://webpack.js.org/
Dengan menggunakan Webpack kita dapat leluasa menggunakan module yang saling bergantungan. Webpack akan menggabungkan seluruh module yang digunakan baik itu modul yang kita tuliskan sendiri atau module yang kita dapatkan melalui NPM menjadi static assets yang siap digunakan pada tahap produksi.
Webpack pertama kali dirilis pada tanggal 10 Maret 2012. Sebelum ada webpack sebenarnya sudah terdapat tools lain yang serupa seperti Browserify. Disamping sebagai module bundler, Browserify sejatinya memiliki tujuan sebagai tools yang dapat membawa node package apapun agar dapat berjalan pada browser (kita dapat melihat tujuan dari namanya “Browserify”). 
202003131641407c75dde829df2e4afb61ce3743710a92.png
Secara tidak langsung ia perlu berperan sebagai module bundler. Sebabnya, ketika menggunakan node package, tentu package tersebut terpisah dari kode yang kita tuliskan sendiri. Untuk menggabungkannya, Node.js menggunakan perintah require(). Dengan menggunakan browserify ini, perintah require() tersebut dapat kita gunakan pada browser (melalui transpiling).
Dari segi konsep, browserify dan webpack sangatlah berbeda. Namun kita dapat mengkategorikan keduanya sebagai module bundler. Kelebihan webpack dibandingkan dengan browserify yaitu webpack dapat memproses berkas/module lain diluar JavaScript seperti TypeScript, Sass tanpa bantuan task runner seperti Grunt atau Gulp.  
Sekitar awal tahun 2014 hingga pertengahan tahun 2015 browserify ini populer digunakan oleh developer. Hingga pada akhir tahun 2015 webpack-lah yang menggantikan kepopulerannya. Saat ini webpack sudah menyentuh versi 4 dengan mengusung kemampuan zero configuration-nya. 

Core Concepts

Untuk lebih memahami bagaimana webpack bekerja, ketahui dulu core concepts yang ada pada webpack.
20200313170447aa5038400842a2473e645458aeb8d7ea.png
Terdapat 5 (lima) konsep penting dalam webpack yang perlu kita ketahui sebelum menggunakan webpack itu sendiri. Dari 5 (lima) konsep tersebut kita tahu bagaimana perilaku dari webpack ketika ia dijalankan. Berikut penjelasan singkat dari kelima konsep tersebut:
  • Entry : Titik awal di mana webpack akan menganalisa berkas dan membentuk dependency graph.
  • Output : Berkas bundel yang dihasilkan dari berkas-berkas yang dianalisis webpack berdasarkan entry point.
  • Loaders :  Transformation tools pada webpack, yang akan memproses setiap berkas selain JavaScript atau JSON yang kita impor menjadi format yang dapat digunakan ke tahap produksi.
  • Plugin :  Digunakan untuk melakukan tugas seperti optimasi bundel, management aset dan sebagainya.
  • Mode : Kondisi yang digunakan webpack sebagai acuan optimasi apa saja yang harus diterapkan dalam melakukan tugasnya. Dalam mode kita dapat menetapkan nilai productiondevelopment ataupun none

Entry

Entry atau entry point merupakan modul pertama yang akan dianalisa oleh webpack ketika ia dijalankan. Melalui entry point inilah webpack akan membentuk dependency graph. Webpack akan mencari tahu modul lain yang digunakan pada entry point dan menggabungkannya menjadi satu static assets.
Pada webpack 4 standarnya nilai entry point akan ditempatkan pada ./src/index.js. Namun kita dapat menetapkan lokasi yang berbeda dengan mengatur properti entry pada berkas webpack configuration (webpack.config.js). Contohnya:

  1. module.exports = {

  2.   entry: './path/to/my/entry/file.js'

  3. };


Kode di atas merupakan cara cepat dalam penulisan properti entry. Sebenarnya entry dapat berupa objek seperti ini:

  1. module.exports = {

  2.   entry: {

  3.     main: './path/to/my/entry/file.js'

  4.   }

  5. };


Kita bisa memanfaatkan objek sebagai nilai entry ketika terdapat banyak entry point yang ingin kita tetapkan.

  1. module.exports = {

  2.   entry: {

  3.     app: './src/app.js',

  4.     adminApp: './src/adminApp.js'

  5.   }

  6. }


Output

Output merupakan salah satu properti yang terdapat pada webpack configuration. Properti ini berfungsi untuk memberitahu webpack di mana dan bagaimana lokasi static assets yang telah dibundel harus disimpan dan diberi nama. Standarnya lokasi penyimpanannya berada pada dist -> main.js. Lokasi dist merupakan lokasi standar untuk menyimpan berkas yang dihasilkan oleh webpack.
Kita dapat mengkonfigurasi bagian output ini melalui properti output pada webpack.config.js seperti contoh berikut ini:

  1. const path = require('path');

  2.  

  3. module.exports = {

  4.   entry: './path/to/my/entry/file.js',

  5.   output: {

  6.     path: path.resolve(__dirname, 'dist'),

  7.     filename: 'my-first-webpack.bundle.js'

  8.   }

  9. };


Pada contoh di atas, kita menggunakan output.filename dan output.path properties untuk memberitahu webpack penamaan dan lokasi static assets yang sudah dibundel. Pada contoh di atas juga kita dapat melihat modul path yang diimpor menggunakan Node.js module. Modul tersebut merupakan modul standar pada Node.js yang digunakan untuk memanipulasi lokasi berkas. Untuk memahami lebih dalam mengenai module path silakan cek  https://nodejs.org/api/path.html
Jika kita menetapkan lebih dari satu entry point, kita perlu menggunakan substitution untuk memastikan berkas yang dihasilkan webpack memiliki nama unik.

  1. module.exports = {

  2.   entry: {

  3.     app: './src/app.js',

  4.     search: './src/search.js'

  5.   },

  6.   output: {

  7.     filename: '[name].js',

  8.     path: __dirname + '/dist'

  9.   }

  10. };

  11.  

  12. // webpack akan menghasilkan: ./dist/app.js, ./dist/search.js


Properti output memiliki banyak fitur dalam proses konfigurasinya. Jika Anda ingin belajar lebih dalam seputar penggunaannya, silakan cek dokumentasi webpack berikut: https://webpack.js.org/configuration/output/

Loaders

Dalam melaksanakan tugas, sejatinya Webpack hanya mengenali berkas JavaScript dan JSON. Namun melalui loaders webpack dapat memproses berkas berformat lain seperti css, sass, pug, jsx atau yang lainnya. Loaders merupakan sebuah transformation tools pada webpack yang akan memproses setiap berkas selain JavaScript atau JSON yang kita impor menjadi format yang dapat digunakan ke tahap produksi.
Jika pada build tools lain seperti Gulp atau Grunt, loaders ini seperti “task”. Task ini sangat membantu dalam menangani proses front-end building. Loader dapat mengubah berkas bahasa pemrograman lain seperti TypeScript ke JavaScript. Yang paling spesial dari loader ini kita dapat melakukan import berkas .css langsung pada entry point layaknya berkas JavaScript pada dependency graph.
202003131716233f57b6b8ca088c0223cd40f39b99af85.png

Kemampuan impor pada modul apapun (contohnya .css) merupakan fitur spesifik dari webpack yang mungkin tak akan kita jumpai pada module bundler atau task runner lain. Alhasil, kita dapat lebih leluasa lagi alias tak terbatas dengan tipe berkas dalam menggunakan module pada webpack.
Untuk menetapkan loaders kita gunakan properti module.rules pada webpack configuration (webpack.config.js). Di dalamnya terdapat dua high level properties yaitu test, dan use. Berikut penjelasan singkatnya:
  • Properti test merupakan tipe berkas yang akan ditransformasikan.
  • Properti use merupakan loader mana yang akan digunakan untuk mentransformasikan berkas tersebut.
Belum terbayang bagaimana penggunaannya? Berikut contoh konfigurasi dari properti loader:

  1. module.exports = {

  2.   module: {

  3.     rules: [

  4.       { test: /.css$/, use: 'css-loader' }

  5.     ]

  6.   }

  7. };


Konfigurasi di atas memiliki properti module.rules dan menetapkan properti test dan use di dalamnya. Konfigurasi seperti ini layaknya kita memberitahu “Hey webpack compiler! Ketika Anda bertemu dengan berkas .css yang dihubungkan menggunakan import atau require statement, gunakanlah css-loader untuk mengubahnya sebelum membungkusnya ke dalam bundle”.
Banyak sekali loader yang dapat kita gunakan pada webpack configuration. Namun loader tersebut tidak disertakan langsung ketika kita menggunakan webpack. Jika kita ingin menggunakan loader katakanlah css-loader, maka kita perlu memasang package loader tersebut melalui npm.

  1. npm install css-loader --save-dev


Contoh sebelumnya merupakan cara ringkas ketika kita menetapkan loader agar mudah dibaca. Melalui module.rules sebenarnya kita dapat menetapkan banyak loader, namun dalam penulisannya kita perlu menetapkan loader tersebut secara eksplisit seperti ini:

  1. module.exports = {

  2.     module: {

  3.         rules: [

  4.             {

  5.                 test: /.css$/,

  6.                 use: [

  7.                     {

  8.                         loader: "style-loader"

  9.                     },

  10.                     {

  11.                         loader: "css-loader"

  12.                     }

  13.                 ]

  14.             }

  15.         ]

  16.     }

  17. }


Dalam menuliskan banyak loader dalam satu rule, urutan deklarasi loader tersebut sangat berpengaruh. Loader akan tereksekusi dengan urutan dari bawah ke atas. Pada contoh di atas eksekusi akan dimulai dari css-loader, lalu dilanjutkan oleh style-loader.
Dengan menuliskan loader secara eksplisit seperti ini, kita juga dapat dengan mudah menambahkan konfigurasi pada loader yang digunakan melalui properti options. Contohnya:

  1. module.exports = {

  2.     module: {

  3.         rules: [

  4.             {

  5.                 test: /.css$/,

  6.                 use: [

  7.                     {

  8.                         loader: "style-loader",

  9.                         options: {

  10.                             // memasukkan style dengan tag <style> di bawah dari element <body> 

  11.                             insert: "body"

  12.                         }

  13.                     },

  14.                     {

  15.                         loader: "css-loader"

  16.                     }

  17.                 ]

  18.             }

  19.         ]

  20.     }

  21. }


Untuk melihat loader apa saja yang dapat kita manfaatkan pada webpack dan konfigurasi apa saja yang dapat diterapkan pada masing-masing loadernya, kita dapat melihatnya secara lengkap pada dokumentasi resmi webpack melalui tautan berikut: Webpack Loaders Documentation

Plugin

Plugin pada webpack digunakan untuk melakukan tugas seperti optimasi bundel, management aset dan sebagainya. Dengan adanya plugin ini, webpack menjadi lebih fleksibel. Plugin merupakan tulang punggung dari webpack. Bahkan webpack sendiri dibangun menggunakan sistem plugin yang sama seperti yang kita lakukan pada webpack configuration.
Webpack Plugin merupakan sebuah JavaScript objek yang dibangun menggunakan JavaScript class yang di dalamnya terdapat method apply dengan satu argument bernama compiler. Kita dapat membuat webpack plugin sederhana dengan cara seperti ini: 

  1. const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

  2.  

  3.  

  4. class ConsoleLogOnBuildWebpackPlugin {

  5.   constructor(options) {

  6.     this.options = options;

  7.   }

  8.  

  9.  

  10.   apply(compiler) {

  11.     compiler.hooks.run.tap(pluginName, compilation => {

  12.       console.log(this.options.message);

  13.     });

  14.   }

  15. }

  16.  

  17.  

  18. module.exports = ConsoleLogOnBuildWebpackPlugin;


Ketika kita menggunakan plugin tersebut pada webpack configuration, ia akan mencetak nilai this.options.message pada console ketika proses build pada webpack berjalan. 
Untuk saat ini jangan terfokus pada cara membuat plugin di webpack. Alih-alih, fokuslah pada bagaimana ia digunakan pada webpack configuration. Karena plugin merupakan objek dan kita mungkin menyimpan konfigurasi ketika ia dibuat, maka dalam membuat objek plugin kita perlu menggunakan keyword new seperti ini:

  1. const ConsoleLogOnBuildWebpackPlugin = require("./console-log-on-build-webpack-plugin.js");

  2.  

  3. module.exports = {

  4.     plugins: [

  5.     new ConsoleLogOnBuildWebpackPlugin({

  6.         message: "The webpack build process is starting!"

  7.     })

  8.     ]

  9. }


Banyak plugin baik standar webpack atau pihak ketiga yang dapat kita manfaatkan pada webpack. Karena itulah kita tidak perlu fokus pada bagaimana membuat plugin. Untuk menggunakan plugin standar webpack, kita dapat mengaksesnya melalui objek webpack seperti ini:

  1. const webpack = require("webpack"); // dibutuhkan untuk mengakses built-in plugins

  2.  

  3. module.exports = {

  4.     plugins: [

  5.     new webpack.ProgressPlugin()

  6.     ]

  7. }


Namun, untuk menggunakan beberapa plugin (di luar plugin standar yang disediakan) kita perlu memasangnya terlebih dahulu melalui npm. Contohnya plugin yang banyak digunakan untuk membuat berkas HTML pada webpack adalah html-webpack-plugin. Untuk memasangnya kita gunakan perintah berikut:

  1. npm install html-webpack-plugin --save-dev


Setelah memasangnya kita dapat menggunakannya pada webpack configuration seperti ini:

  1. const HtmlWebpackPlugin = require('html-webpack-plugin'); //dipasang via npm

  2.  

  3. module.exports = {

  4.   plugins: [

  5.         new HtmlWebpackPlugin({

  6.             template: "./src/index.html",

  7.             filename: "index.html"

  8.         })

  9.     ]

  10. };


Pada contoh di atas, melalui html-webpack-plugin webpack akan menghasilkan berkas HTML untuk proyek kita dan memasukkan berkas yang sudah dibundel.

Mode

mode merupakan salah satu properti yang terdapat pada webpack configuration. Dengan memberikan mode dengan nilai developmentproduction atau none, kita dapat melakukan optimasi pada webpack berdasarkan mode yang kita kehendaki. Jika kita tidak menetapkan nilai pada properti mode, secara default akan bernilai production.

  1. module.exports = {

  2.     mode: 'production'

  3. };


Nilai mode juga dapat kita tetapkan melalui CLI argument seperti berikut:

  1. webpack --mode development


Kita dapat melakukan optimasi pada webpack berdasarkan mode yang kita kehendaki karena tiap properti pada webpack configuration menyesuaikan pada modenya. Misalkan, jika kita menggunakan mode development, kita dapat menggunakan properti devtoolcache, atau properti development lainnya pada webpack configuration.

  1. module.export = {

  2.     mode: 'development',

  3.     devtool: 'eval',

  4.     cache: 'true'

  5. }


Properti devtool atau cache tentu tidak dapat kita gunakan dalam mode production, tetapi kita dapat memanfaatkan properti-properti yang terdapat pada production. Begitu juga ketika kita menetapkan mode none. Untuk lebih lengkapnya, properti apa saja yang dapat kita manfaatkan di masing-masing modenya, silakan cek tautan berikut: Webpack Configuration Mode.
Jika kita ingin mengubah perilaku webpack berdasarkan nilai mode di dalam webpack.config.js, kita fungsi alih-alih objek.

  1. const config = {

  2.   entry: './app.js'

  3.   //...

  4. };

  5.  

  6. module.exports = (env, argv) => {

  7.  

  8.   if (argv.mode === 'development') {

  9.     config.devtool = 'source-map';

  10.   }

  11.  

  12.   if (argv.mode === 'production') {

  13.     //...

  14.   }

  15.  

  16.   return config;

  17. };


Atau kita dapat menggunakan flag –config pada scripts berkas package.json untuk menetapkan berkas webpack configuration yang berbeda pada tiap modenya.

  1. "scripts": {

  2.     "build:prod": "webpack --config webpack.prod.js",

  3.     "build:dev": "webpack --config webpack.dev.js",

  4. }


Using Webpack

Setelah mengetahui apa itu webpack dan seperti apa core concepts-nya, mungkin sebagian dari kita masih bingung jika belum mencobanya langsung. Untuk itu mari kita coba terapkan webpack pada proyek WebClock yang sudah kita buat sebelumnya. Karena pada proyek tersebut kita masih menggunakan tag <script> dalam menggunakan package yang terpasang menggunakan npm.
Sebelum melanjutkan ke materi selanjutnya. Pastikan Anda ikuti instruksi pada modul Node Package Manager hingga proyek WebClock menghadirkan tampilan seperti ini:
20200313192946751f2abb46ff53418e64c03942773582.gif
Belum ada tampilan demikian? Yuk baca kembali modul pada Node Package Manager dan ikuti instruksi di sana. Untuk Anda yang sudah sampai tahap tersebut, ayo kita gunakan webpack mulai dari cara memasangnya.

Installing Webpack

Memasang webpack pada proyek kita sama seperti kita memasang package JQuery atau Moment.js. Namun webpack ini dipasang pada devDependencies karena sejatinya ia hanya digunakan selama proses pengembangan saja. 
Untuk memasang webpack silakan buka proyek Anda. Pada terminal kita tuliskan perintah berikut:

  1. npm install webpack --save-dev

  2. npm install webpack-cli --save-dev


Atau kita dapat menyingkat perintah tersebut dalam penulisan satu baris seperti ini:

  1. npm install webpack webpack-cli --save-dev


Setelah berhasil memasang package webpack dan webpack-cli, maka kita dapat melihat kedua package tersebut pada devDependencies di berkas package.json.
20200313193203eb8ab066c7cf5c7d36ee34ab304ee6be.png
Mengapa kita membutuhkan dua package dalam memasang webpack? Apa fungsi package webpack dan webpack-cli? Package webpack merupakan package inti dari webpack itu sendiri. Sedangkan package webpack-cli merupakan package yang digunakan untuk membantu kita menjalankan webpack melalui sebuah perintah (Command Line Interface). Pada CLI juga kita dapat memberikan argumen seperti menetapkan berkas webpack config, atau mode dalam proses build.
Untuk menjalankan webpack kita perlu menambahkan script dengan perintah webpack pada package.json seperti ini:

  1. "scripts": {

  2.     "test": "echo "Error: no test specified" && exit 1",

  3.     "start": "http-server .",

  4.     "build": "webpack"

  5. },


Kita bisa menghapus script yang lainnya karena sudah tidak akan kita gunakan lagi. Sehingga sekarang berkas package.json akan tampak seperti ini:

  1. {

  2.   "name": "webclock",

  3.   "version": "1.0.0",

  4.   "description": "",

  5.   "main": "index.js",

  6.   "scripts": {

  7.     "build": "webpack"

  8.   },

  9.   "author": "",

  10.   "license": "ISC",

  11.   "dependencies": {

  12.     "jquery": "^3.4.1",

  13.     "moment": "^2.24.0"

  14.   },

  15.   "devDependencies": {

  16.     "webpack": "^4.41.6",

  17.     "webpack-cli": "^3.3.11"

  18.   }

  19. }


Lalu untuk menjalankan script build, kita gunakan perintah berikut:

  1. npm run build


Namun untuk saat ini jika menjalankan script build akan terjadi eror seperti ini:
202003131934278ded0ab441e5a0bdb11a0e5f88fcfe66.png
Dari pesan berikut, kita dapat menyimpulkan bahwa ketika kita tidak/belum menetapkan webpack configuration, nilai entry standarnya akan berlokasi pada src -> index.js

  1. ERROR in Entry module not found: Error: Can't resolve './src' in 'C:UsersDicodingDesktopWebClock'


Untuk itu mari kita sesuaikan dengan membuat folder src dan memindahkan index.js pada folder tersebut. Sehingga struktur proyeknya akan tampak seperti ini:
20200313193530ff33d34b4c276d7f0165d30a79a5179a.png
Lalu coba kita jalankan kembali script build, maka hasilnya akan tampak seperti ini:
20200314052546ad48eb36384caa7e498addc98e6ba500.png
Pada struktur proyek kita juga terlihat terdapat folder baru dengan nama dist, di mana di dalamnya terdapat berkas main.js yang merupakan hasil bundel dari entry point src -> index.js
Jika kita buka berkas tersebut, akan tampil banyak kode yang sulit kita baca. Jangan khawatir mengenai kode yang dihasilkan webpack itu. Namun jika dilihat pada akhir kodenya, kita akan menemukan kode asli yang kita tulis.
20200313193627744f50b9834d39a2b1709b03eebfa5d1.gif
Karena kita sudah menggunakan webpack untuk membundel module. Kita dapat menggunakan perintah import pada src -> index.js dalam menggunakan package npm.

  1. import $ from "jquery";

  2. import moment from "moment";

  3.  

  4. const displayTime = () => {

  5.     moment.locale("id");

  6.     $(".time").text(moment().format("LTS"));

  7.     $(".date").text(moment().format("LL"));

  8. };

  9.  

  10. const updateTime = () => {

  11.     displayTime();

  12.     setTimeout(updateTime, 1000)

  13. };

  14.  

  15. updateTime();


Kemudian pada index.html, kita dapat menggantikan seluruh tag <script> yang ada dengan satu tag <script> yang ditujukan pada dist -> main.js.

  1. <!DOCTYPE html>

  2. <html>

  3.     <head>

  4.         <title>Clock Web</title>

  5.         <link rel="stylesheet" href="style.css">

  6.     </head>

  7.     <body>

  8.         <div class="clock">

  9.             <span class="time"></span>

  10.             <span class="date"></span>

  11.         </div>

  12.         <script src="./dist/main.js"></script>

  13.     </body>

  14. </html>


Lalu kita build ulang proyek dengan menjalankan perintah:

  1. npm run build


Setelah build selesai dan menghasilkan berkas dist -> main.js yang baru, proyek WebClock seharusnya sudah bisa berjalan dengan baik ketika kita membuka berkas index.html.
20200313193821f67bcef3c951b5caf7555647b17fd05b.png

Creating configuration files

Sebelumnya kita menggunakan webpack dengan zero configuration. Apa artinya? Kita dapat menggunakan webpack tanpa membuat berkas webpack configuration sama sekali. Zero configuration ini fitur baru dari webpack 4. Namun kekurangan dalam zero configuration adalah kita tidak dapat menyesuaikan konfigurasi yang kita inginkan ketika menggunakan webpack. Katakanlah jika kita perlu menggunakan sebuah loader atau plugin, tentu dengan zero configuration kita tidak dapat menggunakannya.
Kita dapat membuat berkas webpack configuration dengan membuat berkas JavaScript baru dengan nama webpack.config.js pada root folder proyek kita.
202003131940148f638bc852c2ddab2f771515a65338b0.png
Nama berkas tersebut merupakan nama standar bagi berkas webpack configuration. Kita dapat menetapkan nama lain sesuka kita namun kita perlu menambahkan argument –config pada webpack CLI. Kita akan mencoba hal ini nanti. Saat ini kita fokus dulu bagaimana cara menuliskan sintaks pada berkas webpack configuration.
Pada materi sebelumnya kita sudah mengenal konsep inti dari webpack ini. Ada entryoutputloaderplugins dan mode. Kelima konsep inti tersebut dapat menentukan perilaku webpack dalam melaksanakan tugasnya. Untuk konfigurasi awal, kita tetapkan terlebih dahulu entry dan output pada webpack configuration. Caranya dengan tambahkan kode berikut pada berkas webpack.config.js.

  1. const path = require("path");

  2.  

  3. module.exports = {

  4.     entry: "./src/index.js",

  5.     output: {

  6.         path: path.resolve(__dirname, "dist"),

  7.         filename: "bundle.js"

  8.     }

  9. }


Pada nilai entry walaupun kita menetapkannya namun tidak ada nilai yang berbeda seperti nilai standar yaitu src -> index.js. Namun pada nilai output, kita mengubah penamaan berkas hasil bundel dari main.js (nilai standar) menjadi bundle.js. Sehingga ketika kita jalankan kembali script build.

  1. npm run build


Maka akan terdapat berkas bundel baru bernama bundle.js.
2020031319450425966a4f5f052bd524adcdefcecb82a3.png
Namun kita juga masih dapat melihat berkas main.js yang merupakan berkas lama hasil proses build sebelumnya. Kita dapat menghapusnya karena sudah tidak digunakan lagi. Namun jangan lupa mengubah target berkas JavaScript yang dilampirkan pada index.html menjadi bundle.js.

  1. <!DOCTYPE html>

  2. <html>

  3.     <head>

  4.         <title>Clock Web</title>

  5.         <link rel="stylesheet" href="style.css">

  6.     </head>

  7.     <body>

  8.         <div class="clock">

  9.             <span class="time"></span>

  10.             <span class="date"></span>

  11.         </div>

  12.         <script src="./dist/bundle.js"></script>

  13.     </body>

  14. </html>


Jika kita membuka index.html maka akan tampil hasil yang sama seperti sebelumnya. Karena memang kita tidak melakukan apapun selain mengubah nama hasil bundel dari main.js (nilai standar) menjadi bundle.js.
Pada saat proses bundle, coba kita lihat pada Terminal. Terdapat warning yang menunjukkan bahwa kita tidak menetapkan mode pada berkas webpack configuration.
20200313194640a7040e4ef237e1dc7852c1e0a379a595.png
Jika kita tidak menetapkan nilai pada properti mode maka nilai standar akan diterapkan, yakni nilai production. Namun daripada kita membiarkan properti mode ini tidak memiliki nilai, sebaiknya kita tetapkan saja nilai modenya. Untuk saat ini, kita tetapkan properti mode dengan nilai production.

  1. const path = require("path");

  2.  

  3. module.exports = {

  4.     entry: "./src/index.js",

  5.     output: {

  6.         path: path.resolve(__dirname, "dist"),

  7.         filename: "bundle.js"

  8.     },

  9.     mode: "production"

  10. }


Lalu coba jalankan kembali script build. Maka warning mengenai properti mode tidak akan muncul pada Terminal saat proses build berjalan.
“Sebenarnya kita juga dapat melihat warning lain yang menunjukkan ukuran bundle.js sudah melampaui batas. Kita bisa lihat sendiri dengan membuka berkas bundle.js. Di sana kita akan menemukan banyak sekali kode yang dihasilkan dibandingkan dengan sebelumnya. 
Hal ini disebabkan kode yang kita tulis memiliki ketergantungan (dependencies) terhadap package JQuery dan Moment. Sehingga package tersebut perlu dibundel juga pada berkas bundle.js. karena itulah berkas bundle.js menjadi bengkak ukurannya. 
Ini merupakan salah satu alasan mengapa sebaiknya kita hindari penggunaan package pihak ketiga yang kita bawa hingga tingkat production. Membengkaknya berkas bundle.js, tentu akan berdampak terhadap performa web yang kita bangun nantinya.”

Using Loader

Pada penjelasan core concepts kita sudah tahu bahwa dengan loader, webpack dapat memproses berkas selain JavaScript. Dengan adanya loader kita dapat menggunakan CSS sebagai modul dan ikut terbundel bersama berkas bundle.js. Sehingga kita tidak perlu lagi menerapkan tag <link> pada index.html.

Style and CSS Loader

Untuk menggunakan CSS modul pada webpack, kita membutuhkan dua buah loader. Yang pertama css-loader dan yang kedua style-loadercss-loader merupakan loader untuk memproses berkas dengan format .css. Sedangkan style-loader merupakan loader yang digunakan untuk membuat styling dapat diterapkan secara modular dengan menggunakan import statement. 
Untuk menggunakan kedua loader tersebut, langkah pertama adalah memasangnya melalui npm dengan perintah: 

  1. npm install style-loader css-loader --save-dev


Setelah berhasil, pada berkas webpack.config.js kita tambahkan properti module.rules dan isikan nilai loader seperti ini:

  1. const path = require("path");

  2.  

  3. module.exports = {

  4.     entry: "./src/index.js",

  5.     output: {

  6.         path: path.resolve(__dirname, "dist"),

  7.         filename: "bundle.js"

  8.     },

  9.     mode: "production",

  10.     module: {

  11.         rules: [

  12.             {

  13.                 test: /.css$/,

  14.                 use: [

  15.                     {

  16.                         loader: "style-loader"

  17.                     },

  18.                     {

  19.                         loader: "css-loader"

  20.                     }

  21.                 ]

  22.             }

  23.         ]

  24.     }

  25. }


Setelah menambahkan loader pada webpack.config.js, sekarang kita dapat melakukan impor berkas CSS menggunakan import statement layaknya berkas JavaScript.
Namun sebelum itu, pindahkan terlebih dahulu berkas style.css agar lebih rapi ke dalam direktori baru dengan lokasi src -> style -> style.css. Sehingga struktur proyek akan tampak seperti ini:
20200313195233801f7eecca052bdc6bfed86c4136ea86.png
Lalu pada berkas index.js, lakukan impor berkas style.css pada awal baris kodenya.

  1. import "./style/style.css";


Maka keseluruhan kode pada index.js akan tampak seperti ini:

  1. import "./style/style.css";

  2. import $ from "jquery";

  3. import moment from "moment";

  4.  

  5. const displayTime = () => {

  6.     moment.locale("id");

  7.     $(".time").text(moment().format("LTS"));

  8.     $(".date").text(moment().format("LL"));

  9. };

  10.  

  11. const updateTime = () => {

  12.     displayTime();

  13.     setTimeout(updateTime, 1000)

  14. };

  15.  

  16. updateTime();


Dengan begitu kita tidak membutuhkan lagi tag <link> pada index.html dalam melampirkan stylesheet pada berkas style.css. Kita hapus tag <link> berikut:

  1. <link rel="stylesheet" href="style.css">


Sehingga keseluruhan kode pada index.html akan tampak seperti ini:

  1. <!DOCTYPE html>

  2. <html>

  3.     <head>

  4.         <title>Clock Web</title>

  5.     </head>

  6.     <body>

  7.         <div class="clock">

  8.             <span class="time"></span>

  9.             <span class="date"></span>

  10.         </div>

  11.         <script src="./dist/bundle.js"></script>

  12.     </body>

  13. </html>


Setelah itu coba jalankan kembali script build untuk menghasilkan berkas bundle.js yang baru dan buka index.html pada browser. Seharusnya proyek WebClock menampilkan hasil yang sama seperti sebelumnya.
202003131956308d12987bb21047da2fbf283a93369895.gif
Selamat! Itu artinya, Anda berhasil menerapkan styling dengan cara modular menggunakan webpack loader. Jika Anda lihat pada berkas bundle.js saat ini, kita dapat menemukan styling yang dituliskan. 
2020031319565824078682376dded444e3b638090ac715.png
Hal inilah mengapa styling dapat diterapkan tanpa harus menggunakan tag <link> pada berkas index.html.

Babel Loader

Kita sudah berhasil menggunakan dan merasakan manfaat dari style-loader dan css-loader. Namun sebenarnya masih banyak loader lain yang tak kalah pentingnya untuk diterapkan pada webpack. Salah satunya adalah babel-loader
2020031319583407bf63e74fd402a5583504377dfd8829.png
Mungkin sebagian dari kita sudah ada yang mengetahui apa itu babel atau babel.js? Babel merupakan sebuah transpiler yang bertugas untuk mengubah sintaks JavaScript modern (ES6+) menjadi sintaks yang dapat didukung penuh oleh seluruh browser.
JavaScript merupakan bahasa pemrograman yang berkembang sangat pesat. Komunitasnya besar, dan tiap tahun selalu terdapat versi yang baru. Namun perkembangan yang pesat tadi ternyata membutuhkan waktu yang lama untuk diadaptasi oleh browser atau Node.js. Lalu jika kita ingin mencoba sintaks terbaru di JavaScript apakah kita perlu menunggu hingga seluruh browser berhasil mengadaptasi pembaharuan tersebut? Tentu tidak! 
Dengan babel Anda dapat menuliskan sintaks JavaScript versi terbaru tanpa khawatir memikirkan dukungan pada browser. Karena babel akan mengubah sintaks yang kita tuliskan menjadi kode yang dapat diterima browser.
Jika Anda penasaran bagaimana cara babel bekerja, babel menyediakan sebuah playground yang dapat kita manfaatkan untuk mengubah sintaks JavaScript modern (ES6+) menjadi sintaks lama. Untuk mencobanya, silakan Anda buka tautan berikut: https://babeljs.io/repl.
20200313195932ff6b88f0352e20c9c08f27fd89aff700.png
Pada playground tersebut kita juga dapat memilih preset yang kita inginkan. Secara default preset akan mengarah ES2015 (ES6).
Anda sudah tahu  sekilas mengenai babel. Nah pada webpack kita juga dapat menggunakan babel dalam bentuk loader. Walaupun webpack secara standarnya dapat memproses berkas JavaScript tanpa perlu bantuan loader, namun proses tersebut tidak mengubah sintaks yang kita tuliskan. Artinya jika kita menuliskan sintaks JavaScript modern, maka kita akan menemukannya juga pada berkas bundle.js.
2020031320001286d0cce40a7f1f8ac22836dcbab4867b.png
Walaupun saat ini Google Chrome dan Mozilla Firefox sudah mendukung penulisan sintaks ES6, namun setidaknya kita perlu sedikit peduli terhadap dukungan browser lama seperti Internet Explorer atau browser versi lama lainnya.
Untuk menggunakan babel pada webpack sebagai loader, kita perlu memasang tiga package menggunakan npm pada devDependencies. Yang pertama package @babel/core, yang kedua babel-loader, dan yang ketiga @babel/preset-env.

  1. npm install @babel/core babel-loader @babel/preset-env --save-dev


Package @babel/core merupakan package inti yang harus dipasang ketika kita hendak menggunakan babel, baik pada webpack maupun tools yang lain.
Package babel-loader merupakan package yang diperlukan untuk menggunakan babel sebagai loader pada webpack.
Yang terakhir package @babel/preset-env merupakan package preset yang akan kita gunakan untuk membantu babel-loader dalam melakukan tugasnya. @babel/preset-env merupakan preset cerdas yang memungkinkan kita menggunakan sintaks JavaScript terbaru tanpa menetapkan secara spesifik sintaks JavaScript versi apa yang kita gunakan.
Berkas package.json akan tampak seperti ini setelah memasang ketiga package tersebut:

  1. {

  2.   "name": "webclock",

  3.   "version": "1.0.0",

  4.   "description": "",

  5.   "main": "index.js",

  6.   "scripts": {

  7.     "build": "webpack"

  8.   },

  9.   "author": "",

  10.   "license": "ISC",

  11.   "dependencies": {

  12.     "jquery": "^3.4.1",

  13.     "moment": "^2.24.0"

  14.   },

  15.   "devDependencies": {

  16.     "@babel/core": "^7.8.4",

  17.     "@babel/preset-env": "^7.8.4",

  18.     "babel-loader": "^8.0.6",

  19.     "css-loader": "^3.4.2",

  20.     "style-loader": "^1.1.3",

  21.     "webpack": "^4.41.6",

  22.     "webpack-cli": "^3.3.11"

  23.   }

  24. }


Setelah berhasil memasang ketiga package tersebut, langkah selanjutnya kita dapat gunakan babel-loader dan preset-nya pada webpack configuration.

  1. const path = require("path");

  2.  

  3. module.exports = {

  4.     entry: "./src/index.js",

  5.     output: {

  6.         path: path.resolve(__dirname, "dist"),

  7.         filename: "bundle.js"

  8.     },

  9.     mode: "production",

  10.     module: {

  11.         rules: [

  12.             /* style and css loader */

  13.             {

  14.                 test: /.css$/,

  15.                 use: [

  16.                     {

  17.                         loader: "style-loader"

  18.                     },

  19.                     {

  20.                         loader: "css-loader"

  21.                     }

  22.                 ]

  23.             },

  24.             /* babel loader */

  25.             {

  26.                 test: /.js$/,

  27.                 exclude: "/node_modules/",

  28.                 use: [

  29.                     {

  30.                         loader: "babel-loader",

  31.                         options: {

  32.                             presets: ["@babel/preset-env"]

  33.                         }

  34.                     }

  35.                 ]

  36.             }

  37.         ]

  38.     }

  39. }


Ketika menerapkan rule untuk berkas .js, jangan lupa untuk menetapkan properti exclude dengan nilai “/node_modules/”. Apa artinya? Dengan menetapkan properti exclude itu berarti kita mengecualikan webpack untuk memproses berkas .js yang berada pada folder “node_modules”. Hal ini dapat meminimalisir proses yang tidak diperlukan, sehingga mempercepat proses build pada proyek kita.  
Lalu pada penerapan babel-loader juga kita menggunakan properti options dengan menetapkan properti presets di dalamnya. Pada properti presets kita tetapkan preset (dalam bentuk array literas) yang sudah kita pasang menggunakan npm, yaitu @babel/preset-env.
Setelah menggunakan babel loader pada webpack configuration, mari kita coba build dan buka kembali berkas bundle.js. Maka kode yang kita tuliskan dalam ES6 akan diubah dalam sintaks yang dapat diterima oleh seluruh browser.
20200313200507f7a720e39de819c62a15f1be147aa62e.png
Bahkan pada berkas bundle tersebut dipastikan sudah tidak terdapat lagi sintaks yang dituliskan menggunakan ES6.
20200313200535f035aab9b99622b9e95be3f0d98fc827.png
Namun walaupun sintaksnya sudah diubah, proyek akan tetap berjalan normal seperti biasanya.

Webpack Dev Server

Saat ini setiap terjadi perubahan kode pada proyek, Anda perlu melakukan build ulang untuk melihat hasilnya. Tak peduli perubahan tersebut bersifat  mayor ataupun hanya sekadar ganti warna saja. Karena untuk melihat perubahan terbaru kita juga perlu memperbaharui berkas bundle.js. Tentu sangat merepotkan bukan?
Untunglah webpack menyediakan fitur live-reloading yang dapat mempercepat proses pengembangan menggunakan Webpack Dev Server. Dengan ini kita dapat melihat perubahan secara langsung tanpa harus menjalankan ulang perintah build.
Untuk menggunakan Webpack Dev Server langkah pertama adalah kita pasang package webpack-dev-server pada devDependencies menggunakan npm.

  1. npm install webpack-dev-server --save-dev


Setelah berhasil memasangnya, kita tambahkan script start-dev dengan perintah “webpack-dev-server” pada package.json.

  1. "scripts": {

  2.     "build": "webpack",

  3.     "start-dev": "webpack-dev-server"

  4. }


Mari kita jalankan script start-dev dengan perintah:

  1. npm run start-dev


Setelah menjalankan perintah di atas, pada terminal kita dapat melihat alamat localhost:8080. Alamat tersebut digunakan untuk melihat proyek yang sedang kita kembangkan pada browser.
202003132019248725f53a4bca9fccd53a300da521d958.png
Webpack Dev Server secara standar memiliki fitur live-reloading. Artinya setiap terjadi perubahan terhadap assets yang digunakan (HTML, CSS, atau JS) dan menyimpan perubahannya (save), ia akan melakukan melakukan proses compiling ulang dan menampilkan hasil perubahan langsung pada browser.
20200313202124bafcb1767b5e2c4612e445a32bab8535.gif
Namun jika kita lihat proses compiling memakan waktu yang cukup lama bukan? Pada contoh gif di atas, butuh setidaknya 5 detik untuk Webpack Dev Server menampilkan perubahan terbaru pada browser. Mengapa bisa demikian?
Hal tersebut terjadi karena kita menggunakan mode production dalam menjalankan Webpack Dev Server. Ketika menggunakan mode production maka webpack melakukan bundling module seoptimal mungkin sehingga proses membutuhkan waktu lebih lama dibandingkan dengan mode development. Selain itu, pada webpack configuration kita menggunakan babel-loader. Proses compiling yang lama akan terasa lebih lama lagi karena kita harus melalui proses transpiling kode JavaScript melalui babel-loader
Solusinya, pisahkan webpack configuration untuk development dan production.
Untuk menghentikan service webpack-dev-server, gunakan kombinasi ctrl + c pada terminal yang digunakan. Ingatlahbahwa sebaiknya kita hentikan service webpack-dev-server setiap kali ingin melakukan perubahan pada berkas webpack configuration.

Configuration Environment

Tujuan dari development dan production memanglah berbeda. Pada tahap development webpack akan menerapkan konfigurasi yang selalu optimal untuk mempercepat proses perubahan pada browser (hot reloading). Sedangkan pada proses production kita ingin fokus terhadap optimasi bundling dan kompatibilitasnya pada browser. Karena perbedaan fokus tersebut sebaiknya kita memisahkan konfigurasi antara keduanya.
20200313202603b0a75a4a5caa86c71871610658933e74.png
Namun jika berpatokan pada bagan di atas, antara keduanya terdapat konfigurasi umum (common) seperti entryoutputstyle-loadercss-loader dan HtmlWebpackPlugin. Untuk menghindari penulisan berulang, kita dapat menggunakan tools yang bernama webpack-merge yang berfungsi untuk menggabungkan konfigurasi umum dengan konfigurasi unik tiap environment-nya.
2020031320274773a72ddcc2ce0672b38d5e54a9b9d640.png

Using webpack-merge

Untuk menggunakan webpack-merge langkah awal adalah dengan memasang package tersebut pada devDependencies menggunakan NPM.

  1. npm install webpack-merge --save-dev


Kemudian kita buat berkas webpack konfigurasi baru dengan nama webpack.common.js.
20200313202917357502a1b9a527cd74048d4af80fbaf7.png
Di dalam berkas tersebut kita tuliskan konfigurasi umum yang digunakan pada setiap environment baik itu production atau development.

  1. const path = require("path");

  2. const HtmlWebpackPlugin = require("html-webpack-plugin");

  3.  

  4. module.exports = {

  5.     entry: "./src/index.js",

  6.     output: {

  7.         path: path.resolve(__dirname, "dist"),

  8.         filename: "bundle.js"

  9.     },

  10.     module: {

  11.         rules: [

  12.             /* style and css loader */

  13.             {

  14.                 test: /.css$/,

  15.                 use: [

  16.                     {

  17.                         loader: "style-loader"

  18.                     },

  19.                     {

  20.                         loader: "css-loader"

  21.                     }

  22.                 ]

  23.             }

  24.         ]

  25.     },

  26.     /* plugin */

  27.     plugins: [

  28.         /* HTML Webpack Plugin */

  29.         new HtmlWebpackPlugin({

  30.             template: "./src/template.html",

  31.             filename: "index.html"

  32.         })

  33.     ]

  34. }


Kemudian kita buat 2 (dua) berkas webpack configuration baru dengan nama webpack.prod.js dan webpack.dev.js.
202003132030400247cbd50837f2c5b60c75395d5384c1.png
Kemudian pada masing-masing berkasnya, tuliskan kode berikut:

  1. const merge = require("webpack-merge");

  2. const common = require("./webpack.common.js");

  3.  

  4.  

  5. module.exports = merge(common, {

  6.     mode: "production",

  7.     module: {

  8.         rules: [

  9.             /* babel loader */

  10.             {

  11.                 test: /.js$/,

  12.                 exclude: "/node_modules/",

  13.                 use: [

  14.                     {

  15.                         loader: "babel-loader",

  16.                         options: {

  17.                             presets: ["@babel/preset-env"]

  18.                         }

  19.                     }

  20.                 ]

  21.             }

  22.         ]

  23.     }

  24. })



  1. const merge = require("webpack-merge");

  2. const common = require("./webpack.common.js"); 

  3.  

  4.  

  5. module.exports = merge(common, {

  6.     mode: "development",

  7. })


Di dalam berkas webpack.common.js kita sudah menetapkan nilai entryoutput beberapa loader, dan plugin yang nilainya digunakan pada kedua environment. Sehingga kita tidak perlu menetapkannya lagi pada masing-masing berkas konfigurasi environment-nya.
Perhatikan juga bahwa kita menggunakan merge() dari package webpack-merge, untuk memasukkan konfigurasi umum pada konfigurasi tiap environment-nya.

  1. module.exports = merge(common, )


Setelah menetapkan konfigurasi umum dan konfigurasi pada tiap environment, mari ubah perintah script build dan start-dev pada package.json menjadi seperti ini:

  1. "scripts": {

  2.     "build": "webpack --config webpack.prod.js",

  3.     "start-dev": "webpack-dev-server --config webpack.dev.js"

  4. }


Dengan menambahkan flag –config [config-files] pada script build dan start-dev, maka Anda dapat secara leluasa menghapus berkas webpack.config.js karena memang sudah tidak digunakan lagi. Sehingga pada proyek WebClock hanya terdapat 3 (tiga) berkas webpack configuration.
202003132035126bfffe874273fbdfcae56ed9370be089.png
Coba kita jalankan kembali script start-dev ya. Seharusnya fitur live-reloading akan berjalan lebih cepat.
20200313203633827ca43f7ab670daa8d217cc826e4f88.gif

Apa Itu Shadow DOM?

Shadow DOM dapat mengisolasi sebagian struktur DOM di dalam komponen sehingga tidak dapat disentuh dari luar komponen atau nodenya. Singkatnya kita bisa sebut Shadow DOM sebagai “DOM dalam DOM”. Bagaimana ia bekerja? Perhatikan ilustrasi berikut:

Shadow DOM dapat membuat DOM Tree lain terbentuk secara terisolasi melalui host yang merupakan komponen dari regular DOM Tree (Document Tree). Shadow DOM Tree ini dimulai dari root bayangan (Shadow root), yang dibawahnya dapat memiliki banyak element lagi layaknya Document Tree.
Terdapat beberapa terminologi yang perlu kita ketahui dari ilustrasi di atas:
  • Shadow host : Merupakan komponen/node yang terdapat pada regular DOM di mana shadow DOM terlampir pada komponen/node ini.
  • Shadow tree : DOM Tree di dalam shadow DOM.
  • Shadow boundary : Batas dari shadow DOM dengan regular DOM.
  • Shadow root : Root node dari shadow tree.
Kita dapat memanipulasi elemen yang terdapat di dalam shadow tree layaknya pada document tree, namun cakupannya selama kita berada di dalam shadow boundary. Dengan kata lain, jika kita berada di document tree kita tidak dapat memanipulasi elemen bahkan menerapkan styling pada elemen yang terdapat di dalam shadow tree. Itulah mengapa shadow DOM dapat membuat komponen terenkapsulasi
Untuk melampirkan Shadow DOM pada elemen penggunaan sangat mudah, yaitu dengan menggunakan properti attachShadow pada elemen-nya seperti ini:

  1. // Shadow Host

  2. const divElement = document.createElement("div");

  3.  

  4.  

  5. // element yang berada di dalam Shadow DOM

  6. const headingElement = document.createElement("h1");

  7. headingElement.innerText = "Ini merupakan konten di dalam shadow DOM";

  8.  

  9.  

  10. // Melampirkan shadow root pada shadow host

  11. // Mengatur mode shadow dengan nilai open

  12. const shadowRoot = divElement.attachShadow({mode: "open"});

  13.  

  14.  

  15. // Memasukkan element heading ke dalam shadow root

  16. shadowRoot.appendChild(headingElement);

  17.  

  18.  

  19. // Memasukkan elemen shadow host ke regular DOM

  20. document.body.appendChild(divElement);



  1. <!DOCTYPE html>

  2. <html>

  3.  <head>

  4.    <meta charset="utf-8">

  5.    <meta name="viewport" content="width=device-width">

  6.    <title>Shadow DOM Basic Usage</title>

  7.  </head>

  8.  <body>

  9.  <script src="main.js"></script>

  10.  </body>

  11. </html>


Jika kita lihat pada browser, maka struktur HTML yang akan dihasilkan adalah seperti ini:

2020031020442138d22cf8f4814aacc795f563c3015892.png
Dan struktur DOM tree yang terbentuk akan tampak seperti ini:
20200310204452a9e4788ac5f7f206c20debfa0bc87524.png
Dalam penggunaan attachShadow() kita melampirkan objek dengan properti mode yang memiliki nilai ‘open’. Sebenarnya terdapat dua opsi nilai yang dapat digunakan dalam properti mode, yaitu “open” dan “closed”. 
Menggunakan nilai open berarti kita memperbolehkan untuk mengakses properti shadowRoot melalui elemen yang melampirkan Shadow DOM. 

  1. divElement.attachShadow;


properti shadowRoot mengembalikan struktur DOM yang berada pada shadow tree.
20200310204643a7a1f999761eaf74f9476e50013bb373.gif
Namun jika kita menggunakan nilai closed maka properti shadowRoot akan mengembalikan nilai null

  1. const shadowRoot = divElement.attachShadow({mode: "closed"});

  2. divElement.shadowRoot // null;


Hal ini berarti kita sama sekali tidak dapat mengakses Shadow Tree selain melalui variabel yang kita definisikan ketika melampirkan Shadow DOM.

  1. const shadowRoot = divElement.attachShadow({mode: "closed"});

  2. divElement.shadowRoot // null;

  3. shadowRoot // # shadow-root (closed)


2020031020483296ef7fce493fd7c0528697c3fa03565b.gif
Karena Shadow DOM terisolasi dari document tree maka element yang terdapat di dalamnya pun tidak akan terpengaruh oleh styling yang berada diluar dari shadow root-nya.

  1. <!DOCTYPE html>

  2. <html>

  3.  <head>

  4.    <meta charset="utf-8">

  5.    <meta name="viewport" content="width=device-width">

  6.    <title>Shadow DOM Basic Usage</title>

  7.    <style>

  8.        h1 {

  9.          color: red;

  10.        }

  11.    </style>

  12.  </head>

  13.  <body>

  14.    <h1>Ini merupakan konten yang berada di Document tree</h1>

  15.    <script src="main.js"></script>

  16.  </body>

  17. </html>



  1. // Shadow Host

  2. const divElement = document.createElement("div");

  3.  

  4.  

  5. // element yang berada di dalam Shadow DOM

  6. const headingElement = document.createElement("h1");

  7. headingElement.innerText = "Ini merupakan konten di dalam shadow DOM";

  8.  

  9.  

  10. // Melampirkan shadow root pada shadow host

  11. // Mengatur mode shadow dengan nilai open

  12. const shadowRoot = divElement.attachShadow({mode: "open"});

  13.  

  14.  

  15. // Memasukkan element heading ke dalam shadow root

  16. shadowRoot.appendChild(headingElement);

  17.  

  18.  

  19. // Memasukkan elemen shadow host ke regular DOM

  20. document.body.appendChild(divElement);


Jika dilihat pada browser maka hasilnya akan seperti ini:

20200310205023649403bfa8d61e879afc65f4a050c502.png
Berdasarkan hasil di atas, styling hanya akan diterapkan pada elemen <h1> yang berada di document tree. Sedangkan elemen <h1> yang berada pada shadow dom tidak akan terpengaruh dengan styling tersebut. Lantas, bagaimana caranya kita melakukan styling pada Shadow DOM?
Kita dapat melakukannya dengan menambahkan template <style> di dalam shadowRoot.innerHTML.  Contohnya seperti ini:

  1. // menetapkan styling pada Shadow DOM

  2. shadowRoot.innerHTML += `

  3.  <style>

  4.    h1 {

  5.      color: green;

  6.    }

  7.  </style>

  8. `;


Maka element <style> tersebut akan berada di dalam shadow tree dan akan berdampak pada elemen yang ada di dalamnya.
20200310205206823dcc0fb3c6ef38cb5e0a7563f0590d.png

Belajar Membuat Custom Element Dan Shadow DOM Dasar di Javascript

Ihsan Magazine – Kali ini kita akan mencoba belajar membuat Custom Element dasar di Javascript, apa itu Custom Element? Gampang nya Custom Element seperti kita Membuat Tag HTML baru untuk Front End Developer.

Bisa untuk memperluas elemen bawaan HTML, dan masih banyak lagi, dasarnya menggunakan Elements global digunakan untuk mendefinisikan elemen khusus dan mengajarkan tag baru pada browser.

Panggil customElements.define() dengan nama tag yang ingin Anda buat dan class JavaScript yang memperluas HTMLElement dasar.

Selengkapnya Baca Disini

Kali ini Study Kasus dengan ClubFinder.zip

Jika kita telaah dengan seksama, pada proyek Club Finder terdapat 4 (empat) bagian yang berpotensi untuk dijadikan custom element, yaitu:

  1. App Bar : Komponen di posisi atas yang menunjukkan identitas atau nama dari aplikasi web.
  2. Search Bar : Komponen yang terdiri dari elemen <input> dan <button> dan berfungsi untuk melakukan pencarian club sesuai dengan input pengguna.
  3. Club List : Komponen yang berfungsi untuk menampung data dari hasil pencarian, kemudian menampilkannya dalam bentuk list.
  4. Club Item : Komponen yang menampilkan data individual club yang diberikan dari club list. Komponen ini terdiri dari gambar, nama, dan deskripsi singkat club.

Solution: Membuat app-bar Component

Apakah Anda berhasil menerapkan custom element pada proyek Club Finder? Jika belum, mari kita lakukan bersama-sama. Kita mulai dari komponen termudah terlebih dahulu yaitu App Bar.
Agar mengelola berkas pada proyek jadi lebih mudah, kita perlu membuat folder baru dengan nama “component” di dalam folder src -> script.
20200310192559e057df7d65da0d6b4369a31dfa0bcf60.png
Folder ini akan menampung berkas JavaScript yang digunakan dalam membuat custom element.
Lalu di dalam folder component, buat berkas JavaScript baru dengan nama “app-bar.js”. Kemudian kita buat class dengan nama AppBar yang mewarisi sifat HTMLElement.

  1. class AppBar extends HTMLElement {

  2.   

  3. }


Kemudian di dalam body block classnya, kita implementasi method connectedCallback dan membuat fungsi render.

  1. class AppBar extends HTMLElement {

  2.    connectedCallback(){

  3.  

  4.    }

  5.  

  6.    render() {

  7.       

  8.    }

  9. }


Seperti yang sudah kita ketahui, connectedCallback() akan terpanggil ketika element telah diterapkan pada DOM. Jika kita ingin element ini ketika diterapkan langsung melakukan rendering maka kita dapat memanggil fungsi this.render() di dalam connectedCallback.

  1. class AppBar extends HTMLElement {

  2.    connectedCallback(){

  3.        this.render();

  4.    }

  5.  

  6.    render() {

  7.  

  8.    }

  9. }


Lalu pada fungsi render, kita tuliskan kode yang berfungsi untuk menampilkan elemen yang dibutuhkan pada melalui properti this.innerHTML. Apa saja yang dibutuhkan? Kita bisa melihatnya pada berkas index.html

  1. <header>

  2.        <div id="appBar" class="app-bar">

  3.            <h2>Club Finder</h2>

  4.        </div>

  5. </header>


Di dalam elemen <header> terdapat elemen <div> yang menerapkan class “app-bar”. Nah kita copy element di dalam app-bar, dan paste untuk dijadikan nilai pada this.innerHTML di fungsi render().

  1. class AppBar extends HTMLElement {

  2.    connectedCallback(){

  3.        this.render();

  4.    }

  5.  

  6.    render() {

  7.        this.innerHTML = `<h2>Club Finder</h2>`;

  8.    }

  9. }


Lalu di akhir berkas app-bar.js, jangan lupa untuk definisikan custom element yang kita buat agar dapat digunakan pada DOM.

  1. class AppBar extends HTMLElement {

  2.    connectedCallback(){

  3.        this.render();

  4.    }

  5.  

  6.    render() {

  7.        this.innerHTML = `<h2>Club Finder</h2>`;

  8.    }

  9. }

  10.  

  11. customElements.define("app-bar", AppBar);


Dengan begitu kita dapat mengubah penerapan app-bar pada index.html dengan menggunakan tag <app-bar>.

  1. <header>

  2.       <app-bar></app-bar>

  3. </header>


Terakhir, agar kode pada berkas app-bar.js tereksekusi, impor berkas app-bar.js pada berkas app.js, seperti ini:

  1. import "./src/script/component/app-bar.js";


Tuliskan kode tersebut pada awal berkas app.js, sehingga keseluruhan kode pada berkasnya akan tampak seperti ini:

  1. import "./src/script/component/app-bar.js";

  2. import main from "./src/script/view/main.js";

  3.  

  4. document.addEventListener("DOMContentLoaded", main);


Kemudian coba kita buka proyeknya menggunakan local server. Inilah tampilan hasilnya:
2020031019350448bf1ff8d96767443908e81b171563c3.png
Oops, tampilan App Bar tampak berantakan. Kita perlu memperbaiki css yang digunakan pada elemen App Bar sebelumnya. Buka berkas appbar.css lalu ubah selector-nya dari .app-bar menjadi app-bar.

  1. app-bar {

  2.    padding: 16px;

  3.    width: 100%;

  4.    background-color: cornflowerblue;

  5.    color: white;

  6.    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);

  7. }


Lalu lihat kita lihat hasilnya.
2020031019360718a18a9301e96c9237e9d2bd80d3dda2.png
Yah, kini teks “Club Finder” tidak tampak karena background element tidak bekerja dengan baik. Kenapa begini yah? Pasalnya, custom element standarnya merupakan inline element, sehingga tidak akan mengisi panjang lebar parent element-nya. Solusinya adalah dengan mengubah sifat inline pada custom element menjadi block dengan cara menambahkan properti display dengan nilai block pada selector app-bar.

  1. app-bar {

  2.    display: block;

  3.    padding: 16px;

  4.    width: 100%;

  5.    background-color: cornflowerblue;

  6.    color: white;

  7.    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);

  8. }


Dengan begitu tampilan kita berhasil membuat custom element <app-bar> dengan baik!
20200310193753886f026632febaa362d6775a39bbc28d.png

Membuat search-bar Component

Pembuatan elemen <search-bar> lebih sedikit rumit dari pembuatan komponen sebelumnya, karena di dalam komponen search bar terdapat element <input> dan <button>. Kombinasi kedua element tersebut digunakan dalam mencari data club. Sebisa mungkin kita membuat custom element <search-bar> sehingga   mempermudah kala menggunakan komponen tersebut.
Mari kita mulai dengan membuat berkas JavaScript baru dengan nama search-bar.js. Kemudian di dalamnya kita membuat class SearchBar dengan mewarisi sifat HTMLElement.

  1. class SearchBar extends HTMLElement {

  2.   

  3. }


Kemudian kita implementasi method connectedCallback dan membuat fungsi render.

  1. class SearchBar extends HTMLElement {

  2.    connectedCallback(){

  3.  

  4.    }

  5.   

  6.    render() {

  7.       

  8.    }

  9. }


Lalu panggil fungsi render() di dalam connectedCallback().

  1. class SearchBar extends HTMLElement {

  2.    connectedCallback(){

  3.        this.render();

  4.    }

  5.   

  6.    render() {

  7.  

  8.    }

  9. }


Di dalam fungsi render kita ambil elemen yang dibutuhkan untuk ditampilkan dari berkas index.html.

  1. <div id="search-container" class="search-container">

  2.    <input placeholder="Search football club" id="searchElement" type="search">

  3.     <button id="searchButtonElement" type="submit">Search</button>

  4. </div>


Agar mudah, copy seluruh kode tersebut dan paste untuk dijadikan nilai this.innerHTML di dalam fungsi render.

  1. class SearchBar extends HTMLElement {

  2.    connectedCallback(){

  3.        this.render();

  4.    }

  5.   

  6.    render() {

  7.        this.innerHTML = `

  8.        <div id="search-container" class="search-container">

  9.            <input placeholder="Search football club" id="searchElement" type="search">

  10.            <button id="searchButtonElement" type="submit">Search</button>

  11.        </div>

  12.        `;

  13.    }

  14. }


Karena di dalam elemen ini terdapat <button> yang harus memiliki sebuah event ketika ia ditekan, maka kita harus menyediakan setter. Gunanya untuk menetapkan fungsi event agar dapat mudah diterapkan dari luar class SearchBar.

  1. class SearchBar extends HTMLElement {

  2.    connectedCallback(){

  3.        this.render();

  4.    }

  5.   

  6.    set clickEvent(event) {

  7.        this._clickEvent = event;

  8.        this.render();

  9.    }

  10.  

  11.    render() {

  12.        this.innerHTML = `

  13.        <div id="search-container" class="search-container">

  14.            <input placeholder="Search football club" id="searchElement" type="search">

  15.            <button id="searchButtonElement" type="submit">Search</button>

  16.        </div>

  17.        `;

  18.    }

  19. }


Lalu kita terapkan this._clickEvent sebagai event pada element <button> dengan cara menuliskan kode berikut pada akhir fungsi render():

  1. this.querySelector("#searchButtonElement").addEventListener("click", this._clickEvent);


Sehingga kode pada fungsi render akan tampak seperti ini:

  1. render() {

  2.   this.innerHTML = `

  3.     <div id="search-container" class="search-container">

  4.        <input placeholder="Search football club" id="searchElement" type="search">

  5.        <button id="searchButtonElement" type="submit">Search</button>

  6.     </div>`;

  7.  

  8.    this.querySelector("#searchButtonElement").addEventListener("click", this._clickEvent);

  9. }


Dengan begitu nantinya kita dapat mudah dalam clickEvent pada SearchBar yang digunakan di berkas main.js.
Pada berkas main.js juga kita memanfaatkan value dari element <input> untuk mendapatkan kata kunci pencarian club. Agar mudah mendapatkan nilai value dari elemen <input> yang terdapat pada search bar, kita buat fungsi getter yang mengembalikan nilai value dari elemen <input> tersebut.

  1. get value() {

  2.    return this.querySelector("#searchElement").value;

  3. }


Sehingga keseluruhan kode yang terdapat berkas search-bar.js akan terlihat seperti ini:

  1. class SearchBar extends HTMLElement {

  2.    connectedCallback(){

  3.        this.render();

  4.    }

  5.   

  6.    set clickEvent(event) {

  7.        this._clickEvent = event;

  8.        this.render();

  9.    }

  10.  

  11.    get value() {

  12.        return this.querySelector("#searchElement").value;

  13.    }

  14.  

  15.    render() {

  16.        this.innerHTML = `

  17.        <div id="search-container" class="search-container">

  18.            <input placeholder="Search football club" id="searchElement" type="search">

  19.            <button id="searchButtonElement" type="submit">Search</button>

  20.        </div>

  21.        `;

  22.  

  23.        this.querySelector("#searchButtonElement").addEventListener("click", this._clickEvent);

  24.    }

  25. }


Lalu di akhir berkasnya, jangan lupa untuk definisikan custom element yang kita buat agar dapat digunakan pada DOM.

  1. class SearchBar extends HTMLElement {

  2.    connectedCallback(){

  3.        this.render();

  4.    }

  5.   

  6.    set clickEvent(event) {

  7.        this._clickEvent = event;

  8.        this.render();

  9.    }

  10.  

  11.    get value() {

  12.        return this.querySelector("#searchElement").value;

  13.    }

  14.  

  15.    render() {

  16.        this.innerHTML = `

  17.        <div id="search-container" class="search-container">

  18.            <input placeholder="Search football club" id="searchElement" type="search">

  19.            <button id="searchButtonElement" type="submit">Search</button>

  20.        </div>

  21.        `;

  22.  

  23.        this.querySelector("#searchButtonElement").addEventListener("click", this._clickEvent);

  24.    }

  25. }

  26.  

  27. customElements.define("search-bar", SearchBar);


Yeay, pembuatan custom element sudah selesai. Sekarang saatnya kita menggunakannya! Pertama ubahlah struktur html yang membentuk komponen pencarian dengan menggunakan tag <search-bar>. Silakan buka berkas index.html kemudian ubah kode berikut:

  1. <div id="search-container" class="search-container">

  2.     <input placeholder="Search football club" id="searchElement" type="search">

  3.     <button id="searchButtonElement" type="submit">Search</button>

  4. </div>


Menjadi seperti ini:

  1. <search-bar></search-bar>


Setelah itu, buka berkas src -> script -> view -> main.js dan sesuaikan kode binding elemen berikut:

  1. const searchElement = document.querySelector("#searchElement");


Dengan mengubah selector-nya menjadi “search-bar”.

  1. const searchElement = document.querySelector("search-bar");


Lalu kita tidak membutuhkan deklarasi variabel buttonSearchElement karena sekarang kita dapat mengakses button pada komponen pencarian melalui searchElement. Jadi silakan hapus deklarasi variabel berikut:

  1. const buttonSearchElement = document.querySelector("#searchButtonElement");


Kemudian kita sesuaikan kembali penerapan event click pada komponen pencarian dengan mengubah kode berikut:

  1. buttonSearchElement.addEventListener("click", onButtonSearchClicked);


Menjadi:

  1. searchElement.clickEvent = onButtonSearchClicked;


Terakhir, karena berkas main.js perlu kode pada berkas search-bar.js tereksekusi, kita lakukan impor berkas search-bar.js pada berkas main.js, seperti ini:

  1. import '../component/search-bar.js';


Tuliskan kode tersebut pada awal berkas main.js, sehingga keseluruhan kode pada berkasnya akan tampak seperti ini:

  1. import '../component/search-bar.js';

  2. import DataSource from '../data/data-source.js';

  3.  

  4. const main = () => {

  5.    const searchElement = document.querySelector("search-bar");

  6.    const clubListElement = document.querySelector("#clubList");

  7.  

  8.    const onButtonSearchClicked = async () => {

  9.        try {

  10.            const result = await DataSource.searchClub(searchElement.value);

  11.            renderResult(result);

  12.        } catch (message) {

  13.            fallbackResult(message)

  14.        }

  15.    };

  16.  

  17.    const renderResult = results => {

  18.        clubListElement.innerHTML = "";

  19.        results.forEach(club => {

  20.            const { name, fanArt, description } = club;

  21.            const clubElement = document.createElement("div");

  22.            clubElement.setAttribute("class", "club");

  23.  

  24.            clubElement.innerHTML = `

  25.                <img class="fan-art-club" src="${fanArt}" alt="Fan Art">

  26.                <div class="club-info">

  27.                    <h2>${name}</h2>

  28.                    <p>${description}</p>

  29.                </div>`;

  30.  

  31.            clubListElement.appendChild(clubElement);

  32.        })

  33.    };

  34.  

  35.    const fallbackResult = message => {

  36.        clubListElement.innerHTML = "";

  37.        clubListElement.innerHTML += `<h2 class="placeholder">${message}</h2>`;

  38.    };

  39.  

  40.    searchElement.clickEvent = onButtonSearchClicked;

  41. };

  42.  

  43. export default main;


Kemudian coba kita buka proyeknya menggunakan local server kemudian lakukan pencarian dengan menggunakan kata kunci “Arsenal”.  Hasilnya adalah tampilan berikut:
202003102009124b6eec15399d963670bbeffeecf30a42.png

Solution: Membuat club-list dan club-item Component

Custom element selanjutnya yang perlu kita buat adalah <club-list> dan <club-item>. Masih ingat mengenai Nested Custom Element? Nah dalam membuat kedua custom element ini kita akan menggunakan custom element di dalam custom element. Atau biasa disebut dengan nested custom element.
Mari kita awali dengan membuat dua berkas JavaScript baru dengan nama “club-list.js” dan “club-item.js” pada src -> script -> component

Membuat <club-list> element

Langkah pertama kita buat custom element <club-list> terlebih dahulu. Pada berkas club-list.js, kita buat class ClubList dengan mewarisi sifat HTMLElement.

  1. class ClubList extends HTMLElement {


  2. }


Kemudian kita buat 2 (dua) fungsi di dalamnya yaitu setter clubs, dan render.

  1. class ClubList extends HTMLElement {

  2.    set clubs(clubs) {

  3.  

  4.    }

  5.  

  6.    render() {

  7.       

  8.    }

  9. }


Fungsi set clubs digunakan untuk menetapkan properti this._clubs pada class ini. Nantinya properti tersebut akan digunakan pada fungsi render dalam membuat custom element <club-item>.

  1. set clubs(clubs) {

  2.      this._clubs = clubs;

  3.      this.render();

  4. }


Kemudian di dalam fungsi render, kita lakukan proses perulangan dengan menggunakan forEach pada this._clubs. Pada setiap iterasinya kita akan mendapatkan individual club dan pada saat itu juga kita buat custom element <club-item>. Pada tiap elemen <club-item> dibuat sebagai child dari element <club-list> ini. Hasilnya. fungsi render akan tampak seperti ini:

  1. render() {

  2.        this.innerHTML = "";

  3.        this._clubs.forEach(club => {

  4.            const clubItemElement = document.createElement("club-item");

  5.            clubItemElement.club = club

  6.            this.appendChild(clubItemElement);

  7.        })

  8. }


Perlu satu fungsi lagi pada custom element ini, yaitu fungsi untuk menangani ketika hasil pencarian mengalami kegagalan atau tidak ditemukkan. Maka dari itu mari kita buat fungsi dengan nama renderError() dengan satu buah parameter yang merupakan pesan eror/alasan yang perlu ditampilkan.

  1. renderError(message) {

  2.  

  3. }


Untuk template html yang akan ditampilkan, kita dapat copy dari fungsi fallbackResult pada berkas src -> script -> view -> main.js.

  1. clubListElement.innerHTML = "";

  2. clubListElement.innerHTML += `<h2 class="placeholder">${message}</h2>`;


Lalu paste pada fungsi renderError() dan ubah clubListElement.innerHTML menjadi this.innerHTML.

  1. renderError(message) {

  2.        this.innerHTML = "";

  3.        this.innerHTML += `<h2 class="placeholder">${message}</h2>`;

  4. }


Pada akhir berkas club-list.js jangan lupa untuk definisikan custom element yang kita buat agar dapat digunakan pada DOM.

  1. customElements.define("club-list", ClubList);


Oh ya! Karena pada berkas ini kita menggunakan elemen <club-item> yang nanti akan dituliskan pada berkas club-item.js, maka kita perlu melakukan impor berkas club-item.js di berkas ini.

  1. import './club-item.js';


Sehingga sekarang keseluruhan kode yang terdapat pada berkas ini akan tampak seperti ini:

  1. import './club-item.js';

  2.  

  3. class ClubList extends HTMLElement {

  4.    set clubs(clubs) {

  5.        this._clubs = clubs;

  6.        this.render();

  7.    }

  8.  

  9.    renderError(message) {

  10.        this.innerHTML = "";

  11.        this.innerHTML += `<h2 class="placeholder">${message}</h2>`;

  12.    }

  13.  

  14.    render() {

  15.        this.innerHTML = "";

  16.        this._clubs.forEach(club => {

  17.            const clubItemElement = document.createElement("club-item");

  18.            clubItemElement.club = club

  19.            this.appendChild(clubItemElement);

  20.        })

  21.    }

  22. }

  23.  

  24. customElements.define("club-list", ClubList);


Pembuatan element <club-list> selesai! Sekarang kita lanjut dengan membuat elemen <club-item>.

Membuat <club-item> element

Pada berkas club-item.js, kita buat class ClubItem dengan mewarisi sifat HTMLElement.

  1. class ClubItem extends HTMLElement {

  2.   

  3. }


Kemudian kita buat fungsi setter club dan fungsi render.

  1. class ClubItem extends HTMLElement {

  2.    set club(club) {

  3.  

  4.    }

  5.  

  6.    render() {

  7.       

  8.    }

  9. }


Fungsi setter club berfungsi untuk menetapkan nilai club ke properti this._club yang nantinya akan digunakan pada fungsi render untuk menampilkan data individual club hasil pencarian. Sehingga kita sesuaikan kode di dalam fungsi setter club menjadi seperti ini:

  1. class ClubItem extends HTMLElement {

  2.    set club(club) {

  3.        this._club = club;

  4.        this.render();

  5.    }

  6.  

  7.    render() {

  8.  

  9.    }

  10. }


Lalu kita copy template html yang berada pada fungsi renderResult di berkas src -> script -> view -> main.js.

  1. clubElement.innerHTML = `

  2.          <img class="fan-art-club" src="${fanArt}" alt="Fan Art">

  3.            <div class="club-info">

  4.               <h2>${name}</h2>

  5.               <p>${description}</p>

  6. </div>`;


Kemudian paste template html pada this.innerHTML melalui fungsi render().

  1. class ClubItem extends HTMLElement {

  2.    set club(club) {

  3.        this._club = club;

  4.        this.render();

  5.    }

  6.  

  7.    render() {

  8.        this.innerHTML = `

  9.            <img class="fan-art-club" src="${fanArt}" alt="Fan Art">

  10.            <div class="club-info">

  11.                <h2>${name}</h2>

  12.                <p>${description}</p>

  13.            </div>`;

  14.    }

  15. }


Lalu kita sesuaikan kembali properti-properti yang digunakan pada html template, menjadi seperti ini:

  1. class ClubItem extends HTMLElement {

  2.    set club(club) {

  3.        this._club = club;

  4.        this.render();

  5.    }

  6.  

  7.    render() {

  8.        this.innerHTML = `

  9.            <img class="fan-art-club" src="${this._club.fanArt}" alt="Fan Art">

  10.            <div class="club-info">

  11.                <h2>${this._club.name}</h2>

  12.                <p>${this._club.description}</p>

  13.            </div>`;

  14.    }

  15. }


Karena pada this._club inilah properti dari objek club disimpan.
Kemudian pada akhir berkas club-item.js jangan lupa untuk definisikan custom element yang kita buat agar dapat digunakan pada DOM.

  1. class ClubItem extends HTMLElement {

  2.    set club(club) {

  3.        this._club = club;

  4.        this.render();

  5.    }

  6.  

  7.    render() {

  8.        this.innerHTML = `

  9.            <img class="fan-art-club" src="${this._club.fanArt}" alt="Fan Art">

  10.            <div class="club-info">

  11.                <h2>${this._club.name}</h2>

  12.                <p>${this._club.description}</p>

  13.            </div>`;

  14.    }

  15. }

  16.  

  17. customElements.define("club-item", ClubItem);


Dengan begitu elemen <club-item> sudah siap digunakan.

Menggunakan <club-list> element

Setelah membuat kedua custom element yang dibutuhkan, sekarang saatnya kita menggunakannya!
Silakan buka berkas index.html, kemudian ubah penerapan club list menggunakan elemen <div> berikut:

  1. <div id="clubList"></div>


Menjadi:

  1. <club-list></club-list>


Selanjutnya buka berkas src -> script -> view -> main.js. Kita sesuaikan kembali selector pada saat melakukan binding clubListElement. Ubah kode berikut:

  1. const clubListElement = document.querySelector("#clubList");


Menjadi:

  1. const clubListElement = document.querySelector("club-list");


Lalu kita sesuaikan juga kode yang terdapat di dalam fungsi renderResult. Hapus seluruh logika yang ada di dalam fungsi tersebut.

  1. const renderResult = results => {

  2.        clubListElement.innerHTML = "";

  3.        results.forEach(club => {

  4.            const { name, fanArt, description } = club;

  5.            const clubElement = document.createElement("div");

  6.            clubElement.setAttribute("class", "club");

  7.  

  8.            clubElement.innerHTML = `

  9.                <img class="fan-art-club" src="${fanArt}" alt="Fan Art">

  10.                <div class="club-info">

  11.                    <h2>${name}</h2>

  12.                    <p>${description}</p>

  13.                </div>`;

  14.  

  15.            clubListElement.appendChild(clubElement);

  16.        })

  17. };


Kita cukup menggantinya dengan seperti ini:

  1. const renderResult = results => {

  2.      clubListElement.clubs = results;

  3. };


Sesuaikan juga kode yang terdapat pada fungsi fallbackResult, karena kita sudah membuat fungsi renderError() pada ClubList, maka penggunaanya cukup dilakukan seperti ini:

  1. const fallbackResult = message => {

  2.        clubListElement.renderError(message);

  3. };


Karena kita menggunakan elemen <club-list> pada berkas main.js, maka kita perlu melakukan impor berkas club-list.js pada berkas main.js.

  1. import '../component/club-list.js';


Dengan begitu keseluruhan kode pada berkas main.js akan tampak seperti berikut:

  1. import '../component/club-list.js';

  2. import '../component/search-bar.js';

  3. import DataSource from '../data/data-source.js';

  4.  

  5. const main = () => {

  6.    const searchElement = document.querySelector("search-bar");

  7.    const clubListElement = document.querySelector("club-list");

  8.  

  9.    const onButtonSearchClicked = async () => {

  10.        try {

  11.            const result = await DataSource.searchClub(searchElement.value);

  12.            renderResult(result);

  13.        } catch (message) {

  14.            fallbackResult(message)

  15.        }

  16.    };

  17.  

  18.    const renderResult = results => {

  19.        clubListElement.clubs = results;

  20.    };

  21.  

  22.    const fallbackResult = message => {

  23.        clubListElement.renderError(message);

  24.    };

  25.  

  26.    searchElement.clickEvent = onButtonSearchClicked;

  27. };

  28.  

  29. export default main;


Sekarang kita coba buka proyeknya menggunakan local server lalu tekan tombol pencarian. Voila, inilah tampilan hasilnya:
202003102031456392a6f8c00cbb02e2dc53ed846c7d2a.png
Ops, tampilan daftar club tampak berantakan. Kita perlu menyesuaikan styling-nya juga. Jadi silakan buka berkas src -> style -> clublist.css. Kemudian ubah seluruh selector #clubList menjadi club-list dan selector .club menjadi club-item.

  1. club-list {

  2.    margin-top: 32px;

  3.    width: 100%;

  4.    padding: 16px;

  5. }

  6.  

  7. club-list > .placeholder {

  8.    font-weight: lighter;

  9.    color: rgba(0,0,0,0.5);

  10.    -webkit-user-select: none;

  11.    -moz-user-select: none;

  12.    -ms-user-select: none;

  13.    user-select: none;

  14. }

  15.  

  16. club-item {

  17.    margin-bottom: 18px;

  18.    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);

  19.    border-radius: 10px;

  20.    overflow: hidden;

  21. }

  22.  

  23. club-item .fan-art-club {

  24.    width: 100%;

  25.    max-height: 300px;

  26.    object-fit: cover;

  27.    object-position: center;

  28. }

  29.  

  30. .club-info {

  31.    padding: 24px;

  32. }

  33.  

  34. .club-info > h2 {

  35.    font-weight: lighter;

  36. }

  37.  

  38. .club-info > p {

  39.    margin-top: 10px;

  40.    overflow: hidden;

  41.    text-overflow: ellipsis;

  42.    display: -webkit-box;

  43.    -webkit-box-orient: vertical;

  44.    -webkit-line-clamp: 10; /* number of lines to show */

  45. }


Kemudian tambahkan juga properti display dengan nilai block pada selector club-list dan club-item.

  1. club-list {

  2.    display: block;

  3.    ….

  4. }

  5.  

  6. ….

  7.  

  8. club-item {

  9.    display: block;

  10.    ….

  11. }

  12.  

  13. ….


Sehingga keseluruhan kode pada berkas clublist.css akan tampak seperti ini:

  1. club-list {

  2.    display: block;

  3.    margin-top: 32px;

  4.    width: 100%;

  5.    padding: 16px;

  6. }

  7.  

  8. club-list > .placeholder {

  9.    font-weight: lighter;

  10.    color: rgba(0,0,0,0.5);

  11.    -webkit-user-select: none;

  12.    -moz-user-select: none;

  13.    -ms-user-select: none;

  14.    user-select: none;

  15. }

  16.  

  17. club-item {

  18.    display: block;

  19.    margin-bottom: 18px;

  20.    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);

  21.    border-radius: 10px;

  22.    overflow: hidden;

  23. }

  24.  

  25. club-item .fan-art-club {

  26.    width: 100%;

  27.    max-height: 300px;

  28.    object-fit: cover;

  29.    object-position: center;

  30. }

  31.  

  32. .club-info {

  33.    padding: 24px;

  34. }

  35.  

  36. .club-info > h2 {

  37.    font-weight: lighter;

  38. }

  39.  

  40. .club-info > p {

  41.    margin-top: 10px;

  42.    overflow: hidden;

  43.    text-overflow: ellipsis;

  44.    display: -webkit-box;

  45.    -webkit-box-orient: vertical;

  46.    -webkit-line-clamp: 10; /* number of lines to show */

  47. }


Sekarang kita coba buka kembali proyek club finder dengan menggunakan local server. Seharusnya kini semuanya sudah berjalan dengan baik.
20200310203504226e0e544df5e8e4b9b389c9fa9f3f0c.png

Langkah dari ketiga solution ini bisa Anda temukan juga pada repository berikut:
https://github.com/dicodingacademy/a163-bfwd-labs/tree/109-club-finder-custom-element-solution

Shadow DOM

Menerapkan Shadow DOM pada Proyek Club Finder

Kita mulai dari <app-bar> component yuk. Pertama kita buka dulu proyek club finder dengan text editor yang kita gunakan.
20200313095904249af3ba1d687f1216c8dc44d5fa11fc.png
Kemudian buka berkas script -> component -> app-bar.js, buat constructor dari class tersebut dan di dalamnya kita tetapkan shadow root seperti ini:

  1. class AppBar extends HTMLElement {

  2.  

  3.    constructor() {

  4.        super();

  5.        this.shadowDOM = this.attachShadow({mode: "open"});

  6.    }

  7.  

  8.    connectedCallback(){

  9.        this.render();

  10.    }

  11.  

  12.    render() {

  13.        this.innerHTML = `<h2>Club Finder</h2>`;

  14.    }

  15. }

  16.  

  17. customElements.define("app-bar", AppBar);


Karena kita sudah menerapkan Shadow DOM pada AppBar, jangan lupa pada fungsi render(), kita ubah this.innerHTML menjadi this.shadowDOM.innerHTML.

  1. class AppBar extends HTMLElement {

  2.  

  3.    constructor() {

  4.        super();

  5.        this.shadowDOM = this.attachShadow({mode: "open"});

  6.    }

  7.  

  8.    connectedCallback(){

  9.        this.render();

  10.    }

  11.  

  12.    render() {

  13.        this.shadowDOM.innerHTML = `<h2>Club Finder</h2>`;

  14.    }

  15. }

  16.  

  17. customElements.define("app-bar", AppBar);


Kemudian buka berkas style -> appbar.css dan pindahkan (cut) seluruh kode yang ada pada berkas tersebut.

  1. app-bar {

  2.    display: block;

  3.    padding: 16px;

  4.    width: 100%;

  5.    background-color: cornflowerblue;

  6.    color: white;

  7.    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);

  8. }


Lalu tempel (paste) pada nilai this.shadowDOM.innerHTML dengan dibungkus oleh element <style> tepat sebelum element <h2> pada fungsi render() di berkas app-bar.js seperti ini:

  1. class AppBar extends HTMLElement {

  2.  

  3.    constructor() {

  4.        super();

  5.        this.shadowDOM = this.attachShadow({mode: "open"});

  6.    }

  7.  

  8.    connectedCallback(){

  9.        this.render();

  10.    }

  11.  

  12.    render() {

  13.        this.shadowDOM.innerHTML = `

  14.        <style>

  15.            app-bar {

  16.                display: block;

  17.                padding: 16px;

  18.                width: 100%;

  19.                background-color: cornflowerblue;

  20.                color: white;

  21.                box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);

  22.            }

  23.        </style>

  24.        <h2>Club Finder</h2>`;

  25.    }

  26. }

  27.  

  28. customElements.define("app-bar", AppBar);


Coba kita simpan perubahan yang diterapkan kemudian lihat perubahannya pada browser.
202003131025191a8dcdd75e697b30cba119d06defa3e1.png
Ups, pada browser kita dapat melihat title yang ditampilkan pada <app-bar> tampak berantakan. Untuk menanganinya, kita perlu menyesuaikan kembali style yang diterapkan pada custom element menjadi seperti ini:

  1. class AppBar extends HTMLElement {

  2.  

  3.    constructor() {

  4.        super();

  5.        this.shadowDOM = this.attachShadow({mode: "open"});

  6.    }

  7.  

  8.    connectedCallback(){

  9.        this.render();

  10.    }

  11.  

  12.    render() {

  13.        this.shadowDOM.innerHTML = `

  14.        <style>

  15.            * {

  16.                margin: 0;

  17.                padding: 0;

  18.                box-sizing: border-box;

  19.            }

  20.            :host {

  21.                display: block;

  22.                width: 100%;

  23.                background-color: cornflowerblue;

  24.                color: white;

  25.                box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);

  26.            }

  27.            h2 {

  28.                padding: 16px;

  29.            }

  30.        </style>

  31.        <h2>Club Finder</h2>`;

  32.    }

  33. }

  34.  

  35. customElements.define("app-bar", AppBar);


Pada perubahan styling tersebut kita menambahkan

  1. * {

  2.     margin: 0;

  3.     padding: 0;

  4.     box-sizing: border-box;

  5. }


Yang digunakan untuk menghilangkan seluruh margin dan padding standar yang diterapkan pada element html. Dan kita juga mengubah pengaturan box-sizing menjadi border-box.
Lalu kode pada kode styling lainnya juga kita melihat bahwa selector app-bar digantikan dengan :host. Apa itu :host? Selector