1
0
mirror of https://github.com/svg/svgo.git synced 2025-07-29 20:21:14 +03:00

[new plugin] reimplement inlineDefs

The code is taken from https://github.com/svg/svgo/pull/976, refactored
with new api, covered types and simplified.

Plugin has no dependencies so can be used without changing.

```
const inlineDefs = require('./inlineDefs.js');

module.exports = {
  plugins: [
    'preset-default',
    inlineDefs
  ]
};
```
This commit is contained in:
Bogdan Chadkin
2022-02-21 01:46:04 +03:00
parent 238d3bf600
commit bb3f1a99ef
16 changed files with 403 additions and 0 deletions

166
plugins/inlineDefs.js Normal file
View File

@ -0,0 +1,166 @@
'use strict';
/**
* @typedef {import('../lib/types').XastParent} XastParent
* @typedef {import('../lib/types').XastElement} XastElement
*/
exports.name = 'inlineDefs';
exports.type = 'visitor';
exports.active = true;
exports.description = 'inlines svg definitions';
/**
* @typedef {(element: XastElement, parentNode: XastParent) => void} VisitCallback
*/
/**
* @type {(element: XastParent, fn: VisitCallback) => void}
*/
const visitElements = (node, fn) => {
for (const child of node.children) {
if (child.type === 'element') {
fn(child, node);
visitElements(child, fn);
}
}
};
/**
* Replaces use tag with the corresponding definitions
* if onlyUnique is enabled, replaces only use tags with definitions referred to only once
*
* @type {import('../lib/types').Plugin<{
* onlyUnique?: boolean
* }>}
*/
exports.fn = (root, params) => {
const { onlyUnique = true } = params;
// hacky extract JSAPI class to avoid imports from other modules
const JSAPI = root.constructor;
/**
* @type {[XastElement, XastParent][]}
*/
const uses = [];
/**
* @type {Map<string, number>}
*/
const useCounts = new Map();
/**
* @type {Map<string, XastElement>}
*/
const referencedElements = new Map();
// collect defs container and all uses
visitElements(root, (node, parentNode) => {
if (node.name === 'use') {
uses.push([node, parentNode]);
const href = node.attributes['xlink:href'] || node.attributes.href;
const count = useCounts.get(href) || 0;
useCounts.set(href, count + 1);
}
});
return {
element: {
enter: (node, parentNode) => {
// find elements referenced by all <use>
if (node.attributes.id == null) {
return;
}
const href = `#${node.attributes.id}`;
const count = useCounts.get(href);
// not referenced
if (count == null) {
return;
}
referencedElements.set(href, node);
/// remove id attribute when referenced yb <use> more than once
if (onlyUnique === false && count > 1) {
delete node.attributes.id;
}
// remove elements referenced by <use> only once
if (onlyUnique === true && count === 1) {
parentNode.children = parentNode.children.filter(
(child) => child !== node
);
}
},
exit(node, parentNode) {
// remove empty <defs> container
if (node.name === 'defs') {
if (onlyUnique === false || node.children.length === 0) {
parentNode.children = parentNode.children.filter(
(child) => child !== node
);
}
}
},
},
root: {
exit: () => {
for (const [use, useParentNode] of uses) {
const href = use.attributes['xlink:href'] || use.attributes['href'];
const count = useCounts.get(href) || 0;
const referenced = referencedElements.get(href);
if (onlyUnique === true && count > 1) {
continue;
}
if (referenced == null) {
continue;
}
// copy attrubutes from <use> to referenced element
for (const [name, value] of Object.entries(use.attributes)) {
if (
name !== 'x' &&
name !== 'y' &&
name !== 'xlink:href' &&
name !== 'href'
) {
referenced.attributes[name] = value;
}
}
const x = use.attributes.x;
const y = use.attributes.y;
let attrValue = null;
if (x != null && y != null) {
attrValue = `translate(${x}, ${y})`;
} else if (x != null) {
attrValue = `translate(${x})`;
}
let replacement = referenced;
// wrap referenced element with <g> when <use> had coordinates
if (attrValue != null) {
/**
* @type {XastElement}
*/
const g = {
type: 'element',
name: 'g',
attributes: {
transform: attrValue,
},
children: [referenced],
};
// @ts-ignore
replacement = new JSAPI(g);
}
useParentNode.children = useParentNode.children.map((child) => {
if (child === use) {
return replacement;
} else {
return child;
}
});
}
},
},
};
};

View File

@ -54,3 +54,4 @@ exports.removeXMLProcInst = require('./removeXMLProcInst.js');
exports.reusePaths = require('./reusePaths.js');
exports.sortAttrs = require('./sortAttrs.js');
exports.sortDefsChildren = require('./sortDefsChildren.js');
exports.inlineDefs = require('./inlineDefs.js');

View File

@ -0,0 +1,12 @@
<svg id="test01" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<defs>
<rect width="100" height="100" id="rect1"/>
</defs>
<use xlink:href="#rect1"></use>
</svg>
@@@
<svg id="test01" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<rect width="100" height="100" id="rect1"/>
</svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@ -0,0 +1,20 @@
<svg id="test02" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<defs>
<rect width="100" height="100" id="rect1"/>
<rect width="200" height="200" id="rect2"/>
</defs>
<use xlink:href="#rect1"></use>
<use xlink:href="#rect1"></use>
<use xlink:href="#rect2"></use>
</svg>
@@@
<svg id="test02" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<defs>
<rect width="100" height="100" id="rect1"/>
</defs>
<use xlink:href="#rect1"/>
<use xlink:href="#rect1"/>
<rect width="200" height="200" id="rect2"/>
</svg>

After

Width:  |  Height:  |  Size: 680 B

