Webpack Nedir? Webpack’e Detaylı Bir Bakış

Webpack nedir?

Önceki yazımda JavaScript’in varsayılan olarak sunduğu modules kavramından bahsetmiştim. Bu yazımda da sizlere Webpack ile bir web uygulaması nasıl geliştirilir ona değineceğiz.

Halihazırda web uygulaması geliştiriyorsanız Angular, Vue ve React gibi pek çok modern JavaScript geliştirme platformlarında Webpack’e rast gelmişsinizdir. Webpack, adından da bir web paketleyicisi olduğu anlaşılabileceği gibi, en temel haliyle ele aldığımızda, modern JavaScript uygulamaları için üretilen bir module bundler’dır (modül paketleyicisidir) diyebiliriz. Webpack, bir projede çalıştırıldığında, projenin ihtiyaç duyabileceği her modül tipini alan bir dependency graph (bağımlılık grafiği) oluşturur ve bu grafiğin işlenmesi sonucu çıktı olarak bir uygulama paketi üretir.

Modül nedir?

Modül kavramı, sadedece JavaScript’e özgü olmamakla birlikte, bir yazılımın işlev bazında kendi başına çalışabileceği parçalara bölündüğünde, oluşan her bir parça modül olarak nitelendirilir. Her modül bütün bir programın özel bir fonksiyonundan sorumludur. Bu sayede code-splitting gibi kod ayırma teknikleriyle uygulamanın daha performanslı çalışabileceği gibi, aynı zamanda da modüller sayesinde uygulamanın test edilmesi ve bakımı da daha kolay hale gelmektedir. Node.js, neredeyse kuruluşundan beri modüler programlamayı desteklese de, Web’de varsayılan olarak modüler yapıların desteklenmesi ise Node.js’ten çok daha sonra gerçekleşmiştir. Bu süre zarfında modüler JavaScript programlamayı gerçekleştirmek için pek çok farklı araç ortaya çıkmıştır. Bu araçlardan edinilen deneyimleri bünyesine katan Webpack, herhangi bir projede modüler yapının oluşmasına olanak sağlamaktadır.

Javada modüler programlama kullanımına örnek – Kaynak: Oreilly

Webpack modülü nedir?

Node.js modüllerinin aksine, webpack modüllerinde modüller arası bağımlılıklar farklı şekillerde ifade edilebilir. Örneğin:

  • ES2015’teki import ifadesi,
  • CommonJS’teki require() ifadesi,
  • AMD’deki define ve require ifadeleri
  • CSS/SASS/LESS dosyalarındaki @importifadesi,
  • CSS’te görsel yükleme için oluşturulan oluşturulan url(...) fonksiyonu ve HTML’deki <img src=...> ifadesi ile modül bağımlılığı sağlanabilir.

Desteklenen Modül tipleri

Webpack, varsayılan olarak aşağıdaki modül tiplerini desteklemektedir:

  • ECMAScript modülleri
  • CommonJS modülleri
  • AMD modülleri
  • Asset’ler (font, ikon vb.)
  • WebAssembly modülleri

Bu modüllere ek olarak Webpack, loader’lar aracılığıyla JavaScript haricindeki diğer dilleri ve babel gibi preprocessor’leri de desteklemektedir. Loader’lar, webpack’te varsayılan olarak bulunmayan diğer modüllerin nasıl işleneceğini ve nihai olarak nasıl uygulamaya dahil edileceğini belirtir. Webpack topluluğu, pek çok farklı dil ve dil işleyicileri için loader’lar oluşturmuştur. Bu loader’lara örnek verecek olursak:

  • CoffeeScript
  • TypeScript
  • ESNext (Babel)
  • Sass
  • Less
  • Stylus
  • Elm
  • Ek olarak pek çok farklı loader da bulunmaktadır.

Webpack bu sayede güçlü ve zengin bir API sunarak herhangi bir stack’te bağımsız bir uygulama geliştirimine olanak sağlar.

Webpack’teki temel kavramlar

Webpack’te, bir projeyi bundle haline getirmek için Webpack’in 4.0.0 sürümünden beri bir config dosyası oluşturmaya gerek kalmasa da, kendi yapılandırma dosyanızı oluşturmak istediğinizde webpack’in temel kavramlarına aşina olmanız gereklidir. Bunlar:

  • Entry (başlangıç noktası)
  • Output (çıktı dosyası)
  • Loaders (yükleyiciler)
  • Plugins (eklentiler)
  • Mode (PROD ve DEV modları)
  • Browser Compatibility (tarayıcı uyumluluğu) şeklindedir.

