Skip to content

Commit

Permalink
fix(reusePaths): reuse defs tag if exists and remove redundant nodes (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
SethFalco authored Sep 23, 2023
1 parent 9f7894d commit 5f40d8b
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 15 deletions.
88 changes: 73 additions & 15 deletions plugins/reusePaths.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

const { detachNodeFromParent, querySelectorAll } = require('../lib/xast');

/**
* @typedef {import('../lib/types').XastElement} XastElement
* @typedef {import('../lib/types').XastParent} XastParent
Expand All @@ -26,9 +28,18 @@ exports.fn = () => {
*/
const paths = new Map();

/**
* Reference to the first defs element that is a direct child of the svg
* element if one exists.
*
* @type {XastElement}
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs
*/
let svgDefs;

return {
element: {
enter: (node) => {
enter: (node, parentNode) => {
if (node.name === 'path' && node.attributes.d != null) {
const d = node.attributes.d;
const fill = node.attributes.fill || '';
Expand All @@ -41,24 +52,38 @@ exports.fn = () => {
}
list.push(node);
}

if (
svgDefs == null &&
node.name === 'defs' &&
parentNode.type === 'element' &&
parentNode.name === 'svg'
) {
svgDefs = node;
}
},

exit: (node, parentNode) => {
if (node.name === 'svg' && parentNode.type === 'root') {
/**
* @type {XastElement}
*/
const defsTag = {
type: 'element',
name: 'defs',
attributes: {},
children: [],
};
// TODO remove legacy parentNode in v4
Object.defineProperty(defsTag, 'parentNode', {
writable: true,
value: node,
});
let defsTag = svgDefs;

if (defsTag == null) {
defsTag = {
type: 'element',
name: 'defs',
attributes: {},
children: [],
};
// TODO remove legacy parentNode in v4
Object.defineProperty(defsTag, 'parentNode', {
writable: true,
value: node,
});
}

let index = 0;
for (const list of paths.values()) {
if (list.length > 1) {
Expand Down Expand Up @@ -90,19 +115,52 @@ exports.fn = () => {
defsTag.children.push(reusablePath);
// convert paths to <use>
for (const pathNode of list) {
pathNode.name = 'use';
pathNode.attributes['xlink:href'] = '#' + id;
delete pathNode.attributes.d;
delete pathNode.attributes.stroke;
delete pathNode.attributes.fill;

if (
defsTag.children.includes(pathNode) &&
pathNode.children.length === 0
) {
if (Object.keys(pathNode.attributes).length === 0) {
detachNodeFromParent(pathNode, defsTag);
continue;
}

if (
Object.keys(pathNode.attributes).length === 1 &&
pathNode.attributes.id != null
) {
detachNodeFromParent(pathNode, defsTag);
const selector = `[xlink\\:href=#${pathNode.attributes.id}], [href=#${pathNode.attributes.id}]`;
for (const child of querySelectorAll(node, selector)) {
if (child.type !== 'element') {
continue;
}
for (const name of ['href', 'xlink:href']) {
if (child.attributes[name] != null) {
child.attributes[name] = '#' + id;
}
}
}
continue;
}
}

pathNode.name = 'use';
pathNode.attributes['xlink:href'] = '#' + id;
}
}
}
if (defsTag.children.length !== 0) {
if (node.attributes['xmlns:xlink'] == null) {
node.attributes['xmlns:xlink'] = 'http://www.w3.org/1999/xlink';
}
node.children.unshift(defsTag);

if (svgDefs == null) {
node.children.unshift(defsTag);
}
}
}
},
Expand Down
23 changes: 23 additions & 0 deletions test/plugins/reusePaths.04.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions test/plugins/reusePaths.05.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 5f40d8b

Please sign in to comment.