Skip to content

Commit 6e5d9a9

Browse files
committed
Consider specificity of CSS selectors
1 parent fd3f43f commit 6e5d9a9

File tree

4 files changed

+59
-8
lines changed

4 files changed

+59
-8
lines changed

lib/helpers.es6

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import objectAssign from 'object-assign';
2+
import specificity from 'specificity';
23

34

45
export function extendStyle(htmlNode, cssNode) {
@@ -14,6 +15,43 @@ export function extendStyle(htmlNode, cssNode) {
1415
}
1516

1617

18+
export function sortCSSNodesBySpecificity(nodes) {
19+
// Sort CSS nodes by specificity (ascending): div - .foo - #bar
20+
return nodes.sort((a, b) => {
21+
a = getSpecificity(a.selector);
22+
b = getSpecificity(b.selector);
23+
24+
if (a > b) {
25+
return 1;
26+
} else if (a < b) {
27+
return -1;
28+
} else {
29+
return 0;
30+
}
31+
});
32+
}
33+
34+
35+
var specificityCache = {};
36+
function getSpecificity(selector) {
37+
if (specificityCache[selector] !== undefined) {
38+
return specificityCache[selector];
39+
}
40+
41+
const specificityResult = specificity.calculate(selector)[0];
42+
const specificityParts = specificityResult.specificity.split(',').reverse();
43+
// Convert "0,1,3,2" to 132 (2*1 + 3*10 + 1*100 + 0*1000)
44+
const totalSpecificity = specificityParts.reduce((totalSpecificity, specificity, i) => {
45+
specificity = parseInt(specificity, 10);
46+
return totalSpecificity + (specificity * Math.pow(10, i));
47+
}, 0);
48+
49+
specificityCache[selector] = totalSpecificity;
50+
51+
return totalSpecificity;
52+
}
53+
54+
1755
function stringifyCss(css) {
1856
const styles = Object.keys(css).map(prop => prop + ': ' + css[prop]);
1957
return styles.join('; ');

lib/inlineCss.es6

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import postcss from 'postcss';
22
import matchHelper from 'posthtml-match-helper';
3-
import { extendStyle } from './helpers';
3+
import { extendStyle, sortCSSNodesBySpecificity } from './helpers';
44

55

66
export default css => {
77
return function inlineCss(tree) {
88
var postcssObj = css.then ? css : postcss().process(css);
99

10-
return postcssObj.then(result => {
11-
return result.root.each(cssNode => {
12-
tree.match(matchHelper(cssNode.selector), htmlNode => {
13-
return extendStyle(htmlNode, cssNode);
10+
return postcssObj
11+
.then(result => sortCSSNodesBySpecificity(result.root.nodes))
12+
.then(cssNodes => {
13+
cssNodes.forEach(cssNode => {
14+
tree.match(matchHelper(cssNode.selector), htmlNode => {
15+
return extendStyle(htmlNode, cssNode);
16+
});
1417
});
1518
});
16-
});
1719
};
1820
};

test/helpers.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import expect from 'expect';
2+
import { sortCSSNodesBySpecificity } from '../lib/helpers';
3+
4+
describe('Helpers', () => {
5+
it('sortCSSNodesBySpecificity()', () => {
6+
const cssNodes = [{selector: '#foo'}, {selector: '.bar'}, {selector: 'div'}];
7+
const sortedCssNodes = [{selector: 'div'}, {selector: '.bar'}, {selector: '#foo'}];
8+
9+
expect(sortCSSNodesBySpecificity(cssNodes)).toEqual(sortedCssNodes);
10+
});
11+
});

test/plugin.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import posthtml from 'posthtml';
33
import postcss from 'postcss';
44
import inlineCss from '..';
55

6-
const css = 'div { color: red; padding: 1px }' +
7-
'.lead { font-size: 14px; color: blue }';
6+
const css = '.lead { font-size: 14px; color: blue }' +
7+
'div { color: red; padding: 1px }';
88

99
const html = '<div style="margin: 0; color: blue">hello</div>' +
1010
'<div class="lead">world</div>';

0 commit comments

Comments
 (0)