* * *

Frontend dünyası son yıllarda daha karmaşık ve daha takip edilemez bir hale geldi. Neredeyse her gün yeni kavramlar, kütüphaneler ve yöntemler hayatımıza girmeye başladı. Bu yazıda bu karmaşıklığı bir nebze ortadan kaldıran, production için daha performanslı ve daha optimize bir çıktı alabilmemizi sağlayan webpack'den bahsetmeye çalışacağım.

Webpack'e geçmeden önce bu yazıda neler öğreneceğiz nelerden bahsedeceğiz liste halinde görelim.

Neler Öğreneceğiz?

  • Neden webpack kullanmalıyız?
  • Hangi sorunları çözüyor?
  • Webpack nedir?
  • Webpack konseptleri nelerdir?
  • Webpack kurulumu nasıl yapılır?
  • Projeye nasıl dahil edilir?
  • Webpack plugin'leri nasıl kullanılır?

Webpack Hangi Sorunları Çözüyor?

Eskiden frontend geliştirmek için 3 - 5 kütüphane, tek bir javascript ve css dosyası yeterli oluyordu. Hem javascript'in aşırı popülerleşmesi hem de bir web projesinden beklentilerin artması ile birlikte onlarca kütüphane ve geliştirme yapmak için birden fazla javascript ve css dosyasına ihtiyaç duymaya başladık. Bu ihtiyaçlar da birden fazla problemi beraberinde getirdi.

Web sayfasında onlarca dosya çağırmak tarayıcıların aynı anda paralel olarak indirebileceği dosya sayısının belirli bir limitinin olmasından dolayı performans sorunlarına sebep olmaya başladı. Bu sorunu çözmek için kullandığımız dosyaları küçültmemiz ve yapabiliyorsak tek bir dosya haline getirmemiz gerekiyordu. Zamanla bu işlemleri ve daha fazlasını yapabilen gulp, grunt gibi javascript task runner'lar geliştirildi.

Javascript task runner'lar sıkıştırma, bundle oluşturma, optimizasyon, css preprocessor'ların compile edilmesi, sprite oluşturma gibi bir çok işlemi bizim yerimize yapan araçlardır. Tabi bu araçlar da günümüz frontend yapısına ayak uydurmaya yeterli olamadı. Çünkü bu araçlar build işlemlerini dosyalar arasındaki ilişkiyi gözetmeksizin yapabiliyordu.

Sonunda kullandığımız kütüphaneler ile yaptığımız geliştirmeler arasındaki ilişkilerin de gözetilerek bir build işlemi yapabildiğimiz webpack ortaya çıktı. Zamanla webpack hem yetenekli hem çok yönlü hem de bir çok soruna çözüm bulan ve çok popüler bir araç haline geldi.

Webpack Nedir?

webpack nedir?

Webpack Node.js tabanlı çok akıllı bir modül paketleyicisidir (module bundler). Birden fazla dosya tipini işleyebilir bu dosyalar üzerinde farklı işlemler (transpile, concat, minify vs.) yapabilir ve belirtilen biçimde statik bundle'lar (paketler) oluşturabilir. Ayrıca modüllerin tüm bağımlılıklarını takip eder, bir bundle oluşturulacağı zaman ise kullanılan bağımlılıkları bildiği için optimize edilmiş ve duplicate (kopya) bağımlılıklardan arındırılmış bir çıktı oluşturur.

Webpack Konseptleri Nelerdir?

Webpack kendi içinde 5 adet konsepte sahiptir. Webpack kurulumuna geçmeden önce bu kavramları açıklamaya çalışayım.

  • Entry: Webpack'in dependency graph'ını (bağımlılık grafiği) oluşturubilmesi için bir başlangıç noktasına ihtiyacı vardır. Webpack bu başlangıç dosyasından başlar ve tüm modülleri gezerek bağımlılıkları yönetmeye başlar. Webpack'de ./src/index.js default başlangıç dosyasıdır. Bu dosya dışında farklı dosya yolu verebiliriz. Ayrıca entry olarak birden fazla başlangıç dosyası da girmek mümkün.
module.exports = {
  entry: {
    detail: './src/detail.js',
    search: './src/search.js',
    vendor: './src/vendor.js'
  }
}
  • Output: Webpack'in tüm işlemlerden sonra oluşturduğu bundle dosyasını hangi klasöre yazacağını belirtiğimiz kısımdır. Bu klasör genellikle dist veya build klasörü olur. Fakat siz istediğiniz bir klasörü seçebilirsiniz. Aşağıdaki örnekte webpack işlemlerini bitirdikten sonra dist klasörü altında detail.js, search.js ve vendor.js dosyaları oluşturur.