Şimdi bu kavramlara kısaca değinelim.

Entry (başlangıç dosyası)

entry özelliği sayesinde Webpack’e, projenin bağımlılık grafiğini nereden başlayarak oluşturması gerektiği bildirilir. Varsayılan değeri ./src/index.js olan entry özelliği aşağıdaki gibi de değiştirilebilir:

// webpack.config.js
module.exports = {
  entry: './path/to/my/entry/file.js'
};

Output (çıktı dosyası)

output özelliği ile, projenin Webpack tarafından işlenmesi sonucu oluşturulan bundle paketinin nereye konulacağını ve paket adının nasıl isimlendirileceği belirtilir. output özelliği, varsayılan olarak ./dist/main.js değerini içermekle birlikte, üretilen diğer dosyaların da ./dist dizini içerisine konulmasını sağlar. output özelliği aşağıdaki gibi yapılandırılabilir:

// webpack.config.js
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'
  }
};

path özelliği sayesinde oluşturulan dosyaların atılacağı dizini, filename sayesinde de dosyanın adı belirtilmektedir. Yukarıda require() fonksiyonu ile alınan path modülü ise, Node.js’in temel modüllerinden biridir ve dosya path’lerinin ayarlanması için kullanılmaktadır.

Loaders (yükleyiciler)

Webpack varsayılan olarak sadece JavaScript ve JSON dosyalarını işleyebilmektedir. Loader’lar sayesinde, projenin bağımlılıkları arasına dahil edilen diğer dosya türleri de işlenebilmekte ve tarayıcının anlayabileceği modüller haline dönüştürülebilmektedir.

Loader’lar en basit haliyle iki özelliği içerir:

  1. test özelliğine regex olarak bir ifade verilir. Böylece o regex’e dahil olan tüm dosyalar ilgili loader tarafından ele alınır.
  2. use özelliği ile hangi loader’ın bu dosyaları dönüştüreceği belirtilir. Örnek bir loader kullanımı aşağıdaki gibidir:
// webpack.config.js
const path = require('path');

module.exports = {
  output: {
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' }
    ]
  }
};

Yukarıdaki config dosyasında rules özelliği ile, bir modül için test ve use isminde iki property tanımlanmıştır. Webpack bu config sayesinde, proje içerisinde import/require ifadesinde dahil edilen txt dosyalarını raw-loader ile dönüştürerek bundle’a ekler.

Plugin’ler (eklentiler)

Loader’lar çeşitli modül tiplerinin kullanılabilmesine yardımcı olurken, plugin’ler ise; bundle optimizasyonu, asset yönetimi ve PROD/DEV gibi environment değişkenlerinin kod içerisine inject edilmesi gibi pek çok görevin yerine getirilmesinde rol almaktadır. Bir plugin’in kullanılabilmesi için, require() fonksiyonu ile config içerisine dahil edilmesi ve plugins array’ine eklenmesi gereklidir. Ayrıca plugin’leri konfigüre etmek için kendilerine özel parametreleri de bulunmaktadır. Bir plugin, config dosyasında birden fazla yerde, farklı amaçlar doğrultusunda kullanılabileceği için, new operatörü ile bir instance’ının oluşturarak kullanılması gereklidir. Basitçe bir plugin kullanımı aşağıdaki gibidir:

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = {
  entry: 'index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index_bundle.js'
  },
  plugins: [new HtmlWebpackPlugin({template: './src/index.html'})]
};

Bu örnekte kullanılan html-webpack-plugin sayesinde, webpack tarafından oluşturulan tüm bundle’lar, yeni bir index.html dosyası oluşturularak içerisine inject edilecektir:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack App</title>
  </head>
  <body>
    <script src="index_bundle.js"></script>
  </body>
</html>

Mode

Webpack’in varsayılan olarak uyguladığı optimizasyonları aktif hale getirmek için kullanılan mode parametresi; productiondevelopment veya none olarak ayarlanabilir. Varsayılan değeri production olmakla birlikte aşağıdaki gibi yapılandırılabilir:

// webpack.config.js
module.exports = {
  mode: 'production'
};

