Skip to content

Commit 8a3cade

Browse files
committed
feat: Basic block definition generation.
1 parent 601414d commit 8a3cade

File tree

6 files changed

+1060
-77
lines changed

6 files changed

+1060
-77
lines changed

packages/@css-blocks/core/src/BlockCompiler/BlockDefinitionCompiler.ts

+218-11
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,231 @@
11
import { postcss } from "opticss";
22

3-
import { Block } from "../BlockTree";
3+
import { AttributeSelector, BlockExport, BlockReference, BlockSyntaxVersion, ClassSelector, Declaration, DefinitionAST, DefinitionRoot, ForeignAttributeSelector, GlobalDeclaration, LocalBlockExport, Mapper, Name, Rename, Rule, ScopeSelector, Selector, Visitor, builders, map as mapToDefinition, visit } from "../BlockParser/ast";
4+
import { BLOCK_GLOBAL } from "../BlockSyntax";
5+
import { Block, BlockClass, Style, isAttrValue, isBlockClass } from "../BlockTree";
46
import { ResolvedConfiguration } from "../configuration";
57

68
export const INLINE_DEFINITION_FILE = Symbol("Inline Definition");
79

8-
export type PathResolver = (config: ResolvedConfiguration, fromBlock: Block, toBlock: Block, fromPath: string) => string;
10+
export type PathResolver = (block: Block, fromPath: string) => string;
911

10-
export const filesystemPathResolver: PathResolver = (_config: ResolvedConfiguration, _fromBlock: Block, _toBlock: Block, fromPath: string): string => {
11-
return fromPath.replace(".block", "");
12-
};
12+
class CompiledDefinitionMapper implements Mapper<DefinitionAST> {
13+
pathResolver: PathResolver;
14+
block: Block;
15+
constructor(block: Block, pathResolver: PathResolver) {
16+
this.block = block;
17+
this.pathResolver = pathResolver;
18+
}
19+
BlockExport(fromPath: string, exports: Array<Name | Rename>) {
20+
fromPath = this.pathResolver(this.block, fromPath);
21+
return builders.blockExport(fromPath, exports);
22+
}
23+
BlockReference(fromPath: string, defaultName: string, references: Array<Name | Rename>) {
24+
fromPath = this.pathResolver(this.block, fromPath);
25+
return builders.blockReference(fromPath, defaultName, references);
26+
}
27+
}
28+
29+
class SelectorASTBuilder implements Visitor<DefinitionAST> {
30+
selector: string;
31+
constructor() {
32+
this.selector = "";
33+
}
34+
AttributeSelector(attr: AttributeSelector): void {
35+
this.selector += `[${attr.attribute}`;
36+
if (attr.matches) {
37+
this.selector += attr.matches.matcher;
38+
this.selector += `"${attr.matches.value}"`;
39+
}
40+
this.selector += "]";
41+
}
42+
ForeignAttributeSelector(attr: ForeignAttributeSelector): void {
43+
this.selector += `[${attr.ns}|${attr.attribute}`;
44+
if (attr.matches) {
45+
this.selector += attr.matches.matcher;
46+
this.selector += `"${attr.matches.value}"`;
47+
}
48+
this.selector += "]";
49+
}
50+
ScopeSelector(_scopeSelector: ScopeSelector): void {
51+
this.selector += `:scope`;
52+
}
53+
ClassSelector(classSelector: ClassSelector): void {
54+
this.selector += `.${classSelector.name}`;
55+
}
56+
}
57+
58+
class CompiledDefinitionPostcssASTBuilder implements Visitor<DefinitionAST> {
59+
postcss: typeof postcss;
60+
root: postcss.Root;
61+
currentRule: postcss.Rule | undefined;
62+
constructor(postcssImpl: typeof postcss) {
63+
this.postcss = postcssImpl;
64+
this.root = this.postcss.root();
65+
}
66+
67+
namedReferences(references: Array<Name | Rename>): string {
68+
let result = "";
69+
result += "(";
70+
let prevRef = false;
71+
for (let ref of references) {
72+
if (prevRef) {
73+
result += ", ";
74+
}
75+
result += ref.name;
76+
if (ref.asName) {
77+
result += ` as ${ref.asName}`;
78+
}
79+
prevRef = true;
80+
}
81+
result += ")";
82+
return result;
83+
}
84+
85+
BlockSyntaxVersion(blockSyntaxVersion: BlockSyntaxVersion): void {
86+
this.root.append(this.postcss.atRule({
87+
name: "block-syntax-version",
88+
params: blockSyntaxVersion.version.toString(),
89+
}));
90+
}
91+
92+
BlockReference(blockReference: BlockReference): void {
93+
let params = "";
94+
if (blockReference.defaultName) {
95+
params += blockReference.defaultName;
96+
}
97+
if (blockReference.defaultName && blockReference.references) {
98+
params += ", ";
99+
}
100+
if (blockReference.references) {
101+
params += this.namedReferences(blockReference.references);
102+
}
103+
params += ` from "${blockReference.fromPath}"`;
104+
let atRule = this.postcss.atRule({
105+
name: "block",
106+
params,
107+
});
108+
this.root.append(atRule);
109+
}
110+
111+
LocalBlockExport(localBlockExport: LocalBlockExport): void {
112+
let params = this.namedReferences(localBlockExport.exports);
113+
let atRule = this.postcss.atRule({
114+
name: "export",
115+
params,
116+
});
117+
this.root.append(atRule);
118+
}
119+
120+
BlockExport(blockExport: BlockExport): void {
121+
let params = this.namedReferences(blockExport.exports);
122+
params += ` from "${blockExport.fromPath}"`;
123+
let atRule = this.postcss.atRule({
124+
name: "export",
125+
params,
126+
});
127+
this.root.append(atRule);
128+
}
129+
130+
Rule(rule: Rule<DefinitionAST>): void | boolean {
131+
let selectors = new Array<string>();
132+
for (let sel of rule.selectors) {
133+
let visitor = new SelectorASTBuilder();
134+
visit(visitor, sel);
135+
selectors.push(visitor.selector);
136+
}
137+
let currentRule = postcss.rule({selectors});
138+
for (let declaration of rule.declarations) {
139+
currentRule.append(
140+
postcss.decl({prop: declaration.property, value: declaration.value}),
141+
);
142+
}
143+
this.root.append(currentRule);
144+
return false;
145+
}
146+
GlobalDeclaration(globalDeclaration: GlobalDeclaration): false {
147+
let selectorBuilder = new SelectorASTBuilder();
148+
visit(selectorBuilder, globalDeclaration.selector);
149+
let atRule = postcss.atRule({name: BLOCK_GLOBAL, params: selectorBuilder.selector});
150+
this.root.append(atRule);
151+
return false;
152+
}
153+
}
13154