module.exports = {
  entry: {
    detail: './src/detail.js',
    search: './src/search.js',
    vendor: './src/vendor.js'
  },
  output: {
    filename: '[name].js',
    path: __dirname + '/dist'
  }
}
  • Loaders: Webpack sadece javascript ve json dosyalarını işleyebilir. Webpack'e diğer dosya tiplerini de işleyebilme yeteneği kazandırmak için loader'ları kullanırız.

  • Plugins: Plugin'ler webpack ekosisteminde çok önemli yere sahiptirler. Loader'ların yetmediği bazı işlemleri ve görevleri plugin'ler yardımıyla yaparız. Plugin'ler asset management, bundle minimization, optimization gibi birçok görevi yerine getirebilir.

  • Mode: Genellikle bir uygulama geliştirken development ve production olmak üzere iki tip kaynak koduna sahip oluruz. Webpack tarafında da bunu ayarlamak mümkün. Production modu kodun optimize edilmiş halini, development modu geliştirme yaparken kullandığımız optimize edilmemiş halini işaret eder. Bu ayarı iki şekilde yapabiliriz.

module.exports = {
  mode: 'development'
}
webpack --mode=development

Webpack Kurulumu Nasıl Yapılır?

Webpack hakkında birçok bilgiye sahip olduk. Gelin şimdi uzun uzun bahsettiğimiz webpack'i projemize kuralım.

# Global olarak kurulum
npm install -g webpack webpack-cli

# Proje bazlı kurulum
npm install --save-dev webpack webpack-cli

💡 Webpack versiyonları ile baş ağrısı çekmek istemiyorsanız proje bazlı bir webpack kurulumu yapmak daha faydalı olacaktır. Yazının bu kısmından itibaren proje bazlı kurulum üzerinden anlatmaya çalışacağım.

Webpack Kullanarak Uygulama Geliştirelim

Evet şimdi kolları sıvayıp webpack'i kullanmaya sıra geldi. Öncelikle projemiz için bir klasör oluşturalım. Bu işlemleri adım adım uygulayarak takip etmenizi tavsiye ediyorum. webpack-example adında bir klasör oluşturalım ve npm init -y komutunu terminalde çalıştıralım. Bu komut ile birlikte proje dizinimizde dependency'lerimizi tuttuğumuz package.json dosyası oluşturulmuş oldu.

Projemize webpack kurmak için aşağıdaki komutu terminalde çalıştıralım.

npm install --save-dev webpack webpack-cli

package.json dosyamız görseldeki gibi güncellenmiş olacaktır.

webpack nasıl kullanılır package.json?

Webpack konfigürasyonlarını tanımlamak için webpack.config.js dosyasını oluşturmamız gerekiyor. Proje dizininde bu dosyayı oluşturalım.

Webpack konfigürasyonunu entry olarak src/ klasörü output olarak dist/ klasörü olacak şekilde ayarlayalım.

module.exports = {
  entry: {
    index: './src/index.js'
  },
  output: {
    filename: '[name].[chunkhash].js',
    // [chunkhash]: her build işleminde benzersiz bir çıktı üretmek için kullanılır.
    path: __dirname + '/dist'
  }
}

src klasörü altında index.js dosyası oluşturup test için iki sayıyı toplayan bir fonksiyon oluşturup bu fonksiyonu çağıralım.

const sum = (a, b) => a + b

console.log(sum(4, 5))

Son olarak package.json dosyasında scripts property'sini güncelleyelim.

"scripts": {
  "dev": "webpack --mode=development",
  "build": "webpack --mode=production"
},

npm run dev komutunu terminalde çalıştırıp sihri görelim 😄. Adımları doğru uyguladıysanız proje dizininde dist klasörü altında index.[hash].js dosyamızın oluştuğunu görebilirsiniz. Bu dosyayı incelediğimizde optimize edilmemiş ve yorum satırları bulunan bir içeriğe sahip olduğunu görebiliriz. Çünkü webpack'i development modunda çalıştırdık.

Terminalde npm run build komutunu çalıştırıp production modunda bir çıktı üretelim. Bu sefer dist klasöründe tamamen optimize ve minify edilmiş bir içeriğe sahip dosya oluşturuldu.

Fakat bir sorun var gibi... Her build işleminden sonra dist klasörü altında yeni index.js dosyaları oluşuyor.

clean-webpack-plugin'i nedir?

Build işlemlerinden sonra temiz bir dist klasörü elde için clean-webpack-plugin paketini aşağıdaki komutla yükleyelim.

npm install --save-dev clean-webpack-plugin

Plugin'ler hakkında ufak bir bilgi elde etmiştik. Config dosyamızı güncelleyip indirdiğimiz plugin'i kullanalım.