mode‘un anlamını daha iyi kavramak için bir örnek vermek gerekirse, Webpack’te mode özelliği olmasaydı, üstteki tek satırlık ifadeyi belirtmek için aşağıdaki gibi ayarlamalar eklememiz gerekecekti:

// webpack.production.config.js
module.exports = {
 performance: {
   hints: 'warning'
 },
 output: {
   pathinfo: false
 },
 optimization: {
   moduleIds: 'deterministic',
   chunkIds: 'deterministic',
   mangleExports: 'deterministic',
   nodeEnv: 'production',
   flagIncludedChunks: true,
   occurrenceOrder: true,
   concatenateModules: true,
   splitChunks: {
     hidePathInfo: true,
     minSize: 30000,
     maxAsyncRequests: 5,
     maxInitialRequests: 3,
   },
   emitOnErrors: false,
   checkWasmTypes: true,
   minimize: true,
 },
 plugins: [
   new TerserPlugin(/* ... */),
   new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
   new webpack.optimize.ModuleConcatenationPlugin(),
   new webpack.NoEmitOnErrorsPlugin()
 ]
}

Burada da fark edileceği gibi normalde elle ayarlanması gereken pek çok kısım Webpack’te mode özelliği ile varsayılan olarak sunulmaktadır.

Tarayıcı uyumluluğu

Webpack, ES5 uyumlu olan tüm tarayıcıları desteklemektedir (IE8 ve altında desteği yoktur). Bunun nedeni ise import ifadeleri için Webpack’in Promise‘e olan ihtiyacından dolayı kaynaklanmaktadır. Daha eski tarayıcıların desteklenmesi için import ifadesinin kullanımından önce bir polyfill yüklenmesi gereklidir.

Webpack kullanımı

Webpack’in kullanımını pekiştirmek için bir proje üzerinde deneyerek ilerlemek faydalı olacaktır.

Temel kurulum

Öncelikle webpack-demo adında bir dizin oluşturarak projemize başlayalım:

mkdir webpack-demo
cd webpack-demo
npm init -y
npm install webpack webpack-cli --save-dev

Üstteki komut ile, webpack-demo dizini içerisinde package.json dosyası oluşturularak, Webpack komutlarının terminalde çalıştırılabileceği Webpack CLI bağımlılığı yüklenmektedir.

Şimdi src/index.js dosyasını oluşturalım ve içerisinde lodash kütüphanesini kullanalım:

function component() {
  const element = document.createElement('div');

  // Lodash, script olarak eklendiğinden dolayı bu satırın çalışması için,
  //   lodash'in daha önceden yüklenmiş olması gereklidir.
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');

  return element;
}

document.body.appendChild(component());

Şimdi bu dosyayı kullanacak olan index.html dosyasını oluşturalım:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Getting Started</title>
    <script src="https://unpkg.com/lodash@4.17.20"></script>
  </head>
  <body>
    <script src="./src/index.js"></script>
  </body>
</html>

Ayrıca package.json dosyasında yer alan "main": "index.js" kısmını kaldıralım ve "private": true, özelliğini ekleyelim. Bu sayede yanlışlıkla proje paketinin publish edilmesi durumu önlenmiş olacaktır. package.json dosyasının son hali aşağıdaki gibi olmalıdır:

{
   "name": "webpack-demo",
   "version": "1.0.0",
   "description": "",
   "private": true,
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1"
   },
   "keywords": [],
   "author": "",
   "license": "ISC",
   "devDependencies": {
     "webpack": "^5.4.0",
     "webpack-cli": "^4.2.0"
   }
}

Bu örnekte <script> etiketleri içerisinde proje bağımlılıklarını açık bir şekilde ekledik. index.js dosyasının çalışabilmesi için, sayfanın yüklenmesinden önce lodash’in yüklenmesine ihtiyacı vardır. Çünkü index.js dosyası lodash ile ilgili açıkça bir gereksinimi olduğunu belirtmemektedir ve sadece _ isimli bir global değişkenin olduğunu varsaymaktadır.

JavaScript projelerini bu şekilde yönetmenin yol açtığı birkaç problem vardır:

  • Yazılan kodun harici bir kütüphaneye ihtiyacı olduğu ilk bakışta belli değildir.
  • Projenin bağımlı olduğu bir kütüphane ekli değilse veya farklı sırada eklenmişse, uygulama doğru bir şekilde çalışmayacaktır.
  • Eğer bir bağımlılık eklendiği halde kullanılmasa bile tarayıcı gereksiz kodları indirmek durumunda kalacaktır. Şimdi bunun yerine webpack’i kullanalım.

