Skip to content

Commit 803eb39

Browse files
committed
Add WebpackStaticImagesPlugin to handle static image processing
- Introduced WebpackStaticImagesPlugin to copy and convert static images to WebP format. - Configured plugin in plugins.js with input and output directories, quality settings, and console output options.
1 parent d8070be commit 803eb39

2 files changed

Lines changed: 132 additions & 0 deletions

File tree

config/plugins.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl
1414
const WebpackImageSizesPlugin = require('./webpack-image-sizes-plugin')
1515
const WebpackThemeJsonPlugin = require('./webpack-theme-json-plugin')
1616
const SpriteHashPlugin = require('./webpack-sprite-hash-plugin')
17+
const WebpackStaticImagesPlugin = require('./webpack-static-images-plugin')
1718

1819
module.exports = {
1920
get: function (mode) {
@@ -85,6 +86,12 @@ module.exports = {
8586
defaultImageFormat: 'jpg', // Generated image format (jpg, png, webp, avif)
8687
silence: true, // Suppress console output
8788
}),
89+
new WebpackStaticImagesPlugin({
90+
inputDir: 'src/img/static',
91+
outputDir: 'dist/images',
92+
quality: 80,
93+
silence: false, // Suppress console output
94+
}),
8895
]
8996

9097
if (mode === 'production') {
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
const fs = require('fs')
2+
const path = require('path')
3+
4+
// Try to require sharp, fallback gracefully if not available
5+
let sharp
6+
try {
7+
sharp = require('sharp')
8+
} catch (error) {
9+
console.warn('⚠️ Sharp not available. WebP conversion will be disabled.')
10+
}
11+
12+
/**
13+
* Webpack plugin to copy and automatically convert static images in a folder to WebP.
14+
*/
15+
class WebpackStaticImagesPlugin {
16+
/**
17+
* Creates an instance of WebpackStaticImagesPlugin.
18+
*
19+
* @param {Object} [options={}] - Configuration options
20+
* @param {string} [options.inputDir='src/img/static'] - Input directory
21+
* @param {string} [options.outputDir='dist/images'] - Output directory
22+
* @param {number} [options.quality=80] - WebP compression quality
23+
* @param {boolean} [options.silence=false] - Disable console output
24+
*/
25+
constructor(options = {}) {
26+
this.options = {
27+
inputDir: 'src/img/static',
28+
outputDir: 'dist/images',
29+
quality: 80,
30+
silence: false,
31+
...options,
32+
}
33+
}
34+
35+
/**
36+
* Logs a message to the console if silence option is not enabled.
37+
*/
38+
log(level, ...args) {
39+
if (!this.options.silence) {
40+
console[level](...args)
41+
}
42+
}
43+
44+
/**
45+
* Entry point for Webpack.
46+
*/
47+
apply(compiler) {
48+
if (!sharp) {
49+
return
50+
} // If Sharp is not installed, silently cancel.
51+
52+
// Use afterEmit to ensure that Webpack has created the dist folder.
53+
compiler.hooks.afterEmit.tapPromise('WebpackStaticImagesPlugin', async (compilation) => {
54+
const { context } = compiler
55+
const inputPath = path.resolve(context, this.options.inputDir)
56+
const outputPath = path.resolve(context, this.options.outputDir)
57+
58+
// Check if the source directory exists.
59+
if (!fs.existsSync(inputPath)) {
60+
this.log('warn', `⚠️ Source directory not found: ${inputPath}`)
61+
return
62+
}
63+
64+
// Create the output directory if it doesn't exist.
65+
if (!fs.existsSync(outputPath)) {
66+
fs.mkdirSync(outputPath, { recursive: true })
67+
}
68+
69+
try {
70+
this.log('log', '🔄 Starting static images processing...')
71+
await this.processImages(inputPath, outputPath)
72+
this.log('log', '🎉 Static images processing completed!')
73+
} catch (error) {
74+
this.log('error', '❌ Error during static images processing:', error)
75+
}
76+
})
77+
}
78+
79+
/**
80+
* Processes the images in the directory.
81+
*/
82+
async processImages(inputPath, outputPath) {
83+
const files = fs.readdirSync(inputPath)
84+
const promises = []
85+
let count = 0
86+
87+
for (const file of files) {
88+
// Only process JPG and PNG files.
89+
if (file.match(/\.(png|jpe?g)$/i)) {
90+
const filePath = path.join(inputPath, file)
91+
const fileName = path.parse(file).name
92+
93+
// Create the output paths.
94+
const outputOriginal = path.join(outputPath, file)
95+
const outputWebp = path.join(outputPath, `${fileName}.webp`)
96+
97+
// Prepare the Sharp instances.
98+
// (Sharp returns Promises, it is crucial to wait for them.)
99+
const copyPromise = sharp(filePath).toFile(outputOriginal)
100+
const webpPromise = sharp(filePath).webp({ quality: this.options.quality }).toFile(outputWebp)
101+
102+
// Group the two actions for this file.
103+
const filePromise = Promise.all([copyPromise, webpPromise])
104+
.then(() => {
105+
this.log('log', ` ✅ Converted: ${file} (+ .webp version)`)
106+
})
107+
.catch((err) => {
108+
this.log('error', ` ❌ Error on ${file}:`, err.message)
109+
})
110+
111+
promises.push(filePromise)
112+
count++
113+
}
114+
}
115+
116+
// Wait for all images to be generated before returning to Webpack.
117+
if (count > 0) {
118+
await Promise.all(promises)
119+
} else {
120+
this.log('log', ' ℹ️ No images to process in the directory.')
121+
}
122+
}
123+
}
124+
125+
module.exports = WebpackStaticImagesPlugin

0 commit comments

Comments
 (0)