better-svelte-email 1.0.3 → 1.1.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.
Files changed (26) hide show
  1. package/README.md +1 -1
  2. package/dist/render/index.js +7 -6
  3. package/dist/render/utils/css/extract-rules-per-class.d.ts +2 -2
  4. package/dist/render/utils/css/extract-rules-per-class.js +13 -24
  5. package/dist/render/utils/css/get-custom-properties.d.ts +2 -2
  6. package/dist/render/utils/css/get-custom-properties.js +16 -31
  7. package/dist/render/utils/css/is-rule-inlinable.d.ts +1 -1
  8. package/dist/render/utils/css/is-rule-inlinable.js +30 -4
  9. package/dist/render/utils/css/make-inline-styles-for.d.ts +2 -2
  10. package/dist/render/utils/css/make-inline-styles-for.js +38 -41
  11. package/dist/render/utils/css/resolve-all-css-variables.d.ts +3 -3
  12. package/dist/render/utils/css/resolve-all-css-variables.js +107 -95
  13. package/dist/render/utils/css/resolve-calc-expressions.d.ts +2 -5
  14. package/dist/render/utils/css/resolve-calc-expressions.js +155 -118
  15. package/dist/render/utils/css/sanitize-declarations.d.ts +2 -2
  16. package/dist/render/utils/css/sanitize-declarations.js +226 -282
  17. package/dist/render/utils/css/sanitize-non-inlinable-rules.d.ts +2 -2
  18. package/dist/render/utils/css/sanitize-non-inlinable-rules.js +14 -19
  19. package/dist/render/utils/css/sanitize-stylesheet.d.ts +2 -2
  20. package/dist/render/utils/css/sanitize-stylesheet.js +4 -4
  21. package/dist/render/utils/tailwindcss/add-inlined-styles-to-element.d.ts +1 -1
  22. package/dist/render/utils/tailwindcss/setup-tailwind.d.ts +2 -2
  23. package/dist/render/utils/tailwindcss/setup-tailwind.js +3 -3
  24. package/package.json +5 -4
  25. package/dist/render/utils/css/unwrap-value.d.ts +0 -2
  26. 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** - Extensive test coverage with unit and integration tests
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
 