Bundle’ın oluşturulması

Projenin kaynak kodu ile üretilen bundle paketinin kodunu birbirinden ayırmak için dist adında bir dizin oluşturalım ve index.html‘i bu dizine taşıyalım. Projenin dizin yapısı aşağıdaki gibi olacaktır:

webpack-demo
|- package.json
|- /dist
  |- index.html
|- /src
  |- index.js

index.js‘in bağımlılığı olan lodash kütüphanesini npm ile indirelim:

npm install --save lodash

lodash kütüphanesini index.js içerisinde import edelim:

import _ from 'lodash';

function component() {
  const element = document.createElement('div');

  // Lodash, now imported by this script
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');

  return element;
}

document.body.appendChild(component());

Kodu bundle haline getireceğimiz için index.html‘i buna göre değiştirmeye başlayalım. lodash’in bulunduğu <script> etiketini silelim ve src/index.js‘in olduğu kısma da üretilecek olan main.js‘i ekleyelim. index.html dosyasının son hali aşağıdaki gibi olacaktır:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Getting Started</title>
  </head>
  <body>
   <script src="main.js"></script>
  </body>
</html>

Oluşturduğumuz bu yapı ile artık index.js açıkça lodash kütüphanesinin eklenmesini bekliyor ve _ değişkeni olarak kullanıyor. Böylece global değişkeni kirletmemiş oluyor. Modülün ihtiyaç duyduğu bağımlılıkları belirtilmesi ile, webpack bu bilgiyi alarak bir bağımlılık grafiği oluşturur. Sonra bu grafiği kullanarak kodların doğru sırada çalıştırılacağı optimize bir bundle üretir. Şimdi src/index.js dosyasını girdi olarak alması ve sonucunda dist/main.js dosyasını üretmesi için npx webpack komutunu çalıştıralım.

$ npx webpack
asset main.js 69.3 KiB [emitted] [minimized] (name: main) 1 related asset
runtime modules 1010 bytes 5 modules
cacheable modules 530 KiB
  ./src/index.js 265 bytes [built] [code generated]
  ./node_modules/lodash/lodash.js 530 KiB [built] [code generated]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.20.0 compiled with 1 warning in 1817 ms
  • Not: Buradaki uyarı, webpack yapılandırmasının henüz ayarlanmadığı için varsayılan olarak ‘production’ ayarlandığını bildiriyor. Bilgilendirme amaçlı olduğu için bu uyarıyı şimdilik göz ardı edebilirsiniz.

Şimdi dist/index.html‘i tarayıcınızda açtığınızda “Hello webpack” yazısını göreceksiniz.

open dist/index.html
Getting Started 2021-02-04 11-51-07

Webpack’in yapılandırılması

Az önce aldığımız uyarı metninde de gördüğümüz şekilde production ortamı ve diğer ayarlamaların yapılması için bir webpack.config.js dosyasına ihtiyacımız vardır. Şimdi bu dosyayı package.json ile aynı yerde oluşturalım ve içerisine mode’u ekleyelim:

module.exports = {
  mode: 'production'
};

Şimdi tekrar npx webpack komutunu çalıştırdığımızda uyarıyı görmüyor olacağız:

$ npx webpack
asset main.js 69.3 KiB [compared for emit] [minimized] (name: main) 1 related asset
runtime modules 1010 bytes 5 modules
cacheable modules 530 KiB
  ./src/index.js 265 bytes [built] [code generated]
  ./node_modules/lodash/lodash.js 530 KiB [built] [code generated]
webpack 5.20.0 compiled successfully in 1746 ms

Webpack’te mode özelliğinin yanında entry ve output parametrelerini de aşağıdaki gibi değiştirebiliriz:

const path = require("path");

module.exports = {
    mode: 'production',
    entry: { index: path.resolve(__dirname, "source", "index.js") },
    output: {
        path: path.resolve(__dirname, "build"),
        filename: 'main.js'
    }
};

Bu değişiklik sayesinde webpack, giriş noktası olarak source/index.js dosyasını baz alacak ve çıktıyı da build/main.js dosyası olarak oluşturacaktır.

