1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111 | 1x
1x
1x
1x
1x
1x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
6x
5x
2x
2x
6x
1x
1x
1x
1x
6x
6x
6x
6x
6x
6x
4x
2x
6x
6x
14x
6x
5x
1x
1x
1x
1x
6x
| const postcss = require('postcss');
const parser = require('postcss-selector-parser');
const _ = require('lodash/fp');
const parseAttributeNodes = require('./parse-attribute-nodes');
const appendAttr = require('./append-attr');
/**
* @param {Object} options
* @param {string} options.id
* @param {function} [options.onComponents]
*/
module.exports = postcss.plugin('stylesheet', (options = {}) => {
const { id, onComponents = () => null } = options;
return (root, result) => {
let allComponents = {};
result.root.walkRules(/\b[A-Z]/, rule => {
const blockComponents = new Set();
let components = {};
const parseComponentTagsAndAttributesSelectors = selectorRoot => {
for (const selector of selectorRoot.nodes) {
for (const tag of selector.nodes.filter(matchComponentTags)) {
const tagIndex = selector.nodes.indexOf(tag);
const componentName = tag.value;
const componentClassName = `${componentName}_${id}`;
let nextCombinatorIndex = findNextCombinatorIndexFrom(tagIndex, selector.nodes);
Eif (nextCombinatorIndex === -1) {
nextCombinatorIndex = selector.nodes.length;
blockComponents.add(componentName);
}
const attributeNodes = getAttributeNodes(tagIndex, nextCombinatorIndex, selector.nodes);
components = _.update(
componentName,
_.flow([
_.set('className', componentClassName),
_.update('attributes', (attributes = []) => [
...attributes,
...parseAttributeNodes(id, componentName, attributeNodes),
]),
]),
components
);
tag.replaceWith(parser.className({ value: componentClassName }));
}
}
};
try {
rule.selector = parser(parseComponentTagsAndAttributesSelectors).process(
rule.selector
).result;
} catch (err) {
if (err.message !== 'Expected pseudo-class or pseudo-element') {
throw err;
}
}
Eif (blockComponents.size) {
rule.walkDecls(decl => {
if (isAttr(decl.value)) {
for (const component of blockComponents) {
components = appendAttr(components, component, rule, decl);
}
}
});
rule.walkAtRules('apply', atRule => {
Eif (isElementBase(atRule)) {
for (const component of blockComponents) {
components = _.set([component, 'base'], atRule.params, components);
atRule.remove();
}
}
});
}
for (const [componentName, component] of Object.entries(components)) {
allComponents = _.update(
componentName,
_.flow([
_.update('className', className => className || component.className),
_.update('attributes', (attributes = []) => {
Iif (!component.attributes) {
return attributes;
}
return attributes.concat(component.attributes);
}),
_.update('attrs', (attrs = []) => {
if (!component.attrs) {
return attrs;
}
return attrs.concat(component.attrs);
}),
_.update('base', base => base || component.base),
]),
allComponents
);
}
});
onComponents(allComponents);
};
});
const and = _.curry((predicates, value) => _.every(predicate => predicate(value), predicates));
const isComponentElement = ({ value }) => value.search(/[A-Z]/) === 0;
const isAttr = value => value.search(/attr\(.+?\)/) !== -1;
const isElementBase = ({ params }) => params.search(/[A-z]/) === 0;
const matchComponentTags = and([_.matches({ type: 'tag' }), isComponentElement]);
const findNextCombinatorIndexFrom = _.findIndexFrom({ type: 'combinator' });
const getAttributeNodes = (tagIndex, nextCombinatorIndex, nodes) =>
_.flow([_.slice(tagIndex, nextCombinatorIndex), _.filter({ type: 'attribute' })])(nodes);
|