View File

@ -0,0 +1,15 @@
<svg id="test03" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<defs>
<rect width="100" height="100" id="rect1"/>
<rect width="200" height="200" id="rect2"/>
</defs>
<use xlink:href="#rect1"></use>
<use xlink:href="#rect2"></use>
</svg>
@@@
<svg id="test03" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<rect width="100" height="100" id="rect1"/>
<rect width="200" height="200" id="rect2"/>
</svg>

After

Width:  |  Height:  |  Size: 555 B

View File

@ -0,0 +1,14 @@
<svg id="test04" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<defs>
<rect width="100" height="100" id="rect1"/>
</defs>
<use xlink:href="#rect1" x="50" y="60"></use>
</svg>
@@@
<svg id="test04" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<g transform="translate(50, 60)">
<rect width="100" height="100" id="rect1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 480 B

View File

@ -0,0 +1,17 @@
<svg id="test05" 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" stroke-width="4" opacity=".4"/>
</mask>
</defs>
<use xlink:href="#m"/>
</svg>
@@@
<svg id="test05" width="315" height="92" viewBox="0 0 315 92" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<mask id="m" x="0" y="0" width="301" height="31" fill="#fff">
<rect id="a" x="7" y="5" width="301" height="31" rx="3" stroke-width="4" opacity=".4"/>
</mask>
</svg>

After

Width:  |  Height:  |  Size: 747 B

View File

@ -0,0 +1,22 @@
<svg id="test06" 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"/>
<g id="c">
<use xlink:href="#b"/>
</g>
<g id="b">
<use xlink:href="#a"/>
</g>
</defs>
<use xlink:href="#c"/>
</svg>
@@@
<svg id="test06" 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 id="c">
<g id="b">
<rect id="a" x="7" y="5" width="301" height="31" rx="3"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 682 B

View File

@ -0,0 +1,18 @@
<svg id="test07" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<defs>
<rect width="100" height="100" id="rect1"/>
</defs>
<use xlink:href="#rect1"></use>
<use xlink:href="#rect1"></use>
</svg>
@@@
<svg id="test07" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<rect width="100" height="100"/>
<rect width="100" height="100"/>
</svg>
@@@
{ "onlyUnique": false }

After

Width:  |  Height:  |  Size: 515 B

View File

@ -0,0 +1,12 @@
<svg id="test08" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<rect width="100" height="100" id="rect1"/>
</defs>
<use href="#rect1"></use>
</svg>
@@@
<svg id="test08" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" id="rect1"/>
</svg>

After

Width:  |  Height:  |  Size: 327 B

View File

@ -0,0 +1,20 @@
<svg id="test09" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<rect width="100" height="100" id="rect1"/>
<rect width="200" height="200" id="rect2"/>
</defs>
<use href="#rect1"></use>
<use href="#rect1"></use>
<use href="#rect2"></use>
</svg>
@@@
<svg id="test09" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<rect width="100" height="100" id="rect1"/>
</defs>
<use href="#rect1"/>
<use href="#rect1"/>
<rect width="200" height="200" id="rect2"/>
</svg>

After

Width:  |  Height:  |  Size: 564 B

View File

@ -0,0 +1,15 @@
<svg id="test10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<rect width="100" height="100" id="rect1"/>
<rect width="200" height="200" id="rect2"/>
</defs>
<use href="#rect1"></use>
<use href="#rect2"></use>
</svg>
@@@
<svg id="test10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" id="rect1"/>
<rect width="200" height="200" id="rect2"/>
</svg>

After

Width:  |  Height:  |  Size: 457 B

View File

@ -0,0 +1,14 @@
<svg id="test11" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<rect width="100" height="100" id="rect1"/>
</defs>
<use href="#rect1" x="50" y="60"></use>
</svg>
@@@
<svg id="test11" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g transform="translate(50, 60)">
<rect width="100" height="100" id="rect1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 392 B

View File

@ -0,0 +1,17 @@
<svg id="test12" width="315" height="92" viewBox="0 0 315 92" xmlns="http://www.w3.org/2000/svg">
<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 href="#a" stroke-width="4" opacity=".4"/>
</mask>
</defs>
<use href="#m"/>
</svg>
@@@
<svg id="test12" width="315" height="92" viewBox="0 0 315 92" xmlns="http://www.w3.org/2000/svg">
<mask id="m" x="0" y="0" width="301" height="31" fill="#fff">
<rect id="a" x="7" y="5" width="301" height="31" rx="3" stroke-width="4" opacity=".4"/>
</mask>
</svg>

After

Width:  |  Height:  |  Size: 649 B

View File

@ -0,0 +1,22 @@
<svg id="test13" width="315" height="92" viewBox="0 0 315 92" xmlns="http://www.w3.org/2000/svg">
<defs>
<rect id="a" x="7" y="5" width="301" height="31" rx="3"/>
<g id="c">
<use href="#b"/>
</g>
<g id="b">
<use href="#a"/>
</g>
</defs>
<use href="#c"/>
</svg>
@@@
<svg id="test13" width="315" height="92" viewBox="0 0 315 92" xmlns="http://www.w3.org/2000/svg">
<g id="c">
<g id="b">
<rect id="a" x="7" y="5" width="301" height="31" rx="3"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 578 B

View File

@ -0,0 +1,18 @@
<svg id="test14" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<rect width="100" height="100" id="rect1"/>
</defs>
<use href="#rect1"></use>
<use href="#rect1"></use>
</svg>
@@@
<svg id="test14" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100"/>
<rect width="100" height="100"/>
</svg>
@@@
{ "onlyUnique": false }

After

Width:  |  Height:  |  Size: 417 B