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;
|
||||
}
|
||||
|
||||
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 {
|
||||
const ast = parse(cssText, { silent: true });
|
||||
const ast = parse(cssText, {
|
||||
silent: true,
|
||||
});
|
||||
|
||||
if (!ast.stylesheet) {
|
||||
return cssText;
|
||||
}
|
||||
|
||||
const selectors: string[] = [];
|
||||
ast.stylesheet.rules.forEach((rule) => {
|
||||
if ('selectors' in rule) {
|
||||
(rule.selectors || []).forEach((selector: string) => {
|
||||
if (HOVER_SELECTOR.test(selector)) {
|
||||
const newSelector = selector.replace(HOVER_SELECTOR, '$1.\\:hover');
|
||||
cssText = cssText.replace(selector, `${selector}, ${newSelector}`);
|
||||
selectors.push(selector);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
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(
|
||||
|
||||
@@ -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', () => {
|
||||
const cssText = 'div:hover::after { color: white }';
|
||||
expect(addHoverClass(cssText)).to.equal(
|
||||
|
||||
Reference in New Issue
Block a user