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 7bafe06

Browse files
more work on building out the graph
1 parent ac6ed5f commit 7bafe06

File tree

7 files changed

+310
-93
lines changed

7 files changed

+310
-93
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +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));
5455
return processTemplatesTree(templatesPath, stylesGraph);
5556
})
5657
.then(optimizeGraph)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Roughly calculate the base specificity (not including !important) for a path
3+
*/
4+
const ELEMENT_SPECIFICITY = 1;
5+
const ATTR_SPECIFICITY = 10;
6+
const ID_SPECIFICITY = 100;
7+
const PATH_SYMBOL = 'PATH_SYMBOL';
8+
9+
function isId(symbol) {
10+
return symbol.indexOf('#') === 0;
11+
}
12+
13+
function isElement(symbol) {
14+
let s0 = symbol.charAt(0);
15+
let s1 = symbol.charAt(1);
16+
return s0.match(/[a-zA-Z]/) || s0 === s1 === ':';
17+
}
18+
19+
function isAttr(symbol) {
20+
let s0 = symbol.charAt(0);
21+
let s1 = symbol.charAt(1);
22+
23+
return s0.match(/[\.\[:]/) || (s0 === '*' && s1 === '[');
24+
}
25+
26+
module.exports = function calculateSpecificity(graph, path) {
27+
let specificity = 0;
28+
let node = graph;
29+
let i = path.length;
30+
31+
while(i-- > 0) {
32+
let segment = path[i];
33+
34+
if (!node[segment]) {
35+
throw new Error('Unknown Path Segment');
36+
}
37+
38+
node = node[segment];
39+
let symbols = node[PATH_SYMBOL];
40+
41+
symbols.forEach((symbol) => {
42+
if (isId(symbol)) {
43+
specificity += ID_SPECIFICITY;
44+
} else if (isElement(symbol)) {
45+
specificity += ELEMENT_SPECIFICITY;
46+
} else if (isAttr(symbol)) {
47+
specificity += ATTR_SPECIFICITY;
48+
}
49+
});
50+
51+
}
52+
53+
return specificity;
54+
};

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

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
# Terminology Explanation
3+
4+
Given the following CSS
5+
6+
```
7+
.foo .bar.baz, div#ham { ...}
8+
```
9+
10+
`selectorString`: `.foo .bar.baz, div#ham`
11+
`pathStrings`: `.foo .bar.baz` `div#ham`
12+
`paths`: `['.foo', '.foo.baz']` `['div#ham']`
13+
`symbols`: `.foo` `.bar` `.baz` `div` `#ham`
14+
`stylePod`: `{ ... }`
15+
16+
# Graph Explanation
17+
18+
We build our graph using reversed paths to make it efficient to math elements in templates
19+
against known rules.
20+
21+
Each component gets it's own sub-graph.
22+
23+
For example, given the following CSS for the component `my-component`
24+
25+
```
26+
.foo-1 .bar-1.foo-2 > div.foo-3 .bar {}
27+
```
28+
29+
We would generate the following data structure.
30+
31+
```
32+
{
33+
.bar {
34+
[PATH_SYMBOL]: ['.bar']
35+
div.foo-3 {
36+
[PATH_SYMBOL]: ['div', '.foo-3']
37+
> {
38+
[PATH_SYMBOL]: ['>']
39+
.bar-1.foo-2 {
40+
[PATH_SYMBOL]: ['bar-1', '.foo-2']
41+
.foo-1 {
42+
[PATH_SYMBOL]: ['.foo-1']
43+
[VALUE_SYMBOL]: <stylePod>
44+
}
45+
}
46+
}
47+
}
48+
}
49+
}
50+
```
51+
52+
With this structure in place, we can take the following steps to
53+
match an element in the DOM.
54+
55+
match(.bar)
56+
matchParent(['div, '.foo-3])
57+
matchImmediateParent(['.bar-1', '.foo-2'])
58+
matchParent(['.foo-1'])
59+
60+
*/
61+
const random = Date.now();
62+
const VALUE_SYMBOL = random + 'VALUE_SYMBOL';
63+
const PATH_SYMBOL = random + 'PATH_SYMBOL';
64+
const SPECIFICITY_SYMBOL = random + 'SPECIFITY_SYMBOL';
65+
const NAME_SYMBOL = random + 'NAME_SYMBOL';
66+
67+
const SELECTOR_SPLIT_SYMBOLS = ',';
68+
const PATH_SPLIT_SYMBOLS = ' ';
69+
const PATH_SPLIT_CLEANUP = /\s+/g;
70+
const SPLIT_SYMBOLS = /[\.|#]/;
71+
72+
function iterateSelectors(selector, cb) {
73+
let selectors = selector.split(SELECTOR_SPLIT_SYMBOLS);
74+
selectors.forEach((path) => {
75+
let segments = path
76+
.split('>')
77+
.join(' > ')
78+
.replace(PATH_SPLIT_CLEANUP, ' ')
79+
.split(PATH_SPLIT_SYMBOLS)
80+
.reverse();
81+
cb(segments);
82+
});
83+
}
84+
85+
function iteratePath(path, cb) {
86+
path.forEach((segment) => {
87+
let tlds = segment.split(SPLIT_SYMBOLS).sort().reverse();
88+
cb(segment, tlds, tlds.join(''));
89+
});
90+
}
91+
92+
class Segment {
93+
constructor(name, symbols = [name]) {
94+
this.name = name;
95+
this.specificity = 0;
96+
this.value = null;
97+
this.symbols = symbols;
98+
this.children = Object.create(null);
99+
}
100+
101+
addChild(name, symbols) {
102+
this.children[name] = this.children[name] || new Segment(name, symbols);
103+
return this.children[name];
104+
}
105+
106+
setValue(value) {
107+
this.value = value;
108+
}
109+
110+
toJSON() {
111+
return {
112+
name: this.name,
113+
value: this.value,
114+
children: this.children
115+
};
116+
}
117+
}
118+
119+
class Namespace {
120+
constructor(name) {
121+
this._name = name;
122+
this._rootName = null;
123+
this._selectorPaths = new Segment(name);
124+
}
125+
126+
setRootName(name) {
127+
this._rootName = name;
128+
this._selectorPaths.addChild(this._rootName);
129+
}
130+
131+
get root() {
132+
return this._rootName ? this._selectorPaths.children[this._rootName] : this._selectorPaths;
133+
}
134+
135+
get(localPathString) {
136+
let componentName = this._rootName || this._name;
137+
138+
if (!localPathString || localPathString === componentName) {
139+
return this.root;
140+
}
141+
142+
return this.root.children[localPathString];
143+
}
144+
145+
set(selectorString, stylePod) {
146+
if (selectorString === '&') {
147+
if (!this._rootName) {
148+
throw new Error(`You forget the component name for ${this._name}!`);
149+
}
150+
let componentName = this._rootName || this._name;
151+
this._selectorPaths.addChild(componentName, stylePod);
152+
} else {
153+
iterateSelectors(selectorString, (path) => {
154+
let currentSegment = this.root;
155+
156+
iteratePath(path, (segment, symbols) => {
157+
currentSegment = currentSegment.addChild(segment, symbols);
158+
});
159+
160+
currentSegment.setValue(stylePod);
161+
});
162+
}
163+
}
164+
165+
toJSON() {
166+
return this._selectorPaths.toJSON();
167+
}
168+
}
169+
170+
class StyleGraph {
171+
constructor(name) {
172+
this.name = name;
173+
this._namespaces = Object.create(null);
174+
}
175+
176+
get(namespace) {
177+
return this._namespaces[namespace] || (this._namespaces[namespace] = new Namespace(namespace));
178+
}
179+
180+
toJSON() {
181+
return this._namespaces;
182+
}
183+
}
184+
185+
module.exports = {
186+
VALUE_SYMBOL,
187+
PATH_SYMBOL,
188+
SPECIFICITY_SYMBOL,
189+
StyleGraph
190+
};

‎lib/optimize/process-styles-tree.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
const walkTree = require('./utils/walk-tree');
22
const postcss = require('postcss');
33
const cssGraphBuilder = require('../walkers/css-graph-builder');
4+
const { StyleGraph } = require('./graph/style-graph');
45
const COMPONENT_PATH = 'components/';
56

67
module.exports = function processStylesTree(styleDir /*, warningStream*/) {
78
const GRAPH = {
8-
dynamic: Object.create(null),
9-
static: Object.create(null)
9+
dynamic: newStyleGraph('dynamic'),
10+
static: newStyleGraph('static')
1011
};
1112

1213
return walkTree(styleDir, (relativePath, fileData) => {
1314
let moduleName = relativePath.substr(styleDir.length + 1);
1415
let isComponent = false;
16+
let componentName;
1517
moduleName = moduleName.replace('.css', '');
1618

1719
if (moduleName.indexOf(COMPONENT_PATH) === 0) {
1820
isComponent = true;
19-
moduleName = moduleName.substr(COMPONENT_PATH.length);
21+
componentName = moduleName.substr(COMPONENT_PATH.length);
2022
}
2123

2224
const processor = postcss();
@@ -30,7 +32,8 @@ module.exports = function processStylesTree(styleDir /*, warningStream*/) {
3032
plugins: [cssGraphBuilder],
3133
graph: GRAPH,
3234
isComponent,
33-
moduleName
35+
moduleName,
36+
componentName
3437
};
3538

3639
if (!opts.plugins || opts.plugins.length < 1) {

‎lib/optimize/process-templates-tree.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,30 @@ const walkTree = require('./utils/walk-tree');
22
const precompile = require('@glimmer/compiler').precompile;
33
const htmlGraphBuilder = require('../walkers/html-graph-builder');
44
const COMPONENT_PATH = 'components/';
5+
const { StyleGraph } = require('./graph/style-graph');
56

67
module.exports = function processTemplatesTree(templatesDir, stylesGraph) {
78
const usedStylesGraph = {
8-
dynamic: Object.create(null),
9-
static: Object.create(null)
9+
dynamic: newStyleGraph('dynamic'),
10+
static: newStyleGraph('static')
1011
};
1112

1213
return walkTree(templatesDir, (relativePath, fileData) => {
1314
let moduleName = relativePath.substr(templatesDir.length + 1);
1415
let isComponent = false;
1516
moduleName = moduleName.replace('.hbs', '');
17+
let componentName;
1618

1719
if (moduleName.indexOf(COMPONENT_PATH) === 0) {
1820
isComponent = true;
19-
moduleName = moduleName.substr(COMPONENT_PATH.length);
21+
componentName = moduleName.substr(COMPONENT_PATH.length);
2022
}
2123

2224
precompile(fileData, {
2325
rawSource: fileData,
2426
isComponent,
2527
moduleName,
28+
componentName,
2629
availableGraph:stylesGraph,
2730
usedGraph:usedStylesGraph,
2831
plugins: {

0 commit comments

Comments
(0)

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