@@ -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
- const nonInlineStyles = {
84
- type: 'StyleSheet',
85
- children: new List().fromArray(Array.from(nonInlinableRules.values()))
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>' + generate(nonInlineStyles) + '</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 { type CssNode, type Rule } from 'css-tree';
2
- export declare function extractRulesPerClass(root: CssNode, classes: string[]): {
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
- walk(root, {
8
- visit: 'Rule',
9
- enter(rule) {
10
- const selectorClasses = [];
11
- walk(rule, {
12
- visit: 'ClassSelector',
13
- enter(classSelector) {
14
- selectorClasses.push(string.decode(classSelector.name));
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 { type CssNode, type Declaration } from 'css-tree';
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(node: CssNode): Map<string, CustomProperty>;
8
+ export declare function getCustomProperties(root: Root): CustomProperties;
@@ -1,36 +1,21 @@
1
- import { generate, walk } from 'css-tree';
2
- export function getCustomProperties(node) {
1
+ export function getCustomProperties(root) {
3
2
  const customProperties = new Map();
4
- walk(node, {
5
- visit: 'Atrule',
6
- enter(atrule) {
7
- if (atrule.name === 'property' && atrule.prelude) {
8
- const prelude = generate(atrule.prelude);
9
- if (prelude.startsWith('--')) {
10
- let syntax;
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 { type Rule } from 'css-tree';
1
+ import type { Rule } from 'postcss';
2
2
  export declare function isRuleInlinable(rule: Rule): boolean;
@@ -1,6 +1,32 @@
1
- import { find } from 'css-tree';
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
- const hasAtRuleInside = find(rule, (node) => node.type === 'Atrule') !== null;
4
- const hasPseudoSelector = find(rule, (node) => node.type === 'PseudoClassSelector' || node.type === 'PseudoElementSelector') !== null;
5
- return !hasAtRuleInside && !hasPseudoSelector;
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 { type CssNode } from 'css-tree';
1
+ import type { Rule } from 'postcss';
2
2
  import type { CustomProperties } from './get-custom-properties.js';
3
- export declare function makeInlineStylesFor(inlinableRules: CssNode[], customProperties: CustomProperties): string;
3
+ export declare function makeInlineStylesFor(inlinableRules: Rule[], customProperties: CustomProperties): string;
@@ -1,56 +1,53 @@
1
- import { generate, walk } from 'css-tree';
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
- walk(rule, {
8
- visit: 'Declaration',
9
- enter(declaration) {
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
- walk(rule, {
18
- visit: 'Function',
19
- enter(func, funcParentListItem) {
20
- if (func.name === 'var') {
21
- let variableName;
22
- walk(func, {
23
- visit: 'Identifier',
24
- enter(identifier) {
25
- variableName = identifier.name;
26
- return this.break;
27
- }
28
- });
29
- if (variableName) {
30
- const definition = localVariableDeclarations.get(variableName);
31
- if (definition) {
32
- funcParentListItem.data = unwrapValue(definition.value);
33
- }
34
- else {
35
- // For most variables tailwindcss defines, they also define a custom
36
- // property for them with an initial value that we can inline here
37
- const customProperty = customProperties.get(variableName);
38
- if (customProperty?.initialValue) {
39
- funcParentListItem.data = unwrapValue(customProperty.initialValue.value);
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 { type CssNode, type Declaration } from 'css-tree';
1
+ import type { Root, Declaration } from 'postcss';
2
2
  export interface VariableDefinition {
3
3
  declaration: Declaration;
4
- path: CssNode[];
4
+ selector: string;
5
5
  variableName: string;
6
6
  definition: string;
7
7
  }
8
- export declare function resolveAllCssVariables(node: CssNode): void;
8
+ export declare function resolveAllCssVariables(root: Root): void;
@@ -1,123 +1,135 @@
1
- import { generate, parse, walk } from 'css-tree';
2
- function doSelectorsIntersect(first, second) {
3
- const firstStringified = generate(first);
4
- const secondStringified = generate(second);
5
- if (firstStringified === secondStringified) {
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
- let hasSomeUniversal = false;
9
- const walker = (node, _parentListItem, parentList) => {
10
- if (hasSomeUniversal)
11
- return;
12
- if (node.type === 'PseudoClassSelector' && node.name === 'root') {
13
- hasSomeUniversal = true;
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 (node.type === 'TypeSelector' && node.name === '*' && parentList.size === 1) {
16
- hasSomeUniversal = true;
19
+ if (parent.type === 'rule') {
20
+ return parent.selector;
17
21
  }
18
- };
19
- walk(first, walker);
20
- walk(second, walker);
21
- if (hasSomeUniversal) {
22
- return true;
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
- export function resolveAllCssVariables(node) {
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 = new Set();
29
- const path = [];
30
- walk(node, {
31
- leave() {
32
- path.shift();
33
- },
34
- enter(node) {
35
- if (node.type === 'Declaration') {
36
- const declaration = node;
37
- // Ignores @layer (properties) { ... } to avoid variable resolution conflicts
38
- if (path.some((ancestor) => ancestor.type === 'Atrule' &&
39
- ancestor.name === 'layer' &&
40
- ancestor.prelude !== null &&
41
- generate(ancestor.prelude).includes('properties'))) {
42
- path.unshift(node);
43
- return;
44
- }
45
- if (/--[\S]+/.test(declaration.property)) {
46
- variableDefinitions.add({
47
- declaration,
48
- path: [...path],
49
- variableName: declaration.property,
50
- definition: generate(declaration.value)
51
- });
52
- }
53
- else {
54
- function parseVariableUsesFrom(node) {
55
- walk(node, {
56
- visit: 'Function',
57
- enter(funcNode) {
58
- if (funcNode.name === 'var') {
59
- const children = funcNode.children.toArray();
60
- const name = generate(children[0]);
61
- const fallback =
62
- // The second argument should be an "," Operator Node,
63
- // such that the actual fallback is only in the third argument
64
- children[2] ? generate(children[2]) : undefined;
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
- parseVariableUsesFrom(declaration.value);
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 (use.path[0]?.type === 'Block' &&
95
- use.path[1]?.type === 'Atrule' &&
96
- use.path[2]?.type === 'Block' &&
97
- use.path[3]?.type === 'Rule' &&
98
- definition.path[0].type === 'Block' &&
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 (use.path[0]?.type === 'Block' &&
108
- use.path[1]?.type === 'Rule' &&
109
- definition.path[0]?.type === 'Block' &&
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 = parse(generate(use.declaration.value).replaceAll(use.raw, use.fallback), { context: '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 { type CssNode } from 'css-tree';
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;