UNPKG

@modern-js/builder-plugin-image-compress

Version:

Image compress plugin for modern-js/web-builder

109 lines (94 loc) 3.75 kB
import { Buffer } from 'buffer'; import type { webpack } from '@modern-js/builder-webpack-provider/types'; import Codecs from './shared/codecs'; import type { FinalOptions } from './types'; export const MODERN_JS_IMAGE_MINIMIZER_PLUGIN_NAME = '@modern-js/builder-plugin-image-compress/minimizer' as const; export interface MinimizedResult { source: webpack.sources.RawSource; } export class ModernJsImageMinimizerPlugin { // eslint-disable-next-line @typescript-eslint/typedef name = MODERN_JS_IMAGE_MINIMIZER_PLUGIN_NAME; options: FinalOptions; constructor(options: FinalOptions) { this.options = options; } async optimize( compiler: webpack.Compiler, compilation: webpack.Compilation, assets: Record<string, webpack.sources.Source>, ): Promise<void> { const cache = compilation.getCache(MODERN_JS_IMAGE_MINIMIZER_PLUGIN_NAME); const { RawSource } = compiler.webpack.sources; const { matchObject } = compiler.webpack.ModuleFilenameHelpers; const buildError = (error: unknown, file?: string, context?: string) => { const cause = error instanceof Error ? error : new Error(); const message = file && context ? `"${file}" in "${context}" from Modern.js Image Minimizer:\n${cause.message}` : cause.message; const ret = new compiler.webpack.WebpackError(message); error instanceof Error && ((ret as any).error = error); return ret; }; const codec = Codecs[this.options.use]; if (!codec) { compilation.errors.push( buildError(new Error(`Codec ${this.options.use} is not supported`)), ); } const opts = { ...codec.defaultOptions, ...this.options }; const handleAsset = async (name: string) => { const info = compilation.getAsset(name)?.info; const fileName = name.split('?')[0]; // 1. Skip double minimize assets from child compilation // 2. Test file by options (e.g. test, include, exclude) if (info?.minimized || !matchObject(opts, fileName)) { return; } const { source: inputSource } = compilation.getAsset(name)!; const eTag = cache.getLazyHashedEtag(inputSource); const cacheItem = cache.getItemCache(name, eTag); let result = await cacheItem.getPromise<MinimizedResult | void>(); try { if (!result) { const input = inputSource.source(); const buf = await codec.handler(Buffer.from(input), opts); result = { source: new RawSource(buf) }; await cacheItem.storePromise(result); } compilation.updateAsset(name, result.source, { minimized: true }); } catch (error) { compilation.errors.push(buildError(error, name, compiler.context)); } }; const promises = Object.keys(assets).map(name => handleAsset(name)); await Promise.all(promises); } apply(compiler: webpack.Compiler) { const handleCompilation = (compilation: webpack.Compilation) => { compilation.hooks.processAssets.tapPromise( { name: this.name, stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE, additionalAssets: true, }, assets => this.optimize(compiler, compilation, assets), ); compilation.hooks.statsPrinter.tap(this.name, stats => { stats.hooks.print .for('asset.info.minimized') .tap( '@modern-js/builder-plugin-image-compress', (minimized, { green, formatFlag }) => minimized && green && formatFlag ? green(formatFlag('minimized')) : '', ); }); }; compiler.hooks.compilation.tap(this.name, handleCompilation); } }