Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 7495b12

Browse files
We can construct the graph
1 parent 7bafe06 commit 7495b12

File tree

10 files changed

+474
-88
lines changed

10 files changed

+474
-88
lines changed

‎README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ When following the conventions listed below, the Optimizing CSS Compiler
3232
- global CSS
3333
- `::pseudo-element` and `:pseudo-class` selectors
3434

35+
## CSS
36+
37+
- use mixins, not global selectors
38+
- no attr selectors
39+
- no sibling selectors
40+
- no ID selectors
41+
- classed and elements only
42+
3543
## Why?
3644

3745
Existing scoped and inline style solutions lead to selector bloat and limited
@@ -154,6 +162,15 @@ padding-top, something like the following would be produced:
154162
}
155163
```
156164

165+
## Prior Art
166+
167+
The following links are some prior art we should investigate the usefulness of:
168+
169+
- https://github.com/prototypal-io/prius
170+
- https://github.com/prototypal-io/cascada-es6
171+
- https://github.com/prototypal-io/cascada
172+
173+
157174
## Installation
158175

159176
* `git clone <repository-url>` this repository

‎lib/broccoli-plugins/optimize-css.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ OptimizingCssCompiler.prototype.build = function optimizeAndCompileCss() {
5151

5252
return processStylesTree(stylesPath)
5353
.then((stylesGraph) => {
54-
console.log('graph', JSON.stringify(stylesGraph.static, null, 2));
54+
// console.log('\ngraph\n', JSON.stringify(stylesGraph.static, null, 2));
5555
return processTemplatesTree(templatesPath, stylesGraph);
5656
})
5757
.then(optimizeGraph)

‎lib/optimize/graph/dom-tree.js

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
class Node {
2+
constructor(reference, parent = null) {
3+
this.reference = reference;
4+
this.parent = parent;
5+
this._children = null;
6+
this._classNames = null;
7+
}
8+
9+
get attributes() {
10+
let type = this.type;
11+
12+
if (type === 'MustacheStatement' || type === 'BlockStatement') {
13+
return this.reference.hash.pairs;
14+
}
15+
return this.reference.attributes || [];
16+
}
17+
18+
get classNames() {
19+
let classNames = this._classNames;
20+
21+
if (classNames === null) {
22+
let potentialClassNames = [];
23+
let attributes = this.attributes;
24+
25+
attributes.forEach((attr) => {
26+
if (attr.name === 'class') {
27+
if (attr.value.type === 'TextNode') {
28+
let availableClassNames = attr.value.chars.split(' ');
29+
availableClassNames = availableClassNames.map(n => `.${n}`);
30+
potentialClassNames.push(...availableClassNames);
31+
}
32+
} else if (attr.key === 'class') {
33+
if (attr.value.type === 'StringLiteral') {
34+
let availableClassNames = attr.value.value.split(' ');
35+
availableClassNames = availableClassNames.map(n => `.${n}`);
36+
potentialClassNames.push(...availableClassNames);
37+
}
38+
}
39+
});
40+
41+
if (potentialClassNames.length) {
42+
classNames = this._classNames = potentialClassNames;
43+
} else {
44+
classNames = this._classNames = [];
45+
}
46+
47+
}
48+
49+
return classNames;
50+
}
51+
52+
get tag() {
53+
let type = this.type;
54+
55+
if (type === 'MustacheStatement' || type === 'BlockStatement') {
56+
return this.reference.path.original;
57+
}
58+
return this.reference.tag || 'document';
59+
}
60+
61+
get type() {
62+
return this.reference.type;
63+
}
64+
65+
get children() {
66+
let children = this._children;
67+
68+
if (children === null) {
69+
let potentialChildren = [];
70+
let ref = this.reference;
71+
let type = this.type;
72+
73+
if (type === 'Program') {
74+
ref.body.forEach((entry) => {
75+
addIfAllowedType(potentialChildren, entry, this);
76+
});
77+
} else if (type === 'ElementNode') {
78+
ref.children.forEach((entry) => {
79+
addIfAllowedType(potentialChildren, entry, this);
80+
});
81+
} else if (type === 'BlockStatement' || type === 'MustacheStatement') {
82+
if (ref.program) {
83+
ref.program.body.forEach((entry) => {
84+
addIfAllowedType(potentialChildren, entry, this);
85+
});
86+
}
87+
} else {
88+
throw new Error('Unknown Ability to Walk');
89+
}
90+
91+
if (potentialChildren.length) {
92+
children = this._children = potentialChildren;
93+
} else {
94+
children = this._children = [];
95+
}
96+
}
97+
98+
return children;
99+
}
100+
101+
visit(cb) {
102+
cb(this);
103+
let children = this.children;
104+
children.forEach((child) => {
105+
child.visit(cb);
106+
});
107+
}
108+
109+
toJSON() {
110+
let children = this.children;
111+
return {
112+
tag: this.tag,
113+
classNames: this.classNames,
114+
children: children.map(c => c.toJSON())
115+
};
116+
}
117+
}
118+
119+
const ALLOWED_TYPES = ['ElementNode', 'BlockStatement', 'MustacheStatement'];
120+
121+
function addIfAllowedType(array, entry, parent) {
122+
let type = entry.type;
123+
124+
if (ALLOWED_TYPES.indexOf(type) !== -1) {
125+
array.push(new Node(entry, parent));
126+
} else {
127+
// console.log('unknown type', entry.type);
128+
}
129+
}
130+
131+
module.exports = Node;

‎lib/optimize/graph/style-graph.js

Lines changed: 117 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
matchParent(['.foo-1'])
5959
6060
*/
61+
const chalk = require('chalk');
6162
const random = Date.now();
6263
const VALUE_SYMBOL = random + 'VALUE_SYMBOL';
6364
const PATH_SYMBOL = random + 'PATH_SYMBOL';
@@ -67,7 +68,21 @@ const NAME_SYMBOL = random + 'NAME_SYMBOL';
6768
const SELECTOR_SPLIT_SYMBOLS = ',';
6869
const PATH_SPLIT_SYMBOLS = ' ';
6970
const PATH_SPLIT_CLEANUP = /\s+/g;
70-
const SPLIT_SYMBOLS = /[\.|#]/;
71+
const SPLIT_SYMBOLS = /\./g;
72+
73+
// we assume already sorted arrays
74+
function isSameArray(symbols, matchers) {
75+
if (symbols.length < matchers.length) { return false; }
76+
77+
for (let i = 0; i < matchers.length; i++) {
78+
let requiredValue = matchers[i];
79+
if (symbols.indexOf(requiredValue) === -1) {
80+
return false;
81+
}
82+
}
83+
84+
return true;
85+
}
7186

7287
function iterateSelectors(selector, cb) {
7388
let selectors = selector.split(SELECTOR_SPLIT_SYMBOLS);
@@ -78,15 +93,37 @@ function iterateSelectors(selector, cb) {
7893
.replace(PATH_SPLIT_CLEANUP, ' ')
7994
.split(PATH_SPLIT_SYMBOLS)
8095
.reverse();
81-
cb(segments);
96+
cb(segments,path);
8297
});
8398
}
8499

85100
function iteratePath(path, cb) {
101+
let depth = TREE_DEPTH;
86102
path.forEach((segment) => {
87-
let tlds = segment.split(SPLIT_SYMBOLS).sort().reverse();
103+
let tlds = segment
104+
.replace(SPLIT_SYMBOLS, '####.')
105+
.split('####')
106+
.filter(a => a)
107+
.sort()
108+
.reverse();
88109
cb(segment, tlds, tlds.join(''));
110+
TREE_DEPTH++;
89111
});
112+
TREE_DEPTH = depth;
113+
}
114+
115+
let TREE_DEPTH = 2;
116+
117+
function padStr(chars, char = '~') {
118+
let ret = '';
119+
while (chars--) {
120+
ret += char;
121+
}
122+
return ret;
123+
}
124+
125+
function log(msg) {
126+
console.log(chalk.grey(chalk.green('=> ') + padStr(TREE_DEPTH, '\t') + msg));
90127
}
91128

92129
class Segment {
@@ -96,11 +133,68 @@ class Segment {
96133
this.value = null;
97134
this.symbols = symbols;
98135
this.children = Object.create(null);
136+
this.pairs = Object.create(null);
99137
}
100138

101139
addChild(name, symbols) {
102-
this.children[name] = this.children[name] || new Segment(name, symbols);
103-
return this.children[name];
140+
log('addChild ' + chalk.white(this.name) + ' ' + chalk.yellow(name));
141+
142+
let segment = this.children[name];
143+
144+
if (!segment) {
145+
segment = this.children[name] = new Segment(name, symbols);
146+
147+
if (symbols && symbols.length > 1) {
148+
for (let i = 0; i < symbols.length; i++) {
149+
let s = symbols[i];
150+
let pair = this.pairs[s] = this.pairs[s] || [];
151+
pair.push([symbols, segment]);
152+
}
153+
}
154+
}
155+
156+
return segment;
157+
}
158+
159+
detectJoin(symbols) {
160+
let pairs = this.pairs;
161+
let keys = Object.keys(pairs);
162+
if (keys.length === 0) {
163+
// console.log('no pairs to match against');
164+
return false;
165+
}
166+
// console.log('available pairs', keys);
167+
let found = [];
168+
for (let i = 0; i < symbols.length; i++) {
169+
let s = symbols[i];
170+
// console.log('seeking ' + s);
171+
let pair = pairs[s];
172+
if (pair) {
173+
// console.log('found ' + s + ' seeking matching pair to ' + JSON.stringify(symbols));
174+
for (let j = 0; j < pair.length; j++) {
175+
let matchers = pair[j][0];
176+
let segment = pair[j][1];
177+
178+
// console.log('matching against ' + JSON.stringify(matchers));
179+
if (found.indexOf(segment) === -1 && isSameArray(symbols, matchers)) {
180+
// console.log('matched pair');
181+
found.push(segment);
182+
}
183+
}
184+
}
185+
}
186+
187+
return found.length ? found : false;
188+
}
189+
190+
forEach(cb) {
191+
const children = this.children;
192+
const keys = Object.keys(children);
193+
194+
for (let i = 0; i < keys.length; i++) {
195+
let key = keys[i];
196+
cb(key, children[key]);
197+
}
104198
}
105199

106200
setValue(value) {
@@ -142,22 +236,37 @@ class Namespace {
142236
return this.root.children[localPathString];
143237
}
144238

239+
detectJoin(localPathString) {
240+
return this.root.detectJoin(localPathString);
241+
}
242+
243+
forEach() {
244+
throw new Error('Namespace.forEach is not implemented');
245+
}
246+
145247
set(selectorString, stylePod) {
146248
if (selectorString === '&') {
147249
if (!this._rootName) {
148250
throw new Error(`You forget the component name for ${this._name}!`);
149251
}
150252
let componentName = this._rootName || this._name;
151-
this._selectorPaths.addChild(componentName, stylePod);
253+
254+
if (!this.root) {
255+
this._selectorPaths.addChild(componentName, [componentName]);
256+
}
257+
this.root.setValue(stylePod)
152258
} else {
153-
iterateSelectors(selectorString, (path) => {
259+
iterateSelectors(selectorString, (pathSegments,path) => {
154260
let currentSegment = this.root;
261+
log('Adding Selector ' + chalk.yellow(path));
262+
TREE_DEPTH++;
155263

156-
iteratePath(path, (segment, symbols) => {
264+
iteratePath(pathSegments, (segment, symbols) => {
157265
currentSegment = currentSegment.addChild(segment, symbols);
158266
});
159267

160268
currentSegment.setValue(stylePod);
269+
TREE_DEPTH--;
161270
});
162271
}
163272
}

‎lib/process-trees.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const OptimizingCssCompiler = require('./broccoli-plugins/optimize-css');
44

55
module.exports = function processTrees(path) {
66
const stylesAndTemplatesTree = new Funnel(path, {
7-
include: [/styles/,/templates/]
7+
include: ['**/**.css','**/**.hbs']
88
});
99
const passThroughTree = new Funnel(path, {
1010
exclude: [/styles/, /templates/]

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /