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.
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
verequire
ifadeleri - CSS/SASS/LESS dosyalarındaki
@import
ifadesi, - 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:
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.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; production
, development
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/[email protected]"></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
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ındakiimport
ifadelerini çözümleyerek, import edilen dosyalar içerisinden alınan css kodlarının ham halde elde edilmesini sağlar.style-loader
:css-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:
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:
- 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:
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.
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:
- Birden fazla entry point tanımlanarak.
optimization.splitChunks
metodu ile oluşan script'i kütüphane bazında ayrı ayrı dosyalara ayırarak.- 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:
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
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
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...