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).
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”).
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.
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 production, development 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:
- module.exports = {
- entry: './path/to/my/entry/file.js'
- };
Kode di atas merupakan cara cepat dalam penulisan properti entry. Sebenarnya entry dapat berupa objek seperti ini:
- module.exports = {
- entry: {
- main: './path/to/my/entry/file.js'
- }
- };
Kita bisa memanfaatkan objek sebagai nilai entry ketika terdapat banyak entry point yang ingin kita tetapkan.
- module.exports = {
- entry: {
- app: './src/app.js',
- adminApp: './src/adminApp.js'
- }
- }
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:
- const path = require('path');
- module.exports = {
- entry: './path/to/my/entry/file.js',
- output: {
- path: path.resolve(__dirname, 'dist'),
- filename: 'my-first-webpack.bundle.js'
- }
- };
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.
- module.exports = {
- entry: {
- app: './src/app.js',
- search: './src/search.js'
- },
- output: {
- filename: '[name].js',
- path: __dirname + '/dist'
- }
- };
- // 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.
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:
- module.exports = {
- module: {
- rules: [
- { test: /\.css$/, use: 'css-loader' }
- ]
- }
- };
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.
- 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:
- module.exports = {
- module: {
- rules: [
- {
- test: /\.css$/,
- use: [
- {
- loader: "style-loader"
- },
- {
- loader: "css-loader"
- }
- ]
- }
- ]
- }
- }
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:
- module.exports = {
- module: {
- rules: [
- {
- test: /\.css$/,
- use: [
- {
- loader: "style-loader",
- options: {
- // memasukkan style dengan tag <style> di bawah dari element <body>
- insert: "body"
- }
- },
- {
- loader: "css-loader"
- }
- ]
- }
- ]
- }
- }
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:
- const pluginName = 'ConsoleLogOnBuildWebpackPlugin';
- class ConsoleLogOnBuildWebpackPlugin {
- constructor(options) {
- this.options = options;
- }
- apply(compiler) {
- compiler.hooks.run.tap(pluginName, compilation => {
- console.log(this.options.message);
- });
- }
- }
- 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:
- const ConsoleLogOnBuildWebpackPlugin = require("./console-log-on-build-webpack-plugin.js");
- module.exports = {
- plugins: [
- new ConsoleLogOnBuildWebpackPlugin({
- message: "The webpack build process is starting!"
- })
- ]
- }
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:
- const webpack = require("webpack"); // dibutuhkan untuk mengakses built-in plugins
- module.exports = {
- plugins: [
- new webpack.ProgressPlugin()
- ]
- }
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:
- npm install html-webpack-plugin --save-dev
Setelah memasangnya kita dapat menggunakannya pada webpack configuration seperti ini:
- const HtmlWebpackPlugin = require('html-webpack-plugin'); //dipasang via npm
- module.exports = {
- plugins: [
- new HtmlWebpackPlugin({
- template: "./src/index.html",
- filename: "index.html"
- })
- ]
- };
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 development, production 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.
- module.exports = {
- mode: 'production'
- };
Nilai mode juga dapat kita tetapkan melalui CLI argument seperti berikut:
- 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 devtool, cache, atau properti development lainnya pada webpack configuration.
- module.export = {
- mode: 'development',
- devtool: 'eval',
- cache: 'true'
- }
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.
- const config = {
- entry: './app.js'
- //...
- };
- module.exports = (env, argv) => {
- if (argv.mode === 'development') {
- config.devtool = 'source-map';
- }
- if (argv.mode === 'production') {
- //...
- }
- return config;
- };
Atau kita dapat menggunakan flag --config pada scripts berkas package.json untuk menetapkan berkas webpack configuration yang berbeda pada tiap modenya.
- "scripts": {
- "build:prod": "webpack --config webpack.prod.js",
- "build:dev": "webpack --config webpack.dev.js",
- }
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:
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:
- npm install webpack --save-dev
- npm install webpack-cli --save-dev
Atau kita dapat menyingkat perintah tersebut dalam penulisan satu baris seperti ini:
- 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.
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:
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
- "start": "http-server .",
- "build": "webpack"
- },
Kita bisa menghapus script yang lainnya karena sudah tidak akan kita gunakan lagi. Sehingga sekarang berkas package.json akan tampak seperti ini:
- {
- "name": "webclock",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "build": "webpack"
- },
- "author": "",
- "license": "ISC",
- "dependencies": {
- "jquery": "^3.4.1",
- "moment": "^2.24.0"
- },
- "devDependencies": {
- "webpack": "^4.41.6",
- "webpack-cli": "^3.3.11"
- }
- }
Lalu untuk menjalankan script build, kita gunakan perintah berikut:
- npm run build
Namun untuk saat ini jika menjalankan script build akan terjadi eror seperti ini:
Dari pesan berikut, kita dapat menyimpulkan bahwa ketika kita tidak/belum menetapkan webpack configuration, nilai entry standarnya akan berlokasi pada src -> index.js.
- ERROR in Entry module not found: Error: Can't resolve './src' in 'C:\Users\Dicoding\Desktop\WebClock'
Untuk itu mari kita sesuaikan dengan membuat folder src dan memindahkan index.js pada folder tersebut. Sehingga struktur proyeknya akan tampak seperti ini:
Lalu coba kita jalankan kembali script build, maka hasilnya akan tampak seperti ini:
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.
Karena kita sudah menggunakan webpack untuk membundel module. Kita dapat menggunakan perintah import pada src -> index.js dalam menggunakan package npm.
- import $ from "jquery";
- import moment from "moment";
- const displayTime = () => {
- moment.locale("id");
- $(".time").text(moment().format("LTS"));
- $(".date").text(moment().format("LL"));
- };
- const updateTime = () => {
- displayTime();
- setTimeout(updateTime, 1000)
- };
- updateTime();
Kemudian pada index.html, kita dapat menggantikan seluruh tag <script> yang ada dengan satu tag <script> yang ditujukan pada dist -> main.js.
- <!DOCTYPE html>
- <html>
- <head>
- <title>Clock Web</title>
- <link rel="stylesheet" href="style.css">
- </head>
- <body>
- <div class="clock">
- <span class="time"></span>
- <span class="date"></span>
- </div>
- <script src="./dist/main.js"></script>
- </body>
- </html>
Lalu kita build ulang proyek dengan menjalankan perintah:
- 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.
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.
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 entry, output, loader, plugins 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.
- const path = require("path");
- module.exports = {
- entry: "./src/index.js",
- output: {
- path: path.resolve(__dirname, "dist"),
- filename: "bundle.js"
- }
- }
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.
- npm run build
Maka akan terdapat berkas bundel baru bernama bundle.js.
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.
- <!DOCTYPE html>
- <html>
- <head>
- <title>Clock Web</title>
- <link rel="stylesheet" href="style.css">
- </head>
- <body>
- <div class="clock">
- <span class="time"></span>
- <span class="date"></span>
- </div>
- <script src="./dist/bundle.js"></script>
- </body>
- </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.
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.
- const path = require("path");
- module.exports = {
- entry: "./src/index.js",
- output: {
- path: path.resolve(__dirname, "dist"),
- filename: "bundle.js"
- },
- mode: "production"
- }
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-loader. css-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:
- 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:
- const path = require("path");
- module.exports = {
- entry: "./src/index.js",
- output: {
- path: path.resolve(__dirname, "dist"),
- filename: "bundle.js"
- },
- mode: "production",
- module: {
- rules: [
- {
- test: /\.css$/,
- use: [
- {
- loader: "style-loader"
- },
- {
- loader: "css-loader"
- }
- ]
- }
- ]
- }
- }
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:
Lalu pada berkas index.js, lakukan impor berkas style.css pada awal baris kodenya.
- import "./style/style.css";
Maka keseluruhan kode pada index.js akan tampak seperti ini:
- import "./style/style.css";
- import $ from "jquery";
- import moment from "moment";
- const displayTime = () => {
- moment.locale("id");
- $(".time").text(moment().format("LTS"));
- $(".date").text(moment().format("LL"));
- };
- const updateTime = () => {
- displayTime();
- setTimeout(updateTime, 1000)
- };
- updateTime();
Dengan begitu kita tidak membutuhkan lagi tag <link> pada index.html dalam melampirkan stylesheet pada berkas style.css. Kita hapus tag <link> berikut:
- <link rel="stylesheet" href="style.css">
Sehingga keseluruhan kode pada index.html akan tampak seperti ini:
- <!DOCTYPE html>
- <html>
- <head>
- <title>Clock Web</title>
- </head>
- <body>
- <div class="clock">
- <span class="time"></span>
- <span class="date"></span>
- </div>
- <script src="./dist/bundle.js"></script>
- </body>
- </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.
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.
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.
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.
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.
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.
- 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:
- {
- "name": "webclock",
- "version": "1.0.0",
- "description": "",
- "main": "index.js",
- "scripts": {
- "build": "webpack"
- },
- "author": "",
- "license": "ISC",
- "dependencies": {
- "jquery": "^3.4.1",
- "moment": "^2.24.0"
- },
- "devDependencies": {
- "@babel/core": "^7.8.4",
- "@babel/preset-env": "^7.8.4",
- "babel-loader": "^8.0.6",
- "css-loader": "^3.4.2",
- "style-loader": "^1.1.3",
- "webpack": "^4.41.6",
- "webpack-cli": "^3.3.11"
- }
- }
Setelah berhasil memasang ketiga package tersebut, langkah selanjutnya kita dapat gunakan babel-loader dan preset-nya pada webpack configuration.
- const path = require("path");
- module.exports = {
- entry: "./src/index.js",
- output: {
- path: path.resolve(__dirname, "dist"),
- filename: "bundle.js"
- },
- mode: "production",
- module: {
- rules: [
- /* style and css loader */
- {
- test: /\.css$/,
- use: [
- {
- loader: "style-loader"
- },
- {
- loader: "css-loader"
- }
- ]
- },
- /* babel loader */
- {
- test: /\.js$/,
- exclude: "/node_modules/",
- use: [
- {
- loader: "babel-loader",
- options: {
- presets: ["@babel/preset-env"]
- }
- }
- ]
- }
- ]
- }
- }
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.
Bahkan pada berkas bundle tersebut dipastikan sudah tidak terdapat lagi sintaks yang dituliskan menggunakan ES6.
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.
- npm install webpack-dev-server --save-dev
Setelah berhasil memasangnya, kita tambahkan script start-dev dengan perintah “webpack-dev-server” pada package.json.
- "scripts": {
- "build": "webpack",
- "start-dev": "webpack-dev-server"
- }
Mari kita jalankan script start-dev dengan perintah:
- 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.
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.
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.
Namun jika berpatokan pada bagan di atas, antara keduanya terdapat konfigurasi umum (common) seperti entry, output, style-loader, css-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.
Using webpack-merge
Untuk menggunakan webpack-merge langkah awal adalah dengan memasang package tersebut pada devDependencies menggunakan NPM.
- npm install webpack-merge --save-dev
Kemudian kita buat berkas webpack konfigurasi baru dengan nama webpack.common.js.
Di dalam berkas tersebut kita tuliskan konfigurasi umum yang digunakan pada setiap environment baik itu production atau development.
- const path = require("path");
- const HtmlWebpackPlugin = require("html-webpack-plugin");
- module.exports = {
- entry: "./src/index.js",
- output: {
- path: path.resolve(__dirname, "dist"),
- filename: "bundle.js"
- },
- module: {
- rules: [
- /* style and css loader */
- {
- test: /\.css$/,
- use: [
- {
- loader: "style-loader"
- },
- {
- loader: "css-loader"
- }
- ]
- }
- ]
- },
- /* plugin */
- plugins: [
- /* HTML Webpack Plugin */
- new HtmlWebpackPlugin({
- template: "./src/template.html",
- filename: "index.html"
- })
- ]
- }
Kemudian kita buat 2 (dua) berkas webpack configuration baru dengan nama webpack.prod.js dan webpack.dev.js.
Kemudian pada masing-masing berkasnya, tuliskan kode berikut:
- const merge = require("webpack-merge");
- const common = require("./webpack.common.js");
- module.exports = merge(common, {
- mode: "production",
- module: {
- rules: [
- /* babel loader */
- {
- test: /\.js$/,
- exclude: "/node_modules/",
- use: [
- {
- loader: "babel-loader",
- options: {
- presets: ["@babel/preset-env"]
- }
- }
- ]
- }
- ]
- }
- })
- const merge = require("webpack-merge");
- const common = require("./webpack.common.js");
- module.exports = merge(common, {
- mode: "development",
- })
Di dalam berkas webpack.common.js kita sudah menetapkan nilai entry, output 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.
- 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:
- "scripts": {
- "build": "webpack --config webpack.prod.js",
- "start-dev": "webpack-dev-server --config webpack.dev.js"
- }
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.
Coba kita jalankan kembali script start-dev ya. Seharusnya fitur live-reloading akan berjalan lebih cepat.