Skip to content

Commit cabd495

Browse files
committed
feat: Basic runtime data generation with optimizer disabled.
1 parent a825a16 commit cabd495

File tree

9 files changed

+369
-32
lines changed

9 files changed

+369
-32
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,7 @@ export class Analysis<K extends keyof TemplateTypes> {
469469
}
470470
element.seal();
471471
analysis.elements.set(elID, element);
472+
if(analysis.onElementAnalyzed) analysis.onElementAnalyzed(element);
472473
});
473474

474475
return analysis;

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export abstract class Analyzer<K extends keyof TemplateTypes> {
3333
protected analysisMap: Map<string, Analysis<K>>;
3434
protected staticStyles: MultiMap<Style, Analysis<K>>;
3535
protected dynamicStyles: MultiMap<Style, Analysis<K>>;
36+
public stylesFound: Set<Style>;
3637

3738
constructor (
3839
blockFactory: BlockFactory,
@@ -49,6 +50,7 @@ export abstract class Analyzer<K extends keyof TemplateTypes> {
4950
this.analysisMap = new Map();
5051
this.staticStyles = new MultiMap();
5152
this.dynamicStyles = new MultiMap();
53+
this.stylesFound = new Set();
5254
}
5355

5456
abstract analyze(dir: string, entryPoints: string[]): Promise<Analyzer<K>>;
@@ -61,11 +63,15 @@ export abstract class Analyzer<K extends keyof TemplateTypes> {
6163
this.analysisMap = new Map();
6264
this.staticStyles = new MultiMap();
6365
this.dynamicStyles = new MultiMap();
66+
this.stylesFound = new Set();
6467
this.blockFactory.reset();
6568
}
6669

6770
newAnalysis(info: TemplateTypes[K]): Analysis<K> {
6871
let analysis = new Analysis<K>(info, this.validatorOptions, (element) => {
72+
for (let s of element.stylesFound()) {
73+
this.stylesFound.add(s);
74+
}
6975
for (let s of [...element.classesFound(false), ...element.attributesFound(false)]) {
7076
this.staticStyles.set(s, analysis);
7177
}
@@ -89,7 +95,7 @@ export abstract class Analyzer<K extends keyof TemplateTypes> {
8995
return analyses;
9096
}
9197

92-
styleCount(): number { return this.staticStyles.size; }
98+
staticCount(): number { return this.staticStyles.size; }
9399

94100
dynamicCount(): number { return this.dynamicStyles.size; }
95101

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

+49-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MultiMap, ObjectDictionary } from "@opticss/util";
1+
import { MultiMap, ObjectDictionary, unionInto } from "@opticss/util";
22
import {
33
CompoundSelector,
44
ParsedSelector,
@@ -84,6 +84,7 @@ export class Block
8484
*/
8585
public precompiledStylesheet: postcss.Root | undefined;
8686
public blockReferencePaths: Map<string, Block>;
87+
private _resolveImplementedBlocksResult: Set<Block> | undefined;
8788

8889
/**
8990
* Creates a new Block.
@@ -448,6 +449,24 @@ export class Block
448449
return result;
449450
}
450451

452+
/**
453+
* Outputs a dictionary of style source string to most specific style in the
454+
* inheritance hierarchy.
455+
*/
456+
resolvedStyleInterface(): ObjectDictionary<Styles> {
457+
// starting with the base block and working up to the most specific sub-block
458+
// we record the most specific style associated with the style name for the given block.
459+
let blocks = [...this.resolveInheritance(), this];
460+
let styleInterface: ObjectDictionary<Styles> = {};
461+
for (let b of blocks) {
462+
let styles = b.all(true);
463+
for (let style of styles) {
464+
styleInterface[style.asSource()] = style;
465+
}
466+
}
467+
return styleInterface;
468+
}
469+
451470
/**
452471
* Fetch a dictionary of styles associated with this block, using any preset
453472
* selector as the key. If a given style doesn't have a preset selector, it
@@ -650,6 +669,35 @@ export class Block
650669
return false;
651670
}
652671

672+
/**
673+
* Gets all the blocks that are implemented by this block or by any ancestor.
674+
* This includes:
675+
* - This block.
676+
* - The blocks this block inherits from.
677+
* - The blocks the above blocks explicitly declare that they implement.
678+
* - The blocks all the above blocks implement transitively.
679+
* Such that `blockA.resolveImplementedBlocks().has(blockB)` is true iff
680+
* `blockA` implements the interface of `blockB`.
681+
*/
682+
resolveImplementedBlocks(): Set<Block> {
683+
if (this._resolveImplementedBlocksResult) {
684+
return this._resolveImplementedBlocksResult;
685+
}
686+
let implemented = new Set<Block>([this]);
687+
if (this._base) {
688+
unionInto(implemented, this._base.resolveImplementedBlocks());
689+
}
690+
for (let impl of this.getImplementedBlocks()) {
691+
unionInto(implemented, impl.resolveImplementedBlocks());
692+
}
693+
this._resolveImplementedBlocksResult = implemented;
694+
return implemented;
695+
}
696+
697+
isImplementationOf(other: Block): boolean {
698+
return this.resolveImplementedBlocks().has(other);
699+
}
700+
653701
/**
654702
* Objects that contain Blocks are often passed into assorted libraries' options
655703
* hashes. Some libraries like to `JSON.stringify()` their options to create

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

+1-5
Original file line numberDiff line numberDiff line change
@@ -280,11 +280,7 @@ export class BlockClass extends Style<BlockClass, Block, Block, Attribute> {
280280
}
281281

282282
/**
283-
* Adds a new Style for this Style to compose.
284-
* TODO: Currently, conditions are grouped exclusively by the 'and' operator.
285-
* We can abstract boolean operators to keep an internal representation
286-
* of logic between css and template files and only resolve them to the
287-
* requested language interface at rewrite time.
283+
* Checks whether this style composes another style.
288284
*/
289285
composes(style: Styles, resolve = true): boolean {
290286
let compositions = resolve ? this.resolveComposedStyles() : new Set(this._composedStyles);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { ObjectDictionary } from "@opticss/util";
2+
// Every CSS Block style is assigned a unique number that is globally unique
3+
export type GlobalStyleIndex = number;
4+
// This represents an index that is local to a specific block
5+
export type LocalStyleIndex = number;
6+
// For clarity as to what type of information the strings are
7+
export type Classname = string;
8+
export type OptimizedClassname = string;
9+
// this is a really fancy way to alias to `number`
10+
export type OutputClassnameIndex = Exclude<keyof AggregateRewriteData["outputClassnames"], string>;
11+
// this is a really fancy way to alias to `number`
12+
export type GlobalBlockIndex = Exclude<keyof AggregateRewriteData["blocks"], string>;
13+
// this is a really fancy way to alias to `number`
14+
export type OptimizationIndex = Exclude<keyof AggregateRewriteData["optimizations"], string>;
15+
16+
// These 5 lines are a compact AST for boolean expressions.
17+
// Normally an AST would use objects with more expressive keys, but we want a
18+
// more compact data structure in this AST for fewer bytes in our output.
19+
// An array represents a sub-expression and the first element in that array indicates the
20+
// sub-expression type.
21+
//
22+
// When a GlobalStyleIndex is encountered, that part of the boolean expression
23+
// is considered true iff the current input state has that GlobalStyleIndex applied.
24+
export enum Operator { AND = 1, OR = 2, NOT = 3 }
25+
export type ConditionalStyleExpression = AndStyleExpression | OrStyleExpression | NotStyleExpression;
26+
export type StyleExpression = number | ConditionalStyleExpression;
27+
export type AndStyleExpression = [Operator.AND, ...StyleExpression[]];
28+
export type OrStyleExpression = [Operator.OR, ...StyleExpression[]];
29+
export type NotStyleExpression = [Operator.NOT, StyleExpression];
30+
31+
export interface ConditionalStyle {
32+
style: GlobalBlockIndex;
33+
conditions: ConditionalStyleExpression;
34+
}
35+
36+
export interface StyleRequirements {
37+
// The key is a GlobalStyleIndex.
38+
//
39+
// Value is condition that must be met for this style to be applied
40+
// even if the style itself is not dynamic.
41+
[styleIndex: number]: ConditionalStyleExpression;
42+
}
43+
44+
export interface ImpliedStyles {
45+
// The key is a GlobalStyleIndex
46+
//
47+
// Value is an unordered set.
48+
// - GlobalStyleIndex: a style that is also applied in conjunction with the given style.
49+
// - string: public class name (block alias) that is also applied in conjunction with the given style.
50+
//
51+
// Note: This list is only the directly implied styles. The full implication
52+
// graph is resolved at runtime.
53+
[styleIndex: number]: Array<GlobalStyleIndex | string | ConditionalStyle>;
54+
}
55+
56+
export type OptimizationEntry = [OutputClassnameIndex, StyleExpression];
57+
58+
export interface AggregateRewriteData {
59+
// Maps a block's unique ID to an index of AggregateRewriteData["blocks"].
60+
blockIds: ObjectDictionary<GlobalBlockIndex>;
61+
blocks: Array<BlockInfo>;
62+
63+
// This is a list of all the class names that might be returned by the rewrite helper
64+
// with the exception of public class names (block aliases) that are found in the styleImplications.
65+
// Note: classnames from the original source that are not optimized are also returned here.
66+
// Note: classnames from the original source that the optimizer has flagged as obsolete are not listed here.
67+
outputClassnames: Array<OptimizedClassname>;
68+
styleRequirements: StyleRequirements;
69+
impliedStyles: ImpliedStyles;
70+
// Adds the class name to the output if the style expression matches the current input state.
71+
optimizations: Array<OptimizationEntry>;
72+
// possibleOptimizations: {
73+
// // Key is a GlobalStyleIndex.
74+
// // Value is a list of all outputs that might apply for the given GlobalStyleIndex.
75+
// [styleIndex: number]: Array<OptimizationIndex>;
76+
// };
77+
}
78+
79+
export interface BlockInfo {
80+
// The styles of this block
81+
// Note: this includes all the styles that are inherited but not overridden
82+
// but does not include the styles that are inherited but then overridden.
83+
blockInterfaceStyles: ObjectDictionary<LocalStyleIndex>;
84+
// Given a block that implements this block's interface or inherits from this interface
85+
// Get an array of GlobalStyleIndex values that directly correspond to the same index as BlockInfo["styles"].
86+
// Note: If an implementation inherits from this block, this array will contain
87+
// GlobalStyleIndex values from this BlockInfo's styles wherever those styles
88+
// are not overridden. Thus it is guaranteed that each value in `implementationStyles`
89+
// has the same length as the `styles` array.
90+
implementations: {
91+
// The key is a GlobalStyleIndex
92+
[blockIndex: number]: Array<GlobalStyleIndex | null>;
93+
};
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Analyzer } from "@css-blocks/core";
2+
import { TEMPLATE_TYPE } from "@css-blocks/ember-support";
3+
import { TemplateIntegrationOptions } from "@opticss/template-api";
4+
5+
export class EmberAnalyzer extends Analyzer<TEMPLATE_TYPE> {
6+
analyze(_dir: string, _entryPoints: string[]): Promise<Analyzer<"HandlebarsTemplate">> {
7+
throw new Error("Method not implemented.");
8+
}
9+
get optimizationOptions(): TemplateIntegrationOptions {
10+
return {
11+
rewriteIdents: {
12+
id: false,
13+
class: true,
14+
},
15+
analyzedAttributes: ["class"],
16+
analyzedTagnames: false,
17+
};
18+
}
19+
}

0 commit comments

Comments
 (0)