From 316d478cd17847e6daf3118bca9c351681df505f Mon Sep 17 00:00:00 2001 From: Marc RDWZ Date: Sun, 24 Dec 2023 02:46:46 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20Added=20output=20minification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/services/svg-service/index.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/domain/services/svg-service/index.ts b/src/domain/services/svg-service/index.ts index ca316c3..63f2d88 100644 --- a/src/domain/services/svg-service/index.ts +++ b/src/domain/services/svg-service/index.ts @@ -1,3 +1,4 @@ +const { optimize } = require('svgo'); import { Insights, PWAMetric } from "@domain/valueobjects/insights"; import { InsightCategory, @@ -81,9 +82,23 @@ export class SvgService { ); } + const result = optimize(baseSvgElement.outerHTML, { + multipass: true, + plugins: [ + { + name: "preset-default", + params: { + overrides: { + removeViewBox: false + } + } + } + ] + }); + // Write to file if (this.outputPath) { - fs.writeFileSync(this.outputPath, baseSvgElement.outerHTML); + fs.writeFileSync(this.outputPath, result.data); } return baseSvgElement.outerHTML; From 0e5abd2f4e9ec51ec92f93600cc942052c45bc6c Mon Sep 17 00:00:00 2001 From: Marc RDWZ Date: Mon, 25 Dec 2023 05:19:48 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9E=95=20Added=20svgo=20to=20dependencie?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 349 ++++++++++++++++++++- package.json | 3 +- src/domain/services/svg-service/index.ts | 370 +++++++++++------------ 3 files changed, 527 insertions(+), 195 deletions(-) diff --git a/package-lock.json b/package-lock.json index 50d3219..5e2510b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { "name": "psi-svg", - "version": "1.0.4", + "version": "1.0.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "psi-svg", - "version": "1.0.4", - "license": "ISC", + "version": "1.0.7", + "license": "Apache-2.0", "dependencies": { "@types/express": "^4.17.21", "@types/node": "^20.9.0", @@ -15,6 +15,7 @@ "express": "^4.18.2", "jsdom": "^22.1.0", "svg.js": "^2.7.1", + "svgo": "^3.1.0", "typescript": "^5.2.2", "winston": "^3.11.0", "yargs": "^17.7.2" @@ -126,6 +127,14 @@ "node": ">= 10" } }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -200,9 +209,9 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, "node_modules/@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", "dependencies": { "undici-types": "~5.26.4" } @@ -408,6 +417,11 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -595,6 +609,74 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" + }, "node_modules/cssstyle": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", @@ -691,6 +773,30 @@ "node": ">=8" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, "node_modules/domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -702,6 +808,33 @@ "node": ">=12" } }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", @@ -1315,6 +1448,11 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1432,6 +1570,17 @@ "node": ">=0.10.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/nwsapi": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", @@ -1497,6 +1646,11 @@ "node": ">=8" } }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -1826,6 +1980,14 @@ "node": ">=8" } }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -1888,6 +2050,38 @@ "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" }, + "node_modules/svgo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.1.0.tgz", + "integrity": "sha512-R5SnNA89w1dYgNv570591F66v34b3eQShpIBcQtZtM5trJwm1VvxbIoMpRYY3ybTAutcKTLEmTsdnaknOHbiQA==", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.2.1", + "css-what": "^6.1.0", + "csso": "5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -2371,6 +2565,11 @@ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" }, + "@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==" + }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -2445,9 +2644,9 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, "@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", "requires": { "undici-types": "~5.26.4" } @@ -2610,6 +2809,11 @@ "unpipe": "1.0.0" } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -2758,6 +2962,56 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "requires": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" + }, + "csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "requires": { + "css-tree": "~2.2.0" + }, + "dependencies": { + "css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "requires": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + } + }, + "mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" + } + } + }, "cssstyle": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", @@ -2829,6 +3083,21 @@ "path-type": "^4.0.0" } }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, "domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -2837,6 +3106,24 @@ "webidl-conversions": "^7.0.0" } }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, "dotenv": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", @@ -3290,6 +3577,11 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3367,6 +3659,14 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "requires": { + "boolbase": "^1.0.0" + } + }, "nwsapi": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", @@ -3417,6 +3717,11 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -3642,6 +3947,11 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -3689,6 +3999,27 @@ "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" }, + "svgo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.1.0.tgz", + "integrity": "sha512-R5SnNA89w1dYgNv570591F66v34b3eQShpIBcQtZtM5trJwm1VvxbIoMpRYY3ybTAutcKTLEmTsdnaknOHbiQA==", + "requires": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.2.1", + "css-what": "^6.1.0", + "csso": "5.0.5", + "picocolors": "^1.0.0" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + } + } + }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", diff --git a/package.json b/package.json index 887e529..bdac483 100644 --- a/package.json +++ b/package.json @@ -42,13 +42,14 @@ "express": "^4.18.2", "jsdom": "^22.1.0", "svg.js": "^2.7.1", + "svgo": "^3.1.0", "typescript": "^5.2.2", "winston": "^3.11.0", "yargs": "^17.7.2" }, "devDependencies": { - "tsc-alias": "^1.8.8", "ts-node": "^10.9.1", + "tsc-alias": "^1.8.8", "tsconfig-paths": "^4.2.0" }, "bin": { diff --git a/src/domain/services/svg-service/index.ts b/src/domain/services/svg-service/index.ts index 63f2d88..cd3d2f1 100644 --- a/src/domain/services/svg-service/index.ts +++ b/src/domain/services/svg-service/index.ts @@ -1,185 +1,185 @@ -const { optimize } = require('svgo'); -import { Insights, PWAMetric } from "@domain/valueobjects/insights"; -import { - InsightCategory, - Options, - getInsightCategoryText, -} from "@domain/valueobjects/options"; -const { JSDOM } = require("jsdom"); -const fs = require("fs"); -const path = require("path"); -require("dotenv").config(); - -export class SvgService { - private assetsDir: string; - private outputPath: string | undefined; - - constructor(outputPath?: string) { - const currentFileDir = path.dirname(__filename); - this.assetsDir = `${currentFileDir}/../../../../assets`; - - if (outputPath) { - this.outputPath = outputPath; - } - } - - public generateInsightsSvg(insights: Insights, options: Options): string { - const numericScores = insights.getNumericScoresByCategory(); - const baseXOffset = - 500 - - (Object.values(numericScores).length + (insights.getPWAScore() ? 1 : 0)) * - 100; - - // Create base SVG - const dom = new JSDOM(""); - const { document } = dom.window; - const baseSvgElement = document.createElement("svg"); - baseSvgElement.setAttribute("width", 1000); - baseSvgElement.setAttribute("height", options.getShowLegend() ? 330 : 200); - baseSvgElement.setAttribute("fill", "none"); - baseSvgElement.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); - baseSvgElement.setAttribute("xmlns", "http://www.w3.org/2000/svg"); - baseSvgElement.innerHTML += ``; - - // Add CSS - const baseStyle = fs.readFileSync( - `${this.assetsDir}/css/style.css`, - "utf8" - ); - const baseStyleElement = document.createElement("style"); - baseStyleElement.textContent = baseStyle; - baseSvgElement.innerHTML += baseStyleElement.outerHTML; - - // Add gauges - const gaugeSVGs: string[] = []; - Object.entries(numericScores).forEach(([category, score], i) => { - gaugeSVGs.push( - this.getGaugeSvg( - category as InsightCategory, - score, - baseXOffset + i * 200 - ) - ); - }); - const pwaScore = insights.getPWAScore(); - if (pwaScore) { - gaugeSVGs.push( - this.getPWASvg( - pwaScore, - baseXOffset + Object.values(numericScores).length * 200 - ) - ); - } - gaugeSVGs.forEach((metricSvg) => { - baseSvgElement.innerHTML += metricSvg; - }); - - // Add legend - if (options.getShowLegend()) { - baseSvgElement.innerHTML += fs.readFileSync( - `${this.assetsDir}/img/vector/legend.svg`, - "utf8" - ); - } - - const result = optimize(baseSvgElement.outerHTML, { - multipass: true, - plugins: [ - { - name: "preset-default", - params: { - overrides: { - removeViewBox: false - } - } - } - ] - }); - - // Write to file - if (this.outputPath) { - fs.writeFileSync(this.outputPath, result.data); - } - - return baseSvgElement.outerHTML; - } - - private getGaugeCssClass(score: number): string { - if (score >= 90) { - return "guage-green"; - } else if (score >= 50) { - return "guage-orange"; - } else if (score >= 0) { - return "guage-red"; - } - return "guage-undefined"; - } - - private getGaugeSvg( - category: InsightCategory, - score: number, - xOffset: number - ): string { - const gaugeSvg = fs.readFileSync( - `${this.assetsDir}/img/vector/gauge.svg`, - "utf8" - ); - const gaugeDom = new JSDOM(gaugeSvg, { contentType: "image/svg+xml" }); - const { document: gaugeDocument } = gaugeDom.window; - - const gaugeSVGElement = gaugeDocument.querySelector("svg"); - const gaugeClass = this.getGaugeCssClass(score); - gaugeSVGElement.setAttribute( - "class", - `${gaugeSVGElement.getAttribute("class")} ${gaugeClass}` - ); - gaugeSVGElement.setAttribute("x", xOffset); - - const scoreTextElement = gaugeDocument.querySelector("#score"); - scoreTextElement.textContent = score.toString(); - - const categoryTextElement = gaugeDocument.querySelector("#category"); - categoryTextElement.textContent = getInsightCategoryText(category); - - const scoreCircleElement = gaugeDocument.querySelector("#score-circle"); - scoreCircleElement.setAttribute( - "stroke-dasharray", - (score * 351.858) / 100 - ); - - return gaugeSVGElement.outerHTML; - } - - private getPWASvg(score: PWAMetric, xOffset: number): string { - let pwaSvgFileName; - switch (score) { - case PWAMetric.NA: - pwaSvgFileName = "na.svg"; - break; - case PWAMetric.RELIABLE: - pwaSvgFileName = "reliable.svg"; - break; - case PWAMetric.INSTALLABLE: - pwaSvgFileName = "installable.svg"; - break; - case PWAMetric.OPTIMIZED: - pwaSvgFileName = "optimized.svg"; - break; - case PWAMetric.ALL: - pwaSvgFileName = "all.svg"; - break; - } - - const pwaSvg = fs.readFileSync( - `${this.assetsDir}/img/vector/pwa/${pwaSvgFileName}`, - "utf8" - ); - const pwaDom = new JSDOM(pwaSvg, { contentType: "image/svg+xml" }); - const { document: pwaDocument } = pwaDom.window; - - const pwaSVGElement = pwaDocument.querySelector("svg"); - pwaSVGElement.setAttribute("x", xOffset); - - return pwaSVGElement.outerHTML; - } -} +import { optimize } from 'svgo'; +import { Insights, PWAMetric } from "@domain/valueobjects/insights"; +import { + InsightCategory, + Options, + getInsightCategoryText, +} from "@domain/valueobjects/options"; +const { JSDOM } = require("jsdom"); +const fs = require("fs"); +const path = require("path"); +require("dotenv").config(); + +export class SvgService { + private assetsDir: string; + private outputPath: string | undefined; + + constructor(outputPath?: string) { + const currentFileDir = path.dirname(__filename); + this.assetsDir = `${currentFileDir}/../../../../assets`; + + if (outputPath) { + this.outputPath = outputPath; + } + } + + public generateInsightsSvg(insights: Insights, options: Options): string { + const numericScores = insights.getNumericScoresByCategory(); + const baseXOffset = + 500 - + (Object.values(numericScores).length + (insights.getPWAScore() ? 1 : 0)) * + 100; + + // Create base SVG + const dom = new JSDOM(""); + const { document } = dom.window; + const baseSvgElement = document.createElement("svg"); + baseSvgElement.setAttribute("width", 1000); + baseSvgElement.setAttribute("height", options.getShowLegend() ? 330 : 200); + baseSvgElement.setAttribute("fill", "none"); + baseSvgElement.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); + baseSvgElement.setAttribute("xmlns", "http://www.w3.org/2000/svg"); + baseSvgElement.innerHTML += ``; + + // Add CSS + const baseStyle = fs.readFileSync( + `${this.assetsDir}/css/style.css`, + "utf8" + ); + const baseStyleElement = document.createElement("style"); + baseStyleElement.textContent = baseStyle; + baseSvgElement.innerHTML += baseStyleElement.outerHTML; + + // Add gauges + const gaugeSVGs: string[] = []; + Object.entries(numericScores).forEach(([category, score], i) => { + gaugeSVGs.push( + this.getGaugeSvg( + category as InsightCategory, + score, + baseXOffset + i * 200 + ) + ); + }); + const pwaScore = insights.getPWAScore(); + if (pwaScore) { + gaugeSVGs.push( + this.getPWASvg( + pwaScore, + baseXOffset + Object.values(numericScores).length * 200 + ) + ); + } + gaugeSVGs.forEach((metricSvg) => { + baseSvgElement.innerHTML += metricSvg; + }); + + // Add legend + if (options.getShowLegend()) { + baseSvgElement.innerHTML += fs.readFileSync( + `${this.assetsDir}/img/vector/legend.svg`, + "utf8" + ); + } + + const result = optimize(baseSvgElement.outerHTML, { + multipass: true, + plugins: [ + { + name: "preset-default", + params: { + overrides: { + removeViewBox: false + } + } + } + ] + }); + + // Write to file + if (this.outputPath) { + fs.writeFileSync(this.outputPath, result.data); + } + + return baseSvgElement.outerHTML; + } + + private getGaugeCssClass(score: number): string { + if (score >= 90) { + return "guage-green"; + } else if (score >= 50) { + return "guage-orange"; + } else if (score >= 0) { + return "guage-red"; + } + return "guage-undefined"; + } + + private getGaugeSvg( + category: InsightCategory, + score: number, + xOffset: number + ): string { + const gaugeSvg = fs.readFileSync( + `${this.assetsDir}/img/vector/gauge.svg`, + "utf8" + ); + const gaugeDom = new JSDOM(gaugeSvg, { contentType: "image/svg+xml" }); + const { document: gaugeDocument } = gaugeDom.window; + + const gaugeSVGElement = gaugeDocument.querySelector("svg"); + const gaugeClass = this.getGaugeCssClass(score); + gaugeSVGElement.setAttribute( + "class", + `${gaugeSVGElement.getAttribute("class")} ${gaugeClass}` + ); + gaugeSVGElement.setAttribute("x", xOffset); + + const scoreTextElement = gaugeDocument.querySelector("#score"); + scoreTextElement.textContent = score.toString(); + + const categoryTextElement = gaugeDocument.querySelector("#category"); + categoryTextElement.textContent = getInsightCategoryText(category); + + const scoreCircleElement = gaugeDocument.querySelector("#score-circle"); + scoreCircleElement.setAttribute( + "stroke-dasharray", + (score * 351.858) / 100 + ); + + return gaugeSVGElement.outerHTML; + } + + private getPWASvg(score: PWAMetric, xOffset: number): string { + let pwaSvgFileName; + switch (score) { + case PWAMetric.NA: + pwaSvgFileName = "na.svg"; + break; + case PWAMetric.RELIABLE: + pwaSvgFileName = "reliable.svg"; + break; + case PWAMetric.INSTALLABLE: + pwaSvgFileName = "installable.svg"; + break; + case PWAMetric.OPTIMIZED: + pwaSvgFileName = "optimized.svg"; + break; + case PWAMetric.ALL: + pwaSvgFileName = "all.svg"; + break; + } + + const pwaSvg = fs.readFileSync( + `${this.assetsDir}/img/vector/pwa/${pwaSvgFileName}`, + "utf8" + ); + const pwaDom = new JSDOM(pwaSvg, { contentType: "image/svg+xml" }); + const { document: pwaDocument } = pwaDom.window; + + const pwaSVGElement = pwaDocument.querySelector("svg"); + pwaSVGElement.setAttribute("x", xOffset); + + return pwaSVGElement.outerHTML; + } +}