Webpack Nedir? Ne Amaçla Kullanılır?

Web Application JS Module bundler yazısında bahsi geçen paketleticilerle (bundler) ilgili detaylara geçebiliriz.

AA

13 Haziran 2019 tarihli kontrole göre Webpack 2.593.099 repository, 1.608 watch, 49.365 star ve 6.198 fork’a sahipti1. İkinci sıradaki Parcel, 40.328 repository, 490 watch, 32.003 star ve 1.460 fork2 iken Rollup 177.967 repository, 270 watch, 15.798 star ve 713 fork ile 3. sıradaydı3. Bu değerleri temel alarak ilk sırada Webpack’e değinmeye karar verdim.

Webpack

Webpack

Webpack, Node.js tabanlı bir JavaScript module bundler (modül paketleyici). Özetle, pek çok bağımlılığa (dependency) sahip paketleri / modülleri ve/veya dosyaları çeşitli işlemlerden geçirip (transpile, concat, minify…) belirtilen biçimde statik bir şekilde paketleyerek sunan bir araç olarak ifade edebiliriz. Evet, bu özellikler kısmen Gulp ve Grunt gibi task runner’ları (görev çalıştırıcı) akla getiriyor olabilir. Ancak, işleyiş süreçleri biraz farklı. Hemen açıklayayım. Task runner’lar belirtilen görevleri sırayla başarılı bir şekilde işleme alsalar da tanımlı diğer görevleri, modülleri ve bağımlılıkları ilişkiler (structure) bağlamında değerlendirmezler. İşte bu noktada, uygulama development ve production süreçlerinde dosyaları değil, projeyi kontrol edebileceğimiz araçlara ihtiyaç duyarız. Webpack, geliştirme ortamında kullanabileceğimiz bundle araçlarından biridir4.

Webpack kullanımında entry point (index.js, main.js vb.) için basit bir yol tanımlanır. Sonrası require, import ilişkiler, ifadeler (statement) ve tanımlarla Webpack tarafından anlamlandırılır ve işleme alınır. Örneğin, Babel5 (transpiler / source-to-source compiler) ile JavaScript dosyaları (ES6+, JSX vb.) eski tarayıcılar için derlenir. Bu arada, Webpack için JavaScript module bundler temel bir ifade olsa da eklentilerle bu kapsam çok daha genişletilebilmekte.

Webpack

Webpack Çalışma Biçimi

Webpack ES5 (ECMAScript 5)6 destekleyen tüm internet tarayıcılarında aşağıdaki yetenekleri sergileyebilir. Bu destek kapsamın dışındaki eski tarayıcılar için polyfill kullanılması gerekir7. Webpack, async I/O ve çoklu önbellek seviyelerini (caching level) kullanır.

  • ES modülleri, CommonJS ve AMD (Asynchronous Module Definition) modüllerini ve hatta bu modüllerin birlikte kulllanımlarını dahi anlayıp paketler
  • Başlangıç işlem süresini (runtime) optimize edebilmek adına eşzamansız olarak (asynchronously) yüklenen tek bir paket (single bundle) veya çoklu parçalar (chunks) oluşturabilir.
  • Bağımlılıklar (dependencies) runtime boyutunu optimize edebilmek amacıyla derleme (compilation) sürecinde çözümlenir.
  • Loader’lar derleme sırasında dosyaları önceden işleyebilir (compiling). Örneğin, TypeScript’ten JavaScript’e, Handlebar ifadelerinden derlenmiş fonksiyonlara, imajlardan Base64’e (binary data) dönüştürülmesi gibi.
  • Uygulamanın gerektirdiği her şeyi yapabilmek için modüler bir eklenti yapısı sunar.

Webpack Concepts

Webpack işleyişinde eklenti (plugin), yükleyici (loader), mode (mod) gibi belirli konseptler (core concepts) kullanılmaktadır. Öne çıkan bazı başlıklara kısaca bakalım.

Plugin’ler (eklentiler), zengin bir eklenti arayüzüne sahiptir. Genel olarak pek çok işlem için bu arayüz kullanılmakta. Bu eklentilerden bazıları; html-webpack-plugin, extract-text-webpack-plugin, compression-webpack-plugin.

Loader’lar (yükleyiciler), bir modülün kaynak kodundaki dönüştürme işlemlerini gerçekleştiriler; görev yöneticiler (task runner) gibi düşünülebiliriz; import ve/veya “load” edilen dosyaları (Files, JSON, Transpiling, Templating, Styling, Linting ve Testing, Frameworks gibi) ön işlemden (pre-process) geçirirler. file-loader, url-loader, json-loader ve babel-loader öne çıkan örnek yükleyicilerden bazılarıdır. Detaylı liste için Webpack > Loader8 sayfasına göz atabilirsiniz.

Webpack İndirme İşlemi

Webpack’i npm veya yarn paket yöneticilerden biriyle indirebiliriz.

npm install --save-dev webpack
yarn add webpack --dev

Webpack Kullanımı

Webpack kullanımına örnek olarak basit bir işlem gerçekleştirelim. Örnek projenin adını ben webpack-test olarak belirledim. İlk olarak, bir Node.js paket yöneticisi (npm veya yarn) ile projemizi başlatalım ve webpack’i kuralım.

yarn init -y
# ya da
npm init -y
yarn add webpack --dev

Bu işlemlerin ardından package.json içeriğimiz şu şekilde (sürüm farklılıkları olabilir) olacaktır.

{
  "name": "webpack-test",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "webpack": "^4.34.0"
  }
}