Şimdilik entry ve output ayarlarında bir değişiklik yapmayalım (yaptıysak silelim) ve varsayılan değerleri kullanarak devam edelim.

HTML dosyasının src dizinine taşınması (html-webpack-plugin kullanımı)

dist dizinine attığımız index.html dosyasını src dizininden üreterek kullanmak için html-webpack-plugin’i yükleyelim:

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

Yükleme işlemi tamamlandıktan sonra webpack.config.js dosyasını aşağıdaki gibi değiştirelim:

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

module.exports = {
    mode: 'production',
    plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, "src", "index.html")
        })
    ]
};

Şimdi html-webpack plugin’in varsayılan olarak nasıl çalıştığını görebilmek için src dizini içerisine index.html dosyasını aşağıdaki gibi sadece yorum satırı olacak şekilde oluşturalım:

<!-- index.html -->

Şimdi npx webpack komutunu çalıştırdığınızda dist içerisinde index.html dosyası aşağıdaki gibi görüntülenecektir:

<head><script defer="defer" src="main.js"></script></head>

Buradan da farkedilebileceği gibi, index.html dosyası sadece yorum satırı içeren boş bir html dosyası olsa da html-webpack-plugin’i bu dosyayı alıp, yorum satırlarını silerek içerisine <head> ve <script> etiketlerini varsayılan olarak eklemektedir. Şimdi src/index.html dosyasını aşağıdaki gibi değiştirerek nasıl bir sonuç üretiyor görelim:

<!DOCTYPE html>
<html lang="tr">
  <head>
    <meta charset="UTF-8" />
    <title>Merhaba dünya</title>
  </head>
  <body>
  	<span>Merhaba</span>
  </body>
</html>

npx webpack komutunu çalıştırdığınızda dist/index.html içerisinde aşağıdaki gibi çıktı verdiğini göreceksiniz:

<!doctype html><html lang="tr"><head><meta charset="UTF-8"/><title>Merhaba dünya</title><script defer="defer" src="main.js"></script></head><body><span>Merhaba</span></body></html>

Buradan da fark edilebileceği gibi webpack, src/index.html dosyası içerisinde sadece oluşturulan main.js dosyasını inject etti ve bunun haricinde kendi koyduğumuz elemanları aynen korumuş oldu.

webpack-dev-server ile local sunucu üzerinde geliştirim yapma

Webpack ile geliştirim yaparken sürekli npx webpack komutunu çalıştırmak yorucu olabilir. Bu işi otomatik hale getirmek için webpack-dev-server metodunu kullanabiliriz. Aşağıdaki npm komutunu aşağıdaki gibi çalıştıralım:

npm install webpack-dev-server --save-dev

Paket yüklendikten sonra, package.json dosyası içerisinde scripts değerini aşağıdaki gibi değiştirebiliriz:

"scripts": {
   "build:prod": "webpack --mode production",
   "build:dev": "webpack --mode development",
   "start": "webpack serve --open 'Google Chrome'",
},

Bu sayede npm start komutunu çalıştırdığınızda webpack, ilgili script’lerden bundle’ı oluşturacak ve bunu local server’da çalıştırarak Google Chrome’u açacak, böylece proje kodunu değiştirip kaydettiğinizde otomatik olarak web tarayıcısında da çıktısı değiştirilecektir. Ek olarak, webpack’in otomatik bir şekilde değişikliklerin tarayıcıya yansıtılması işlemini websocket üzerinden yürüttüğünü de belirtelim.

CSS’in import edilmesi

Webpack varsayılan olarak Javascript kodunu import edebildiği için CSS import edilme işleminde loader kullanmamız gerekiyor. Bunun için iki ayrı loader’a ihtiyacımız var. Bu loader’ları aşağıdaki gibi yükleyelim

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

Buradaki paketlerin ne işe yaradığına değinmemiz gerekirse:

  • css-loader: CSS dosyalarındaki import ifadelerini çözümleyerek, import edilen dosyalar içerisinden alınan css kodlarının ham halde elde edilmesini sağlar.
  • style-loadercss-loader‘dan elde edilen kodların sayfa içerisine <style> etiketleri arasında eklenmesini sağlar. Bu iki paket farklı işlemleri yerine getirse de genellikle bir arada kullanılmaktadır. Şimdi yüklenen loader’ları webpack.config.js dosyası içerisine ekleyelim:
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");

