Bir web projesinde görselleri farklı ekran boyutlarına göre hazırlamak, dosya boyutlarını küçültmek ve WebP formatına dönüştürmek oldukça önemli. Ben de bu iş için hazırladığım küçük bir Node.js projesinde Gulp kullanıyordum.
Başlangıçta her şey gayet basitti: Kaynak görselleri src klasörüne koyuyordum, Gulp bunları dört farklı genişlikte yeniden boyutlandırıyor, optimize ediyor ve WebP formatına dönüştürüyordu.
Fakat Arch Linux üzerinde projeyi çalıştırdığımda işler biraz karıştı. Önce eksik ikili dosyalarla uğraştım, ardından npm'in engellediği kurulum betikleriyle karşılaştım. En sonunda npm audit çıktısında onlarca güvenlik açığı görünce kullandığım araçları yeniden değerlendirmeye karar verdim.
Bu yazıda Node.js kurulumundan başlayarak yaşadığım sorunları ve sonunda Gulp tabanlı sistemi Sharp kullanarak nasıl sadeleştirdiğimi anlatacağım.
Arch Linux üzerinde Node.js ve npm kurulumu#
Öncelikle Arch Linux'un resmi depolarından Node.js ve npm paketlerini kurdum:
sudo pacman -S nodejs npm
Kurulumun ardından sürümleri kontrol ettim:
node --version
npm --version
Bu projedeki testlerimde kullandığım sürümler:
- Node.js:
v26.13.0 - npm:
12.2.1
Node.js ve npm kurulduktan sonra mevcut projeye geçtim:
cd ~/Projects/image-responsive
İlk sistem: Gulp ile görsel optimizasyonu#
Projenin ilk halinde farklı görevleri farklı Gulp eklentileri üstleniyordu.
Kullandığım paketler şunlardı:
gulp: Görevleri yönetmek için.gulp-rezzy: Görselleri farklı genişliklerde üretmek için.gulp-imagemin: Görselleri optimize etmek için.gulp-webp: WebP dönüşümü için.del: Eski çıktı klasörünü temizlemek için.
İlk package.json dosyamın temel bağımlılıkları şöyleydi:
{
"dependencies": {
"del": "^8.0.1",
"gulp": "^5.0.1",
"gulp-imagemin": "^9.2.0",
"gulp-rename": "^2.1.0",
"gulp-webp": "^5.1.0"
},
"devDependencies": {
"gulp-rezzy": "^3.0.0"
}
}
Gulp yapılandırmasında görselleri dört farklı boyutta hazırlıyordum:
rezzy([
{ width: 320, suffix: "-sm" },
{ width: 640, suffix: "-md" },
{ width: 1024, suffix: "-lg" },
{ width: 1920, suffix: "-xl" },
])
Böylece her kaynak görsel için şu dosyaların oluşmasını bekliyordum:
| Dosya eki | Genişlik |
|---|---|
-sm |
320 px |
-md |
640 px |
-lg |
1024 px |
-xl |
1920 px |
İlk bakışta gayet kullanışlı bir sistemdi. Ancak ilk build denemesinde bir sorunla karşılaştım.
İlk sorun: optipng-bin ENOENT hatası#
İlk build komutunu çalıştırdım:
npm run build
Gulp görevleri başlamasına rağmen görsel optimizasyonu sırasında şu hatayı aldım:
Error in plugin "gulp-imagemin"
Error: spawn .../node_modules/optipng-bin/vendor/optipng ENOENT
Buradaki ENOENT hatası, çalıştırılmak istenen dosyanın bulunamadığını gösteriyordu.
İlk kontrol ettiğim yer optipng-bin paketinin içindeki vendor klasörü oldu. Gerçekten de çalıştırılabilir OptiPNG dosyası bulunmuyordu.
Paketin package.json dosyasını incelediğimde şu kurulum betiğini gördüm:
{
"scripts": {
"postinstall": "node lib/install.js"
}
}
Yani paket kurulurken OptiPNG ikili dosyasının hazırlanması gerekiyordu.
Sorunun nedeni npm'in bazı bağımlılıkların kurulum betiklerini güvenlik politikası nedeniyle engellemesiydi.
npm install-scripts ve engellenen kurulum betikleri#
Paketleri yeniden kurduğumda npm şu uyarıyı verdi:
npm warn install-scripts
5 packages had install scripts blocked
because they are not covered by allowScripts:
Engellenen paketler şunlardı:
cwebp-bin
gifsicle
mozjpeg
optipng-bin
sharp
Bu paketler görsel işleme araçlarının ikili dosyalarını hazırlamak veya ilgili kurulum işlemlerini tamamlamak için kurulum betiklerine ihtiyaç duyuyordu.
npm'in bu betikleri engellemesi, paketlerin otomatik olarak çalıştırılmasını önleyen bir güvenlik önlemiydi. Ancak bu projede gerekli ikili dosyaların hazırlanamamasına neden olmuştu.
Paketleri inceleyip gerekli kurulum betiklerini onayladım:
npm install-scripts approve cwebp-bin
npm install-scripts approve gifsicle
npm install-scripts approve mozjpeg
npm install-scripts approve optipng-bin
npm install-scripts approve sharp
Ardından OptiPNG paketini yeniden oluşturdum:
npm rebuild optipng-bin --foreground-scripts
Kontrol:
node_modules/optipng-bin/vendor/optipng -v
OptiPNG sürüm bilgisini artık alabildiğim için ilk eksik ikili dosya sorununu çözmüştüm.
Ancak bu kez başka bir eksik dosyayla karşılaştım.
İkinci sorun: cwebp-bin ENOENT#
OptiPNG sorunu çözüldükten sonra yeniden build aldım:
npm run build
Bu kez hata gulp-webp tarafından geldi:
Error in plugin "gulp-webp"
Error: spawn .../node_modules/cwebp-bin/vendor/cwebp ENOENT
Bu da WebP dönüşümünde kullanılan cwebp ikili dosyasının bulunamadığını gösteriyordu.
Önceki sorundaki gibi burada da kurulum betiklerinin çalıştırılması gerekiyordu. Onay ve yeniden kurulum işlemlerinin ardından build başarılı oldu.
gulp-imagemin: Minified 4 images
(saved 1.82 MB - 30.3%)
Finished 'images'
İşlem tamamlandı! Resimler dist klasörüne kaydedildi.
Dört görselde toplam 1,82 MB tasarruf elde etmiştim. Gulp sistemi artık çalışıyordu.
Ancak asıl sorun henüz karşıma çıkmamıştı.
npm audit: 41 güvenlik açığı#
Çalışan bir projenin güvenli olduğu anlamına gelmediğini bir kez daha görmüş oldum.
Bağımlılıkları kontrol etmek için şu komutu çalıştırdım:
npm audit
İlk sonuç oldukça dikkat çekiciydi:
41 vulnerabilities
15 moderate
24 high
2 critical
Özellikle şu paketler dikkatimi çekti:
decompress: Arşiv çıkarma sırasında hedef klasör dışına dosya yazılmasına neden olabilen güvenlik açıkları.cross-spawn: Regular Expression Denial of Service (ReDoS) açığı.decode-uri-component: Hatalı kodlanmış girdilerle hizmet engelleme açıkları.fast-xml-parser: XML işleme kaynaklı güvenlik sorunları.sharp: Altındaki libvips, libheif ve librsvg bağımlılıklarıyla ilişkili güvenlik bildirimleri.svgo,uuid,minimatchve diğer dolaylı bağımlılıklar.
İlk olarak npm'in güvenli şekilde uygulayabildiği güncellemeleri denedim:
npm audit fix
Bu işlemden sonra sonuç şöyle değişti:
37 vulnerabilities
16 moderate
20 high
1 critical
Dört açık kapanmıştı. Fakat kalan sorunlar hâlâ önemliydi.
npm, bütün sorunları çözmek için şu komutu öneriyordu:
npm audit fix --force
Ancak bu komut gulp-imagemin@9.2.0 yerine gulp-imagemin@7.1.0 kurmayı öneriyordu. Bu da kırıcı bir sürüm değişikliğiydi.
Sırf audit sayısını düşürmek için çalışan bir projeyi geriye götürmek istemedim.
Bunun yerine hangi paketin hangi bağımlılığı getirdiğini araştırdım.
npm explain ile sorunlu bağımlılıkları bulmak#
Önce doğrudan kullandığım paketleri ve sürümlerini kontrol ettim:
npm ls gulp-imagemin gulp-webp gulp-rezzy sharp
Ardından güvenlik açığı bulunan paketlerin neden kurulduğunu inceledim:
npm explain decompress
npm explain sharp
Burada iki önemli bağımlılık zinciri ortaya çıktı.
Eski görüntü optimizasyon araçları#
decompress paketi tek başına projenin doğrudan bağımlılığı değildi.
Şu zincir üzerinden geliyordu:
gulp-webp
└── imagemin-webp
└── cwebp-bin
└── bin-build
└── decompress
Benzer zincirler gifsicle, mozjpeg ve optipng-bin paketlerinde de bulunuyordu.
Yani bir WebP dönüştürme işlemi için oldukça fazla dolaylı bağımlılık yüklemiştim.
Sharp sürümü ve gulp-rezzy#
Diğer sorun gulp-rezzy tarafından getirilen Sharp bağımlılığıydı:
gulp-rezzy@3.0.0
└── sharp@0.33.5
Audit çıktısında Sharp için de güvenlik bildirimleri bulunuyordu.
Burada artık yalnızca sürüm güncellemesi yapmak yerine kullandığım mimariyi değiştirmek daha mantıklı görünüyordu.
Gulp'tan Sharp'a geçiş#
Aslında projenin temel ihtiyaçları çok karmaşık değildi:
- Kaynak görselleri bulmak.
- Görselleri dört farklı genişlikte yeniden boyutlandırmak.
- WebP formatına dönüştürmek.
- Çıktıları
distklasörüne kaydetmek. - Önceki çıktıları temizlemek.
Bütün bunlar için ayrı ayrı Gulp eklentileri kullanmak zorunda değildim.
Bu nedenle görüntü işleme işini doğrudan Sharp'a taşıdım.
Önce eski bağımlılıkları kaldırdım:
npm uninstall del gulp gulp-imagemin gulp-rename gulp-webp gulp-rezzy
Ardından Sharp'ın güncel sürümünü kurdum:
npm install sharp@^0.35.5
Böylece görüntü işleme tarafında tek bir ana bağımlılığımız kaldı.
Yeni package.json:
{
"name": "image-responsive",
"version": "1.0.0",
"private": true,
"type": "module",
"description": "Resimleri farklı boyutlarda WebP formatına dönüştürme ve optimizasyon",
"scripts": {
"build": "node build.mjs"
},
"dependencies": {
"sharp": "^0.35.5"
}
}
Artık Gulp yapılandırması yerine doğrudan Node.js ile çalışan bir build.mjs dosyamız vardı.
Yeni build.mjs dosyası#
Yeni sistemde Sharp ile görselleri yeniden boyutlandırıyor ve WebP olarak kaydediyorum.
import sharp from 'sharp';
import {
readdir,
mkdir,
rm,
} from 'node:fs/promises';
import path from 'node:path';
const INPUT_DIR = 'src';
const OUTPUT_DIR = 'dist';
const SIZES = [
{ width: 320, suffix: '-sm' },
{ width: 640, suffix: '-md' },
{ width: 1024, suffix: '-lg' },
{ width: 1920, suffix: '-xl' },
];
const WEBP_OPTIONS = {
quality: 82,
effort: 5,
};
const SUPPORTED_FORMATS = new Set([
'.jpg',
'.jpeg',
'.png',
'.gif',
]);
async function findImages(directory) {
const entries = await readdir(directory, {
withFileTypes: true,
});
const files = [];
for (const entry of entries) {
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) {
files.push(...await findImages(fullPath));
} else if (
entry.isFile() &&
SUPPORTED_FORMATS.has(path.extname(entry.name).toLowerCase())
) {
files.push(fullPath);
}
}
return files;
}
async function processImage(inputPath) {
const relativePath = path.relative(INPUT_DIR, inputPath);
const outputDirectory = path.join(
OUTPUT_DIR,
path.dirname(relativePath),
);
const extension = path.extname(inputPath);
const filename = path.basename(inputPath, extension);
await mkdir(outputDirectory, { recursive: true });
for (const size of SIZES) {
const outputPath = path.join(
outputDirectory,
`${filename}${size.suffix}.webp`,
);
await sharp(inputPath, {
animated: true,
})
.rotate()
.resize({
width: size.width,
})
.webp(WEBP_OPTIONS)
.toFile(outputPath);
console.log(`✓ ${outputPath} (${size.width}px)`);
}
}
async function build() {
console.log('Eski dist klasörü temizleniyor...');
await rm(OUTPUT_DIR, {
recursive: true,
force: true,
});
const images = await findImages(INPUT_DIR);
if (images.length === 0) {
console.log('İşlenecek görsel bulunamadı.');
return;
}
console.log(`${images.length} görsel bulundu.\n`);
for (const image of images) {
await processImage(image);
}
console.log(
`\nİşlem tamamlandı! ${images.length * SIZES.length} WebP dosyası oluşturuldu.`,
);
}
build().catch((error) => {
console.error('\nBuild hatası:', error);
process.exitCode = 1;
});
Bu dosyanın yaptığı işlemler:
srcklasörünü ve alt klasörlerini tarar.- JPG, JPEG, PNG ve GIF dosyalarını bulur.
- Eski
distklasörünü temizler. - Her görseli dört farklı genişlikte üretir.
- Görselleri WebP formatına dönüştürür.
- Kaynak klasörün alt klasör yapısını çıktı klasöründe korur.
Burada önemli bir ayrıntı var: Dört boyutun da üretilmesini istiyoruz. Bu nedenle kaynak görselin genişliğine göre boyutları filtrelemiyoruz. Küçük bir görsel de gerektiğinde 1920 piksele büyütülerek kaydediliyor. Elbette büyütme, görsele yeni ayrıntı kazandırmıyor; yalnızca istenen boyutta çıktı üretiyor.
Sonuç: Daha az bağımlılık, daha temiz bir proje#
Yeni sistemi çalıştırdığımda:
npm run build
İlk testte görsel başarıyla işlendi ve WebP çıktıları üretildi.
Ardından bağımlılıkları kontrol ettim:
npm ls
Sonuç:
image-responsive@1.0.0
└── sharp@0.35.5
En önemlisi güvenlik denetimi:
npm audit
Çıktı:
found 0 vulnerabilities
Başlangıçta 41 güvenlik açığı bulunan, birden fazla görüntü işleme eklentisine bağımlı olan projemizi tek bir görüntü işleme motoruna indirdik.
| Özellik | Eski sistem | Yeni sistem |
|---|---|---|
| Görev yönetimi | Gulp 5 | Node.js |
| Boyutlandırma | gulp-rezzy | Sharp |
| Optimizasyon | gulp-imagemin | Sharp |
| WebP dönüşümü | gulp-webp | Sharp |
| Görüntü işleme bağımlılığı | Birden fazla paket | Sharp |
| Güvenlik denetimi | 41 açık | 0 açık |
Son söz#
Bu küçük proje bana bir kez daha şunu gösterdi: Bir işi yapabilmek için her zaman çok sayıda eklentiye ihtiyacımız yok.
Gulp kötü bir araç olduğu için bırakmadım. Benim kullanım senaryomda Gulp'ın sunduğu görev yönetimi ve ayrı görüntü işleme eklentileri artık gereksiz bir karmaşıklık oluşturuyordu.
Sharp ile aynı işi daha az bağımlılıkla yapabiliyorum. Üstelik npm güvenlik denetiminde de temiz bir sonuç elde ettim.
Bir projeyi yalnızca çalışıyor diye olduğu gibi bırakmak yerine, zaman zaman bağımlılık ağacını incelemek ve gerçekten ihtiyaç duyulmayan parçaları kaldırmak gerekiyor.
Kaynak kod: GitHub'daki image-responsive deposu
Not: Bu çalışma Arch Linux üzerinde Node.js 26.13.0 ve npm 12.2.1 ile gerçekleştirildi.