feat(metrics): improved optimizers (#680)
This commit is contained in:
@@ -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`)
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user