module.exports = {
    mode: 'production',
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ["style-loader", "css-loader"]
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, "src", "index.html")
        })
    ]
};

Şimdi bu loader’ı kullanabilmek amacıyla src/style.css olarak küçük bir CSS dosyası oluşturalım.

span {
    color: red;
}

Daha sonra src/index.js dosyasını sadeleştirelim ve bu CSS dosyasını içerisinde import edecek şekilde aşağıdaki gibi değiştelim:

import "./style.css";
console.log("Merhaba dünya!");

Artık projeyi çalıştırabiliriz:

npm start

Geliştirici seçeneklerini açtığınızda da görebileceğiniz gibi javascript tarafından import edilen CSS dosyası, Webpack tarafından HTML koduna da inject edilmektedir:

Merhaba dünya 2021-02-04 15-48-43

CSS’in HTML koduna direkt olarak inject edilmesi yerine ayrı dosyalar halinde üretilmesi için MiniCssExtractPlugin de kullanılabilir. Bunun için aşağıdaki gibi ekleyelim:

npm install --save-dev mini-css-extract-plugin

webpack.config.js dosyasını aşağıdaki gibi değiştirelim:

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');


module.exports = {
    mode: 'production',
    module: {
        rules: [
            {
                test: /\.css$/,
                //use: ["style-loader", "css-loader"]
                use: [MiniCssExtractPlugin.loader, 'css-loader'],
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, "src", "index.html")
        }),
        new MiniCssExtractPlugin()
    ]
};

npm start komutunu çalıştırdığınızda, CSS kodunuzun aşağıdaki gibi main.css olarak sayfa içerisine eklendiğini görebilirsiniz:

Merhaba dünya 2021-02-04 16-19-18
  • Dipnot: Proje içerisine dahil edilen loader’ların config dosyasında array içerisinde eklenme sırası önemlidir. Webpack, eklenen loader’ları solddan sağa doğru (başka bir deyişle array index’ine göre) kullandığı için, yanlış sırada eklenen loader’lardan dolayı hata oluşacaktır.

Eski tarayıcılarla uyumlu javascript kodunun oluşturulması

JavaScript’in yeni nesil özelliklerinin eski tarayıcılarda da kullanılabilmesi için babel ile birlikte dönüştürülmesi gereklidir. Babel, bir Javascript derleyicisi ve dönüştürücüsüdür. Girdi olarak modern JavaScript özellikleri ile yazılmış kodu alır ve neredeyse her tarayıcıda çalıştırılabilecek formata (ES5 koduna) dönüştürür. Babel’ın webpack’te kullanılabilmesi için aşağıdaki paketlerin yüklenmesi gereklidir:

  • @babel/core: Babel’ın temel kod dönüştürme fonksiyonlarını barındırır.
  • babel-loader: webpack’in babel’ı kullanması için olan adaptör bileşeni.
  • @babel/preset-env: Modern JavaScript kodunun ES5 karşılıklarına dönüştürülebilmesi için gereklidir. Şimdi bu bağımlılıkları yükleyelim:
npm install @babel/core babel-loader @babel/preset-env --save-dev

Paketler yüklendikten sonra babel’ın preset-env‘yi kullanabilmesi için babel.config.json dosyasını aşağıdaki gibi oluşturalım:

{
  "presets": [
    "@babel/preset-env"
  ]
}

Daha sonra javascript dosyalarının babel tarafından derlenmesi için webpack.config.js dosyasını aşağıdaki gibi değiştirelim:

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');


module.exports = {
    mode: 'production',
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader'],
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: ["babel-loader"]
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, "src", "index.html")
        }),
        new MiniCssExtractPlugin()
    ]
};

Şimdi babel’ın kodu nasıl değiştirdiğini görebilmek için src/index.js dosyasını aşağıdaki gibi değiştirelim:

import "./style.css";
console.log("Merhaba dünya!");

const myFunc = () => {
    return [1, 2];
};

const [a, b] = myFunc();

Şimdi kodun derlenmesi için npm run build:dev komutunu çalıştıralım. Komut tamamlandığında dist/main.js dosyasının içeriği aşağıdaki gibi olacaktır:

main.js — webpack-demo 2021-02-04 18-21-26

Bu dosya içerisinde myFunc fonksiyonunu aratarak, nasıl kullanıldığını bulabilirsiniz.

