Skip to content

Commit 79c05ff

Browse files
committed
Add in variable expansion/unroll for defining new variable value where usage could apply
1 parent cba0da4 commit 79c05ff

8 files changed

+95
-32
lines changed

index.js

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,50 +76,99 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
7676

7777

7878

79+
7980
// Collect all of the variables defined
8081
// ---------------------------------------------------------
8182
// ---------------------------------------------------------
82-
eachCssVariableDeclaration(css, function(decl) {
83+
eachCssVariableDeclaration(css, function(variableDecl) {
8384
// We cache the parent rule because after decl removal, it will be undefined
84-
const declParentRule = decl.parent;
85-
const variableName = decl.prop;
85+
const variableDeclParentRule = variableDecl.parent;
86+
const variableName = variableDecl.prop;
87+
const variableValue = variableDecl.value;
88+
const isImportant = variableDecl.important || false;
89+
const variableSelectorBranchs = generateSelectorBranchesFromPostcssNode(variableDeclParentRule);
90+
91+
debug(`Collecting ${variableName}=${variableValue} isImportant=${isImportant} from ${variableDeclParentRule.selector.toString()}`);
8692

8793
map[variableName] = (map[variableName] || []).concat({
8894
name: variableName,
89-
value: decl.value,
90-
isImportant: decl.important || false,
91-
selectorBranches: generateSelectorBranchesFromPostcssNode(declParentRule)
95+
value: variableValue,
96+
isImportant,
97+
selectorBranches: variableSelectorBranchs
98+
});
99+
100+
101+
// Expand/rollout/unroll variable usage
102+
// Where we define variables, also add in any usage that falls under scope
103+
// ex.
104+
// Before:
105+
// .foo { --color: #f00; color: var(--color); }
106+
// .foo:hover { --color: #0f0; };
107+
// After:
108+
// .foo { color: #f00; }
109+
// .foo:hover { color: #0f0; }
110+
// --------------------------------
111+
css.walkDecls(function(usageDecl) {
112+
// Avoid duplicating the usage decl on itself
113+
if(variableDeclParentRule === usageDecl.parent) {
114+
return;
115+
}
116+
117+
const usageSelectorBranches = generateSelectorBranchesFromPostcssNode(usageDecl.parent);
118+
119+
variableSelectorBranchs.some((variableSelectorBranch) => {
120+
return usageSelectorBranches.some((usageSelectorBranch) => {
121+
// In this case, we look whether the usage is under the scope of the definition
122+
const isUnderScope = isSelectorBranchUnderScope(usageSelectorBranch, variableSelectorBranch);
123+
124+
debug(`Should expand usage? isUnderScope=${isUnderScope}`, usageSelectorBranch.selector.toString(), '|', variableSelectorBranch.selector.toString())
125+
126+
if(isUnderScope) {
127+
usageDecl.value.replace(new RegExp(RE_VAR_FUNC.source, 'g'), (match, matchedVariableName) => {
128+
if(matchedVariableName === variableName) {
129+
variableDecl.after(usageDecl.clone());
130+
}
131+
});
132+
}
133+
134+
return isUnderScope;
135+
});
136+
});
92137
});
93138

139+
140+
94141
// Remove the variable declaration because they are pretty much useless after we resolve them
95142
if(!opts.preserve) {
96-
decl.remove();
143+
variableDecl.remove();
97144
}
98145
// Or we can also just show the computed value used for that variable
99146
else if(opts.preserve === 'computed') {
100147
// TODO: put computed value here
101148
}
102149

103150
// Clean up the rule that declared them if it doesn't have anything left after we potentially remove the variable decl
104-
if(declParentRule.nodes.length <= 0) {
105-
declParentRule.remove();
151+
if(variableDeclParentRule.nodes.length <= 0) {
152+
variableDeclParentRule.remove();
106153
}
107154
});
108155

109156
debug('map', map);
110157

158+
debug('After collecting variables ------');
159+
debug(css.toString());
160+
debug('---------------------------------');
111161

112162

113163
// Resolve variables everywhere
114164
// ---------------------------------------------------------
115165
// ---------------------------------------------------------
116166
css.walkDecls(function(decl) {
117-
// If it uses variables
118-
// and is not a variable declarations that we may be preserving from earlier
167+
// Avoid any variable decls, `--foo: var(--bar);`, that may have been preserved
119168
if(!RE_VAR_PROP.test(decl.prop)) {
120169
const selectorBranches = generateSelectorBranchesFromPostcssNode(decl.parent);
121170

122-
decl.value = decl.value.replace(new RegExp(RE_VAR_FUNC.source, 'g'), (match, variableName) => {
171+
decl.value = decl.value.replace(new RegExp(RE_VAR_FUNC.source, 'g'), (match, variableName, fallback) => {
123172
debug('usage', variableName);
124173
const variableEntries = map[variableName] || [];
125174

@@ -131,10 +180,11 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
131180
// We only need to find one branch that matches
132181
variableEntry.selectorBranches.some((variableSelectorBranch) => {
133182
return selectorBranches.some((selectorBranch) => {
183+
// Look whether the variable definition is under the scope of the usage
134184
const isUnderScope = isSelectorBranchUnderScope(variableSelectorBranch, selectorBranch);
135185
const specificity = getSpecificity(variableSelectorBranch.selector.toString());
136186

137-
debug(`isUnderScope=${isUnderScope} compareSpecificity=${compareSpecificity(specificity, currentGreatestSpecificity)} specificity=${specificity}`, variableSelectorBranch.selector.toString(), selectorBranch.selector.toString())
187+
debug(`isUnderScope=${isUnderScope} compareSpecificity=${compareSpecificity(specificity, currentGreatestSpecificity)} specificity=${specificity}`, variableSelectorBranch.selector.toString(), '|', selectorBranch.selector.toString())
138188

139189
if(isUnderScope && compareSpecificity(specificity, currentGreatestSpecificity) >= 0) {
140190
currentGreatestSpecificity = specificity;
@@ -148,7 +198,13 @@ module.exports = postcss.plugin('postcss-css-variables', function(options) {
148198

149199
debug('currentGreatestVariableEntry', currentGreatestVariableEntry);
150200

151-
return currentGreatestVariableEntry.value;
201+
const resultantValue = (currentGreatestVariableEntry && currentGreatestVariableEntry.value) || fallback;
202+
203+
if(!resultantValue) {
204+
result.warn(['variable ' + variableName + ' is undefined and used without a fallback', { node: decl }]);
205+
}
206+
207+
return resultantValue || 'undefined';
152208
});
153209
}
154210
});
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
:root {
2-
--foo1: 150px;
2+
--foo1: 150px;
33
}
44

55
.box-bar {
6-
width: var(--missing,calc(var(--foo1) + 100px));
6+
width: var(--missing,calc(var(--foo1) + 100px));
77
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
.box-bar {
2-
width: calc(150px + 100px);
3-
}
2+
width: calc(150px + 100px);
3+
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
:root {
2-
--foo-default: 100px;
3-
--foo-width: 150px;
2+
--foo-default: 100px;
3+
--foo-width: 150px;
44
}
55

66
.box-foo {
7-
width: var(--missing);
7+
width: var(--missing);
88
}
99

1010
.box-bar {
11-
width: var(--missing, var(--foo-default));
12-
}
11+
width: var(--missing, var(--foo-default));
12+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.box-foo {
2-
width: undefined;
2+
width: undefined;
33
}
44

55
.box-bar {
6-
width: 100px;
7-
}
6+
width: 100px;
7+
}

test/fixtures/pseudo-selector-declare-variable.css

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@
77
--foo-color: #00ff00;
88
}
99

10-
11-
/* This should add nothing to `.foo`, wrong scope */
1210
.bar:hover + .foo {
1311
--foo-color: #f0f000;
1412
}
13+
1514
/* This should add nothing to `.foo`, wrong scope */
1615
.foo:hover + .bar {
1716
--foo-color: #0000ff;
1817
}
1918
/* This should add nothing to `.foo`, wrong scope */
2019
.foo:hover + .bar:focus {
2120
--foo-color: #000f0f;
22-
}
21+
}

test/fixtures/pseudo-selector-declare-variable.expected.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@
55
.foo:hover {
66
color: #00ff00;
77
}
8+
9+
.bar:hover + .foo {
10+
color: #f0f000;
11+
}

test/test.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,18 @@ var testPlugin = function(filePath, expectedFilePath, options) {
3535
preset: { plugins: [normalizeWhitespace, discardComments] }
3636
})
3737
])
38-
.process(String(actualBuffer));
38+
.process(String(actualBuffer), {
39+
from: filePath,
40+
});
3941

4042
var expectedResult = postcss([
4143
cssnano({
4244
preset: { plugins: [normalizeWhitespace, discardComments] }
4345
})
4446
])
45-
.process(String(expectedBuffer));
47+
.process(String(expectedBuffer), {
48+
from: expectedFilePath,
49+
});
4650

4751
return Promise.props({
4852
actualResult: actualResult,
@@ -56,7 +60,7 @@ var testPlugin = function(filePath, expectedFilePath, options) {
5660

5761
var fixtureBasePath = './test/fixtures/';
5862
var test = function(message, fixtureName, options) {
59-
it(message, function() {
63+
it(`${message} (${fixtureName})`, function() {
6064
return testPlugin(
6165
path.join(fixtureBasePath, fixtureName + '.css'),
6266
path.join(fixtureBasePath, fixtureName + '.expected.css'),

0 commit comments

Comments
 (0)