Skip to content

Commit

Permalink
feat(mergeGradients): new plugin (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnkenny54 authored Nov 16, 2024
1 parent 44d2109 commit 7ef4f56
Show file tree
Hide file tree
Showing 23 changed files with 396 additions and 104 deletions.
2 changes: 2 additions & 0 deletions lib/builtin.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import * as createGroups from '../plugins/createGroups.js';
import * as inlineStyles from '../plugins/inlineStyles.js';
import * as inlineUse from '../plugins/inlineUse.js';
import * as mergePaths from '../plugins/mergePaths.js';
import * as mergeGradients from '../plugins/mergeGradients.js';
import * as minifyColors from '../plugins/minifyColors.js';
import * as minifyGradients from '../plugins/minifyGradients.js';
import * as minifyPathData from '../plugins/minifyPathData.js';
Expand Down Expand Up @@ -82,6 +83,7 @@ export const builtin = Object.freeze([
createGroups,
inlineStyles,
inlineUse,
mergeGradients,
mergePaths,
minifyColors,
minifyGradients,
Expand Down
75 changes: 43 additions & 32 deletions lib/css.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
} from './svgo/tools.js';

/**
* @typedef {import('./types.js').XastElement} XastElement
* @typedef {import('./types.js').CSSFeatures} CSSFeatures
* @typedef{{type:'AttributeSelector',name:string,matcher:string|null,value:string|null,flags:string|null}} AttributeSelector
* @typedef{{type:'ClassSelector',name:string}} ClassSelector
Expand All @@ -15,7 +14,6 @@ import {
* @typedef{{type:'TypeSelector',name:string}} TypeSelector
* @typedef{AttributeSelector|ClassSelector|IdSelector|PseudoClassSelector|PseudoElementSelector|TypeSelector} SimpleSelector
* @typedef {import('./types.js').CSSDeclarationMap} CSSDeclarationMap
* @typedef {XastElement&{style?:string,declarations?:CSSDeclarationMap}} XastElementExtended
*/

export class CSSRuleSet {
Expand Down Expand Up @@ -78,6 +76,19 @@ export class CSSRuleSet {
return false;
}

/**
* @param {string} id
* @returns {boolean}
*/
hasIdSelector(id) {
for (const rule of this.#rules) {
if (rule.hasIdSelector(id)) {
return true;
}
}
return false;
}

#initFeatures() {
const features = new Set();
if (this.#atRule) {
Expand Down Expand Up @@ -108,7 +119,7 @@ export class CSSRule {
#specificity;
#declarations;
#isInMediaQuery;
/** @type {(function(XastElement):boolean)|undefined} */
/** @type {(function(import('./types.js').XastElement):boolean)|undefined} */
#matcher;

/**
Expand Down Expand Up @@ -205,12 +216,19 @@ export class CSSRule {
return this.#selector.hasAttributeSelector(attName);
}

/**
* @param {string} id
*/
hasIdSelector(id) {
return this.#selector.hasIdSelector(id);
}

hasPseudos() {
return this.#selector.hasPseudos();
}

/**
* @returns {(function(XastElement):boolean)|undefined}
* @returns {(function(import('./types.js').XastElement):boolean)|undefined}
*/
#initMatcher() {
const sequences = this.#selector.getSequences();
Expand Down Expand Up @@ -241,7 +259,7 @@ export class CSSRule {
}

/**
* @param {XastElement} element
* @param {import('./types.js').XastElement} element
* @return {boolean|null}
*/
_matches(element) {
Expand All @@ -252,7 +270,7 @@ export class CSSRule {
}

/**
* @param {XastElement} element
* @param {import('./types.js').XastElement} element
* @return {boolean}
*/
// eslint-disable-next-line no-unused-vars
Expand Down Expand Up @@ -352,6 +370,13 @@ export class CSSSelector {
return this.#selectorSequences.some((s) => s.hasAttributeSelector(attName));
}

/**
* @param {string} id
*/
hasIdSelector(id) {
return this.#selectorSequences.some((s) => s.hasIdSelector(id));
}

hasPseudos() {
return this.#strWithoutPseudos !== undefined;
}
Expand Down Expand Up @@ -485,6 +510,18 @@ export class CSSSelectorSequence {
return false;
}

/**
* @param {string} id
*/
hasIdSelector(id) {
for (const selector of this.#simpleSelectors) {
if (selector.type === 'IdSelector') {
return selector.name === id;
}
}
return false;
}

/**
* @param {Map<string,string>} idMap
*/
Expand Down Expand Up @@ -512,29 +549,3 @@ export class CSSParseError extends Error {
super(message);
}
}

/**
* @param {XastElementExtended} element
* @param {CSSDeclarationMap} properties
*/
export function writeStyleAttribute(element, properties) {
let style = '';
for (const [p, decValue] of properties.entries()) {
if (style !== '') {
style += ';';
}
style += `${p}:${decValue.value}`;
if (decValue.important) {
style += '!important';
}
}
if (style) {
element.attributes.style = style;
element.style = style;
element.declarations = properties;
} else {
delete element.attributes.style;
delete element.style;
delete element.declarations;
}
}
30 changes: 21 additions & 9 deletions lib/docdata.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { CSSParseError } from './css.js';
import { cssPropToString, getStyleDeclarations } from './css-tools.js';

/**
* @typedef {import('../lib/types.js').XastElement} XastElement
* @typedef {import('../lib/types.js').XastParent} XastParent
* @typedef {import('../lib/types.js').XastRoot} XastRoot
*/
Expand All @@ -36,7 +35,7 @@ export class StyleData {
#referencedClasses;

/**
* @param {XastElement[]} styleElements
* @param {import('../lib/types.js').XastElement[]} styleElements
* @param {CSSRuleSet[]} ruleSets
*/
constructor(styleElements, ruleSets) {
Expand Down Expand Up @@ -178,7 +177,7 @@ export class StyleData {
}

/**
* @param {XastElement} element
* @param {import('../lib/types.js').XastElement} element
* @param {{element:XastParent,styles?:Map<string,string|null>}[]} parentInfo
* @param {CSSDeclarationMap} [declarations]
* @returns {Map<string,string|null>}
Expand Down Expand Up @@ -245,7 +244,7 @@ export class StyleData {
}

/**
* @param {XastElement} element
* @param {import('../lib/types.js').XastElement} element
*/
getMatchingRules(element) {
const rules = [];
Expand Down Expand Up @@ -306,6 +305,19 @@ export class StyleData {
return this.getReferencedClasses().has(className);
}

/**
* @param {string} id
* @returns {boolean}
*/
hasIdSelector(id) {
for (const ruleSet of this.#ruleSets) {
if (ruleSet.hasIdSelector(id)) {
return true;
}
}
return false;
}

/**
* @param {CSSFeatures[]} features
*/
Expand Down Expand Up @@ -333,7 +345,7 @@ export class StyleData {

mergeStyles() {
/**
* @param {XastElement} element
* @param {import('../lib/types.js').XastElement} element
*/
function gatherCSS(element) {
let css = getCSS(element);
Expand Down Expand Up @@ -486,7 +498,7 @@ class DocData {
}

/**
* @param {XastElement} styleElement
* @param {import('../lib/types.js').XastElement} styleElement
*/
function getCSS(styleElement) {
let css = '';
Expand All @@ -499,7 +511,7 @@ function getCSS(styleElement) {
}

/**
* @param {XastElement} styleElement
* @param {import('../lib/types.js').XastElement} styleElement
*/
function getRuleSets(styleElement) {
/** @type {CSSRuleSet[]} */
Expand Down Expand Up @@ -537,11 +549,11 @@ function getRuleSets(styleElement) {
* @param {XastRoot} root
*/
export const getDocData = (root) => {
/** @type {XastElement[]} */
/** @type {import('../lib/types.js').XastElement[]} */
const styleElements = [];
/** @type {CSSRuleSet[]} */
const ruleSets = [];
/** @type {Map<XastElement, XastParent>} */
/** @type {Map<import('../lib/types.js').XastElement, XastParent>} */
const parents = new Map();
let styleError = false;
let hasAnimations = false;
Expand Down
65 changes: 65 additions & 0 deletions lib/svgo/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,45 @@ export function updateReferencedDeclarationIds(decls, idMap) {
}
}

/**
* @param {import('../types.js').XastElement} element
* @param {string} attName
* @param {Map<string,string>} idMap
*/
export function updateReferencedId(element, attName, idMap) {
if (attName === 'style') {
updateReferencedStyleId(element, idMap);
return;
}

const attValue = element.attributes[attName];
const ids = getReferencedIdsInAttribute(attName, attValue);
if (ids.length !== 1) {
throw new Error();
}
const newId = idMap.get(ids[0].id);
if (newId === undefined) {
throw new Error();
}
element.attributes[attName] = attValue.replace(
'#' + ids[0].literalString,
'#' + newId,
);
}

/**
* @param {import('../types.js').XastElement} element
* @param {Map<string,string>} idMap
*/
function updateReferencedStyleId(element, idMap) {
const decls = getStyleDeclarations(element);
if (decls === undefined) {
throw new Error();
}
updateReferencedDeclarationIds(decls, idMap);
writeStyleAttribute(element, decls);
}

/**
* @param {string} attribute
* @param {string} value
Expand Down Expand Up @@ -584,3 +623,29 @@ export const toFixed = (num, precision) => {
const pow = 10 ** precision;
return Math.round(num * pow) / pow;
};

/**
* @param {import('../types.js').XastElement&{style?:string,declarations?:import('../types.js').CSSDeclarationMap}} element
* @param {import('../types.js').CSSDeclarationMap} properties
*/
export function writeStyleAttribute(element, properties) {
let style = '';
for (const [p, decValue] of properties.entries()) {
if (style !== '') {
style += ';';
}
style += `${p}:${decValue.value}`;
if (decValue.important) {
style += '!important';
}
}
if (style) {
element.attributes.style = style;
element.style = style;
element.declarations = properties;
} else {
delete element.attributes.style;
delete element.style;
delete element.declarations;
}
}
1 change: 1 addition & 0 deletions lib/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export class StyleData {
getReferencedIds(): Map<string, CSSRule[]>;
hasAttributeSelector(attName?: string): boolean;
hasClassReference(className: string): boolean;
hasIdSelector(id: string): boolean;
hasOnlyFeatures(features: CSSFeatures[]): boolean;
hasStyles(): boolean;
mergeStyles(): void;
Expand Down
Loading

0 comments on commit 7ef4f56

Please sign in to comment.