An attribute value that is conditionally set
An attribute group where one is set conditionally
An attribute value that is only set when its condition is true and its dynamic class is set
An attribute group that are only set when its dynamic class is set and where one (or none) is selected at runtime.
An attribute value that is only set when its dynamic class is set
An attribute value that is dynamic for any reason
An attribute group that are dynamic for any reason
Any type of dynamic attribute or group of attributes.
a ternary expression where different classes can be set when true or false
A FileIdentifier is a string with a whatever internal encoding is needed to uniquely resolve a file or relative importPath against the identifier by an importer. FileIdentifiers may be serialized across processes and should not encode any transient state. If an importer wraps another importer, it is responsible for mangling and de-mangling the import identifier to ensure that the namespaces of the importers do not collide.
Care should be taken to ensure that the same block file is never returned with different identifiers. The identifier a returned on an ImportedFile should be different from the identifier that was requested if the requested identifier was not canonical. The block factory will ensure that all blocks returned to the consumer are unique to the canonical identifier.
Importers have a special importerData
property on the CSS Blocks configuration
options hash where custom importers can request for additional importer configuration
to be passed. All Importer
methods are passed the configuration hash.
Valid user-provided options for the CSS Blocks plugin.
A map of supported syntaxes to the preprocessor function for that syntax. The keys must be one of the members of {Syntax}.
Options that can/will be read but not changed. Default values will have already been provided.
CSS
Default importer. Returns ImportedFile
from disk
Reduces multiple SourceLocation
objects into a single object capturing the
actual location of the source code on disk.
An array of SourceLoation objects.
An object containing the line number and column number.
Postcss can only consume source maps if they are inline, this takes a sourcemap from preprocessor output and adds it to the file's contents. This should be called from within a css preprocessor function when an inline sourcemap is needed and is provided for convenience.
Parses a CompoundSelector and returns the discovered Block Object. Validates the given selector is well-formed in the process.
The block that contains this selector we're validating.
The CompoundSelector
in question.
The full postcss.Rule
for nice error reporting.
Returns the block's name, type and node.
Verify that the external block referenced by a rule
selects an Attribute that
exists in the external block and is exposed as a global.
The current block making the external reference.
Assert that obj
is of type Style
. Throw if not.
Style to test.
Assert that a provided selector follows all the combinator rules required of block declarations.
The block in this selector belongs to.
The PostCSS Rule.
The ParsedSelector
to verify.
Extract an Attribute's value from a selectorParser
attribute selector
Prevent Attribute from being applied to an element without their associated class.
Error callback.
Prevent Attributes from being applied to an element without their associated class.
Error callback.
Internal method used to generate human readable error messages when parsing.
The block type we're generating a human readable name for.
Options for output, currently just to specify plurality.
A human readable descriptor for the given BlockType
.
Prevent two BlockClasses from the same Block hierarchy from being applied together.
Error callback.
Given two sets representing property concerns, return a set containing the properties that conflict with each other
Style one.
Style two.
A Set that contains the full set of property conflicts.
Verify that we are not applying multiple attribute values from a single attribute group in the same objstr
call.
Test if a given Style object is in conflict with with a PropMap.
If the they are in conflict, store the conflicting Styles list in
the conflicts
PropMap.
The Style object to we're testing.
Where we store conflicting Style data.
Safely expand a property value pair into its constituent longhands, even if it is not a valid declaration.
A CSS property value.
For each extends
property found in the passed ruleset, set the block's base
to the foreign block. If block is not found, throw.
Ruleset to crawl.
Block object being processed.
Source file name, used for error output.
Pull getParsedSelectors try-catch out to prevent de-opt of main walkRules function.
Block The block to fetch ParsedSelectors from.
postcss.Rule The postcss rule to parse.
string The filepath of the file we are parsing for error reporting.
The ParsedSelector array.
For each implements
property found in the passed ruleset, track the foreign
block. If block is not found, throw.
Ruleset to crawl
Block object being processed
Source file name, used for error output.
object The object to test.
If the supplied object o
is a Attribute
.
Check if given selector node is an attribute selector
The selector to test.
True if attribute selector, false if not.
Test if the provided node representation is a class level object, aka: operating on an element contained by the root, not the root itself.
The CompoundSelector's descriptor object.
Test if the provided node representation is an external block.
The NodeAndType's descriptor object.
Test if the provided node representation is a root level object, aka: operating on the root element.
The NodeAndType's descriptor object.
Check if given selector node is targeting the root block node
Simple print function for a ruleset conflict error message.
The property we're printing on this Ruleset.
The Ruleset we're printing.
Process all @block-debug
statements, output debug statement to console or in comment as requested.
PostCSS Root for block.
Block to resolve references for
Prevent conflicting styles from being applied to the same element without an explicit resolution.
Error callback.
For every shorthand property in our conflicts map, remove all its possible longhand expressions that are set to the same value. Do this recursively to catch shorthands that expand to other shorthands.
The property we're pruning.
The ConflictMap we're modifying.
Resolve all block references for a given block.
Block to resolve references for
Promise that resolves when all references have been loaded.
Prevent BlockClasses from being applied to the same element is their Root.
Error callback.
Logging utility function to fetch the filename, line number and column number of a given selector.
The source file name that contains this rule.
The PostCSS Rule object containing this selector.
The PostCSS selector node in question.
An object representing the filename, line number and column number.
Should this selector be parsed as a block selector? Right now, only ignore selectors in @keyframes blocks.
The postcss rule to evaluate.
If this is a block selector or not.
Logging utility function to fetch the filename, line number and column number
of a given postcss.Node
.
The source file name that contains this rule.
The PostCSS Node object in question.
An object representing the filename, line number and column number.
Extract an Attribute's name from an attribute selector
Generated using TypeDoc
@css-blocks/core
@css-blocks/core
drives everything that happens between reading a Block file and outputting final CSS! This package nearly deserves to be a mono-repo in its own right. AllBlockSyntax
features, functionality for constructingBlockTrees
, the base class for allAnalyzer
s, theBlockFactory
andBlockCompiler
implementations, and more, live in this package. As such, this codebase is best described in "packages", as you'll see below.Options
process.cwd()
"BEM"
{}
FilesystemImporter
@block-reference
.{}
4
false
css
, all blocks will be ran through it, even those that were pre-processed for another syntax. This can be disabled by settingdisablePreprocessChaining
to true.Packages
/src/BlockTree
A
BlockTree
is an in-memory data model that captures all the complexity represented in a single Block file and exposes APIs for querying and manipulating its attributes.BlockTrees
are strongly typed (read: children must be of a single type), N-Ary (read: every layer may have any number of child nodes) trees composed of four (4) possible node types. These nodes must be arranged in the following hierarchy:The core implementation of
BlockTree
nodes, which every node type extends, lives in/src/BlockTree/Inheritable.ts
. All nodes have common APIs concerning tree hierarchy and inheritance / resolution.These four (4) node types each fit in to one of two (2) classifications: Container Nodes and Style Nodes. These classifications and node types are described in detail below.
Container Nodes
Container Nodes...contain other nodes ;) These nodes contain logical groupings of like-nodes. Each container node type may implement convenience methods that make sense for the type of nodes it contains.
Block
A
Block
node is always the root of anyBlockTree
. ABlock
may be parent to any number ofBlockClass
es. The:scope
selector is considered a special kind ofBlockClass
selector and is also stored as a child ofBlock
.Block
nodes also store all data related to any@block-reference
s, theblock-name
, implementedBlocks
, the inheritedBlock
, and any other metadata stored in the Block file.Block
s also have a specialrootClass
property that points directly to the childBlockClass
that represents the parsed:scope
selector .Attribute
An
Attribute
node represents a single unique attributenamespace|name
pair and is a parent to any number ofAttrValue
nodes, which represent all the possible attribute values for thisAttribute
discovered in the Block file. AnAttribute
node's parent is always aBlockClass
. Attribute selectors where no value is specified are considered a special kind ofAttrValue
and is also stored as a child ofAttribute
.Attribute
nodes expose APIs suited for querying information about their childrenAttrValue
.Style Nodes
Style nodes represent fully resolved selectors that apply a CSS ruleset the the matching elements. For css-blocks these are
BlockClass
andAttrValue
nodes.Style nodes inherit from the abstract
/src/BlockTree/Style.ts
class and augment the baseInheritable
class to storeRulesetContainer
objects to track property concerns and property resolutions from all rulesets that target this Style and its pseudo-elements, and possess methods to query own and inherited generated class names for the Style node.BlockClass
BlockClass
nodes represent class selectors discovered in a Block file.BlockClass
nodes may contain one to manyAttribute
container nodes and have methods for querying own and inheritedAttribute
and theirAttrValue
nodes.AttrValue
AttrValue
nodes represent an fully qualified attribute selector (meaning namespace, name and value are all defined) discovered in a Block file.AttrValue
nodes are leaf nodes and may have no children.All Together Now
All these
BlockTree
objects, and the APIs they provide, enable css-blocks core to build an in-memory representation of any Block file, its dependencies, and all its data, in a format that is easily traversable and query-able.For details on all the APIs available on
BlockTree
s and their constituent parts, I invite you to explore the API documentation./src/BlockCompiler
The BlockCompiler package delivers a single class: the
BlockCompiler
(go figure 😉).BlockCompiler
s are responsible for taking aBlock
object, apostcss.Root
(and an optionalAnalyzer
to help guide final stylesheet output), and returning a transformedpostcss.Root
with all classes and states replaces with their globally unique output names, and all resolution and inheritance selectors emitted in the stylesheet.Much of what currently goes in to Block compilation amounts to a find and replace of all classes and states with their generated, globally unique, class names. The format of these generated selectors are dictated by the
OutputMode
specified in the CSS Blocks' configuration, and defaults to BEM.Block compilation becomes a little more complicated once we begin emitting conflict resolution selectors. In cases where explicit resolutions are provided, or when one Block inherits from another and re-defined an inherited CSS property, we need to emit a conflict resolution selector so the browser exhibits the expected behavior. This involves merging two, potentially complicated, selectors so the new selector will only match when both overridden selectors are applied. For example:
Input
/ other.css / :scope { block-name: "other"; } :scope[state|active] .bar { color: blue; }
/ main.css / @block-reference other from "./other.css"; :scope { block-name: "main"; } :scope:hover .foo { color: red; color: resolve("other.bar"); }
Output
/ Compiled "other.css" / .other--active .other__bar { color: blue; }
/ Compiled "main.css" / .main:hover .main__foo { color: red; }
/ Emitted Resolution Selector / .other--active.main:hover .main__foo.other__bar { color: blue; }
/src/BlockParser
The BlockParser package contains all constructs that handle converting an Block file into a
BlockTree
, including preprocessor integrations.BlockFactory
The primary class delivered by BlockParser is the
BlockFactory
. TheBlockFactory
is responsible for creating new Block objects and ensuring that every unique Block file is only parsed once. If the same file is re-requested, theBlockFactory
will return the same promise as the previous parse request which will resolve with the same sharedBlock
object. Like most Factory Pattern implementations, most consumers will exclusively interface with theBlockFactory
when creating newBlock
s and should not have to worry about the parser itself.Preprocessor Support
It is also the responsibility of the
BlockFactory
to only start Block compilation after all user-provided preprocessor steps have been finished. The configuration options for preprocessor integration can be found in this package.BlockParser
Under the hood, the
BlockFactory
uses aBlockParser
to convert the providedpostcss.Root
into a newBlock
object. TheBlockParser
is modeled after something akin to the Builder Pattern and runs the newly mintedBlock
through a series of feature-specific "middleware". Each middleware, found insrc/BlockParser/features
, is responsible for reading, and augmenting the newBlock
, with a single language feature concern.It is important that the supplied
postcss.Root
is not transformed in any way during this parse phase. The postcss tree should be considered read-only in allBlockParser
feature middleware and only be used to construct the resultingBlockTree
./src/BlockSyntax
The BlockSyntax package delivers CSS Blocks specific syntax constants, and a few simple parsing functions, used in Block files. [You can look at the API documentation][TODO] for details.
All CSS Blocks specific syntax used in Block files should be defined here. No other package should be re-defining these constants for parsing functions.
/src/Analyzer
The Analyzer package delivers the classes, data models, and types that are required by a [Template Integration][TODO]. It is the Template Integrations' responsibility to, given a number of template entry-points, know how to crawl the template dependency tree and analyze every element of every template discovered.
There are three (3) core classes that drive every Template Analyzer integration:
Analyzer
The
Analyzer
class is the base class that all Template Integrations must derive from. It represents the project-wide analysis of all templates reachable from the list of entry-points provided. An extender ofAnalyzer
is expected to implement the abstractanalyze(...entry-points: string)
method, as this is what will be called by Build Integrations to kick off analysis.The
Analyzer
has a factory method,getAnalysis()
to retrieve a newAnalysis
object (described below) for each template discovered. TheAnalyzer
will then crawl the contents of the template and log all Block usage data for the template on thatAnalysis
object.Analyzer
s have a number of convenience methods for accessing and iterating overAnalysis
objects created after an analysis pass.Analysis
objects may be re-used (ex: in dev rebuilds) by calling theirreset()
method to clear all caches and saved data from the previous analysis pass. These methods can be explored over in the [Analysis API documentation][TODO].Analysis
The
Analysis
object represents a single template's Block usage data. These are created by theAnalyzer
during an analysis pass for every template discovered when crawling the template dependency tree.It is the Template Integration's responsibility to assemble each
Analysis
to accurately represent the Block usage in the template. This includes adding all referenced Blocks to theAnalysis
object, and creating a newElementAnalysis
(described below) for every element in the template and registering all used Block styles with theElementAnalysis
.Analysis
objects function as factories forElementAnalysis
objects. When a new element is discovered, Template Integrations can callstartElement()
on the current template'sAnalysis
, to create a newElementAnalysis
. The integration can then add discovered Block styles to theElementAnalysis
. Once all Block styles have been registered with theElementAnalysis
, the integration may then callendElement()
to seal theElementAnalysis
.ElementAnalysis
The
ElementAnalysis
object represents a single element's Block usage data and are retrieved from a template'sAnalysis
object using theAnalysis.startElement()
factory function. The last returnedElementAnalysis
object remains un-sealed untilAnalysis.endElement()
is called.Un-sealed
ElementAnalysis
objects may be used to store Block style usage data saved to them (read:BlockClass
andAttrValue
s). Any given Block style used in a template may be either Static, Dynamic, or Mutually Exclusive.For example, given the following Block file, we can determine the type of usage in the handlebars snippets below:
.my-class { / ... / } .other-class { / ... / } [state|active] { / ... / } [state|color="red"] { / ... / } [state|color="blue"] { / ... / }
Static styles are guaranteed to never change:
<div class="my-class" state:active="true"></div>
Dynamic styles may or may not be applied depending on application state:
<div class="{{style-if value 'my-class'}}" state:active={{isActive}}></div>
Mutually Exclusive styles are guaranteed to never be used on the element at the same time:
{{!--
my-<span class="hljs-keyword">class</span>
andother-<span class="hljs-keyword">class</span>
are mutually exclusive --}} {{!--[state|color=red]
and[state|color=blue]
are mutually exclusive --}} <div class="{{style-if value 'my-class' 'other-class'}}" state:color={{color}}></div>Every Template Integration's syntax for consuming Blocks will differ slightly. It is the responsibility of the integration to implement template parsing and Block object discovery to feed in to the
ElementAnalysis
APIs. You can read more about these style tracking methods on the [ElementAnalysis
API documentation][https://css-blocks.com/api/classes/_css_blocks_core.elementanalysis.html].Once an
ElementAnalysis
is sealed, a number of automatic validations run on it to ensure no template rules have been violated. These template validators are defined as independent plugins and may be enabled or disabled individually. By default, they are all enabled. These validations live under/src/Analyzer/validations
and include:attribute-group-validator
: Verify that any given State attribute is only applied once to an element.attribute-parent-validator
: Ensure that State attributes are always applied with their owner class.class-paris-validator
: If two classes from the same block are applied to the same element, throw.property-conflict-validator
: If two styles might be applied at the same time on the same element, and they have an un-resolved conflicting property concern, throw.root-class-validator
: Prevent the:scope
class and aBlockClass
from being applied to the same element./src/TemplateRewriter
Because each Template Integration has to leverage whatever plugin / AST transform system is provided by the templating system, Rewriters are a little more free-form than Analyzers. As such, there is no single base class for Rewriters to extend from.
Instead, this package delivers data models that Template Integrations may leverage to query data about how to rewrite elements they encounter during the rewrite phase. It is the responsibility of the Build Integration to shuttle these rewrite data to the actual rewriter integration.
TODO: Write more about
TemplateRewriter
data models and their APIs./src/configuration
The configuration package contains the CSS Blocks build configuration utilities, including Typescript types for the configuration hash, a configuration reader to normalize user-provided configuration hashes with default values.
See the options table at the top of this file for configuration object details.
/src/importing
CSS Blocks needs to know where to get a Block file's contents when provided a file
FileIdentifier
from an@block-reference
. Most of the time, this file path will be a file on disk, in which case the default importer delivered with CSS Blocks will work out of the box. However, in cases where custom resolution of@block-reference
s are required, consumers are able to provide their own implementation of the CSS BlocksImporter
interface to deliver this custom behavior.A custom importer may be passed to CSS Blocks via the
importer
options of the configuration object. Custom importers will understand how to resolve information about theFileIdentifier
passed to@block-reference
and are used to abstract application or platform specific path resolution logic.Any CSS Blocks
Importer
must implement the interface defined for a CSS BlocksImporter
in/src/importing/types.ts
. Every importer is required to have a number of introspection methods that return standard metadata for a givenFileIdentifier
:FileIdentifier
block-name
is set.FileIdentifier
is backed by the filesystem, return the absolute file path.Syntax
.However, the primary method for any importer is its
import()
method.import()
returns a promise that resolves with a metadata object which not only contains all the information outlined above, but also the stringified contents of the file. It is these contents that theBlockFactory
will use to create aBlockTree
.For any custom importers that require extra data to be passed by the end-user, the
importerData
CSS BLocks config option has been specially reserved as a namespaced location for extra importer data to be passed. All importer methods are passsed the full CSS Blocks config object as their last argument.CSS Blocks ships with two (2) pre-defined importers.
FilesystemImporter
: This is the default importer used by CSS Blocks if no other is provided. It enables@block-reference
s to resolve relative and absolute file referencesPathAliasImporter
: The PathAliasImporter is a replacement for the fileystem importer. Relative import paths are first checked to see if they match an existing file relative to the from identifier (when provided). Then if the relative import path has a first segment that is any of the aliases provided the path will be made absolute using that alias's path location. Finally any relative path is resolved against therootDir
specified in the CSS Block configuration options./src/util
Utilities used inside the CSS Blocks repo. These are:
Object.assign
, but for Sets.