feat(metrics): improved optimizers (#680)

This commit is contained in:
Simon Lecoq
2021-11-26 05:15:36 +01:00
committed by GitHub
parent 91ed522dce
commit 490d3cf2e4
3 changed files with 80 additions and 32 deletions

View File

@@ -1,8 +1,6 @@
//Imports //Imports
import ejs from "ejs" import ejs from "ejs"
import SVGO from "svgo"
import util from "util" import util from "util"
import xmlformat from "xml-formatter"
import * as utils from "./utils.mjs" import * as utils from "./utils.mjs"
//Setup //Setup
@@ -174,32 +172,12 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
if (q["config.gemoji"]) if (q["config.gemoji"])
rendered = await imports.svg.gemojis(rendered, {rest}) rendered = await imports.svg.gemojis(rendered, {rest})
//Optimize rendering //Optimize rendering
if (!q.raw) if ((conf.settings?.optimize === true) || (conf.settings?.optimize?.includes?.("css")))
rendered = xmlformat(rendered, {lineSeparator:"\n", collapseContent:true}) rendered = await imports.svg.optimize.css(rendered)
if ((conf.settings?.optimize) && (!q.raw)) { if ((conf.settings?.optimize === true) || (conf.settings?.optimize?.includes?.("xml")))
console.debug(`metrics/compute/${login} > optimize`) rendered = await imports.svg.optimize.xml(rendered, q)
if (experimental.has("--optimize")) { if ((conf.settings?.optimize === true) || (conf.settings?.optimize?.includes?.("svg")))
const {error, data:optimized} = await SVGO.optimize(rendered, { rendered = await imports.svg.optimize.svg(rendered, q, experimental)
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)`)
}
//Verify svg //Verify svg
if (verify) { if (verify) {
console.debug(`metrics/compute/${login} > verify SVG`) console.debug(`metrics/compute/${login} > verify SVG`)

View File

@@ -26,6 +26,11 @@ import emoji from "emoji-name-map"
import minimatch from "minimatch" import minimatch from "minimatch"
import crypto from "crypto" import crypto from "crypto"
import linguist from "linguist-js" import linguist from "linguist-js"
import purgecss from "purgecss"
import csso from "csso"
import SVGO from "svgo"
import xmlformat from "xml-formatter"
prism_lang() prism_lang()
//Exports //Exports
@@ -155,7 +160,7 @@ export function htmlunescape(string, u = {"&":true, "<":true, ">":true, '"':true
/**Chartist */ /**Chartist */
export async function chartist() { export async function chartist() {
const css = `<style>${await fs.readFile(paths.join(__module(import.meta.url), "../../../node_modules", "node-chartist/dist/main.css")).catch(_ => "")}</style>` const css = `<style data-optimizable="true">${await fs.readFile(paths.join(__module(import.meta.url), "../../../node_modules", "node-chartist/dist/main.css")).catch(_ => "")}</style>`
return (await nodechartist(...arguments)) return (await nodechartist(...arguments))
.replace(/class="ct-chart-line">/, `class="ct-chart-line">${css}`) .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) rendered = rendered.replace(new RegExp(emoji, "g"), gemoji)
return rendered return rendered
}, },
/**Optimizers */
optimize:{
/**CSS optimizer */
async css(rendered) {
//Extract styles
console.debug("metrics/svg/optimize/css > optimizing")
const regex = /<style data-optimizable="true">(?<style>[\s\S]*?)<\/style>/
const cleaned = "<!-- (optimized css) -->"
const css = []
while (regex.test(rendered)) {
const style = htmlunescape(rendered.match(regex)?.groups?.style ?? "")
rendered = rendered.replace(regex, cleaned)
css.push({raw:style})
}
const content = [{raw:rendered, extension:"html"}]
//Purge CSS
const purged = await new purgecss.PurgeCSS().purge({content, css})
const optimized = `<style>${csso.minify(purged.map(({css}) => css).join("\n")).css}</style>`
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 */ /**Wait */

View File

@@ -106,8 +106,14 @@ inputs:
# Some templates may not support this option # Some templates may not support this option
optimize: optimize:
description: SVG optimization description: SVG optimization
type: boolean type: array
default: yes 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 additional templates from remote repositories
setup_community_templates: setup_community_templates:
@@ -280,13 +286,14 @@ inputs:
default: no default: no
# Experimental features # Experimental features
# Note that no backward compatibility are guaranteed for these features
experimental_features: experimental_features:
description: Experimental features description: Experimental features
type: array type: array
format: space-separated format: space-separated
default: "" default: ""
values: values:
- --optimize - --optimize-svg
# Use mocked data to bypass external APIs # Use mocked data to bypass external APIs
use_mocked_data: use_mocked_data: