@vheemstra/vite-plugin-imagemin
Version:
A vite plugin for compressing image assets
5 lines (4 loc) • 14.5 kB
JavaScript
import c from"chalk";import Ce from"imagemin";import Fe from"is-apng";import{lstatSync as Le,readdirSync as Ae,unlinkSync as pe}from"node:fs";import{readFile as Ie,writeFile as je}from"node:fs/promises";import z from"node:path";import{performance as K}from"node:perf_hooks";import{createFilter as Te,normalizePath as V}from"vite";import ve from"node:crypto";import{mkdir as Se,readFile as ce,rm as xe,writeFile as Y}from"node:fs/promises";import{isAbsolute as Ee,resolve as ze}from"node:path";import{normalizePath as oe}from"vite";import{existsSync as me,mkdirSync as Pe,readFileSync as we,statSync as ke}from"node:fs";import{dirname as re,join as ie,parse as be,resolve as te}from"node:path";import{cwd as ye}from"node:process";var j=e=>typeof e=="function",T=e=>typeof e=="boolean",x=e=>typeof e=="string",R=e=>typeof e=="object"&&e!==null&&!Array.isArray(e);var ae=e=>Object.prototype.toString.call(e)==="[object RegExp]",U=e=>x(e)||ae(e)||Array.isArray(e)&&e.filter(t=>!x(t)&&!ae(t)).length==0,O=e=>e.replace(/[[\]{}()*+?.,/\\^$|#-]/g,"\\$&");function N(e,t=493){let r=/\/[^/]*$/;return Array.from(new Set(e.map(a=>a.replace(r,"")))).map(a=>[a,a.split("/").length]).sort((a,n)=>n[1]-a[1]).reduce((a,[n])=>(a.some(h=>h.startsWith(n))||a.push(n),a),[]).map(a=>(me(a)||Pe(a,{recursive:!0,mode:t}),a))}function ne(){let e="",t=te(ye()),{root:r}=be(t),a=te(t,r);for(;t&&t!==a&&t!==r;){e=ie(t,"package.json");try{if(ke(e,{throwIfNoEntry:!1})?.isFile())break}catch{}t=re(t)}return e&&re(e)}var se=e=>{let t=ie(e,"package.json");try{let r=we(t,"utf8");return JSON.parse(r).name}catch{return"file-cache"}};var F=!1,E="",ge="",H="",q=new Map,M=new Map;function he(e){return ve.createHash("md5").update(e).digest("hex")}function Re(e){return he(Object.entries(e).filter(([t])=>!["cache","clearCache","logByteDivider","logger","verbose"].includes(t)).sort(([t],[r])=>t.localeCompare(r)).map(([t,r],a)=>`${a}_${t}_${r}`).join("|"))}async function $e(e,t){ge=Re(t);let r=oe(ne()),a=se(r);x(t.cacheDir)?E=oe(Ee(t.cacheDir)?t.cacheDir:ze(e,t.cacheDir)):E=`${r}/node_modules/.cache/vite-plugin-imagemin`,E=E+`/${a}/`,t.clearCache&&await xe(E.slice(0,-1),{recursive:!0,force:!0}),F&&await Se(E.slice(0,-1),{recursive:!0})}async function De(){H=`${E}/contents-${ge}.json`;try{let e=JSON.parse(await ce(H,"utf-8"));M=new Map(Object.entries(e))}catch{M=new Map}q=new Map(M)}async function le({fileName:e,directory:t,stats:r,buffer:a,restoreTo:n}){if(F){if(!e)return{error:new Error("Missing filename")};let h=(t??E)+e;if(!a)try{a=await ce(h)}catch(i){return{error:i}}let s=he(a),g=q.get(h);if(g&&g.hash===s){if(n)try{await Y(n+e,a)}catch(i){return{error:i}}return{changed:!1,value:g}}M.set(h,{hash:s,oldSize:r?.oldSize??1,newSize:r?.newSize??1})}return{changed:!0}}var L={init:async(e,t)=>{F=e.cache!==!1,await $e(t,e),F&&await De()},prepareDirs:e=>{F&&N(e.map(t=>E+t))},checkAndUpdate:le,update:async({fileName:e,buffer:t,directory:r,stats:a})=>{if(!F)return!1;if(!e)return{error:new Error("Missing filename")};if(!t)return{error:new Error("Missing content for cache file")};let n=(r??E)+e;try{await Y(n,t)}catch(h){return{error:new Error(`Could not write cache file [${h.message}]`)}}return await le({fileName:e,directory:r,buffer:t,stats:a})},reconcile:async()=>{if(!F)return!0;try{return await Y(H,JSON.stringify(Object.fromEntries(M)),"utf-8"),q=new Map(M),!0}catch{return!1}}};var Q=e=>{let t=!1;if(e&&R(e)){let r=!1;t=Object.entries(e).reduce((a,[n,h])=>(typeof h=="function"?(a[n]=[h],r=!0):Array.isArray(h)&&(a[n]=h.filter(s=>j(s)),r=r||a[n].length>0),a),{}),r||(t=!1)}return t},Oe=e=>{if(!R(e))return!1;let t=Q(e.plugins);if(!t)return!1;let r=!1;if(e?.makeAvif&&R(e.makeAvif?.plugins)){let n=Q(e.makeAvif.plugins);n&&(r={plugins:n,formatFilePath:j(e.makeAvif.formatFilePath)?e.makeAvif.formatFilePath:h=>`${h}.avif`,skipIfLargerThan:e.makeAvif.skipIfLargerThan===!1||x(e.makeAvif.skipIfLargerThan)?e.makeAvif.skipIfLargerThan:"optimized"})}let a=!1;if(e?.makeWebp&&R(e.makeWebp?.plugins)){let n=Q(e.makeWebp.plugins);n&&(a={plugins:n,formatFilePath:j(e.makeWebp.formatFilePath)?e.makeWebp.formatFilePath:h=>`${h}.webp`,skipIfLargerThan:e.makeWebp.skipIfLargerThan===!1||x(e.makeWebp.skipIfLargerThan)?e.makeWebp.skipIfLargerThan:"optimized"})}return{root:x(e?.root)?e.root:void 0,include:U(e?.include)?e.include:[/\.(png|jpg|jpeg|gif|svg)$/i],exclude:U(e?.exclude)?e.exclude:[/node_modules/],onlyAssets:T(e?.onlyAssets)?e.onlyAssets:!1,formatFilePath:j(e?.formatFilePath)?e.formatFilePath:n=>n,skipIfLarger:T(e?.skipIfLarger)?e.skipIfLarger:!0,cache:T(e?.cache)?e.cache:!0,cacheDir:x(e?.cacheDir)?e.cacheDir:void 0,cacheKey:x(e?.cacheKey)?e.cacheKey:"",clearCache:T(e?.clearCache)?e.clearCache:!1,plugins:t,makeAvif:r,makeWebp:a,verbose:T(e?.verbose)?e.verbose:!0,logger:R(e?.logger)&&j(e?.logger?.info)?e.logger:!1,logByteDivider:e?.logByteDivider&&Number.isInteger(e?.logByteDivider)?e.logByteDivider:1e3}};function de(e,t){let r=[];try{Le(e).isDirectory()?Ae(e).forEach(n=>{r=r.concat(de(z.join(e,z.sep,n),t))}):r.push(e)}catch(a){t.error("Error: "+a?.message)}return r}function fe({oldPath:e,newPath:t,oldSize:r,newSize:a,duration:n,fromCache:h,precisions:s,bytesDivider:g,sizeUnit:i}){let P=a/r-1;return{oldPath:e,newPath:t,oldSize:r,newSize:a,ratio:P,duration:n,oldSizeString:`${(r/g).toFixed(s.size)} ${i}`,newSizeString:`${(a/g).toFixed(s.size)} ${i}`,ratioString:`${P>0?"+":P===0?" ":""}${(P*100).toFixed(s.ratio)} %`,durationString:h?"Cache":`${n.toFixed(s.duration)} ms`,fromCache:h,optimizedDeleted:!1,avifDeleted:!1,webpDeleted:!1}}async function Me({filePathFrom:e,fileToStack:t=[],baseDir:r="",precisions:a,bytesDivider:n,sizeUnit:h}){if(!e?.length)return Promise.reject({oldPath:e,newPath:"",error:"Empty filepath",errorType:"error"});if(!t?.length)return Promise.reject({oldPath:e,newPath:"",error:"Empty to-stack",errorType:"error"});let s,g=0;try{s=await Ie(r+e),g=s.byteLength}catch(d){return Promise.reject({oldPath:e,newPath:"",error:`Error reading file [${d.message}]`,errorType:"error"})}if(e.match(/\.a?png$/i)&&Fe(s))return Promise.reject({oldPath:e,newPath:"",error:"Animated PNGs not supported",errorType:"skip"});let i=await L.checkAndUpdate({fileName:e,directory:r,buffer:s,restoreTo:!1}),P=!!(i?.error||i?.changed),b=K.now();return Promise.allSettled(t.map(async d=>{let l=d.toPath;if(!P){let v=await L.checkAndUpdate({fileName:l,restoreTo:r});if(!v?.error&&!v?.changed)return Promise.resolve(fe({oldPath:e,newPath:l,oldSize:v?.value?.oldSize??1,newSize:v?.value?.newSize??1,duration:0,fromCache:!0,precisions:a,bytesDivider:n,sizeUnit:h}))}let w,y=0;try{w=await Ce.buffer(s,{plugins:d.plugins}),y=w.byteLength}catch(v){return Promise.reject({oldPath:e,newPath:l,error:`Error processing file:
${v?.message??v}`,errorType:"error"})}if(y<=g||e!==l||d.skipIfLarger===!1)try{await je(r+l,w)}catch(v){return Promise.reject({oldPath:e,newPath:l,error:`Error writing file [${v.message}]`,errorType:"error"})}return await L.update({fileName:l,buffer:w,stats:{oldSize:g,newSize:y}}),Promise.resolve(fe({oldPath:e,newPath:l,oldSize:g,newSize:y,duration:K.now()-b,fromCache:!1,precisions:a,bytesDivider:n,sizeUnit:h}))}))}function We(e){let t={from:0,to:0},r={oldPath:0,newPath:0,oldSize:0,newSize:0,ratio:0,duration:0},a={},n={};return e.forEach(h=>{let s;h.status==="fulfilled"?h.value.forEach(g=>{g.status==="fulfilled"?(s=g.value,a[s.oldPath]||(a[s.oldPath]=[]),a[s.oldPath].push({...s}),t.from+=s.oldSize,t.to+=s.newSize,r.oldSize=Math.max(r.oldSize,s.oldSizeString.length),r.newSize=Math.max(r.newSize,s.newSizeString.length),r.ratio=Math.max(r.ratio,s.ratioString.length),r.duration=Math.max(r.duration,s.durationString.length)):(s=g.reason,n[s.oldPath]||(n[s.oldPath]=[]),n[s.oldPath].push({...s})),r.oldPath=Math.max(r.oldPath,s.oldPath.length),r.newPath=Math.max(r.newPath,s.newPath.length)}):(s=h.reason,n[s.oldPath]||(n[s.oldPath]=[]),n[s.oldPath].push({...s}),r.oldPath=Math.max(r.oldPath,s.oldPath.length))}),{totalSize:t,maxLengths:r,processedFiles:a,erroredFiles:n}}function Be(e,t,r){let a=[" \u2514\u2500 "," \u251C\u2500 "],n=a[0].length,h=2,s=Math.max(r.oldPath,r.newPath+n),g=Math.max(r.oldSize,r.newSize);e.forEach((i,P,b)=>{let d=z.basename(i.oldPath),l=z.basename(i.newPath),w=[" "];P===0&&(w.push(c.dim(i.oldPath.replace(new RegExp(`${O(d)}$`),"")),d," ".repeat(s-i.oldPath.length+h)," ".repeat(g-i.oldSizeString.length),i.oldSizeString,c.dim(" \u2502 ")),t.info(w.join("")),w=[" "]),w.push(c.dim(b.length===P+1?a[0]:a[1]),c.dim(i.newPath.replace(new RegExp(`${O(l)}$`),""))),i.optimizedDeleted=!l.match(/\.(webp|avif)$/i)&&i.optimizedDeleted,i.webpDeleted=l.endsWith(".webp")&&i.webpDeleted,i.avifDeleted=l.endsWith(".avif")&&i.avifDeleted,i.optimizedDeleted||i.webpDeleted||i.avifDeleted?w.push(i.fromCache?c.blue.dim(l):c.dim(l)," ".repeat(s-n-i.newPath.length+h)," ".repeat(g-i.newSizeString.length),c.dim(i.newSizeString),c.dim(" \u2502 ")," ".repeat(r.ratio-i.ratioString.length),c.dim(i.ratioString),c.dim(` \u2502 Skipped \u2502 Larger than ${i.optimizedDeleted||i.webpDeleted||i.avifDeleted}`)):w.push(i.ratio<0?i.fromCache?c.blue(l):c.green(l):i.ratio>0?c.yellow(l):l," ".repeat(s-n-i.newPath.length+h)," ".repeat(g-i.newSizeString.length),c.dim(i.newSizeString),c.dim(" \u2502 ")," ".repeat(r.ratio-i.ratioString.length),i.ratio<0?c.green(i.ratioString):i.ratio>0?c.red(i.ratioString):i.ratioString,c.dim(" \u2502 ")," ".repeat(r.duration-i.durationString.length),c.dim(i.durationString)),t.info(w.join(""))})}function Ve(e,t,r){t.info("");let a=[" \u2514\u2500 "," \u251C\u2500 "],n=a[0].length,h=2,s=Math.max(r.oldPath,r.newPath+n);e.forEach((g,i,P)=>{let b=g.oldPath.length?z.basename(g.oldPath):"",d=g.newPath.length?z.basename(g.newPath):"",l=[" "];if(i===0&&(l.push(b.length?c.dim(g.oldPath.replace(new RegExp(`${O(b)}$`),""))+b:"(empty filepath)"),t.info(l.join("")),l=[" "]),P.length===1&&d.length===0)switch(l.push(c.dim(a[0])),g.errorType){case"skip":l.push(c.black.bgWhite(" SKIPPED ")," ",g.error);break;case"warning":l.push(c.bgYellow(" WARNING ")," ",c.yellow(g.error));break;default:l.push(c.bgRed(" ERROR ")," ",c.red(g.error));break}else switch(l.push(c.dim(P.length===i+1?a[0]:a[1]),d.length?c.dim(g.newPath.replace(new RegExp(`${O(d)}$`),""))+d:"(empty filepath)"," ".repeat(s-(d.length?g.newPath.length:16)+h)),g.errorType){case"skip":l.push(c.black.bgWhite(" SKIPPED ")," ",g.error);break;case"warning":l.push(c.bgYellow(" WARNING ")," ",c.yellow(g.error));break;default:l.push(c.bgRed(" ERROR ")," ",c.red(g.error));break}t.info(l.join(""))})}function Ne(e){let t=c.yellow("\u26A1")+c.cyan("vite-plugin-imagemin"),r=Oe(e);if(!r)throw new Error("Missing valid `plugins` option");let a,n,h,s,g=r.onlyAssets,i=r.verbose,P=r.formatFilePath,b=r.logByteDivider,d=b===1e3?"kB":"KiB";let l={info:()=>{},warn:()=>{},error:()=>{}},w=Te(r.include,r.exclude),y={size:2,ratio:2,duration:0},v=!1;return{name:"vite-plugin-imagemin",enforce:"post",apply:"build",configResolved:J=>{a=J,n=r.root||a.root,n=V(z.isAbsolute(n)?n:z.resolve(process.cwd(),n)),h=V(z.resolve(n,a.build.outDir)),s=V(z.resolve(h,a.build.assetsDir)),i&&(l=r.logger||a.logger)},async closeBundle(){let J=K.now();l.info(""),await L.init(r,n);let ue=g?s:h,W=`${n}/`,X=new RegExp(`^${O(W)}`),Z=de(ue,l).filter(w).map(o=>[V(o).replace(X,""),V(P(o)).replace(X,"")]);if(Z.length===0)return;let A={jpg:/\.jpe?g$/i,gif:/\.gif$/i,png:/\.png$/i,svg:/\.svg$/i},$={},B=[];Z.forEach(([o,f])=>{if($[o]=[],Object.keys(r.plugins).forEach(u=>{(A?.[u]&&A[u].test(o)||o.endsWith(`.${u}`))&&($[o].push({toPath:f,plugins:r.plugins[u],skipIfLarger:r.skipIfLarger}),B.push(f))}),R(r.makeAvif)){let u=r.makeAvif.formatFilePath,S=r.makeAvif.skipIfLargerThan;Object.entries(r.makeAvif.plugins).forEach(([p,k])=>{(A?.[p]&&A[p].test(o)||o.endsWith(`.${p}`))&&($[o].push({toPath:u(f),plugins:k,skipIfLarger:S}),B.push(u(f)))})}if(R(r.makeWebp)){let u=r.makeWebp.formatFilePath,S=r.makeWebp.skipIfLargerThan;Object.entries(r.makeWebp.plugins).forEach(([p,k])=>{(A?.[p]&&A[p].test(o)||o.endsWith(`.${p}`))&&($[o].push({toPath:u(f),plugins:k,skipIfLarger:S}),B.push(u(f)))})}$[o].length===0?delete $[o]:v=!0}),N(B.map(o=>W+o)),L.prepareDirs(B);let{totalSize:I,maxLengths:_,processedFiles:m,erroredFiles:ee}=await Promise.allSettled(Object.entries($).map(([o,f])=>Me({filePathFrom:o,fileToStack:f,baseDir:W,precisions:y,bytesDivider:b,sizeUnit:d}))).then(o=>We(o));if(v&&(l.info([t," processed these files:"].join("")),Object.keys(m).sort((o,f)=>o.localeCompare(f)).forEach(o=>{let f=m[o].findIndex(p=>!p.newPath.match(/\.(webp|avif)$/i));r.skipIfLarger&&f>=0&&m[o][f].ratio>0&&(m[o][f].optimizedDeleted="original");let u=m[o].findIndex(p=>p.newPath.endsWith(".webp"));if(r.makeWebp&&r.makeWebp.skipIfLargerThan&&u>=0){let p=m[o][u],k=!1;if(r.makeWebp.skipIfLargerThan==="smallest")k=m[o].slice().sort((C,G)=>C.ratio-G.ratio)[0].ratio<p.ratio;else if(r.makeWebp.skipIfLargerThan==="optimized"){let D=m[o].find(C=>C.newPath.replace(/\.webp$/,""));k=!!(D&&D.ratio<p.ratio)}k&&(pe(W+p.newPath),m[o][u].webpDeleted=r.makeWebp.skipIfLargerThan)}let S=m[o].findIndex(p=>p.newPath.endsWith(".avif"));if(r.makeAvif&&r.makeAvif.skipIfLargerThan&&S>=0){let p=m[o][S],k=!1;if(r.makeAvif.skipIfLargerThan==="smallest")k=m[o].slice().sort((C,G)=>C.ratio-G.ratio)[0].ratio<p.ratio;else if(r.makeAvif.skipIfLargerThan==="optimized"){let D=m[o].find(C=>C.newPath.replace(/\.avif$/,""));k=!!(D&&D.ratio<p.ratio)}k&&(pe(W+p.newPath),m[o][S].avifDeleted=r.makeAvif.skipIfLargerThan)}m[o].forEach(p=>{(p.optimizedDeleted||p.webpDeleted||p.avifDeleted)&&(I.from-=p.oldSize,I.to-=p.newSize)}),Be(m[o],l,_)}),L.reconcile(),Object.keys(ee).sort((o,f)=>o.localeCompare(f)).forEach(o=>{Ve(ee[o],l,_)}),i)){let o=`${(I.from/b).toFixed(y.size)}`,f=`${(I.to/b).toFixed(y.size)}`,u=`${(K.now()-J).toFixed(y.duration)}`,S=Math.max(o.length,f.length,u.length)+2,p=(I.to/I.from-1)*100,k=isNaN(p)?"0 %":p<0?c.green(`-${Math.abs(p).toFixed(y.ratio)} %`):p>0?c.red(`+${Math.abs(p).toFixed(y.ratio)} %`):`${Math.abs(p).toFixed(y.ratio)} %`;l.info(""),l.info([c.dim("Total size: ")," ".repeat(S-o.length),o,` ${d}`].join("")),l.info([c.dim("Minified size:")," ".repeat(S-f.length),f,` ${d}`," ",k].join("")),l.info([c.dim("Total time: "),u," ms"].join(""))}}}}export{Ne as default,fe as formatProcessedFile,de as getAllFiles,Ve as logErrors,Be as logResults,Oe as parseOptions,Q as parsePlugins,Me as processFile,We as processResults};
/* istanbul ignore next -- @preserve */
/* istanbul ignore else -- @preserve */