이미지 압축 자동화에 이어.. 포맷도 자동으로 바꿔보자!
기본적으로 설치해야하는 것들은 이미지 압축 자동화와 같다. (node.js/ npm 설치부터 imagemin-sharp 설치까지)
기본적으로 node js와 npm 설치가 필요하다.
npm install
npm init -y
💡 -y : default 값으로 설정된 package.json 추가
imagemin과 압축/포맷 변경에 사용할 imagemin-sharp, imagemin-webp를 설치한다.
npm install imagemin
npm install imagemin-sharp --save
npm install imagemin-webp
지난번 이미지 압축 자동화때 만들었던 js파일을 개선했다. if문을 추가해 imagemin-sharp 와 imagemin-webp 플러그인을 한 파일에서 컨트롤 할 수 있도록 했다.
import imagemin from 'imagemin';
import imageminSharp from 'imagemin-sharp';
import imageminWebp from 'imagemin-webp';
async function optimizeImages(folderName, plugin) {
const INPUT = `imgs/${folderName}/*.{jpg,png}`;
const OUTPUT = `imgs/${folderName}`;
let plugins;
if (plugin === 'sharp') {
plugins = [
imageminSharp({
chainSharp: async (sharp) => {
return sharp;
},
}),
];
console.log(`✨ ${folderName}의 이미지 압축이 완료되었습니다. ✨`);
} else if (plugin === 'webp') {
const webpOutput = `imgs/${folderName}/webp`;
const convertedFiles = await imagemin([INPUT], {
destination: webpOutput,
plugins: [
imageminWebp({
quality: 75,
}),
],
});
console.log(`💎 ${folderName}의 WebP 폴더 생성이 완료되었습니다. 💎`);
} else {
console.error(`🚨 Error: Invalid plugin specified.`);
return;
}
}
if ((process.argv[2] === 'build:sharp' || process.argv[2] === 'build:webp') && process.argv.length >= 4) {
const folderName = process.argv[3];
let plugin;
if (process.argv[2] === 'build:sharp') {
plugin = 'sharp';
} else if (process.argv[2] === 'build:webp') {
plugin = 'webp';
}
optimizeImages(folderName, plugin).catch((error) => {
console.error(`🚨 Error optimizing images for ${folderName} using ${plugin} plugin:`, error);
});
} else {
console.error('🚧 Error: 폴더명이 누락되었습니다.');
}
생각하지 못했던 부분에서 문제를 발견했다.
입력한 폴더의 하위 폴더에있는 이미지의 경우 압축과 포맷 변환이 이루어지지 않았다. path
모듈을 추가해 경로를 변경했다.
그런데.. 이 부분이 생각보다 간단하게 수정되지 않았다.. 단순하게 경로만 변경해서 되는 것이 아니었다...
폴더 내 모든 하위 폴더와 파일을 탐색하기 위해서는 재귀함수
를 써야한다고 한다.
재귀함수란..간단하게 자신을 재참조하는 함수
를 말한다고 한다.
그러니까 대략..
1. 각 항목이 폴더인지 파일인지 확인하고
2. 파일인 경우는 sharp(또는 webp) 실행
3. 폴더일 경우 재귀함수 실행(해당 폴더 내 하위 폴더와 파일을 탐색하기 위해)
이런 과정을 거치는듯 하다..
아닐시 알려주세요 제발제발..
import imagemin from 'imagemin';
import imageminSharp from 'imagemin-sharp';
import imageminWebp from 'imagemin-webp';
import fs from 'fs';
import path from 'path';
async function optimizeImages(folderPath, plugin) {
const files = fs.readdirSync(folderPath);
for (const file of files) {
const filePath = path.join(folderPath, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
await optimizeImages(filePath, plugin);
} else {
const ext = path.extname(file).toLowerCase();
if (!['.jpg', '.png', '.jpeg'].includes(ext)) {
console.log(`⛔️ ${filePath}은 지원하지 않는 파일 형식입니다.`);
continue;
}
if (plugin === 'sharp') {
const convertedFiles = await imagemin([filePath], {
destination: folderPath,
plugins: [
imageminSharp({
chainSharp: async (sharp) => {
return sharp;
},
}),
],
});
console.log(`🪄 ${filePath}의 이미지 압축이 완료되었습니다.`);
} else if (plugin === 'webp') {
const webpOutput = path.join(folderPath, 'webp');
fs.mkdirSync(webpOutput, { recursive: true });
const webpFilePath = path.join(webpOutput, file);
await imagemin([filePath], {
destination: webpOutput,
plugins: [
imageminWebp({
quality: 75,
}),
],
});
console.log(`💎 ${filePath}의 WebP 변환 및 저장이 완료되었습니다.`);
}
}
}
}
그리하여.. 조금 장황하게 .. 바뀌게 되었다 😓
잘못된 폴더명을 입력했을 경우 알림 문구를 띄워주는 부분을 추가하고, 폴더명이 누락됐을 경우 띄워주는 알림 부분도 수정했다.
(나름대로 여러번의 수정 작업을 거쳤다...험난쓰)
function optimizeFolder(folderName, plugin) {
const folderPath = path.join('imgs', folderName);
if (!fs.existsSync(folderPath)) {
console.error(`🚧 Error: '${folderName}' 폴더가 존재하지 않습니다.`);
return;
}
optimizeImages(folderPath, plugin)
.then(() => {
console.log(`✨✨✨ ${folderName}의 이미지 변환 및 저장이 완료되었습니다. ✨✨✨`);
})
.catch((error) => {
console.error(`🚨 Error optimizing images in ${folderName} using ${plugin} plugin:`, error);
});
}
if ((process.argv[2] === 'build:sharp' || process.argv[2] === 'build:webp') && process.argv.length >= 4) {
const folderName = process.argv[3];
let plugin;
if (process.argv[2] === 'build:sharp') {
plugin = 'sharp';
} else if (process.argv[2] === 'build:webp') {
plugin = 'webp';
}
optimizeFolder(folderName, plugin);
} else {
console.error('🚧 Error: 폴더명이 누락되었습니다.');
}
{
"scripts": { // 실행 명령어
"sharp": "node --experimental-modules imgopt.mjs sharp",
"webp": "node --experimental-modules imgopt.mjs webp"
},
"dependencies": { // 패키지 추가
"imagemin": "^8.0.1",
"imagemin-sharp": "^1.0.6",
"imagemin-webp": "^8.0.0"
}
}
댓글을 통해 glob/globby라는걸 알게 되었고, 추가적으로 적용해보았다. 결과적으로 기존보다 훨씬 빨라진 느낌.. 초반에 있던..MAX_SIZE 알림도 추가했다.
globby를 사용하기 위해서는 설치가 필요하다.
npm install globby
참고:
https://github.com/isaacs/node-glob
https://www.npmjs.com/package/glob
https://github.com/imagemin/imagemin
https://github.com/sindresorhus/globby#globbing-patterns
import imagemin from 'imagemin';
import imageminSharp from 'imagemin-sharp';
import imageminWebp from 'imagemin-webp';
import fs from 'fs';
import path from 'path';
import {globby} from 'globby';
const MAX_SIZE = 5000;
let warnedFiles = []; // 초과하는 이미지 파일 이름을 저장할 배열
async function optimizeImages(files, folderPath, plugin) {
const promises = files.map(async (filePath) => {
const ext = path.extname(filePath).toLowerCase();
if (!['.jpg', '.png', '.jpeg'].includes(ext)) {
console.log(`⛔️ ${filePath}은 지원하지 않는 파일 형식입니다.`);
return;
}
if (plugin === 'sharp') {
try {
const result = await imagemin([filePath], {
destination: folderPath,
plugins: [
imageminSharp({
chainSharp: async (sharp) => {
const meta = await sharp.metadata();
if (meta.width > MAX_SIZE) {
const fileName = path.basename(filePath);
if (!warnedFiles.includes(fileName)) {
warnedFiles.push(fileName);
}
}
return sharp;
},
}),
],
});
console.log(`🪄 ${filePath}의 이미지 압축이 완료되었습니다.`);
return result;
} catch (error) {
console.error(`🚨 Error optimizing image:`, error);
}
} else if (plugin === 'webp') {
const webpOutput = path.join(folderPath, 'webp');
fs.mkdirSync(webpOutput, { recursive: true });
const webpFilePath = path.join(webpOutput, path.basename(filePath, ext) + '.webp');
try {
const result = await imagemin([filePath], {
destination: webpOutput,
plugins: [
imageminWebp({
quality: 75,
}),
],
});
console.log(`💎 ${filePath}의 WebP 변환 및 저장이 완료되었습니다.`);
return result;
} catch (error) {
console.error(`🚨 Error converting to WebP:`, error);
}
}
});
return Promise.all(promises);
}
function formatFileList(fileList) {
return fileList.map((fileName) => `'${fileName}'`).join(', ');
}
async function optimizeFolder(folderName, plugin) {
const folderPath = path.join('imgs', folderName);
if (!fs.existsSync(folderPath)) {
console.error(`🚧 Error: '${folderName}' 폴더가 존재하지 않습니다.`);
return;
}
const files = await globby(path.join(folderPath, '**/*.{jpg,png,jpeg}'));
try {
await optimizeImages(files, folderPath, plugin);
// 초과하는 이미지 파일 리스트 출력
if (warnedFiles.length > 0) {
const fileListString = formatFileList(warnedFiles);
console.warn(`🚨 주의 : width 값이 ${MAX_SIZE}px을 초과하는 이미지가 있습니다. (${fileListString})`);
}
} catch (error) {
console.error(`🚨 Error optimizing images:`, error);
}
}
if ((process.argv[2] === 'build:sharp' || process.argv[2] === 'build:webp') && process.argv.length >= 4) {
const folderName = process.argv[3];
let plugin;
if (process.argv[2] === 'build:sharp') {
plugin = 'sharp';
} else if (process.argv[2] === 'build:webp') {
plugin = 'webp';
}
optimizeFolder(folderName, plugin);
} else {
console.error('🚧 Error: 폴더명이 누락되었습니다.');
}
🐱: 최적화를 실무에 쉽고 빠르게 적용하기위해 몇가지 아이디어를 떠올려봤는데..
그 중 하나가 지난번 업데이트했던 이미지 용량 줄이기 자동화 이다.
비슷한 원리로 이미지 포맷 또한 (jpg/png -> webP) 자동으로 바꿀 수 있다면 편하겠다 싶었다. 사실.. 생각만 하고 막연히 그건 어렵겠지.. 싶었는데..
이게 되네...?
능력자님들은 이미 다 만들어둔 것이다.. 안 쓸 이유가 없지
플러그인을 적재적소에 사용하는 것도 능력이라고 생각합니다.
이 커스텀 된 플러그인 또한 한 번 사용해 보도록 하겠습니다.
감사합니다. (띠용 이게되네?)