Webpack ile React projesi geliştirme

React bileşenlerini Webpack ile birlikte kullanabilmek için babel’ın yanında bir de React için olan babel preset’ini de yüklememiz gerekiyor.

npm install @babel/preset-react --save-dev

Paketler yüklendikten sonra babel.config.json dosyası içerisinde aşağıdaki gibi babel’ı yapılandırabiliriz:

{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

React’in çalıştırılabilmesi için ayrıca react ve react-dom kütüphanelerini de ekleyelim:

npm install react react-dom

Artık React bileşeni yazabiliriz. Şimdi src/index.js dosyasını aşağıdaki gibi bir App bileşenini çalıştıracak şekilde değiştirelim:

import React, { useState } from "react";
import { render } from "react-dom";

const App = () => {
    const [state, setState] = useState("CLICK ME");

    const handleClick = () => {
    	setState("CLICKED")
    }

    return <button onClick={handleClick}>{state}</button>;
}

render(<App />, document.getElementById("root"));

App bileşeninin render edileceği root id’li div elemanını da src/index.html içerisine ekleyelim:

<!DOCTYPE html>
<html lang="tr">
  <head>
    <meta charset="UTF-8" />
    <title>Merhaba dünya</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

npm start komutu ile çalıştırdığınızda aşağıdaki şekilde App bileşeni render edilmiş olacaktır.

Merhaba dünya 2021-02-04 20-47-08

Dynamic import ile Code-splitting işlemini gerçekleştirme

Webpack ile code-splitting yapılarak (kodu parçalara ayırarak) performanslı bir uygulama oluşması sağlanabilir. Kodun parçalara ayrılması sayesinde sadece istenen anda istenen kütüphanenin yüklenmesi gerçekleştirilebilir. Böylece uygulamanın tarayıcıda yüklenmesi hızlı bir şekilde gerçekleşir ve performans artışı sağlanır. Code splitting tekniği 3 şekilde gerçekleştirilebilir:

  1. Birden fazla entry point tanımlanarak.
  2. optimization.splitChunks metodu ile oluşan script’i kütüphane bazında ayrı ayrı dosyalara ayırarak.
  3. Dynamic import yöntemi ile kod bazında ayırarak. Birden fazla entry point kullanımı yerine, artık webpack 4 ile birlikte splitChunks tercih edildiği için bu yazıda sadece 2 ve 3. maddeyi ele alacağız.
// Birden fazla entry point kullanımı örneği:
module.exports = {
  //...
  entry: {
    home: './home.js',
    about: './about.js',
    contact: './contact.js'
  }
};

optimization.splitChunks metodu ile vendor kodlarının ayrılması

momentjs gibi büyük bir kütüphaneyi projenize dahil ettiğinizde main.js dosyası büyük boyutlara ulaşacaktır. Bunun yerine splitChunks metodu ile kodu parçalara ayırabilirsiniz. Bu durumu kodda görmek için bir deneme yapalım ve projemize moment kütüphanesini ekleyelim:

npm install moment

Şimdi src/index.js içerisindeki tüm kodu silelim ve içerisine sadece aşağıdaki satırı ekleyelim:

import moment from "moment";

Şimdi kodu aşağıdaki gibi prod ortamı için derleyelim:

npm run build:prod

Komutu çalıştırdığınızda aşağıdaki gibi 244KB olan maksimum limiti aştığına dair mesajları göreceksiniz:

zaferayan@zafer-mbp~Githubwebpack-demo 2021-02-05 17-48-56

Burada da görüldüğü gibi, momentjs gibi büyük bir kütüphaneyi projenin başlangıç script’ine eklemek uygulamanın yavaş çalışmasına neden olacaktır. Bunun yerine kütüphane kodlarını optimization.splitchunks özelliği ile ayrıştırabilirsiniz. Şimdi bunun için webpack.config.js dosyasına gelelim ve aşağıdaki gibi optimization kısmını ekleyelim:

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');


module.exports = {
    mode: 'production',
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader'],
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: ["babel-loader"]
            }
        ]
    },
    optimization: {
        splitChunks: { chunks: "all" }
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, "src", "index.html")
        }),
        new MiniCssExtractPlugin()
    ]
};

Şimdi tekrar aşağıdaki kodu çalıştırıp kodu derleyelim:

