Skip to content

Commit

Permalink
fix: use smarter class separation, fixes #46
Browse files Browse the repository at this point in the history
  • Loading branch information
theKashey committed Jul 26, 2023
1 parent fd0dd3c commit ee2132d
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 1 deletion.
44 changes: 44 additions & 0 deletions __tests__/edge-cases/broken-split-46.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { loadStyleDefinitions } from '../../src';
import { getCriticalStyles } from '../../src/getCSS';

describe('missing styles', () => {
it('result should contain full fill value', async () => {
const styles = loadStyleDefinitions(
() => ['test.css'],
() => `
.-lottie-player svg path[fill="rgb(255,255,255)"] {
fill: var(--color-background)
}
`
);
await styles;

expect(styles.ast['test.css'].selectors).toMatchInlineSnapshot(`
Array [
Object {
"declaration": 1,
"hash": ".-lottie-player svg path[fill=\\"rgb(255,255,255)\\"]1noj2ak-1etm6d20",
"media": Array [],
"parents": Array [
"-lottie-player",
],
"pieces": Array [
"-lottie-player",
],
"postfix": "svg path[fill=\\"rgb(255,255,255)\\"]",
"selector": ".-lottie-player svg path[fill=\\"rgb(255,255,255)\\"]",
},
]
`);

const extracted = getCriticalStyles(
'<div class="-lottie-player"><svg><path fill="rgb(255,255,255)"></path></svg></div>',
styles
);

expect(extracted).toMatchInlineSnapshot(`
"<style type=\\"text/css\\" data-used-styles=\\"test.css\\">.-lottie-player svg path[fill=\\"rgb(255,255,255)\\"] { fill: var(--color-background); }
</style>"
`);
});
});
3 changes: 2 additions & 1 deletion src/parser/toAst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as crc32 from 'crc-32';
import * as postcss from 'postcss';
import { AtRule, Rule } from 'postcss';

import { splitSelector } from '../utils/split-selectors';
import { AtRules, SingleStyleAst, StyleBodies, StyleBody, StyleSelector } from './ast';
import { createRange, localRangeMax, localRangeMin, rangesIntervalEqual } from './ranges';
import { extractParents, mapSelector } from './utils';
Expand Down Expand Up @@ -91,7 +92,7 @@ export const buildAst = (CSS: string, file = ''): SingleStyleAst => {
return;
}

const ruleSelectors = rule.selector.split(',');
const ruleSelectors = splitSelector(rule.selector);

ruleSelectors
.map((sel) => sel.trim())
Expand Down
File renamed without changes.
14 changes: 14 additions & 0 deletions src/utils/__tests__/split-selectors.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { splitSelector } from '../split-selectors';

describe('split selectors', () => {
it('simple', () => {
expect(splitSelector('.a')).toEqual(['.a']);
expect(splitSelector('.a,.b')).toEqual(['.a', '.b']);
expect(splitSelector('.a:before,:after')).toEqual(['.a:before', ':after']);
});

it('complex', () => {
expect(splitSelector('a ~ span,b')).toEqual(['a ~ span', 'b']);
expect(splitSelector("a#item p[alt^='test'],.body.test")).toEqual(["a#item p[alt^='test']", '.body.test']);
});
});
92 changes: 92 additions & 0 deletions src/utils/split-selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* @fileOverview inspired by https://github.com/perry-mitchell/css-selector-splitter/tree/master
*/

const BLOCKS: Record<string, string> = {
'(': ')',
'[': ']',
};

const QUOTES: Record<string, string> = {
'"': '"',
"'": "'",
};

const FINALIZERS: Record<string, string> = {
')': '(',
']': '[',
};

const SPLIT_ON: Record<string, string> = {
',': ',',
};

export const splitSelector = (selector: string): string[] => {
const selectors: string[] = [];

const stack: string[] = [];
const joiners: string[] = [];
let currentSelector = '';

for (let i = 0; i < selector.length; i += 1) {
const char = selector[i];

if (BLOCKS[char] || QUOTES[char]) {
if (stack.length === 0) {
stack.push(char);
} else {
const lastBrace = stack[stack.length - 1];

if (QUOTES[lastBrace]) {
// within quotes
if (char === lastBrace) {
// closing quote
stack.pop();
}
} else {
// inside brackets or square brackets
stack.push(char);
}
}

currentSelector += char;
} else if (FINALIZERS.hasOwnProperty(char)) {
const lastBrace = stack[stack.length - 1];
const matchingOpener = FINALIZERS[char];

if (lastBrace === matchingOpener) {
stack.pop();
}

currentSelector += char;
} else if (SPLIT_ON[char]) {
if (!stack.length) {
// we're not inside another block, so we can split using the comma/splitter
const lastJoiner = joiners[joiners.length - 1];

if (lastJoiner === ' ' && currentSelector.length <= 0) {
// we just split by a space, but there seems to be another split character, so use
// this new one instead of the previous space
joiners[joiners.length - 1] = char;
} else if (currentSelector.length <= 0) {
// skip this character, as it's just padding
} else {
// split by this character
const newLength = selectors.push(currentSelector);
joiners[newLength - 1] = char;
currentSelector = '';
}
} else {
// we're inside another block, so ignore the comma/splitter
currentSelector += char;
}
} else {
// just add this character
currentSelector += char;
}
}

selectors.push(currentSelector);

return selectors.map((selector) => selector.trim()).filter((cssSelector) => cssSelector.length > 0);
};

0 comments on commit ee2132d

Please sign in to comment.