better-svelte-email 1.0.3 → 1.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/render/index.js +7 -6
- package/dist/render/utils/css/extract-rules-per-class.d.ts +2 -2
- package/dist/render/utils/css/extract-rules-per-class.js +13 -24
- package/dist/render/utils/css/get-custom-properties.d.ts +2 -2
- package/dist/render/utils/css/get-custom-properties.js +16 -31
- package/dist/render/utils/css/is-rule-inlinable.d.ts +1 -1
- package/dist/render/utils/css/is-rule-inlinable.js +30 -4
- package/dist/render/utils/css/make-inline-styles-for.d.ts +2 -2
- package/dist/render/utils/css/make-inline-styles-for.js +38 -41
- package/dist/render/utils/css/resolve-all-css-variables.d.ts +3 -3
- package/dist/render/utils/css/resolve-all-css-variables.js +107 -95
- package/dist/render/utils/css/resolve-calc-expressions.d.ts +2 -5
- package/dist/render/utils/css/resolve-calc-expressions.js +155 -118
- package/dist/render/utils/css/sanitize-declarations.d.ts +2 -2
- package/dist/render/utils/css/sanitize-declarations.js +226 -282
- package/dist/render/utils/css/sanitize-non-inlinable-rules.d.ts +2 -2
- package/dist/render/utils/css/sanitize-non-inlinable-rules.js +14 -19
- package/dist/render/utils/css/sanitize-stylesheet.d.ts +2 -2
- package/dist/render/utils/css/sanitize-stylesheet.js +4 -4
- package/dist/render/utils/tailwindcss/add-inlined-styles-to-element.d.ts +1 -1
- package/dist/render/utils/tailwindcss/setup-tailwind.d.ts +2 -2
- package/dist/render/utils/tailwindcss/setup-tailwind.js +3 -3
- package/package.json +5 -4
- package/dist/render/utils/css/unwrap-value.d.ts +0 -2
- package/dist/render/utils/css/unwrap-value.js +0 -6
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ See the [documentation](https://better-svelte-email.konixy.fr/docs) for a comple
|
|
|
31
31
|
- **Tailwind v4 Support** - Transforms Tailwind classes to inline styles for email clients
|
|
32
32
|
- **Built-in Email Preview** - Visual email preview and test sending
|
|
33
33
|
- **TypeScript First** - Fully typed with comprehensive type definitions
|
|
34
|
-
- **Well Tested** -
|
|
34
|
+
- **Well Tested** - >90% test coverage with unit and integration tests
|
|
35
35
|
|
|
36
36
|
_See [Roadmap](./ROADMAP.md) for future features and planned improvements._
|
|
37
37
|
|
package/dist/render/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { render as svelteRender } from 'svelte/server';
|
|
2
2
|
import { parse, serialize } from 'parse5';
|
|
3
|
+
import postcss from 'postcss';
|
|
3
4
|
import { walk } from './utils/html/walk.js';
|
|
4
5
|
import { setupTailwind } from './utils/tailwindcss/setup-tailwind.js';
|
|
5
6
|
import { sanitizeStyleSheet } from './utils/css/sanitize-stylesheet.js';
|
|
6
7
|
import { extractRulesPerClass } from './utils/css/extract-rules-per-class.js';
|
|
7
8
|
import { getCustomProperties } from './utils/css/get-custom-properties.js';
|
|
8
|
-
import { generate, List } from 'css-tree';
|
|
9
9
|
import { sanitizeNonInlinableRules } from './utils/css/sanitize-non-inlinable-rules.js';
|
|
10
10
|
import { addInlinedStylesToElement } from './utils/tailwindcss/add-inlined-styles-to-element.js';
|
|
11
11
|
import { isValidNode } from './utils/html/is-valid-node.js';
|
|
@@ -80,10 +80,11 @@ export default class Renderer {
|
|
|
80
80
|
sanitizeStyleSheet(styleSheet);
|
|
81
81
|
const { inlinable: inlinableRules, nonInlinable: nonInlinableRules } = extractRulesPerClass(styleSheet, classesUsed);
|
|
82
82
|
const customProperties = getCustomProperties(styleSheet);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
// Create a new Root for non-inline styles
|
|
84
|
+
const nonInlineStyles = postcss.root();
|
|
85
|
+
for (const rule of nonInlinableRules.values()) {
|
|
86
|
+
nonInlineStyles.append(rule.clone());
|
|
87
|
+
}
|
|
87
88
|
sanitizeNonInlinableRules(nonInlineStyles);
|
|
88
89
|
const hasNonInlineStylesToApply = nonInlinableRules.size > 0;
|
|
89
90
|
let appliedNonInlineStyles = false;
|
|
@@ -105,7 +106,7 @@ export default class Renderer {
|
|
|
105
106
|
}
|
|
106
107
|
if (hasHead && hasNonInlineStylesToApply) {
|
|
107
108
|
appliedNonInlineStyles = true;
|
|
108
|
-
serialized = serialized.replace('<head>', '<head>' + '<style>' +
|
|
109
|
+
serialized = serialized.replace('<head>', '<head>' + '<style>' + nonInlineStyles.toString() + '</style>');
|
|
109
110
|
}
|
|
110
111
|
if (hasNonInlineStylesToApply && !appliedNonInlineStyles) {
|
|
111
112
|
throw new Error(`You are trying to use the following Tailwind classes that cannot be inlined: ${Array.from(nonInlinableRules.keys()).join(' ')}.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function extractRulesPerClass(root:
|
|
1
|
+
import type { Root, Rule } from 'postcss';
|
|
2
|
+
export declare function extractRulesPerClass(root: Root, classes: string[]): {
|
|
3
3
|
inlinable: Map<string, Rule>;
|
|
4
4
|
nonInlinable: Map<string, Rule>;
|
|
5
5
|
};
|
|
@@ -1,32 +1,21 @@
|
|
|
1
|
-
import { string, walk } from 'css-tree';
|
|
2
1
|
import { isRuleInlinable } from './is-rule-inlinable.js';
|
|
2
|
+
function unescapeClassName(name) {
|
|
3
|
+
return name.replace(/\\(.)/g, '$1');
|
|
4
|
+
}
|
|
3
5
|
export function extractRulesPerClass(root, classes) {
|
|
4
6
|
const classSet = new Set(classes);
|
|
5
7
|
const inlinableRules = new Map();
|
|
6
8
|
const nonInlinableRules = new Map();
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (isRuleInlinable(rule)) {
|
|
18
|
-
for (const className of selectorClasses) {
|
|
19
|
-
if (classSet.has(className)) {
|
|
20
|
-
inlinableRules.set(className, rule);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
for (const className of selectorClasses) {
|
|
26
|
-
if (classSet.has(className)) {
|
|
27
|
-
nonInlinableRules.set(className, rule);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
9
|
+
root.walkRules((rule) => {
|
|
10
|
+
// Extract class names from selector using regex
|
|
11
|
+
// The regex matches class names including escaped characters (like \: or \/)
|
|
12
|
+
// Note: \\. must come FIRST in the alternation to properly match escapes
|
|
13
|
+
const classMatches = rule.selector.matchAll(/\.((?:\\.|[^\s.:>+~[#,])+)/g);
|
|
14
|
+
const selectorClasses = [...classMatches].map((m) => unescapeClassName(m[1]));
|
|
15
|
+
const targetMap = isRuleInlinable(rule) ? inlinableRules : nonInlinableRules;
|
|
16
|
+
for (const className of selectorClasses) {
|
|
17
|
+
if (classSet.has(className)) {
|
|
18
|
+
targetMap.set(className, rule);
|
|
30
19
|
}
|
|
31
20
|
}
|
|
32
21
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Root, Declaration } from 'postcss';
|
|
2
2
|
export interface CustomProperty {
|
|
3
3
|
syntax?: Declaration;
|
|
4
4
|
inherits?: Declaration;
|
|
5
5
|
initialValue?: Declaration;
|
|
6
6
|
}
|
|
7
7
|
export type CustomProperties = Map<string, CustomProperty>;
|
|
8
|
-
export declare function getCustomProperties(
|
|
8
|
+
export declare function getCustomProperties(root: Root): CustomProperties;
|
|
@@ -1,36 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
export function getCustomProperties(node) {
|
|
1
|
+
export function getCustomProperties(root) {
|
|
3
2
|
const customProperties = new Map();
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
let inherits;
|
|
12
|
-
let initialValue;
|
|
13
|
-
walk(atrule, {
|
|
14
|
-
visit: 'Declaration',
|
|
15
|
-
enter(declaration) {
|
|
16
|
-
if (declaration.property === 'syntax') {
|
|
17
|
-
syntax = declaration;
|
|
18
|
-
}
|
|
19
|
-
if (declaration.property === 'inherits') {
|
|
20
|
-
inherits = declaration;
|
|
21
|
-
}
|
|
22
|
-
if (declaration.property === 'initial-value') {
|
|
23
|
-
initialValue = declaration;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
customProperties.set(prelude, {
|
|
28
|
-
syntax,
|
|
29
|
-
inherits,
|
|
30
|
-
initialValue
|
|
31
|
-
});
|
|
3
|
+
root.walkAtRules('property', (atRule) => {
|
|
4
|
+
const propertyName = atRule.params.trim();
|
|
5
|
+
if (propertyName.startsWith('--')) {
|
|
6
|
+
const prop = {};
|
|
7
|
+
atRule.walkDecls((decl) => {
|
|
8
|
+
if (decl.prop === 'syntax') {
|
|
9
|
+
prop.syntax = decl;
|
|
32
10
|
}
|
|
33
|
-
|
|
11
|
+
if (decl.prop === 'inherits') {
|
|
12
|
+
prop.inherits = decl;
|
|
13
|
+
}
|
|
14
|
+
if (decl.prop === 'initial-value') {
|
|
15
|
+
prop.initialValue = decl;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
customProperties.set(propertyName, prop);
|
|
34
19
|
}
|
|
35
20
|
});
|
|
36
21
|
return customProperties;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Rule } from 'postcss';
|
|
2
2
|
export declare function isRuleInlinable(rule: Rule): boolean;
|
|
@@ -1,6 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
// At-rules that prevent inlining when found inside a rule (CSS nesting)
|
|
2
|
+
const NON_INLINABLE_AT_RULES = new Set(['media', 'supports', 'container', 'document']);
|
|
2
3
|
export function isRuleInlinable(rule) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
// Check if rule CONTAINS a conditional at-rule (for CSS nesting like Tailwind v4)
|
|
5
|
+
// e.g., .sm\:bg-blue-300 { @media (width >= 40rem) { ... } }
|
|
6
|
+
let hasAtRuleInside = false;
|
|
7
|
+
rule.walk((node) => {
|
|
8
|
+
if (node.type === 'atrule') {
|
|
9
|
+
hasAtRuleInside = true;
|
|
10
|
+
return false; // Stop walking
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
if (hasAtRuleInside) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
// Check if rule is INSIDE a conditional at-rule (media query, etc.)
|
|
17
|
+
// Note: @layer is just a grouping mechanism, it doesn't prevent inlining
|
|
18
|
+
let parent = rule.parent;
|
|
19
|
+
while (parent && parent.type !== 'root') {
|
|
20
|
+
if (parent.type === 'atrule') {
|
|
21
|
+
const atRule = parent;
|
|
22
|
+
if (NON_INLINABLE_AT_RULES.has(atRule.name)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
parent = parent.parent;
|
|
27
|
+
}
|
|
28
|
+
// Check for pseudo selectors in the selector string
|
|
29
|
+
// Matches :hover, ::before, :nth-child(), etc.
|
|
30
|
+
const hasPseudoSelector = /::?[\w-]+(\([^)]*\))?/.test(rule.selector);
|
|
31
|
+
return !hasPseudoSelector;
|
|
6
32
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Rule } from 'postcss';
|
|
2
2
|
import type { CustomProperties } from './get-custom-properties.js';
|
|
3
|
-
export declare function makeInlineStylesFor(inlinableRules:
|
|
3
|
+
export declare function makeInlineStylesFor(inlinableRules: Rule[], customProperties: CustomProperties): string;
|
|
@@ -1,56 +1,53 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { unwrapValue } from './unwrap-value.js';
|
|
1
|
+
import valueParser from 'postcss-value-parser';
|
|
3
2
|
export function makeInlineStylesFor(inlinableRules, customProperties) {
|
|
4
3
|
let styles = '';
|
|
4
|
+
// Collect local variable declarations
|
|
5
5
|
const localVariableDeclarations = new Map();
|
|
6
6
|
for (const rule of inlinableRules) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (declaration.property.startsWith('--')) {
|
|
11
|
-
localVariableDeclarations.set(declaration.property, declaration);
|
|
12
|
-
}
|
|
7
|
+
rule.walkDecls((decl) => {
|
|
8
|
+
if (decl.prop.startsWith('--')) {
|
|
9
|
+
localVariableDeclarations.set(decl.prop, decl);
|
|
13
10
|
}
|
|
14
11
|
});
|
|
15
12
|
}
|
|
13
|
+
// Process rules and resolve variables
|
|
16
14
|
for (const rule of inlinableRules) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
15
|
+
rule.walkDecls((decl) => {
|
|
16
|
+
// Skip variable declarations
|
|
17
|
+
if (decl.prop.startsWith('--'))
|
|
18
|
+
return;
|
|
19
|
+
let value = decl.value;
|
|
20
|
+
// Resolve var() references
|
|
21
|
+
if (value.includes('var(')) {
|
|
22
|
+
const parsed = valueParser(value);
|
|
23
|
+
parsed.walk((node) => {
|
|
24
|
+
if (node.type === 'function' && node.value === 'var') {
|
|
25
|
+
const varNameNode = node.nodes[0];
|
|
26
|
+
const variableName = varNameNode ? valueParser.stringify(varNameNode).trim() : '';
|
|
27
|
+
if (variableName) {
|
|
28
|
+
// Check local declarations first
|
|
29
|
+
const localDef = localVariableDeclarations.get(variableName);
|
|
30
|
+
if (localDef) {
|
|
31
|
+
node.type = 'word';
|
|
32
|
+
node.value = localDef.value;
|
|
33
|
+
node.nodes = [];
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Check custom properties (from @property rules)
|
|
37
|
+
const customProp = customProperties.get(variableName);
|
|
38
|
+
if (customProp?.initialValue) {
|
|
39
|
+
node.type = 'word';
|
|
40
|
+
node.value = customProp.initialValue.value;
|
|
41
|
+
node.nodes = [];
|
|
42
|
+
}
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
});
|
|
46
|
-
walk(rule, {
|
|
47
|
-
visit: 'Declaration',
|
|
48
|
-
enter(declaration) {
|
|
49
|
-
if (declaration.property.startsWith('--')) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
styles += `${declaration.property}: ${generate(declaration.value)} ${declaration.important ? '!important' : ''};`;
|
|
46
|
+
});
|
|
47
|
+
value = valueParser.stringify(parsed.nodes);
|
|
53
48
|
}
|
|
49
|
+
const important = decl.important ? '!important' : '';
|
|
50
|
+
styles += `${decl.prop}: ${value} ${important};`;
|
|
54
51
|
});
|
|
55
52
|
}
|
|
56
53
|
return styles;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Root, Declaration } from 'postcss';
|
|
2
2
|
export interface VariableDefinition {
|
|
3
3
|
declaration: Declaration;
|
|
4
|
-
|
|
4
|
+
selector: string;
|
|
5
5
|
variableName: string;
|
|
6
6
|
definition: string;
|
|
7
7
|
}
|
|
8
|
-
export declare function resolveAllCssVariables(
|
|
8
|
+
export declare function resolveAllCssVariables(root: Root): void;
|
|
@@ -1,123 +1,135 @@
|
|
|
1
|
-
import
|
|
2
|
-
function
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return true;
|
|
1
|
+
import valueParser from 'postcss-value-parser';
|
|
2
|
+
function getSelector(decl) {
|
|
3
|
+
const parent = decl.parent;
|
|
4
|
+
if (parent?.type === 'rule') {
|
|
5
|
+
return parent.selector;
|
|
7
6
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
return '*';
|
|
8
|
+
}
|
|
9
|
+
function getAtRuleSelector(decl) {
|
|
10
|
+
let parent = decl.parent;
|
|
11
|
+
while (parent) {
|
|
12
|
+
if (parent.type === 'atrule') {
|
|
13
|
+
// Check if parent of atrule is a rule
|
|
14
|
+
const atRuleParent = parent.parent;
|
|
15
|
+
if (atRuleParent?.type === 'rule') {
|
|
16
|
+
return atRuleParent.selector;
|
|
17
|
+
}
|
|
14
18
|
}
|
|
15
|
-
if (
|
|
16
|
-
|
|
19
|
+
if (parent.type === 'rule') {
|
|
20
|
+
return parent.selector;
|
|
17
21
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
parent = parent.parent;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
function isInAtRule(decl) {
|
|
27
|
+
let parent = decl.parent;
|
|
28
|
+
while (parent) {
|
|
29
|
+
if (parent.type === 'atrule') {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
parent = parent.parent;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
function isInPropertiesLayer(decl) {
|
|
37
|
+
let parent = decl.parent;
|
|
38
|
+
while (parent) {
|
|
39
|
+
if (parent.type === 'atrule') {
|
|
40
|
+
const atRule = parent;
|
|
41
|
+
if (atRule.name === 'layer' && atRule.params?.includes('properties')) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
parent = parent.parent;
|
|
23
46
|
}
|
|
24
47
|
return false;
|
|
25
48
|
}
|
|
26
|
-
|
|
49
|
+
function doSelectorsIntersect(first, second) {
|
|
50
|
+
if (first === second)
|
|
51
|
+
return true;
|
|
52
|
+
// Check for universal selectors
|
|
53
|
+
if (first.includes(':root') || second.includes(':root'))
|
|
54
|
+
return true;
|
|
55
|
+
if (first === '*' || second === '*')
|
|
56
|
+
return true;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
export function resolveAllCssVariables(root) {
|
|
27
60
|
const variableDefinitions = new Set();
|
|
28
|
-
const variableUses =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
variableUses.add({
|
|
66
|
-
declaration,
|
|
67
|
-
path: [...path],
|
|
68
|
-
fallback,
|
|
69
|
-
variableName: name,
|
|
70
|
-
raw: generate(funcNode)
|
|
71
|
-
});
|
|
72
|
-
if (fallback?.includes('var(')) {
|
|
73
|
-
const parsedFallback = parse(fallback, {
|
|
74
|
-
context: 'value'
|
|
75
|
-
});
|
|
76
|
-
parseVariableUsesFrom(parsedFallback);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
61
|
+
const variableUses = [];
|
|
62
|
+
// First pass: collect variable definitions and uses
|
|
63
|
+
root.walkDecls((decl) => {
|
|
64
|
+
// Skip @layer (properties) { ... } to avoid variable resolution conflicts
|
|
65
|
+
if (isInPropertiesLayer(decl)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (decl.prop.startsWith('--')) {
|
|
69
|
+
variableDefinitions.add({
|
|
70
|
+
declaration: decl,
|
|
71
|
+
selector: getSelector(decl),
|
|
72
|
+
variableName: decl.prop,
|
|
73
|
+
definition: decl.value
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
else if (decl.value.includes('var(')) {
|
|
77
|
+
const parseVariableUses = (value) => {
|
|
78
|
+
const parsed = valueParser(value);
|
|
79
|
+
parsed.walk((node) => {
|
|
80
|
+
if (node.type === 'function' && node.value === 'var') {
|
|
81
|
+
const varNameNode = node.nodes[0];
|
|
82
|
+
const varName = varNameNode ? valueParser.stringify(varNameNode).trim() : '';
|
|
83
|
+
// Find fallback (after the comma)
|
|
84
|
+
let fallback;
|
|
85
|
+
const commaIndex = node.nodes.findIndex((n) => n.type === 'div' && n.value === ',');
|
|
86
|
+
if (commaIndex !== -1) {
|
|
87
|
+
fallback = valueParser.stringify(node.nodes.slice(commaIndex + 1)).trim();
|
|
88
|
+
}
|
|
89
|
+
const raw = valueParser.stringify(node);
|
|
90
|
+
variableUses.push({
|
|
91
|
+
declaration: decl,
|
|
92
|
+
selector: getSelector(decl),
|
|
93
|
+
inAtRule: isInAtRule(decl),
|
|
94
|
+
atRuleSelector: getAtRuleSelector(decl),
|
|
95
|
+
fallback,
|
|
96
|
+
variableName: varName,
|
|
97
|
+
raw
|
|
80
98
|
});
|
|
99
|
+
// If fallback contains var(), recursively parse those too
|
|
100
|
+
if (fallback?.includes('var(')) {
|
|
101
|
+
parseVariableUses(fallback);
|
|
102
|
+
}
|
|
81
103
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
path.unshift(node);
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
parseVariableUses(decl.value);
|
|
86
107
|
}
|
|
87
108
|
});
|
|
109
|
+
// Second pass: resolve variables
|
|
88
110
|
for (const use of variableUses) {
|
|
89
111
|
let hasReplaced = false;
|
|
90
112
|
for (const definition of variableDefinitions) {
|
|
91
113
|
if (use.variableName !== definition.variableName) {
|
|
92
114
|
continue;
|
|
93
115
|
}
|
|
94
|
-
if
|
|
95
|
-
|
|
96
|
-
use.
|
|
97
|
-
use.
|
|
98
|
-
|
|
99
|
-
definition.path[1].type === 'Rule' &&
|
|
100
|
-
doSelectorsIntersect(use.path[3].prelude, definition.path[1].prelude)) {
|
|
101
|
-
use.declaration.value = parse(generate(use.declaration.value).replaceAll(use.raw, definition.definition), {
|
|
102
|
-
context: 'value'
|
|
103
|
-
});
|
|
116
|
+
// Check if use is in an at-rule and definition is in a matching rule
|
|
117
|
+
if (use.inAtRule &&
|
|
118
|
+
use.atRuleSelector &&
|
|
119
|
+
doSelectorsIntersect(use.atRuleSelector, definition.selector)) {
|
|
120
|
+
use.declaration.value = use.declaration.value.replaceAll(use.raw, definition.definition);
|
|
104
121
|
hasReplaced = true;
|
|
105
122
|
break;
|
|
106
123
|
}
|
|
107
|
-
if
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
definition.path[1]?.type === 'Rule' &&
|
|
111
|
-
doSelectorsIntersect(use.path[1].prelude, definition.path[1].prelude)) {
|
|
112
|
-
use.declaration.value = parse(generate(use.declaration.value).replaceAll(use.raw, definition.definition), {
|
|
113
|
-
context: 'value'
|
|
114
|
-
});
|
|
124
|
+
// Check if both are in rules with matching selectors
|
|
125
|
+
if (!use.inAtRule && doSelectorsIntersect(use.selector, definition.selector)) {
|
|
126
|
+
use.declaration.value = use.declaration.value.replaceAll(use.raw, definition.definition);
|
|
115
127
|
hasReplaced = true;
|
|
116
128
|
break;
|
|
117
129
|
}
|
|
118
130
|
}
|
|
119
131
|
if (!hasReplaced && use.fallback) {
|
|
120
|
-
use.declaration.value =
|
|
132
|
+
use.declaration.value = use.declaration.value.replaceAll(use.raw, use.fallback);
|
|
121
133
|
}
|
|
122
134
|
}
|
|
123
135
|
}
|
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
* Intentionally only resolves `*` and `/` operations without dealing with parenthesis, because this is the only thing required to run Tailwind v4
|
|
4
|
-
*/
|
|
5
|
-
export declare function resolveCalcExpressions(node: CssNode): void;
|
|
1
|
+
import type { Root } from 'postcss';
|
|
2
|
+
export declare function resolveCalcExpressions(root: Root): void;
|