diff --git a/CHANGELOG.md b/CHANGELOG.md index c0d998f6..6d3f8067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.14 + +- Refactor + ## 0.0.13 - Cleanup diff --git a/dist/index.d.ts b/dist/index.d.ts index dfd23c40..d360b5c6 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,7 +1,7 @@ import type { AstroIntegration } from "astro"; import Options from "./options"; /** - * It takes in an object of options, and returns an object with a name and a hook + * It takes in an object of options, and returns an object that Astro can use to create a plugin * @param {Options} integrationOptions - Options = {} * @returns A function that returns an object. */ diff --git a/dist/index.js b/dist/index.js index 62ea6708..f862728f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1 +1 @@ -import g from"fast-glob";import p from"fs";import*as h from"csso";import*as y from"html-minifier-terser";import{minify as v}from"terser";import d from"sharp";import w from"svgo";const u=async(s,i=2)=>{if(s===0)return"0 Bytes";const e=1024,a=i<0?0:i,t=["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"],r=Math.floor(Math.log(s)/Math.log(e));return`${parseFloat((s/e**r).toFixed(a))} ${t[r]}`},b=async(s,i={})=>{const e=s.options.input.file.split(".").pop();if(!e)return;const a={avci:"avif",avcs:"avif",avifs:"avif",heic:"heif",heics:"heif",heifs:"heif",jfif:"jpeg",jif:"jpeg",jpe:"jpeg",jpg:"jpeg"},t=typeof a[e]<"u"?a[e]:typeof i[e]<"u"?e:!1;if(["avif","gif","heif","jpeg","png","raw","tiff","webp"].includes(t)&&i[t]!==!1)return await s[t](i[t]).toBuffer()},j=async(s,i=2)=>{for(const e in s)if(Object.prototype.hasOwnProperty.call(s,e)){const a=s[e];if(!a)continue;switch(e){case"css":await l(`${s.path}**/*.css`,i,e,t=>h.minify(t,a).css);break;case"html":await l(`${s.path}**/*.html`,i,e,async t=>await y.minify(t,a));break;case"js":await l(`${s.path}**/*.{js,mjs,cjs}`,i,e,async t=>(await v(t,a)).code);break;case"img":await l(`${s.path}**/*.{avci,avcs,avif,avifs,gif,heic,heics,heif,heifs,jfif,jif,jpe,jpeg,jpg,png,raw,tiff,webp}`,i,e,async t=>await b(t,a),async t=>await d(t));break;case"svg":await l(`${s.path}**/*.svg`,i,e,async t=>w.optimize(t,a).data);break;default:break}}},l=async(s,i=2,e="",a=async r=>r,t=async r=>await p.promises.readFile(r,"utf-8"))=>{let r={files:await g(s),sizebefore:0},f={files:0,total:0};for(;r.files.length>0;){const o=r.files.shift();if(o)try{const n=(await p.promises.stat(o)).size;r.sizebefore+=n;const c=await a(await t(o));if(!c)continue;if(n>Buffer.byteLength(c)){await p.promises.writeFile(o,c,"utf-8");const m=(await p.promises.stat(o)).size;f.files++,f.total+=n-m,i>1&&console.info("\x1B[32mCompressed "+o.replace(/^.*[\\\/]/,"")+" for "+await u(n-m)+" ("+((n-m)/n*100).toFixed(2)+"% reduction).\x1B[39m")}}catch{console.log("Error: Cannot compress file "+o+"!")}}i>0&&console.info("\x1B[32mSuccessfully compressed a total of "+f.files+" "+e.toUpperCase()+" "+(f.files===1?"file":"files")+" for "+await u(f.total)+".\x1B[39m")};function A(s={}){var a;const e=Object.assign({path:"./dist/",css:{clone:!1,comments:!1,debug:!1,forceMediaMerge:!0,restructure:!0,sourceMap:!1},html:{caseSensitive:!0,collapseBooleanAttributes:!0,collapseInlineTagWhitespace:!1,collapseWhitespace:!0,conservativeCollapse:!1,continueOnParseError:!1,customAttrAssign:[],customAttrCollapse:"",customAttrSurround:[],customEventAttributes:[/^on[a-z]{3,}$/],decodeEntities:!1,html5:!0,ignoreCustomComments:[],ignoreCustomFragments:[],includeAutoGeneratedTags:!0,keepClosingSlash:!0,maxLineLength:null,minifyCSS:!0,minifyJS:!0,minifyURLs:!1,preserveLineBreaks:!1,preventAttributesEscaping:!1,processConditionalComments:!0,processScripts:["module"],quoteCharacter:"",removeAttributeQuotes:!0,removeComments:!0,removeEmptyAttributes:!0,removeEmptyElements:!1,removeOptionalTags:!1,removeRedundantAttributes:!0,removeScriptTypeAttributes:!0,removeStyleLinkTypeAttributes:!0,removeTagWhitespace:!0,sortAttributes:!0,sortClassName:!0,trimCustomFragments:!1,useShortDoctype:!1},js:{ecma:5,enclose:!1,keep_classnames:!1,keep_fnames:!1,ie8:!1,module:!1,safari10:!1,toplevel:!1},img:{avif:{chromaSubsampling:"4:4:4",effort:9},gif:{effort:10},heif:{chromaSubsampling:"4:4:4"},jpeg:{chromaSubsampling:"4:4:4",mozjpeg:!0,trellisQuantisation:!0,overshootDeringing:!0,optimiseScans:!0},png:{compressionLevel:9,palette:!0},raw:{},tiff:{compression:"lzw"},webp:{effort:6}},svg:{multipass:!0,js2svg:{indent:0,pretty:!1},plugins:["preset-default"]},logger:2},s);return e.path=(a=e.path)!=null&&a.endsWith("/")?e.path:`${e.path}/`,{name:"astro-compress",hooks:{"astro:config:done":async t=>{e.path=e.path?e.path:t.config.outDir.toString()},"astro:build:done":async()=>{await j(e,e.logger)}}}}export{A as default}; +import g from"fast-glob";import p from"fs";import*as h from"csso";import*as y from"html-minifier-terser";import{minify as v}from"terser";import d from"sharp";import w from"svgo";const u=async(s,i=2)=>{if(s===0)return"0 Bytes";const e=1024,a=i<0?0:i,t=["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"],o=Math.floor(Math.log(s)/Math.log(e));return`${parseFloat((s/e**o).toFixed(a))} ${t[o]}`},b=async(s,i={})=>{const e=s.options.input.file.split(".").pop();if(!e)return;const a={avci:"avif",avcs:"avif",avifs:"avif",heic:"heif",heics:"heif",heifs:"heif",jfif:"jpeg",jif:"jpeg",jpe:"jpeg",jpg:"jpeg"},t=typeof a[e]<"u"?a[e]:typeof i[e]<"u"?e:!1;if(["avif","gif","heif","jpeg","png","raw","tiff","webp"].includes(t)&&i[t]!==!1)return await s[t](i[t]).toBuffer()},j=async(s,i=2)=>{for(const e in s)if(Object.prototype.hasOwnProperty.call(s,e)){const a=s[e];if(!a)continue;switch(e){case"css":await l(`${s.path}**/*.css`,i,e,t=>h.minify(t,a).css);break;case"html":await l(`${s.path}**/*.html`,i,e,async t=>await y.minify(t,a));break;case"js":await l(`${s.path}**/*.{js,mjs,cjs}`,i,e,async t=>(await v(t,a)).code);break;case"img":await l(`${s.path}**/*.{avci,avcs,avif,avifs,gif,heic,heics,heif,heifs,jfif,jif,jpe,jpeg,jpg,png,raw,tiff,webp}`,i,e,async t=>await b(t,a),async t=>await d(t));break;case"svg":await l(`${s.path}**/*.svg`,i,e,async t=>w.optimize(t,a).data);break;default:break}}},l=async(s,i=2,e="",a=async o=>o,t=async o=>await p.promises.readFile(o,"utf-8"))=>{const o=await g(s),n={initial:0,files:0,total:0};for(const f of o)try{const r=(await p.promises.stat(f)).size;n.initial+=r;const c=await a(await t(f));if(!c)continue;if(r>Buffer.byteLength(c)){await p.promises.writeFile(f,c,"utf-8");const m=(await p.promises.stat(f)).size;n.files++,n.total+=r-m,i>1&&console.info("\x1B[32mCompressed "+f.replace(/^.*[\\\/]/,"")+" for "+await u(r-m)+" ("+((r-m)/r*100).toFixed(2)+"% reduction).\x1B[39m")}}catch{console.log("Error: Cannot compress file "+f+"!")}i>0&&console.info("\x1B[32mSuccessfully compressed a total of "+n.files+" "+e.toUpperCase()+" "+(n.files===1?"file":"files")+" for "+await u(n.total)+".\x1B[39m")};function A(s={}){var a;const e=Object.assign({path:"./dist/",css:{clone:!1,comments:!1,debug:!1,forceMediaMerge:!0,restructure:!0,sourceMap:!1},html:{caseSensitive:!0,collapseBooleanAttributes:!0,collapseInlineTagWhitespace:!1,collapseWhitespace:!0,conservativeCollapse:!1,continueOnParseError:!1,customAttrAssign:[],customAttrCollapse:"",customAttrSurround:[],customEventAttributes:[/^on[a-z]{3,}$/],decodeEntities:!1,html5:!0,ignoreCustomComments:[],ignoreCustomFragments:[],includeAutoGeneratedTags:!0,keepClosingSlash:!0,maxLineLength:null,minifyCSS:!0,minifyJS:!0,minifyURLs:!1,preserveLineBreaks:!1,preventAttributesEscaping:!1,processConditionalComments:!0,processScripts:["module"],quoteCharacter:"",removeAttributeQuotes:!0,removeComments:!0,removeEmptyAttributes:!0,removeEmptyElements:!1,removeOptionalTags:!1,removeRedundantAttributes:!0,removeScriptTypeAttributes:!0,removeStyleLinkTypeAttributes:!0,removeTagWhitespace:!0,sortAttributes:!0,sortClassName:!0,trimCustomFragments:!1,useShortDoctype:!1},js:{ecma:5,enclose:!1,keep_classnames:!1,keep_fnames:!1,ie8:!1,module:!1,safari10:!1,toplevel:!1},img:{avif:{chromaSubsampling:"4:4:4",effort:9},gif:{effort:10},heif:{chromaSubsampling:"4:4:4"},jpeg:{chromaSubsampling:"4:4:4",mozjpeg:!0,trellisQuantisation:!0,overshootDeringing:!0,optimiseScans:!0},png:{compressionLevel:9,palette:!0},raw:{},tiff:{compression:"lzw"},webp:{effort:6}},svg:{multipass:!0,js2svg:{indent:0,pretty:!1},plugins:["preset-default"]},logger:2},s);return e.path=(a=e.path)!=null&&a.endsWith("/")?e.path:`${e.path}/`,{name:"astro-compress",hooks:{"astro:config:done":async t=>{e.path=e.path?e.path:t.config.outDir.toString()},"astro:build:done":async()=>{await j(e,e.logger)}}}}export{A as default}; diff --git a/package.json b/package.json index 44eeb73e..d5f85341 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "astro-compress", - "version": "0.0.13", + "version": "0.0.14", "type": "module", "description": "🗜️ AstroJS compression utilities. Compress HTML, CSS, JavaScript and more.", "repository": { diff --git a/src/index.ts b/src/index.ts index d07d79b8..ceeb9e3b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,8 +19,7 @@ import svgoMinify from "svgo"; * readable way * @param {number} bytes - The number of bytes to format. * @param [decimals=2] - The number of decimals to show. - * @returns the size of the file in bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes, - * exabytes, zettabytes, or yottabytes. + * @returns A function that takes two parameters, bytes and decimals. */ const formatBytes = async (bytes: number, decimals = 2) => { if (bytes === 0) return "0 Bytes"; @@ -36,7 +35,7 @@ const formatBytes = async (bytes: number, decimals = 2) => { /** * It takes a sharp file and an options object, and returns a buffer of the file if the file type is - * valid and the options object has a valid option for the file type + * valid and the options object has a valid option for that file type * @param {any} sharpFile - The sharp file object * @param {IMG} options - IMG = {} * @returns A function that takes two arguments, sharpFile and options. @@ -90,8 +89,8 @@ const sharp = async (sharpFile: any, options: IMG = {}) => { }; /** - * It takes a settings object, loops through each key, and then runs the appropriate function for each - * key + * It takes a settings object, loops through each key, and calls the appropriate function to minify the + * files * @param {Options} settings - Options - The settings object. * @param {number} [debug=2] - 0 = no output, 1 = output file names, 2 = output file names and sizes */ @@ -161,12 +160,11 @@ const pipeAll = async (settings: Options, debug: number = 2) => { /** * It takes a glob, a debug level, a type, a write function, and a read function, and then it - * compresses all the files that match the glob using the write function, and then it prints out how - * much it compressed the files by using the read function + * compresses all the files that match the glob using the write function, and then it logs the results + * to the console using the debug level, type, and read function * @param {string} glob - The glob pattern to search for files. - * @param {number} debug - 0 = no output, 1 = output only when files are compressed, 2 = output for - * every file - * @param {string} type - The type of file you're compressing. + * @param {number} [debug=2] - The level of debug output. 0 = none, 1 = summary, 2 = detailed. + * @param {string} [type] - The type of file you're compressing. This is used for the console output. * @param write - (data: string) => Promise = async (data) => data, * @param read - (file: string) => Promise = async (file) => */ @@ -175,63 +173,57 @@ const parse = async ( debug: number = 2, type: string = "", write: (data: string) => Promise = async (data) => data, - read: (file: string) => Promise = async (file) => + read: (file: string) => Promise = async (file) => await fs.promises.readFile(file, "utf-8") ) => { - let pipe = { - files: await FastGlob(glob), - sizebefore: 0, - }; + const files = await FastGlob(glob); - let savings = { + const savings = { + initial: 0, files: 0, total: 0, }; - while (pipe.files.length > 0) { - const file = pipe.files.shift(); - - if (file) { - try { - const fileSizeBefore = (await fs.promises.stat(file)).size; - pipe.sizebefore += fileSizeBefore; + for (const file of files) { + try { + const fileSizeBefore = (await fs.promises.stat(file)).size; + savings.initial += fileSizeBefore; - const writeBuffer = await write(await read(file)); + const writeBuffer = await write(await read(file)); - if (!writeBuffer) { - continue; - } + if (!writeBuffer) { + continue; + } - if (fileSizeBefore > Buffer.byteLength(writeBuffer)) { - await fs.promises.writeFile(file, writeBuffer, "utf-8"); - - const fileSizeAfter = (await fs.promises.stat(file)).size; - - savings.files++; - savings.total += fileSizeBefore - fileSizeAfter; - - if (debug > 1) { - console.info( - "\u001b[32mCompressed " + - file.replace(/^.*[\\\/]/, "") + - " for " + - (await formatBytes( - fileSizeBefore - fileSizeAfter - )) + - " (" + - ( - ((fileSizeBefore - fileSizeAfter) / - fileSizeBefore) * - 100 - ).toFixed(2) + - "% reduction)" + - ".\u001b[39m" - ); - } + if (fileSizeBefore > Buffer.byteLength(writeBuffer)) { + await fs.promises.writeFile(file, writeBuffer, "utf-8"); + + const fileSizeAfter = (await fs.promises.stat(file)).size; + + savings.files++; + savings.total += fileSizeBefore - fileSizeAfter; + + if (debug > 1) { + console.info( + "\u001b[32mCompressed " + + file.replace(/^.*[\\\/]/, "") + + " for " + + (await formatBytes( + fileSizeBefore - fileSizeAfter + )) + + " (" + + ( + ((fileSizeBefore - fileSizeAfter) / + fileSizeBefore) * + 100 + ).toFixed(2) + + "% reduction)" + + ".\u001b[39m" + ); } - } catch (error) { - console.log("Error: Cannot compress file " + file + "!"); } + } catch (error) { + console.log("Error: Cannot compress file " + file + "!"); } } @@ -251,7 +243,7 @@ const parse = async ( }; /** - * It takes in an object of options, and returns an object with a name and a hook + * It takes in an object of options, and returns an object that Astro can use to create a plugin * @param {Options} integrationOptions - Options = {} * @returns A function that returns an object. */