svgo
svgo copied to clipboard
Inline things in defs referenced only once
Since ids are mangled by default, they hardly can be referenced reliably outside. So it's worth inlining if we detect they're used only once (after removing invisible and other parts). Such references might happen pretty often even in hand-written code.
Yep, but I don't understand your point. What's next?
Sorry, I don't understand what you don't understand. I'm suggesting a feature for the tool.
Consider this example @GreLI.
This defs/mask/use structure is typical of some graphics programs output like Illustrator or Sketch.
With IDs:
<svg width="315" height="92" viewBox="0 0 315 92" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<rect id="a" x="7" y="5" width="301" height="31" rx="3"/>
<mask id="m" x="0" y="0" width="301" height="31" fill="#fff">
<use xlink:href="#a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd" opacity=".1">
<g stroke="#FFF">
<g>
<g opacity=".8">
<g>
<use mask="url(#m)" stroke-width="4" opacity=".4" xlink:href="#a"/>
</g>
</g>
</g>
</g>
</g>
</svg>
Optimized by inlining referenced element:
<svg width="315" height="92" viewBox="0 0 315 92" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g fill="none" fill-rule="evenodd" opacity=".1">
<g stroke="#FFF" opacity=".8">
<rect x="7" y="5" width="301" height="31" rx="3" stroke-width="4" opacity=".4"/>
</g>
</g>
</svg>
@themadcreator I like this idea a lot, but are you sure your example is correct? I believe Sketch uses the shape-with-mask pattern to recreate the stroke-position: inside setting which is not available in SVG. The mask is supposed to hide the part of the stroke that falls outside the shape. By removing the mask you are breaking this and effectively doubling the width stroke.
Here's a screenshot of the before and after:
Though that example was not great, this feature would still be neat for cases where the referenced element is truly only used once within the SVG.
Here's a snippet from one of mine:
<defs>
<path id="SVGID_1_" d="M8.9,12.3c0,0,2,0,2.8,0c1.8,0,3.6-3.4,3.6-5.8c0-3.2-2.5-5.8-5.6-5.8C7.5,0.7,5.5,2,4.6,4
C2.4,4.1,0.7,6,0.7,8.2c0,2.3,2.1,4.1,4,4.1C6.1,12.3,8.9,12.3,8.9,12.3z"/>
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" style="overflow:visible" />
</clipPath>
Can easily become
<defs>
<clipPath id="a">
<path d="M8.9 12.3h2.8c1.8 0 3.6-3.4 3.6-5.8 0-3.2-2.5-5.8-5.6-5.8C7.5.7 5.5 2 4.6 4 2.4 4.1.7 6 .7 8.2c0 2.3 2.1 4.1 4 4.1h4.2z"/>
</clipPath>
</defs>
I'm just starting to use SVGO and SVGs in my workflow, but I'm seeing a number of examples like this exported from Sketch with <mask>
, <clipPath>
and other elements.
I just found out firefox has some issues with rendering SVGs that use xlink:href
on pages with a <base>
tag. This kind of automatic refactor would be really killer for helping with those bugs.
See this codepen for example: http://codepen.io/anon/pen/oWgMov (renders on chrome, but not ff) And this firefox bug (marked fixed, but I think there is still an issue): https://bugzilla.mozilla.org/show_bug.cgi?id=652991
@ProdigySim: Is this related?: https://github.com/svg/svgo/pull/733
@GreLI I'd really love a feature like this, currently, I'm working with a designer who uses masks to apply colors to icons in sketch and during the build, I'd like to remove them because they're essentially useless to me and causing display issues in production.
specifically in this case:
<svg
width="60"
height="60"
viewBox="0 0 60 60"
xmlns:link="http://www.w3.org/1999/xlink"
>
<defs>
<path
d="M20.588 26L19.1 22.424h-7.2L10.412 26h-2.52l7.416-17.04h.336L23.084 26h-2.496zm-7.776-5.784l2.688-6.48 2.688 6.48h-5.376zM47.744 34.2v.288L40.64 48.792h6.864V51H37.256v-.336l7.152-14.256h-7.056V34.2h10.392zM27 18.253c0-.14.114-.253.256-.253h2.488a.25.25 0 0 1 .256.253v23.494c0 .14-.114.253-.256.253h-2.488a.25.25 0 0 1-.256-.253V18.253z"
id="a"
/>
</defs>
<use xlink:href="#a" fillRule="evenodd" />
</svg>
the xlink:href is causing issues due to all references being named a
in sketch.
@lifeiscontent: What about using the svgo prefixIds plugin?
@strarsis that works, but it'd be ideal to simplify the SVG.
@lifeiscontent: You can use a custom hash/prefix callback, so you can use some custom unique string minifier.
@strarsis how do you use a callback in SVGO?
@lifeiscontent: https://github.com/svg/svgo/issues/913#issuecomment-373416266
This would be very useful for exported svg from sketch, most <defs>
are only being used once.
I dare adding another example : With svgo only :
<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path d="M8.18 8H6v13h4.78L8.18 8zm2.04 0l2.6 13H26V8H10.22zM2 24h28l-2 3H4l-2-3zM5 6h22a1 1 0 0 1 1 1v15a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z" id="a"/>
</defs>
<use fill="#0082C3" xlink:href="#a" fill-rule="evenodd"/>
</svg>
With svgood (https://github.com/BrentonWheeler/svgood) + svgo
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path d="M8.18 8H6v13h4.78L8.18 8zm2.04 0l2.6 13H26V8H10.22zM2 24h28l-2 3H4l-2-3zM5 6h22a1 1 0 0 1 1 1v15a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/>
</svg>
@lifeiscontent the same thing here. Our designer also uses masks to apply colors, and doing so produces a lot of useless and sometimes broken code. I currently clean this up manually, but it takes a lot of time. I think this is not an uncommon problem.
Hello, everyone! I am writing an SVG image spread across various files, and combining it on Node during build by using a self‐made script (to make ids unique based on file path) that uses jsdom, and then passing the result to SVGO.
As such, I have a lot of elements in defs
that are used by a single use
element from a different file in the source, and I would really appreciate this kind of feature!
So, I figured I’d implement it myself! (And that’s what I did.)
If anyone thinks it would be useful, feel free to copy and paste:
{
type: "full",
fn: item =>
{
let referenced = {}
let referencedOnce = {}
let references = {}
let parents = new Map()
let action = item =>
{
if(item.hasAttr("id"))
referenced[item.attr("id").value] = item
if(item.hasAttr("href") && item.attr("href").value.startsWith("#"))
{
let id = item.attr("href").value.slice(1)
referencedOnce[id] = referencedOnce[id] === undefined
if(referencedOnce[id]) references[id] = item
}
for(let name in item.attrs)
{
let value = item.attrs[name].value
let match = value.match(/\burl\(("|')?#(.+?)\1\)/)
if(match)
referencedOnce[match[2]] = false
}
}
let walk = item =>
{
if(!item.isElem()) return
action(item)
if(!item.content) return
for(let child of item.content)
{
parents.set(child, item)
walk(child)
}
}
walk(item)
for(let id in referenced)
{
let item = referenced[id]
switch(referencedOnce[id])
{
case undefined:
item.removeAttr("id")
break
case true:
let parent = parents.get(item)
if(!parent.isElem("defs")) break
let replaced = references[id]
let replacing = parents.get(replaced)
parents.set(item, replaced)
parent.content = parent.content.filter(child => child !== item)
for(let child of replacing.content)
{
if(child === replaced)
{
item.removeAttr("id")
child.removeAttr("href")
child.renameElem("g")
child.content = [item]
}
}
break
}
}
return item
},
}
With this, I was successfully able to reduce my (current) SVG by 1.4kB (from 18.1kB to 16.7kB).
Unfortunately, it only works with use
elements (and not mask
, etc.), so @lifeiscontent’s and @mixtur’s use cases are not covered.
Note: This replaces the use
elements with g
. I experienced best results when rerunning SVGO several times on the same input to allow it to collapse all those g
elements. I used the following loop:
let data = "<svg />" // Your SVG.
for(let i = 0; i < 12; i++)
{
let newdata = (await svgo.optimize(data)).data
if(data === newdata) break
data = newdata
}
I set an upper bound of twelve iterations, but in reality, it stops after about five on the break
.
I hope someone is able to appreciate this! Cheers〜
Edit: Note that this doesn’t work well when the use
element has either an x
or a y
attribute (or both).
PR ready: https://github.com/svg/svgo/pull/1279 No recursion yet, but svgo multipass feature should handle this.
You can already try this feature branch by installing it using this npm command:
npm install github:strarsis/svgo#dereferenceUses-plugin
Note that this plugin currently doesn't run by default and has to explicitly enabled:
svgo --enable=dereferenceUses [...]
Hi there! I am also curious as to why the PR hasn't been merged. Is there a reason?
Thanks in advance!
Edit: Just realized there is an unresolved comment in the PR.