const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  // ...
  plugins: [new CleanWebpackPlugin()]
}

Webpack İle HTML Dosyalarını Yönetmek

Şu ana kadar build işlemlerini webpack ile yaptık. Fakat yaptığımız işlemleri hala bir web tarayıcısında görebilmiş değiliz. Bunun için webpack'in html dosyalarını işlemesini sağlatmalıyız. Dolayısıyla projemize aşağıdaki komutu çalıştırarak html-webpack-plugin plugin'ini yükleyelim.

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

src klasörü altına aşağıdaki gibi bir index.html dosyası oluşturalım.

<!DOCTYPE html>
<html lang="tr">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    Hello From Webpack
  </body>
</html>

Şimdi de build sırasında html dosyalarının da işleme alınması için config dosyamızı güncelleyelim.

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  // ...
  plugins: [
    // ...
    new HtmlWebpackPlugin({
      template: './src/index.html',
      inject: true
      // inject: true => Otomatik olarak build dosyasını script tag'ı olarak eklemeyi sağlar.
    })
  ]
}

npm run dev komutunu çalıştıralım. dist klasöründe artık index.html dosyamız da var. Bu dosyayı tarayıcıda açıp console'u açarsanız yaptığımız örnekteki toplama işleminin sonucunu görebilirsiniz.

webpack-dev-server

Geliştirme yaparken local server oluşturmak ve herhangi bir değişlik yaptımızda tarayıcının otomatik yenilenmesini sağlayan webpack-dev-server paketini kuralım ve zamandan tasarruf edelim 😋.

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

Kurulumdan sonra package.json dosyasını da güncellememiz gerekiyor. Dosyayı açıp aşağıdaki gibi güncelleyelim.

"scripts": {
  // ...
  "start": "webpack-dev-server --mode=development"
  // ...
},

npm run start komutunu çalıştırdıktan sonra terminalde muhtemelen aşağıdaki gibi bir görüntü oluşacak.

webpack-dev-server nedir?

Tarayıcınızdan http://localhost:8080 adresine gidip kodunuzda herhangi bir değişiklik yaparsanız sayfanın otomatik olarak yenilendiğini görebilirsiniz 🎉.

ES6 Kodunu Transpile Etmek

Geliştiriciler olarak modern javascript kullanmayı severiz. Fakat eski tarayıcılar bunu anlayamaz ve yazdığımız kod maalesef çalışmaz. Bu yüzden production'daki kodumuzun her zaman eski tarayılara uyumlu olması gerekir. Webpack tarafında bu transpile işlemini yapabilmek için loader'lara ihtiyacımız vardır. Gerekli paketleri aşağıdaki komutu çalıştırarak projemize yükleyelim.

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

Gerekli paketleri yükledikten sonra config dosyamıza loader tanımlayalım.

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: [/.js$/], // test => Hangi dosya tiplerinin işlemden geçeceğini belirttiğimiz property
        exclude: /(node_modules)/, // exclude => Hangi klasörlerin işlemden geçmeyeceğini belirttiğimiz property
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
  // ...
}

Artık özgürce yeni javascript özelliklerini kullanabiliriz. Kodumuz build olduğunda tarayıcıların anlayabileceği formata otomatik olarak çevrilecektir 🎉.

CSS Dosyaları İle Çalışmak

Webpack'de css dosyaları ile çalışabilmek için css-loader ve style-loader olmak üzere iki adet loader'a ihtiyacımız vardır. css-loader uygulamadaki tüm stilleri toplar ve string haline dönüştürür. style-loader ise bu string çıktısını alır ve html sayfamızda <style> tagleri arasına yazar. Hemen bu paketleri yükleyelim.

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

Yükleme işlemi bittikten sonra config dosyamızı güncelleyelim.

module.exports = {
  // ...
  module: {
    rules: [
      // ...
      {
        test: [/.css$/],
        use: ['style-loader', 'css-loader']
      }
      // ...
    ]
  }
  // ...
}

Şimdi src klasörü altında styles adında bir klasör ve index.css dosyası oluşturalım ve çalıştığına emin olmak için css dosyamıza aşağıdaki kodu ekleyelim. Son olarak da index.css dosyasını index.js dosyasında çağıralım.

body {
  font-size: 40px;
  color: blue;
}
import './styles/index.css'

// ...

npm run start komutunu çalıştırarak tarayıcıda sonucu görelim.

webpack style loader ve css loader nedir?

Sass Dosyalarını CSS Dosyalarına Çevirmek

Webpack ile sass dosyalarını css dosyalarına çevirmek için node-sass ve sass-loader paketlerini yüklememiz gerekir. sass-loader node-sass yardımıyla sass dosyalarını css dosyalarına çevirir. Paketleri indirip config dosyamızı güncelleyelim.

