From 490d3cf2e4dbb0bd52bbe41c1024c8a30db0a217 Mon Sep 17 00:00:00 2001
From: Simon Lecoq <22963968+lowlighter@users.noreply.github.com>
Date: Fri, 26 Nov 2021 05:15:36 +0100
Subject: [PATCH] feat(metrics): improved optimizers (#680)
---
source/app/metrics/index.mjs | 34 +++--------------
source/app/metrics/utils.mjs | 65 +++++++++++++++++++++++++++++++-
source/plugins/core/metadata.yml | 13 +++++--
3 files changed, 80 insertions(+), 32 deletions(-)
diff --git a/source/app/metrics/index.mjs b/source/app/metrics/index.mjs
index ebb3c986..736ab872 100644
--- a/source/app/metrics/index.mjs
+++ b/source/app/metrics/index.mjs
@@ -1,8 +1,6 @@
//Imports
import ejs from "ejs"
-import SVGO from "svgo"
import util from "util"
-import xmlformat from "xml-formatter"
import * as utils from "./utils.mjs"
//Setup
@@ -174,32 +172,12 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
if (q["config.gemoji"])
rendered = await imports.svg.gemojis(rendered, {rest})
//Optimize rendering
- if (!q.raw)
- rendered = xmlformat(rendered, {lineSeparator:"\n", collapseContent:true})
- if ((conf.settings?.optimize) && (!q.raw)) {
- console.debug(`metrics/compute/${login} > optimize`)
- if (experimental.has("--optimize")) {
- const {error, data:optimized} = await SVGO.optimize(rendered, {
- multipass:true,
- plugins:SVGO.extendDefaultPlugins([
- //Additional cleanup
- {name:"cleanupListOfValues"},
- {name:"removeRasterImages"},
- {name:"removeScriptElement"},
- //Force CSS style consistency
- {name:"inlineStyles", active:false},
- {name:"removeViewBox", active:false},
- ]),
- })
- if (error)
- throw new Error(`Could not optimize SVG: \n${error}`)
- rendered = optimized
- console.debug(`metrics/compute/${login} > optimize > success`)
- }
- else
- console.debug(`metrics/compute/${login} > optimize > this feature is currently disabled due to display issues (use --optimize flag in experimental features to force enable it)`)
-
- }
+ if ((conf.settings?.optimize === true) || (conf.settings?.optimize?.includes?.("css")))
+ rendered = await imports.svg.optimize.css(rendered)
+ if ((conf.settings?.optimize === true) || (conf.settings?.optimize?.includes?.("xml")))
+ rendered = await imports.svg.optimize.xml(rendered, q)
+ if ((conf.settings?.optimize === true) || (conf.settings?.optimize?.includes?.("svg")))
+ rendered = await imports.svg.optimize.svg(rendered, q, experimental)
//Verify svg
if (verify) {
console.debug(`metrics/compute/${login} > verify SVG`)
diff --git a/source/app/metrics/utils.mjs b/source/app/metrics/utils.mjs
index 16d1a92e..4b2f42b9 100644
--- a/source/app/metrics/utils.mjs
+++ b/source/app/metrics/utils.mjs
@@ -26,6 +26,11 @@ import emoji from "emoji-name-map"
import minimatch from "minimatch"
import crypto from "crypto"
import linguist from "linguist-js"
+import purgecss from "purgecss"
+import csso from "csso"
+import SVGO from "svgo"
+import xmlformat from "xml-formatter"
+
prism_lang()
//Exports
@@ -155,7 +160,7 @@ export function htmlunescape(string, u = {"&":true, "<":true, ">":true, '"':true
/**Chartist */
export async function chartist() {
- const css = ``
+ const css = ``
return (await nodechartist(...arguments))
.replace(/class="ct-chart-line">/, `class="ct-chart-line">${css}`)
}
@@ -510,6 +515,64 @@ export const svg = {
rendered = rendered.replace(new RegExp(emoji, "g"), gemoji)
return rendered
},
+ /**Optimizers */
+ optimize:{
+ /**CSS optimizer */
+ async css(rendered) {
+ //Extract styles
+ console.debug("metrics/svg/optimize/css > optimizing")
+ const regex = /`
+ return rendered.replace(cleaned, optimized)
+ },
+ /**XML optimizer */
+ async xml(rendered, {raw = false} = {}) {
+ console.debug("metrics/svg/optimize/xml > optimizing")
+ if (raw) {
+ console.debug("metrics/svg/optimize/xml > skipped as raw option is enabled")
+ return rendered
+ }
+ return xmlformat(rendered, {lineSeparator:"\n", collapseContent:true})
+ },
+ /**SVG optimizer */
+ async svg(rendered, {raw = false} = {}, experimental = new Set()) {
+ console.debug("metrics/svg/optimize/svg > optimizing")
+ if (raw) {
+ console.debug("metrics/svg/optimize/svg > skipped as raw option is enabled")
+ return rendered
+ }
+ if (!experimental.has("--optimize")) {
+ console.debug("metrics/svg/optimize/svg > this feature require experimental feature flag --optimize-svg")
+ return rendered
+ }
+ const {error, data:optimized} = await SVGO.optimize(rendered, {
+ multipass:true,
+ plugins:SVGO.extendDefaultPlugins([
+ //Additional cleanup
+ {name:"cleanupListOfValues"},
+ {name:"removeRasterImages"},
+ {name:"removeScriptElement"},
+ //Force CSS style consistency
+ {name:"inlineStyles", active:false},
+ {name:"removeViewBox", active:false},
+ ]),
+ })
+ if (error)
+ throw new Error(`Could not optimize SVG: \n${error}`)
+ return optimized
+ }
+ }
}
/**Wait */
diff --git a/source/plugins/core/metadata.yml b/source/plugins/core/metadata.yml
index e01779d5..763ad86d 100644
--- a/source/plugins/core/metadata.yml
+++ b/source/plugins/core/metadata.yml
@@ -106,8 +106,14 @@ inputs:
# Some templates may not support this option
optimize:
description: SVG optimization
- type: boolean
- default: yes
+ type: array
+ default: css, xml
+ format:
+ - comma-separated
+ values:
+ - css # Purge and minify CSS styles
+ - xml # Pretty-print XML
+ - svg # Optimize SVG with SVGO (experimental, require --optimize-svg flag)
# Setup additional templates from remote repositories
setup_community_templates:
@@ -280,13 +286,14 @@ inputs:
default: no
# Experimental features
+ # Note that no backward compatibility are guaranteed for these features
experimental_features:
description: Experimental features
type: array
format: space-separated
default: ""
values:
- - --optimize
+ - --optimize-svg
# Use mocked data to bypass external APIs
use_mocked_data: