Speed up addHoverClass on large stylesheets (#72)
* speed up addHoverClass on large style sheets * longer strings first to prevent accidental partial matches * can add hover class when there is a multi selector with the same prefix * tweak performance
This commit is contained in:
@@ -57,23 +57,50 @@ function getTagName(n: elementNode): string {
|
|||||||
return tagName;
|
return tagName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HOVER_SELECTOR = /([^\\]):hover/g;
|
// based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
||||||
|
function escapeRegExp(string: string) {
|
||||||
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||||
|
}
|
||||||
|
|
||||||
|
const HOVER_SELECTOR = /([^\\]):hover/;
|
||||||
|
const HOVER_SELECTOR_GLOBAL = new RegExp(HOVER_SELECTOR, 'g');
|
||||||
export function addHoverClass(cssText: string): string {
|
export function addHoverClass(cssText: string): string {
|
||||||
const ast = parse(cssText, { silent: true });
|
const ast = parse(cssText, {
|
||||||
|
silent: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (!ast.stylesheet) {
|
if (!ast.stylesheet) {
|
||||||
return cssText;
|
return cssText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectors: string[] = [];
|
||||||
ast.stylesheet.rules.forEach((rule) => {
|
ast.stylesheet.rules.forEach((rule) => {
|
||||||
if ('selectors' in rule) {
|
if ('selectors' in rule) {
|
||||||
(rule.selectors || []).forEach((selector: string) => {
|
(rule.selectors || []).forEach((selector: string) => {
|
||||||
if (HOVER_SELECTOR.test(selector)) {
|
if (HOVER_SELECTOR.test(selector)) {
|
||||||
const newSelector = selector.replace(HOVER_SELECTOR, '$1.\\:hover');
|
selectors.push(selector);
|
||||||
cssText = cssText.replace(selector, `${selector}, ${newSelector}`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return cssText;
|
|
||||||
|
if (selectors.length === 0) return cssText;
|
||||||
|
|
||||||
|
const selectorMatcher = new RegExp(
|
||||||
|
selectors
|
||||||
|
.filter((selector, index) => selectors.indexOf(selector) === index)
|
||||||
|
.sort((a, b) => b.length - a.length)
|
||||||
|
.map((selector) => {
|
||||||
|
return escapeRegExp(selector);
|
||||||
|
})
|
||||||
|
.join('|'),
|
||||||
|
'g',
|
||||||
|
);
|
||||||
|
|
||||||
|
return cssText.replace(selectorMatcher, (selector) => {
|
||||||
|
const newSelector = selector.replace(HOVER_SELECTOR_GLOBAL, '$1.\\:hover');
|
||||||
|
return `${selector}, ${newSelector}`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildNode(
|
function buildNode(
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ describe('add hover class to hover selector related rules', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can add hover class when there is a multi selector with the same prefix', () => {
|
||||||
|
const cssText = '.a:hover, .a:hover::after { color: white }';
|
||||||
|
expect(addHoverClass(cssText)).to.equal(
|
||||||
|
'.a:hover, .a.\\:hover, .a:hover::after, .a.\\:hover::after { color: white }',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('can add hover class when :hover is not the end of selector', () => {
|
it('can add hover class when :hover is not the end of selector', () => {
|
||||||
const cssText = 'div:hover::after { color: white }';
|
const cssText = 'div:hover::after { color: white }';
|
||||||
expect(addHoverClass(cssText)).to.equal(
|
expect(addHoverClass(cssText)).to.equal(
|
||||||
|
|||||||
Reference in New Issue
Block a user