npm install --save-dev node-sass sass-loader
module.exports = {
  // ...
  module: {
    rules: [
      // ...
      {
        test: [/.css$|.scss$|.sass$/],
        use: ['style-loader', 'css-loader', 'sass-loader']
      }
      // ...
    ]
  }
  // ...
}

Buradaki loader'ların sıralaması önemlidir. Çünkü önce sass dosyaları css dosyalarına çevirlmelidir. Ardından stiller stringlere dönüştürülüp html dosyasına yazılmalıdır. Bu sıralamayı değiştirirseniz webpack tarafında hata alırsınız.

src/styles klasörü altına index.sass dosyası ekleyip aşağıdaki gibi güncelleyelim ve index.js dosyasında çağıralım.

$body-bg-color: #ddd

body
  background-color: $body-bg-color
// ...
import './styles/index.sass'

// ...

Tarayıcıyı açtığınızda sass dosyamızdaki kodun css koduna çevrildiğini görebilirsiniz.

Stilleri Css Dosyasına Yazmak

Çoğu zaman stillerimizi inline olarak eklemek yerine css dosyaları halinde tutmak isteriz. Webpack'de stilleri css dosyasına yazmak için mini-css-extract-plugin paketini yüklememiz gerekir. Aşağıdaki komutu çalıştırıp paketimizi yükleyelim ve config dosyamızı güncelleyelim.

npm install --save-dev mini-css-extract-plugin
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  // ...
  module: {
    rules: [
      // ...
      {
        test: [/.css$|.scss$|.sass$/],
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
      }
      // ...
    ]
  },
  plugins: [
    // ...
    new MiniCssExtractPlugin({
      filename: 'style.css'
    })
  ]
}

Config dosyamızda stillerimizi inline olarak kullanmak yerine css dosyasına yazmak istediğimiz style-loader'ı kaldırdık yerine MiniCssExtractPlugin.loader ekledik. Tarayıcıya girip kontrol ettiğimizde style.css dosyamızın başarılı bir şekilde eklendiğini görebiliriz.

webpack mini-css-extract-plugin nedir?

Webpack Alias İle Dosyalara Kolay Ulaşım

Üzerinde çalıştığımız proje büyüdükçe ve iç içe klasör sayısı arttığında belirli seviyelerdeki dosyalara ulaşmak çok zahmetli hale gelebiliyor. Bir örnekle sorunu anlamaya çalışalım.

// ...
import getErrorType from '../../../../utils/getErrorType'
import smileIcon from '../../../../../images/icons/smileIcon.png'

Bu kullanım oldukça çirkin duruyor. Fakat webpack bunu da düşünmüş ve klasörlerimize alias yani bir çeşit takma ad koyabilmemizi ve daha kısa yoldan ulaşabilmemizi sağlayabiliyor. Bunun için config dosyamızda ufak bir değişiklik yapmamız yeterli olacaktır.

// ...
const path = require('path')

module.exports = {
  // ...
  resolve: {
    alias: {
      '@components': path.resolve(__dirname, 'src/components/'),
      '@containers': path.resolve(__dirname, 'src/containers/'),
      '@context': path.resolve(__dirname, 'src/context/'),
      '@images': path.resolve(__dirname, 'src/images/'),
      '@utils': path.resolve(__dirname, 'src/utils/')
    }
  }
}

Config dosyamızdaki güncellemeden sonra çirkin duran kodumuzu aşağıdaki gibi güncelleyebiliriz.

// ...
import getErrorType from '@utils/getErrorType'
import smileIcon from '@images/icons/smileIcon.png'

Kapanış

Oldukça uzun soluklu bir yazı olduğunun farkındayım fakat webpack gibi bir konuyu üstün körü anlatamazdım. Bu yazı ile ilgili soru, öneri veya eklemek istediklerinizi aşağıdaki yorum kısmından yazabilirsiniz. Ayrıca config dosyamızın son halini de aşağıda paylaşıyorum.

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  entry: {
    index: './src/index.js'
  },
  output: {
    filename: '[name].[chunkhash].js',
    path: __dirname + '/dist'
  },
  module: {
    rules: [
      {
        test: [/.js$/],
        exclude: /(node_modules)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      },
      {
        test: [/.css$|.scss$|.sass$/],
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      inject: true
    }),
    new MiniCssExtractPlugin({
      filename: 'style.css'
    })
  ],
  resolve: {
    alias: {
      '@styles': path.resolve(__dirname, 'src/styles/')
    }
  }
}
* * *

Yorumlar

Soru, cevap ve destekleriniz için aşağıdan yorum bırakmayı unutmayın.

* * *
Javascript Mutability ve Immutability KavramlarıNpx Nedir?