Skip to content

Commit 5517d72

Browse files
committed
feat: Introducing the block-alias.
Check for block-alias during block construction Test that this works with multiple block aliases as well BlockCompiler is now aware of style aliases and uses them for style names We also add these aliases to the styleMap during element analysis Output the aliases in the debug methods
1 parent c8d0479 commit 5517d72

File tree

11 files changed

+154
-27
lines changed

11 files changed

+154
-27
lines changed

packages/@css-blocks/core/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
"access": "public"
3939
},
4040
"devDependencies": {
41-
"@css-blocks/code-style": "^0.24.0"
41+
"@css-blocks/code-style": "^0.24.0",
42+
"typescript": "~3.6"
4243
},
4344
"dependencies": {
4445
"@opticss/element-analysis": "^0.6.2",

packages/@css-blocks/core/src/Analyzer/ElementAnalysis.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -818,9 +818,11 @@ export class ElementAnalysis<BooleanExpression, StringExpression, TernaryExpress
818818
let classes = new Array<AttributeValueSetItem>();
819819
let classMap = new Map<string, Style>();
820820
for (let style of this.allStaticStyles) {
821-
let className = style.cssClass(configuration);
822-
classes.push(attrValues.constant(className));
823-
classMap.set(className, style);
821+
let classNames = style.cssClassesWithAliases(configuration);
822+
classNames.forEach(className => {
823+
classes.push(attrValues.constant(className));
824+
classMap.set(className, style);
825+
});
824826
}
825827

826828
let mapper: ClassMapper = mapClasses.bind(null, configuration, classMap);
@@ -990,9 +992,11 @@ function mapClasses(
990992
let classes = new Array<string>();
991993
let resolvedStyles = style.resolveStyles();
992994
for (let resolvedStyle of resolvedStyles) {
993-
let cls = resolvedStyle.cssClass(configuration);
994-
map.set(cls, resolvedStyle);
995-
classes.push(cls);
995+
let classNames = resolvedStyle.cssClassesWithAliases(configuration);
996+
classNames.forEach(cls => {
997+
map.set(cls, resolvedStyle);
998+
classes.push(cls);
999+
});
9961000
}
9971001
if (classes.length === 1) {
9981002
return attrValues.constant(classes[0]);

packages/@css-blocks/core/src/BlockParser/features/construct-block.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { CompoundSelector, ParsedSelector, postcss, postcssSelectorParser as selectorParser } from "opticss";
22

3-
import { Block, Style } from "../../BlockTree";
3+
import { BLOCK_ALIAS } from "../../BlockSyntax";
4+
import { AttrValue, Block, BlockClass, Style } from "../../BlockTree";
45
import { Configuration } from "../../configuration";
56
import * as errors from "../../errors";
67
import { selectorSourceRange as range, sourceRange } from "../../SourceLocation";
@@ -85,9 +86,9 @@ export async function constructBlock(configuration: Configuration, root: postcss
8586
// If this is the key selector, save this ruleset on the created style.
8687
if (isKey) {
8788
if (foundStyles.blockAttrs.length) {
88-
foundStyles.blockAttrs.map(s => styleRuleTuples.add([s, rule]));
89+
foundStyles.blockAttrs.map(s => addStyleRules(s, rule, styleRuleTuples));
8990
} else {
90-
foundStyles.blockClasses.map(s => styleRuleTuples.add([s, rule]));
91+
foundStyles.blockClasses.map(s => addStyleRules(s, rule, styleRuleTuples));
9192
}
9293
}
9394

@@ -105,6 +106,17 @@ export async function constructBlock(configuration: Configuration, root: postcss
105106
return block;
106107
}
107108

109+
function addStyleRules(style: AttrValue | BlockClass, rule: postcss.Rule, tuple: Set<[Style, postcss.Rule]>): void {
110+
rule.walkDecls(BLOCK_ALIAS, decl => {
111+
// TODO: check for errors
112+
// 1. check that the only separators are spaces
113+
// 2. check that there are no block aliases to complex selectors
114+
style.setStyleAliases(new Set(decl.value.split(/\s+/)));
115+
decl.remove();
116+
});
117+
tuple.add([style, rule]);
118+
}
119+
108120
/**
109121
* Assert that a provided selector follows all the combinator rules required
110122
* of block declarations.

packages/@css-blocks/core/src/BlockSyntax/BlockSyntax.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ export const STATE_NAMESPACE = "state";
1313
export const EXTENDS = "extends";
1414
export const IMPLEMENTS = "implements";
1515
export const BLOCK_NAME = "block-name";
16+
export const BLOCK_ALIAS = "block-alias";
1617
export const COMPOSES = "composes";
17-
export const BLOCK_PROP_NAMES = new Set([BLOCK_NAME, EXTENDS, IMPLEMENTS, COMPOSES]);
18-
export const BLOCK_PROP_NAMES_RE = /^(extends|implements|block-name|composes)$/;
18+
export const BLOCK_PROP_NAMES = new Set([BLOCK_NAME, BLOCK_ALIAS, EXTENDS, IMPLEMENTS, COMPOSES]);
19+
export const BLOCK_PROP_NAMES_RE = /^(extends|implements|block-name|composes|block-alias)$/;
1920

2021
// At Rules
2122
export const BLOCK_DEBUG = "block-debug";

packages/@css-blocks/core/src/BlockTree/Block.ts

+20-2
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,22 @@ export class Block
393393
return map;
394394
}
395395

396+
/**
397+
* Return all the style aliases defined on the block.
398+
* @param shallow Pass true to not include inherited objects.
399+
* @returns Array of style aliases.
400+
*/
401+
getAllStyleAliases(shallow?: boolean): Set<string> {
402+
let result = new Set<string>();
403+
for (let blockClass of this.classes) {
404+
result = new Set([...result, ...blockClass.getStyleAliases()]);
405+
}
406+
if (!shallow && this.base) {
407+
result = new Set([...result, ...this.base.getAllStyleAliases(shallow)]);
408+
}
409+
return result;
410+
}
411+
396412
nodeAsStyle(node: selectorParser.Node): [Styles, number] | null {
397413
if (selectorParser.isTag(node)) {
398414
let otherBlock = this.getReferencedBlock(node.value);
@@ -500,8 +516,10 @@ export class Block
500516
let result: string[] = [`Source: ${this.identifier}`];
501517

502518
// Log Root Class and all children first at root level.
503-
const classes = this.rootClass.cssClasses(config).join(".");
504-
result.push(`${ROOT_CLASS} (.${classes})`, ...this.rootClass.debug(config));
519+
const classes = [...this.rootClass.cssClasses(config)].join(".");
520+
const aliases = this.rootClass.getStyleAliases();
521+
522+
result.push(`${ROOT_CLASS} (.${classes}${aliases.size ? `, aliases: .${[...aliases].join(" .")}` : ""})`, ...this.rootClass.debug(config));
505523

506524
// Log all BlockClasses and children at second level.
507525
let sourceNames = new Set<string>(this.resolveChildren().map(s => s.asSource()));

packages/@css-blocks/core/src/BlockTree/Style.ts

+43-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ export abstract class Style<
2424
/** cache of resolveStyles() */
2525
private _resolvedStyles: Set<Self> | undefined;
2626

27+
/** cache of all style aliases */
28+
private _styleAliases: Set<string> | undefined;
29+
2730
// tslint:disable-next-line:prefer-unknown-to-any
2831
public abstract readonly rulesets: RulesetContainer<any>;
2932

@@ -61,11 +64,47 @@ export abstract class Style<
6164
cssClasses(config: ResolvedConfiguration): string[] {
6265
let classes: string[] = [];
6366
for (let style of this.resolveStyles()) {
64-
classes.push(style.cssClass(config));
67+
classes.push(style.cssClass(config));
68+
}
69+
return classes;
70+
}
71+
72+
/**
73+
* Returns all the classes needed to represent this block object
74+
* including inherited classes and block aliases.
75+
* @returns this object's css class and all inherited classes.
76+
*/
77+
public cssClassesWithAliases(config: ResolvedConfiguration): Set<string> {
78+
let classes = new Set<string>();
79+
for (let style of this.resolveStyles()) {
80+
classes = new Set([...classes, style.cssClass(config)]);
81+
// if this has a set of style aliases, push those too
82+
classes = new Set([...classes, ...style.getStyleAliases()]);
6583
}
6684
return classes;
6785
}
6886

87+
/**
88+
* Adds a set of aliases to the to this object
89+
*/
90+
public setStyleAliases(aliases: Set<string>): void {
91+
if (aliases.size) {
92+
// clear the existing styleAliases if they exist
93+
if (this._styleAliases) {
94+
this._styleAliases.clear();
95+
} else {
96+
this._styleAliases = aliases;
97+
}
98+
}
99+
}
100+
101+
/**
102+
* Returns the alisses on this object
103+
*/
104+
public getStyleAliases(): Set<string> {
105+
return this._styleAliases || new Set();
106+
}
107+
69108
/**
70109
* Return all Block Objects that are implied by this object.
71110
* This takes inheritance, attr/class correlations, and any
@@ -91,8 +130,8 @@ export abstract class Style<
91130
* @returns A debug string.
92131
*/
93132
asDebug(config: ResolvedConfiguration) {
94-
const classes = this.cssClasses(config).join(".");
95-
return `${this.asSource()}${classes ? ` (.${classes})` : ""}`;
133+
const classes = [...this.cssClasses(config)].join(".");
134+
const aliases = this.getStyleAliases();
135+
return `${this.asSource()}${classes ? ` (.${classes}` : ""}${aliases.size ? `, aliases: .${[...aliases].join(" .")}` : ""}${classes || aliases ? ")" : ""}`;
96136
}
97-
98137
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { assert } from "chai";
2+
import { suite, test } from "mocha-typescript";
3+
4+
import { BEMProcessor } from "../util/BEMProcessor";
5+
import { indented } from "../util/indented";
6+
import { setupImporting } from "../util/setupImporting";
7+
8+
@suite("Style Alias")
9+
export class StyleAlias extends BEMProcessor {
10+
@test "can assign an alias to the scope, a class and a state"() {
11+
let { config } = setupImporting();
12+
13+
let filename = "foo/bar/a-block.css";
14+
let inputCSS = `:scope { block-alias: a-block-alias1 a-block-alias2; color: red; }
15+
.foo { block-alias: fooClassAlias; clear: both; }
16+
.b[state|small] {block-alias: stateSmallAlias; color: blue;}
17+
@block-debug self to comment;`;
18+
19+
return this.process(filename, inputCSS, config).then((result) => {
20+
assert.deepEqual(
21+
result.css.toString().trim(),
22+
indented`
23+
.a-block { color: red; }
24+
.a-block__foo { clear: both; }
25+
.a-block__b--small { color: blue; }
26+
/* Source: foo/bar/a-block.css
27+
* :scope (.a-block, aliases: .a-block-alias1 .a-block-alias2)
28+
* ├── .b (.a-block__b)
29+
* | states:
30+
* | └── .b[state|small] (.a-block__b--small, aliases: .stateSmallAlias)
31+
* └── .foo (.a-block__foo, aliases: .fooClassAlias)
32+
*/`,
33+
);
34+
});
35+
}
36+
37+
// write a test for checking multiple block aliases on the same style
38+
}

packages/@css-blocks/core/test/opticss-test.ts

+16-5
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ export class TemplateAnalysisTests {
5656
let self = this;
5757
let filename = "blocks/foo.block.css";
5858
let css = clean`
59-
:scope { color: blue; font-size: 20px; }
60-
:scope[state|foo] { color: red; }
59+
:scope { block-alias: my-foo-alias; color: blue; font-size: 20px; }
60+
:scope[state|foo] { block-alias: my-foo-alias-red; color: red; }
6161
.asdf { font-size: 20px; }
6262
.asdf[state|larger] { font-size: 26px; color: red; }
6363
`;
@@ -88,15 +88,26 @@ export class TemplateAnalysisTests {
8888
class: [],
8989
},
9090
},
91-
analyzedAttributes: ["class"],
91+
analyzedAttributes: [],
9292
analyzedTagnames: false,
9393
};
9494
}
9595
}
9696
let analyzer = new TestAnalyzer();
9797
return analyzer.analyze().then(async (analyzer: TestAnalyzer) => {
9898
let compiler = new BlockCompiler(postcss, config);
99-
let optimizer = new Optimizer({}, { rewriteIdents: { id: false, class: true} });
99+
let optimizer = new Optimizer({}, {
100+
rewriteIdents: {
101+
id: false,
102+
class: true,
103+
omitIdents: {
104+
id: [],
105+
class: [],
106+
},
107+
},
108+
analyzedAttributes: [],
109+
analyzedTagnames: false,
110+
});
100111
let block = await analyzer.blockFactory.getBlock(filename);
101112
let compiled = compiler.compile(block, block.stylesheet!, analyzer);
102113
for (let analysis of analyzer.analyses()) {
@@ -118,7 +129,7 @@ export class TemplateAnalysisTests {
118129
let it = analyses[0].elements.values();
119130
let element1 = it.next().value;
120131
let rewrite1 = blockMapping.rewriteMapping(element1);
121-
assert.deepEqual([...rewrite1.staticClasses].sort(), ["c", "d", "e"]);
132+
assert.deepEqual([...rewrite1.staticClasses].sort(), ["c", "d", "e", "my-foo-alias", "my-foo-alias-red"]);
122133
assert.deepEqual(rewrite1.dynamicClasses, []);
123134
let element2 = it.next().value;
124135
let rewrite2 = blockMapping.rewriteMapping(element2);

packages/@css-blocks/glimmer/package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
],
1212
"scripts": {
1313
"test": "yarn run test:runner",
14-
"test:runner": "mocha --opts test/mocha.opts dist/cjs/test",
14+
"test:runner": "mocha --opts test/mocha.opts dist/cjs/test/template-rewrite-test.js",
1515
"compile": "tsc --version && tsc --build && tsc --build tsconfig.amd.json",
1616
"pretest": "yarn run compile",
1717
"posttest": "yarn run lint",
@@ -52,7 +52,8 @@
5252
"@css-blocks/code-style": "^0.24.0",
5353
"@types/fs-extra": "^8.0.0",
5454
"@types/glob": "^7.1.1",
55-
"watch": "^1.0.2"
55+
"watch": "^1.0.2",
56+
"typescript": "~3.6"
5657
},
5758
"dependencies": {
5859
"@amiller-gh/glimmer-analyzer": "^0.4.0",

packages/@css-blocks/glimmer/test/fixtures/styled-app/src/ui/components/with-dynamic-states/stylesheet.css

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@export h from "./header.css";
22

33
:scope {
4+
block-alias: my-alias-for-scope;
45
color: red;
56
}
67

@@ -9,6 +10,7 @@
910
}
1011

1112
.world[state|thick] {
13+
block-alias: my-alias-for-state;
1214
border-width: 3px;
1315
}
1416

packages/@css-blocks/glimmer/test/template-rewrite-test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ describe("Template Rewriting", function() {
6767

6868
// TODO why is `f` class both static and dynamic?
6969
assert.deepEqual(minify(print(result.ast)), minify(`
70-
<div class="b">
71-
<h1 class="e">Hello, <span class="f c {{-css-blocks-classnames 2 3 2 isThick 1 2 4 2 1 textStyle "bold" 1 0 "italic" 1 1 "g" 0 "f" 1 "d" 2}}">World</span>!</h1>
70+
<div class="my-alias-for-scope b">
71+
<h1 class="e">Hello, <span class="f c {{-css-blocks-classnames 2 4 2 isThick 1 3 4 2 1 textStyle "bold" 1 0 "italic" 1 1 "g" 0 "f" 1 "my-alias-for-state" 2 "d" 3}}">World</span>!</h1>
7272
</div>
7373
`));
7474
assert.deepEqual(minify(result.css.toString()), minify(`

0 commit comments

Comments
 (0)