Skip to content

Commit

Permalink
fix(removeHiddenElems): remove hidden definitions usage (#1852)
Browse files Browse the repository at this point in the history
  • Loading branch information
SethFalco committed Nov 26, 2023
1 parent 7e259a2 commit e22d533
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 41 deletions.
2 changes: 1 addition & 1 deletion docs/03-plugins/reuse-paths.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ If the path contains other attributes, such as `style` or `transform`, they will

:::tip

If you only need SVG 2 or inline HTML compatibility, it's recommended to include the [Remove Xlink](/docs/plugins/remove-xlink/) plugin towards the end of your pipeline to convert references to `xlink:href` to the SVG 2 `href` attribute.
If you only need SVG 2 or inline HTML compatibility, it's recommended to include the [Remove XLink](/docs/plugins/remove-xlink/) plugin towards the end of your pipeline to convert references to `xlink:href` to the SVG 2 `href` attribute.

:::

Expand Down
3 changes: 2 additions & 1 deletion lib/xast.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ const visit = (node, visitor, parentNode) => {
exports.visit = visit;

/**
* @type {(node: XastChild, parentNode: XastParent) => void}
* @param {XastChild} node
* @param {XastParent} parentNode
*/
const detachNodeFromParent = (node, parentNode) => {
// avoid splice to not break for loops
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"packageManager": "yarn@2.4.3",
"name": "svgo",
"version": "3.0.4",
"version": "3.0.5",
"description": "Nodejs-based tool for optimizing SVG vector graphics files",
"license": "MIT",
"keywords": [
Expand Down
8 changes: 3 additions & 5 deletions plugins/removeAttrs.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,11 @@ exports.fn = (root, params) => {
enter: (node) => {
for (let pattern of attrs) {
// if no element separators (:), assume it's attribute name, and apply to all elements *regardless of value*
if (pattern.includes(elemSeparator) === false) {
pattern = ['.*', elemSeparator, pattern, elemSeparator, '.*'].join(
''
);
if (!pattern.includes(elemSeparator)) {
pattern = ['.*', pattern, '.*'].join(elemSeparator);
// if only 1 separator, assume it's element and attribute name, and apply regardless of attribute value
} else if (pattern.split(elemSeparator).length < 3) {
pattern = [pattern, elemSeparator, '.*'].join('');
pattern = [pattern, '.*'].join(elemSeparator);
}

// create regexps for element, attribute name, and attribute value
Expand Down
111 changes: 78 additions & 33 deletions plugins/removeHiddenElems.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

/**
* @typedef {import('../lib/types').XastChild} XastChild
* @typedef {import('../lib/types').XastElement} XastElement
* @typedef {import('../lib/types').XastParent} XastParent
*/
Expand Down Expand Up @@ -66,11 +67,40 @@ exports.fn = (root, params) => {
*/
const nonRenderedNodes = new Map();

/**
* IDs for removed hidden definitions.
*
* @type {Set<string>}
*/
const removedDefIds = new Set();

/**
* @param {XastChild} node
* @param {XastParent} parentNode
*/
function removeElement(node, parentNode) {
if (
node.type === 'element' &&
node.attributes.id != null &&
parentNode.type === 'element' &&
parentNode.name === 'defs'
) {
removedDefIds.add(node.attributes.id);
}

detachNodeFromParent(node, parentNode);
}

visit(root, {
element: {
enter: (node, parentNode) => {
// transparent non-rendering elements still apply where referenced
if (nonRendering.includes(node.name)) {
if (node.attributes.id == null) {
detachNodeFromParent(node, parentNode);
return visitSkip;
}

nonRenderedNodes.set(node, parentNode);
return visitSkip;
}
Expand All @@ -84,8 +114,7 @@ exports.fn = (root, params) => {
computedStyle.opacity.type === 'static' &&
computedStyle.opacity.value === '0'
) {
detachNodeFromParent(node, parentNode);
return;
removeElement(node, parentNode);
}
},
},
Expand All @@ -105,7 +134,7 @@ exports.fn = (root, params) => {
// keep if any descendant enables visibility
querySelector(node, '[visibility=visible]') == null
) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}

Expand All @@ -122,7 +151,7 @@ exports.fn = (root, params) => {
// markers with display: none still rendered
node.name !== 'marker'
) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}

Expand All @@ -138,7 +167,7 @@ exports.fn = (root, params) => {
node.children.length === 0 &&
node.attributes.r === '0'
) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}

Expand All @@ -154,7 +183,7 @@ exports.fn = (root, params) => {
node.children.length === 0 &&
node.attributes.rx === '0'
) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}

Expand All @@ -170,7 +199,7 @@ exports.fn = (root, params) => {
node.children.length === 0 &&
node.attributes.ry === '0'
) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}

Expand All @@ -186,7 +215,7 @@ exports.fn = (root, params) => {
node.children.length === 0 &&
node.attributes.width === '0'
) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}

Expand All @@ -203,7 +232,7 @@ exports.fn = (root, params) => {
node.children.length === 0 &&
node.attributes.height === '0'
) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}

Expand All @@ -218,7 +247,7 @@ exports.fn = (root, params) => {
node.name === 'pattern' &&
node.attributes.width === '0'
) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}

Expand All @@ -233,7 +262,7 @@ exports.fn = (root, params) => {
node.name === 'pattern' &&
node.attributes.height === '0'
) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}

Expand All @@ -248,7 +277,7 @@ exports.fn = (root, params) => {
node.name === 'image' &&
node.attributes.width === '0'
) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}

Expand All @@ -263,7 +292,7 @@ exports.fn = (root, params) => {
node.name === 'image' &&
node.attributes.height === '0'
) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}