14155
export class BlockDefinitionCompiler {
15156
postcss: typeof postcss;
16157
config: ResolvedConfiguration;
17-
constructor(postcssImpl: typeof postcss, config: ResolvedConfiguration) {
158+
pathResolver: PathResolver;
159+
constructor(postcssImpl: typeof postcss, pathResolver: PathResolver, config: ResolvedConfiguration) {
18160
this.postcss = postcssImpl;
19161
this.config = config;
162+
this.pathResolver = pathResolver;
163+
}
164+
165+
compile(block: Block, reservedClassNames: Set<string>): postcss.Root {
166+
let ast = this.compileToAST(block, reservedClassNames);
167+
let visitor = new CompiledDefinitionPostcssASTBuilder(this.postcss);
168+
visit(visitor, ast);
169+
return visitor.root;
170+
}
171+
172+
globalDeclarations(block: Block): Array<GlobalDeclaration> {
173+
let globals = new Array<GlobalDeclaration>();
174+
for (let attrValue of block.rootClass.allAttributeValues()) {
175+
if (attrValue.isGlobal) {
176+
let selector = builders.attributeSelector(attrValue.attribute.name, attrValue.isPresenceRule ? undefined : attrValue.value);
177+
globals.push(builders.globalDeclaration(selector));
178+
}
179+
}
180+
return globals;
181+
}
182+
183+
compileToAST(block: Block, reservedClassNames: Set<string>): DefinitionRoot {
184+
if (!block.blockAST) {
185+
throw new Error("The block's AST is missing.");
186+
}
187+
let mapper = new CompiledDefinitionMapper(block, this.pathResolver);
188+
let definitionRoot: DefinitionRoot = mapToDefinition(mapper, block.blockAST, "block", "definition");
189+
definitionRoot.children.unshift(...this.globalDeclarations(block));
190+
definitionRoot.children.unshift(builders.blockSyntaxVersion(1));
191+
for (let style of block.all(true)) {
192+
definitionRoot.children.push(this.styleToRule(style, reservedClassNames));
193+
}
194+
return definitionRoot;
20195
}
21196

22-
compile(block: Block, root: postcss.Root, _pathResolver: PathResolver): postcss.Root {
23-
this.blockReferences(root, block);
24-
return root;
197+
styleToRule(style: Style, reservedClassNames: Set<string>): Rule<DefinitionAST> {
198+
let selectors = new Array<Selector<DefinitionAST>>();
199+
let blockClass: BlockClass = isAttrValue(style) ? style.blockClass : style;
200+
let elementSelector: ClassSelector | ScopeSelector;
201+
if (blockClass.isRoot) {
202+
elementSelector = builders.scopeSelector();
203+
} else {
204+
elementSelector = builders.classSelector(blockClass.name);
205+
}
206+
if (isAttrValue(style)) {
207+
let attributeSelector: AttributeSelector;
208+
if (style.isPresenceRule) {
209+
attributeSelector = builders.attributeSelector(style.attribute.name);
210+
} else {
211+
attributeSelector = builders.attributeSelector(style.attribute.name, style.value);
212+
}
213+
selectors.push(builders.keyCompoundSelector(elementSelector, [attributeSelector]));
214+
} else {
215+
selectors.push(elementSelector);
216+
}
217+
let declarations = new Array<Declaration>();
218+
if (isBlockClass(style) && style.isRoot) {
219+
declarations.push(builders.declaration("block-id", `"${style.block.guid}"`));
220+
declarations.push(builders.declaration("block-name", `"${style.block.name}"`));
221+
}
222+
declarations.push(builders.declaration("block-class", style.cssClass(this.config, reservedClassNames)));
223+
declarations.push(builders.declaration("block-interface-index", style.index.toString()));
224+
let aliasValues = new Array(...style.getStyleAliases());
225+
if (aliasValues.length) {
226+
declarations.push(builders.declaration("block-alias", aliasValues.join(" ")));
227+
}
228+
return builders.rule(selectors, declarations);
25229
}
26230

27231
blockReferences(root: postcss.Root, block: Block): void {
@@ -30,9 +234,12 @@ export class BlockDefinitionCompiler {
30234
});
31235
}
32236

33-
insertReference(_css: postcss.Root, _definitionPath: string) {
34-
throw new Error("Method not implemented.");
237+
insertReference(css: postcss.Root, definitionPath: string) {
238+
let comment = this.postcss.comment({text: `#blockDefinitionURL=${definitionPath}`});
239+
Object.assign(comment.raws, {left: "", right: ""});
240+
css.append(comment);
35241
}
242+
36243
insertInlineReference(_css: postcss.Root, _definition: postcss.Root) {
37244
throw new Error("Method not implemented.");
38245
}

packages/@css-blocks/core/src/BlockCompiler/index.ts

+13-7
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
resolveConfiguration,
1616
} from "../configuration";
1717

18-
import { BlockDefinitionCompiler, INLINE_DEFINITION_FILE, PathResolver, filesystemPathResolver } from "./BlockDefinitionCompiler";
18+
import { BlockDefinitionCompiler, INLINE_DEFINITION_FILE } from "./BlockDefinitionCompiler";
1919
import { ConflictResolver } from "./ConflictResolver";
2020

2121
export { INLINE_DEFINITION_FILE } from "./BlockDefinitionCompiler";
@@ -38,19 +38,25 @@ export interface CompiledBlockAndInlineDefinition {
3838
export class BlockCompiler {
3939
private config: ResolvedConfiguration;
4040
private postcss: typeof postcss;
41-
private definitionCompiler: BlockDefinitionCompiler;
41+
private definitionCompiler?: BlockDefinitionCompiler;
4242

4343
constructor(postcssImpl: typeof postcss, opts?: Options) {
4444
this.config = resolveConfiguration(opts);
4545
this.postcss = postcssImpl;
46-
this.definitionCompiler = new BlockDefinitionCompiler(postcssImpl, this.config);
4746
}
4847

49-
compileWithDefinition(block: Block, root: postcss.Root, reservedClassNames: Set<string>, definitionPath: typeof INLINE_DEFINITION_FILE, pathResolver: PathResolver): CompiledBlockAndInlineDefinition;
50-
compileWithDefinition(block: Block, root: postcss.Root, reservedClassNames: Set<string>, definitionPath: string, pathResolver: PathResolver): CompiledBlockAndDefinition;
51-
compileWithDefinition(block: Block, root: postcss.Root, reservedClassNames: Set<string>, definitionPath: string | typeof INLINE_DEFINITION_FILE, pathResolver: PathResolver = filesystemPathResolver): CompiledBlockAndDefinition | CompiledBlockAndInlineDefinition {
48+
setDefinitionCompiler(definitionCompiler: BlockDefinitionCompiler) {
49+
this.definitionCompiler = definitionCompiler;
50+
}
51+
52+
compileWithDefinition(block: Block, root: postcss.Root, reservedClassNames: Set<string>, definitionPath: typeof INLINE_DEFINITION_FILE): CompiledBlockAndInlineDefinition;
53+
compileWithDefinition(block: Block, root: postcss.Root, reservedClassNames: Set<string>, definitionPath: string): CompiledBlockAndDefinition;
54+
compileWithDefinition(block: Block, root: postcss.Root, reservedClassNames: Set<string>, definitionPath: string | typeof INLINE_DEFINITION_FILE): CompiledBlockAndDefinition | CompiledBlockAndInlineDefinition {
55+
if (!this.definitionCompiler) {
56+
throw new Error("No block definition compiler was provided.");
57+
}
5258
let css = this.compile(block, root, reservedClassNames);
53-
let definition = this.definitionCompiler.compile(block, root, pathResolver);
59+
let definition = this.definitionCompiler.compile(block, reservedClassNames);
5460
let result: CompiledBlockAndDefinition | CompiledBlockAndInlineDefinition;
5561

5662
if (definitionPath === INLINE_DEFINITION_FILE) {

0 commit comments

Comments
 (0)