Şimdi, bir script tanımı yapmamız gerekiyor. Bu sayede yarn veya npm ile birlikte belirlediğimiz komutları da ilişkili bir şekilde işleme alabileceğiz. build keyname’e sahip script tanımımızı package.json dosyasına ekleyelim.

{
  "name": "webpack-test",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "webpack": "^4.34.0"
  }
}

Bir sonraki adımımız (eğer cli kullanmayacaksak) webpack config dosyası (ön tanımlı olarak webpack.config.js) oluşturmak olacak. Bu sayede webpack’in dosyaları nasıl / nereye ve ne şekilde bundle edeceğini belirtebiliriz. Elbette bu tanımlar için bazı dosyalar ve klasörler oluşturmamız gerekecek. src adında, içeriğinde entry point’in (örneğin index.js), bundle dosyasının (örneğin bundle.js) js, css ve html dosyalarının yer alacağı klasörleri ve dosyaları oluşturabiliriz.

Entry point (örneğin index.js, main.js gibi) ile bağımlılık grafiğinin (dependency graph) başlangıcını belirtiriz. Ayrıca dist (distribution) adında düzenlenen dosyaların aktarılacağı bir diğer klasörü de örnek olarak görebilirsiniz. Config dosyasında da ayrıca bu tanımlara yer vereceğim. Son durumda, proje klasörümüzün içeriğini node modüllerini hariç tutarak görüntüleyelim.

tree -L 4 -I node_modules
.
├── dist
│   ├── css
│   ├── img
│   ├── js
│   └── view
├── package.json
├── src
│   ├── css
│   ├── img
│   ├── index.js
│   ├── js
│   └── layout
├── webpack.config.js
└── yarn.lock

10 directories, 4 files

index.js içeriğimiz:

console.log("Hello World!")

webpack.config.js içeriğimiz:

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js"
  },
  mode: "development"
}

Başlangıç aşaması için her şey tamam görünüyor. O halde script olarak da tanımladığımız build işlemini başlatalım.

yarn build
# ya da
npm build

Komutu uygulamamızın ardından paketleme işlemi başlayacak ve bundle.js dosyası gerekli tüm bağımlılıkları içerecek şekilde oluşturulacaktır. Örneğin, ilgili js dosyasını node.js ile çalıştıralım.

node ./dist/bundle.js

Görüldüğü üzere komut bize Hello World! şeklinde dönüş sağlayacaktır. Export işlemi için dist adında bir klasör oluşturmuştum. Elbette bu yolu da düzenleyebiliriz. Düzenleme işlemi için path ile yeni bir yol belirtmemiz gerekir. Ancak, bu işlem başı başına yeterli değildir. webpack içeriğinde tanımlı olan path modülünü de çağırmamız gerekir.

const path = require("path")

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.join(__dirname, "dist")
  },
  mode: "development"
}

Tekrar paket yöneticisi ile build işlemini başlattığımızda tıpkı ön tanımlı olarak ilerleyen süreçte olduğu gibi dist adında bir klasör oluşturulduğunu ve bundle.js dosyasının da bu klasör içeriğine aktarıldığını görebilirsiniz. Şimdi, yeni bir kaç JS dosyası daha oluşturalım (dependency) ve webpack ile bu dosyaları nasıl bundle edebileceğimize bakalım. Dosyaları oluşturacağımız klasörümüz /src/js. Oluşturacağımız dosyalar da foo.js ve bar.js olsun. foo.js dosyasını index.js dosyasına (entry point) import etmek istiyorum.

index.js içeriği:

import fooJS from "./js/foo"
import barJS from "./js/bar"

console.log("Hello World!")
console.log(`It is ${fooJS} and ${barJS}`)

foo.js içeriği:

export default "foo"

bar.js içeriği:

export default "bar"

Yaptığımız değişikliklerin doğrudan bundle edilmesini sağlayalım. İlk işlemimizde webpack.config.js içeriğine watch ifadesini true değeri ile ekleyebiliriz9.

const path = require("path")

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.join(__dirname, "dist")
  },
  mode: "development",
  watch: true
}

Bu durumda, yarn build veya npm build komutlarının uygulanmasının ardından dosyalar değişiklikler için izlenecek ve her değişiklik bundle işleminin yenilenmesini sağlayacaktır. Elbette komut satırı arayüzü bu süre boyunca yeni bir komut girişini almayacaktır. Bir diğer yoldan ilerleyelim ve watch key değerine sahip bir script tanımı yapalım. webpack.config.js dosyasındaki watch ifadesini kaldırabiliriz. İlgili tanımlama işlemini package.json içeriğinde gerçekleştireceğiz:

{
  "name": "webpack-test",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "build": "webpack",
    "watch": "webpack --watch"
  },
  "devDependencies": {
    "webpack": "^4.34.0",
    "webpack-cli": "^3.3.4"
  }
}

Tanımlamanın ardından yarn watch komutunu uyguladığımızda, yine webpack dosya değişikliklerini izleyecek ve olası bir değişiklikte bundle’ı güncelleyecektir. Doğrulama yapmak adına index.js, foo.js ve/veya bar.js dosyalarında değişiklikler yapabilirsiniz. Şimdilik işlemlerimizi burada sonlandırabiliriz. Bir sonraki yazıda, yine bu örnek üzerinden ilerleyecek, loader ve plugin kullanımlarına değineceğim.

Son Olarak

Webpack.js ile ilgili teknik bilgiler ve gelişmeler için resmi web adresini10, GitHub1 ve stackoverflow11 sayfasını ziyaret edebilirsiniz. Ayrıca, başlangıç için Sean Larkin12 tarafından hazırlanmış olan webpack.academy kursuna13 da göz atmanızı öneririm.