Expand All @@ -274,12 +303,12 @@ exports.fn = (root, params) => {
// <path d=""/>
if (pathEmptyD && node.name === 'path') {
if (node.attributes.d == null) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}
const pathData = parsePathData(node.attributes.d);
if (pathData.length === 0) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}
// keep single point paths for markers
Expand All @@ -288,7 +317,7 @@ exports.fn = (root, params) => {
computedStyle['marker-start'] == null &&
computedStyle['marker-end'] == null
) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}
return;
Expand All @@ -304,7 +333,7 @@ exports.fn = (root, params) => {
node.name === 'polyline' &&
node.attributes.points == null
) {
detachNodeFromParent(node, parentNode);
removeElement(node, parentNode);
return;
}

Expand All @@ -318,31 +347,47 @@ exports.fn = (root, params) => {
node.name === 'polygon' &&
node.attributes.points == null
) {
detachNodeFromParent(node, parentNode);
return;
removeElement(node, parentNode);
}
},

exit: (node, parentNode) => {
if (node.name !== 'svg' || parentNode.type !== 'root') {
if (node.name === 'defs' && node.children.length === 0) {
removeElement(node, parentNode);
return;
}
for (const [
nonRenderedNode,
nonRenderedParent,
] of nonRenderedNodes.entries()) {
if (nonRenderedNode.attributes.id == null) {
detachNodeFromParent(node, nonRenderedParent);
continue;

if (node.name === 'use') {
const referencesRemovedDef = Object.entries(node.attributes).some(
([attrKey, attrValue]) =>
(attrKey === 'href' || attrKey.endsWith(':href')) &&
removedDefIds.has(
attrValue.slice(attrValue.indexOf('#') + 1).trim()
)
);

if (referencesRemovedDef) {
detachNodeFromParent(node, parentNode);
}

const selector = referencesProps
.map((attr) => `[${attr}="url(#${nonRenderedNode.attributes.id})"]`)
.join(',');
return;
}

if (node.name === 'svg' && parentNode.type === 'root') {
for (const [
nonRenderedNode,
nonRenderedParent,
] of nonRenderedNodes.entries()) {
const selector = referencesProps
.map(
(attr) => `[${attr}="url(#${nonRenderedNode.attributes.id})"]`
)
.join(',');

const element = querySelector(root, selector);
if (element == null) {
detachNodeFromParent(node, nonRenderedParent);
const element = querySelector(root, selector);
if (element == null) {
detachNodeFromParent(node, nonRenderedParent);
}
}
}
},
Expand Down
15 changes: 15 additions & 0 deletions test/plugins/removeHiddenElems.13.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit e22d533

Please sign in to comment.