UNPKG

rsbuild-plugin-sharp-image-optimizer

Version:

A Rsbuild plugin for image optimization using Sharp

238 lines (237 loc) 12.1 kB
"use strict"; let __rslib_import_meta_url__ = 'undefined' == typeof document ? new (require('url'.replace('', ''))).URL('file:' + __filename).href : document.currentScript && document.currentScript.src || new URL('main.js', document.baseURI).href; var __webpack_require__ = {}; __webpack_require__.n = function(module) { var getter = module && module.__esModule ? function() { return module.default; } : function() { return module; }; return __webpack_require__.d(getter, { a: getter }), getter; }, __webpack_require__.d = function(exports1, definition) { for(var key in definition)__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key) && Object.defineProperty(exports1, key, { enumerable: !0, get: definition[key] }); }, __webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }, __webpack_require__.r = function(exports1) { 'undefined' != typeof Symbol && Symbol.toStringTag && Object.defineProperty(exports1, Symbol.toStringTag, { value: 'Module' }), Object.defineProperty(exports1, '__esModule', { value: !0 }); }; var __webpack_exports__ = {}; __webpack_require__.r(__webpack_exports__), __webpack_require__.d(__webpack_exports__, { sharpImageOptimizer: ()=>sharpImageOptimizer }); let external_sharp_namespaceObject = require("sharp"); var external_sharp_default = __webpack_require__.n(external_sharp_namespaceObject); let external_path_namespaceObject = require("path"); var external_path_default = __webpack_require__.n(external_path_namespaceObject); let external_fs_namespaceObject = require("fs"); var external_fs_default = __webpack_require__.n(external_fs_namespaceObject); let external_crypto_namespaceObject = require("crypto"); var external_crypto_default = __webpack_require__.n(external_crypto_namespaceObject); function _define_property(obj, key, value) { return key in obj ? Object.defineProperty(obj, key, { value: value, enumerable: !0, configurable: !0, writable: !0 }) : obj[key] = value, obj; } class CacheManager { ensureCacheDir() { !external_fs_default().existsSync(this.cacheDir) && external_fs_default().mkdirSync(this.cacheDir, { recursive: !0 }); } getCacheKey(inputBuffer, options) { let contentHash = external_crypto_default().createHash('md5').update(inputBuffer).digest('hex'), optionsHash = external_crypto_default().createHash('md5').update(JSON.stringify(options)).digest('hex'); return `${contentHash}-${optionsHash}`; } getCacheFilePath(cacheKey) { return external_path_default().join(this.cacheDir, `${cacheKey}.cache`); } parseSize(sizeStr) { let match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*([KMGT]?B)$/i); if (!match) return 1073741824; let value = parseFloat(match[1]); return value * (({ B: 1, KB: 1024, MB: 1048576, GB: 1073741824 })[match[2].toUpperCase()] || 1); } async cleanupCache() { try { let cacheFiles = external_fs_default().readdirSync(this.cacheDir).filter((file)=>file.endsWith('.cache')).map((file)=>{ let filePath = external_path_default().join(this.cacheDir, file), stats = external_fs_default().statSync(filePath); return { name: file, path: filePath, stats, timestamp: stats.mtime.getTime() }; }).sort((a, b)=>a.timestamp - b.timestamp), maxSize = this.parseSize(this.options.maxSize), currentSize = 0, now = Date.now(); for (let file of cacheFiles)currentSize += file.stats.size; for (let file of cacheFiles)now - file.timestamp > this.options.ttl && (external_fs_default().unlinkSync(file.path), currentSize -= file.stats.size); if (currentSize > maxSize) { for (let file of cacheFiles)if (external_fs_default().existsSync(file.path) && (external_fs_default().unlinkSync(file.path), (currentSize -= file.stats.size) <= maxSize)) break; } } catch (error) { console.warn('Failed to cleanup cache:', error); } } async get(inputBuffer, options) { if (!this.options.enabled) return null; try { let cacheKey = this.getCacheKey(inputBuffer, options), cacheFilePath = this.getCacheFilePath(cacheKey); if (external_fs_default().existsSync(cacheFilePath)) { let stats = external_fs_default().statSync(cacheFilePath); if (Date.now() - stats.mtime.getTime() > this.options.ttl) return external_fs_default().unlinkSync(cacheFilePath), null; return external_fs_default().readFileSync(cacheFilePath); } } catch (error) { console.warn('Failed to read cache:', error); } return null; } async set(inputBuffer, options, outputBuffer) { if (!!this.options.enabled) try { await this.cleanupCache(); let cacheKey = this.getCacheKey(inputBuffer, options), cacheFilePath = this.getCacheFilePath(cacheKey); external_fs_default().writeFileSync(cacheFilePath, outputBuffer); } catch (error) { console.warn('Failed to write cache:', error); } } constructor(options){ _define_property(this, "options", void 0), _define_property(this, "cacheDir", void 0), this.options = options, this.cacheDir = external_path_default().resolve(process.cwd(), options.dir), this.ensureCacheDir(); } } function rspack_plugins_define_property(obj, key, value) { return key in obj ? Object.defineProperty(obj, key, { value: value, enumerable: !0, configurable: !0, writable: !0 }) : obj[key] = value, obj; } class SharpImageOptimizerPlugin { async getOptimizationOptions(format) { let baseOptions = { quality: this.options.quality, effort: this.options.effort }; return 'jpeg' === format || 'jpg' === format ? { quality: this.options.quality } : baseOptions; } async optimizeImage(inputBuffer, format) { let outputBuffer; if (this.cacheManager) { let cacheOptions = this.getOptimizationOptions(format), cached = await this.cacheManager.get(inputBuffer, cacheOptions); if (cached) return cached; } let sharpInstance = external_sharp_default()(inputBuffer), options = this.getOptimizationOptions(format), method = { webp: ()=>sharpInstance.webp(options).toBuffer(), avif: ()=>sharpInstance.avif(options).toBuffer(), png: ()=>sharpInstance.png(options).toBuffer(), jpeg: ()=>sharpInstance.jpeg(options).toBuffer(), jpg: ()=>sharpInstance.jpeg(options).toBuffer() }[format]; if (!method) throw Error(`Unsupported format: ${format}`); return outputBuffer = await method(), this.cacheManager && await this.cacheManager.set(inputBuffer, options, outputBuffer), outputBuffer; } async optimize(compiler, compilation, assets) { let { RawSource } = compiler.webpack.sources, processedAssets = new Map(); for (let [name, asset] of Object.entries(assets)){ if (!!this.options.test.test(name)) try { let inputBuffer = asset.source(), ext = external_path_default().extname(name).toLowerCase(), originalFormat = ext.slice(1), newName = name; if (this.options.format && this.options.format !== originalFormat && !('jpg' === this.options.format && 'jpeg' === originalFormat) && !('jpeg' === this.options.format && 'jpg' === originalFormat)) { var _compilation_getAsset; newName = name.replace(ext, `.${this.options.format}`); let outputBuffer = await this.optimizeImage(inputBuffer, this.options.format); compilation.emitAsset(newName, new RawSource(outputBuffer), { ...null === (_compilation_getAsset = compilation.getAsset(name)) || void 0 === _compilation_getAsset ? void 0 : _compilation_getAsset.info, sourceFilename: name }), processedAssets.set(name, newName); } else { let outputBuffer = await this.optimizeImage(inputBuffer, originalFormat); compilation.updateAsset(name, new RawSource(outputBuffer), asset.info); } } catch (error) { compilation.errors.push(new compiler.webpack.WebpackError(`Failed to process image ${name}: ${error instanceof Error ? error.message : String(error)}`)); } } if (processedAssets.size > 0) { for (let [name, asset] of Object.entries(assets))if (name.endsWith('.js')) { let source = asset.source().toString(), modified = !1; for (let [oldName, newName] of processedAssets.entries()){ let oldPath = oldName.replace(/\\/g, '/'), newPath = newName.replace(/\\/g, '/'); source.includes(oldPath) && (source = source.replace(RegExp(oldPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newPath), modified = !0); } modified && compilation.updateAsset(name, new RawSource(source), asset.info); } for (let [oldName] of processedAssets)compilation.deleteAsset(oldName); } } apply(compiler) { compiler.hooks.compilation.tap('SharpImageOptimizerPlugin', (compilation)=>{ compilation.hooks.processAssets.tapPromise({ name: 'SharpImageOptimizerPlugin', stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE }, async (assets)=>this.optimize(compiler, compilation, assets)); }); } constructor(options = {}){ var _this_options_cache; rspack_plugins_define_property(this, "options", void 0), rspack_plugins_define_property(this, "cacheManager", null), this.options = { test: /\.(png|jpe?g)$/i, quality: 85, effort: 6, ...options }, (null === (_this_options_cache = this.options.cache) || void 0 === _this_options_cache ? void 0 : _this_options_cache.enabled) && (this.cacheManager = new CacheManager(this.options.cache)); } } let sharpImageOptimizer = (options = {})=>{ let { test = /\.(jpe?g|png|webp|avif)$/i, quality = 80, effort = 4, cache: userCache = {}, ...pluginOptions } = options, cache = { enabled: !0, dir: '.image-cache', maxSize: '1GB', ttl: 604800000, ...userCache }; return { name: 'rsbuild-plugin-sharp-image-optimizer', setup (api) { api.modifyBundlerChain((chain, { isProd })=>{ if (isProd) { var _config_output_distPath, _config_output; let imagePath = (null === (_config_output = api.getNormalizedConfig().output) || void 0 === _config_output ? void 0 : null === (_config_output_distPath = _config_output.distPath) || void 0 === _config_output_distPath ? void 0 : _config_output_distPath.image) ?? 'static/image'; chain.plugin('sharp-image-optimizer-plugin').use(SharpImageOptimizerPlugin, [ { test, quality, effort, cache, ...pluginOptions, imagePath } ]); } }); } }; }; var __webpack_export_target__ = exports; for(var __webpack_i__ in __webpack_exports__)__webpack_export_target__[__webpack_i__] = __webpack_exports__[__webpack_i__]; __webpack_exports__.__esModule && Object.defineProperty(__webpack_export_target__, '__esModule', { value: !0 });