react-email
Version:
A live preview of your emails right in your browser.
69 lines (60 loc) • 2.13 kB
text/typescript
import { type CssNode, clone, find, List, type Rule } from 'css-tree';
import { isPartInlinable } from './is-part-inlinable.js';
/**
* Split a rule into inlinable and non-inlinable parts. Caller must only pass rules
* for which isRuleInlinable(rule) is false. Returns clones so the original stylesheet is never mutated.
*
* @returns inlinablePart: rule with only inlinable block children, or null if none.
* nonInlinablePart: rule with only non-inlinable block children, or null if none.
*/
export function splitMixedRule(rule: Rule): {
inlinablePart: Rule | null;
nonInlinablePart: Rule | null;
} {
// If the selector itself contains pseudo-selectors, every declaration in the
// block only applies in that pseudo context — splitting would incorrectly
// inline them as base styles. Return the whole rule as non-inlinable.
const selectorHasPseudo =
rule.prelude !== null &&
find(
rule.prelude,
(node) =>
node.type === 'PseudoClassSelector' ||
node.type === 'PseudoElementSelector',
) !== null;
if (selectorHasPseudo) {
return { inlinablePart: null, nonInlinablePart: clone(rule) as Rule };
}
const ruleCloneInlinable = clone(rule) as Rule;
const ruleCloneNonInlinable = clone(rule) as Rule;
const inlinableParts: CssNode[] = [];
const nonInlinableParts: CssNode[] = [];
for (const part of ruleCloneInlinable.block.children.toArray()) {
if (isPartInlinable(part)) {
inlinableParts.push(part);
} else {
nonInlinableParts.push(part);
}
}
const inlinablePart =
inlinableParts.length > 0
? {
...ruleCloneInlinable,
block: {
type: 'Block' as const,
children: new List<CssNode>().fromArray(inlinableParts),
},
}
: null;
const nonInlinablePart =
nonInlinableParts.length > 0
? {
...ruleCloneNonInlinable,
block: {
type: 'Block' as const,
children: new List<CssNode>().fromArray(nonInlinableParts),
},
}
: null;
return { inlinablePart, nonInlinablePart };
}