npm run build:prod
zaferayan@zafer-mbp~Githubwebpack-demo 2021-02-05 23-05-12

Komut çalıştığında yaklaşık 5KB’lık main.js dosyası ve moment.js‘i barındıran 290KB’lık 762.js dosyası oluşacaktır. Bu sayede projenin kaynak kodu ile vendor kodu ayrılmış olacaktır.

Dynamic import ile kod ayırma işlemleri

Önceki başlıkta splitChunks ile vendor/logic ayrımını sağladık. Fakat bu çok basit bir yöntem olup daha karmaşık durumlar için dynamic import mekanizması bulunmaktadır. Bu özellik her ne kadar ECMAScript 2020’de gelse de, bunun çok daha öncesinde Webpack tarafından aktif olarak sunulmaktadır. Dinamik import kavramı Vue ve React gibi modern kütüphanelerde yaygın olarak kullanılmakla birlikte React’teki kullanımı bir miktar farklılaşmaktadır. Dinamik import işlemi, modül seviyesinde veya ilgili route (rota) seviyesinde gerçekleştirilebilir. Modül seviyesine örnek olarak, kullanıcının bir butona tıklaması anında dinamik olarak herhangi bir modülün yüklenmesi sağlanabilir. Route seviyesindeki kod ayırma işlemine örnek vermek gerekirse, web uygulamasındaki sayfaların birbirinden ayrı olarak dinamik bir şekilde yüklenmesi sağlanabilir. Dinamik import kavramını denemek için src/index.html içerisindeki kodları tamamen silelim ve aşağıdaki gibi bir butonlu hale getirelim:

<html lang="tr">
  <head>
    <meta charset="UTF-8" />
    <title>Dinamik import örneği</title>
  </head>
  <body>
    <button id="btn">Modül yükle</button>
    <div id="dateContainer"></div>
  </body>
</html>

Şimdi buradaki Modül yükle butonuna bastığımızda çağrılacak getCurrentDate metodunu src/modules/dateModule.js içerisinde oluşturalım:

import moment from 'moment';
import localization from 'moment/locale/tr';

export const getCurrentDate = () => {
    return moment()
        .locale('tr', localization)
        .format('MMMM Do YYYY, h:mm:ss a');
}

Şimdi src/index.js içerisinde bu modülü çağıracak olan click event’ini oluşturalım:

const dateModule = () => import("./modules/dateModule");

const btn = document.getElementById("btn");

btn.addEventListener("click", () => {
    dateModule().then(({ getCurrentDate }) => {
        document.getElementById('dateContainer').innerHTML = getCurrentDate();
    });
});.format('MMMM Do YYYY, h:mm:ss a');
}

Burada import("./modules/dateModule"); sayesinde dinamik olarak modül yükleme işlemini gerçekleştiriyoruz. Bu yükleme işlemi asenkron olarak gerçekleştiği için dateModule().then() şeklinde kullanmakta fayda var. Ayrıca dateModule içerisinden getCurrentDate metodunu alabilmek için { getCurrentDate } şeklinde kullanarak ES6’nın object destructring özelliğinden de yararlandığımızı belirtelim. Şimdi development ortamında ayağa kaldıralım ve Chrome Dev Tools’u açarak Modül yükle butonuna tıklayalım:

npm start
Dinamik import örneği 2021-02-06 00-58-36

Dev Tools’ta göreceğiniz gibi momentjs kütüphanesini içeren 762.js dosyası sadece butona tıklama anında indiriliyor. Bu sayede sayfanın açılması hızlanmış ve sonraki işlemlerde istenilen herhangi bir kütüphane kolaylıkla kullanılabilir hale gelmiş oluyor.

Sonuç olarak

Webpack, web projelerindeki asset’lerin yönetilmesi, bundle’ın oluşturulması, code-splitting işlemlerini gerçekleştirmesi gibi pek çok işlevi bir arada sunduğu için Grunt ve Gulp gibi diğer web araçlarını gölgede bırakmış vaziyette. Her ne kadar React projelerini oluşturmak için create-react-app gibi CLI araçları sizi yönlendirse de, birçok web projesinde webpack yaygın olarak kullanıldığı için webpack hakkında biraz bilgi edinmekte fayda var. Sonraki yazımda görüşmek üzere. Hoşça kalın…

Kaynak: https://devnot.com/2021/webpack-nedir-webpacke-detayli-bir-bakis/