rsbuild-plugin-sharp-image-optimizer
Version:
A Rsbuild plugin for image optimization using Sharp
238 lines (237 loc) • 12.1 kB
JavaScript
;
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
});