vite-plugin-single-image-format
Version:
vite-plugin-single-image-format is a Vite/Rollup plugin that converts every raster asset in your build to a single output format – webp, png or avif. It can optionally re‑compress images that are already in the target format and automatically rewrites all
2 lines (1 loc) • 3.6 kB
JavaScript
const d=require("sharp");module.exports=function(S={}){const{format:w="webp",reencode:B=!1,webp:O={},png:N={},avif:q={},htmlSizeMode:y="add-only"}=S,E={quality:88,alphaQuality:90,smartSubsample:!0,...O},R={quality:80,compressionLevel:9,palette:!0,adaptiveFiltering:!0,...N},k={quality:60,lossless:!1,speed:5,...q},$=/\.(png|jpe?g|webp|gif|avif|heif|heic|tiff?|bmp|jp2)$/i,m=[".html",".css",".js",".mjs",".ts",".jsx",".tsx"],b="imgfmt=keep",g=new Map;function j(u,s){return new RegExp(`\\b${s}\\s*=`,"i").test(u)}function v(u){return u.replace(/\s+(?:width|height)\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi,"")}function z(u,s,f,p,h,e){const i=(function(l){const c=(function(o){return o.split(/[?#]/)[0]})(l);for(const o of g.keys())if(c.endsWith(o)||c.endsWith("./"+o)||c.endsWith("/"+o))return o;return null})(p);if(!i)return u;const t=g.get(i);if(!t)return u;if(y==="overwrite")return`<img ${v(s)}src=${f}${p}${f}${v(h)} width="${t.width}" height="${t.height}"${e}>`;const n=j(s+h,"width"),a=j(s+h,"height");return n&&a?u:`<img ${s}src=${f}${p}${f}${h}${n?"":` width="${t.width}"`}${a?"":` height="${t.height}"`}${e}>`}function W(u){return u.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function M(u){const[s,f=""]=u.split("#",2),p=s.startsWith("?"),h=p?s.slice(1):s;if(!h)return(p?"?":"")+(f?"#"+f:"");const e=h.split("&").filter(Boolean).filter(i=>{const[t,n=""]=i.split("=",2);return!(t==="imgfmt"&&n==="keep")});return(e.length?"?"+e.join("&"):"")+(f?"#"+f:"")}return{name:"vite-plugin-single-image-format",apply:"build",enforce:"post",async generateBundle(u,s){const f=new Map,p=new Set,h=[];for(const e of Object.values(s))e.type==="asset"&&m.some(i=>e.fileName.endsWith(i))&&h.push(e.source.toString());if(h.length>0)for(const[e,i]of Object.entries(s)){if(i.type!=="asset"||!$.test(e))continue;const t=`${e}?${b}`;h.some(n=>n.includes(t))&&p.add(e)}for(const[e,i]of Object.entries(s)){if(i.type!=="asset"||!$.test(e))continue;const t=(i.source,Buffer.from(i.source));if(p.has(e)){try{const r=await d(t).metadata();r.width&&r.height&&g.set(e,{width:r.width,height:r.height})}catch{process}continue}const n=e.toLowerCase().endsWith(`.${w}`);if(n&&!B){try{const r=await d(t).metadata();r.width&&r.height&&g.set(e,{width:r.width,height:r.height})}catch{process}continue}const a=w==="webp"?await d(t).webp(E).toBuffer():w==="png"?await d(t).png(R).toBuffer():await d(t).avif(k).toBuffer(),l=await d(a).metadata(),c=l.width&&l.height?{width:l.width,height:l.height}:void 0;if(n){i.source=a,c&&g.set(e,c);continue}const o=e.replace($,`.${w}`);s[o]?(c&&g.set(e,c),i.source=a):(this.emitFile({type:"asset",fileName:o,source:a}),f.set(e,o),c&&g.set(o,c),delete s[e])}if(f.size>0)for(const e of Object.values(s)){if(e.type!=="asset"||!m.some(t=>e.fileName.endsWith(t)))continue;let i=e.source.toString();for(const[t,n]of f){const a=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");i=i.replace(new RegExp(`([./]*)${a}`,"g"),(l,c)=>`${c}${n}`)}e.source=i}{const e=Array.from(p);if(e.length>0)for(const i of Object.values(s)){if(i.type!=="asset"||!m.some(n=>i.fileName.endsWith(n)))continue;let t=i.source.toString();for(const n of e){const a=new RegExp(`(${W(n)})(\\?[^"'\\s)><#]*?)?(#[^"'\\s)><]*)?`,"g");t=t.replace(a,(c,o,r,x)=>r?o+M((r||"")+(x||"")):o+(x??""));const l=new RegExp(`(${W(n)})\\?${b}(?![^"'\\s)><#])`,"g");t=t.replace(l,"$1")}i.source=t}}if(y!=="off"&&g.size>0)for(const e of Object.values(s)){if(e.type!=="asset"||!e.fileName.endsWith(".html"))continue;const i=/<img\s+([^>]*?)src=(["'])([^"']+)\2([^>]*?)(\/?)>/gi,t=e.source.toString().replace(i,(n,a,l,c,o,r)=>z(n,a,l,c,o,r));e.source=t}}}};
;