With the advent of CSS-in-JS, the cascade is less and less relevant to developers, but there's a lot that stylesheets have to offer. In this presentation, Chris Eppstein discusses the new Stylesheet framework and optimizer that LinkedIn is building.
4. “
”
Every presentation about CSS architecture is, at
its essence, a talk about how to manage the
cascade.
— Christopher Eppstein, November 10, 2017, Web directions Summit
This talk is no exception 🙄
5. WHY AM I HERE? BLAME MARK.
https://medium.com/seek-blog/a-unified-styling-language-d0c208de2660
6. BENEFITS OF CSS-IN-JS
According to Mark
Scoped styles
Critical CSS
Smarter optimizations
Package management
Non-browser styling
I think there are more:
No meaningful cascade
Anonymous rule sets.
Determining styles for an element is
straightforward
Deleting an element deletes the styles.
8. WE HAD SOME PROBLEMS
WITH OUR CSS AT LINKEDIN…
Before even launching: Our new single-page
app had 3.2 MB of CSS.
Selector specificity was out of control and was
measurably impacting site speed.
Designs & implementations that were out of
compliance with the design system.
Hard for apps to down-level their style patterns
to the design system.
Our styles had become slow &
bloated.
9. WE HAD CONSTRAINTS ON
OUR POSSIBLE SOLUTIONS…
Design System for dozens of apps built in
different tech stacks and of varying
architectures.
Pure css/scss deliverable for legacy apps.
Make performance a 1st order consideration.
Where a trade-off must be made, prefer runtime
performance to developer experience.
Support for Ember Applications
Your constraints may be
different!
10. WE DECIDED ON THE
PROPERTIES OF AN IDEAL
SOLUTION…
Solution should empower engineers to scale
down to third-world mobile. (~5k of CSS
compressed)
Solution must scale up to hundreds of
engineers on a single application.
Server-side rendering
Inline Critical Styles
Code splitting of CSS
Great developer experience
Empower Design Systems
Tree shaking
It’s good to have goals.
11. AND I HAVE OPINIONS…
I’m generally worried that CSS-in-JS will make
CSS less approachable to people with a design
background.
CSS-in-JS leads to JS-in-CSS – because
abstractions.
CSS-in-JS Is theoretically worse for
performance.
Atomic CSS and other declaration-oriented
systems create high-performance garbage.
THE_BEM__naming-system—is-atrocious
Which, of course, is why I’m
here today!
12. THE PLAN
Component-oriented selectors.
Build a Better BEM
And optimize shared declarations into shared rules styles.
Use build-time code transforms to maximize benefit from optimization.
Generate code split styles and
"uglyify" CSS idents to maximize gzip efficiency.
13. BUT DOESN'T DECLARATION
MERGING BREAK THE
CASCADE?
Demo Sneak Peek Hey, you're pretty smart! It
does, in fact, break the cascade,
if you're not careful
16. RESULTS SO FAR
(in bytes) BEM BEM
+Min
OptiCSS OptiCSS
+ min
Plain 61534 47913 29355 21467
gzip 10574 8992 7283 6340
brotli 8857 7814 6190 5481
CSS Code split for home page
(prototyped)
Yes. That’s less than 5% of what
we’re currently sending on first
page load after gzip.
BEM BEM
+Min
OptiCSS OptiCSS
+ min
Plain 100% 77.8% 47.7% 34.9%
gzip 100% 85% 68.8% 60%
brotli 100% 882% 69.9% 61.9%
Current CSS Size of LinkedIn.com:
~1.6MB, ~140kb gzip
17. OK BUT…
Does it handle dynamic style changes?
Is there runtime overhead?
What about the templates sizes?
Is it hard to use?
Is it hard to adopt?
Seems too good to be true?
18. WHAT IS A BLOCK?
A collection of related design elements and their various modes and
interaction states.
19. DEFINING A BLOCK
Block Root – The class for the root element of
the block’s document subtree.
Block State – A state that the entire block can
be in. Can affect the classes and the states of
those classes.
Class – Indicates a type of element that is styled
within the block.
Class State – A state that the class can be in.
Only affects the elements of that class.
Every block must be in it’s own
file.
Classes are "nouns",
States are "adjectives"
20. A BLOCK IS AN INTERFACE TO
A DESIGN CONCEPT
The existence of Classes and States are inferred
from the selectors that implement them.
A block can inherit from another block.
Classes and states of the same name inherit from
the classes and states of that same name in the
base block.
Additional classes and states can be added.
A block can implement another Block’s interface
The selectors and properties are
implementation details.
22. BLOCK SYNTAX
Classes
Literally a CSS class name.
A special class .root styles the block’s
root element.
States
An attribute selector for an attribute in
the state namespace.
The attribute value is optional.
Only the = attribute operator is
allowed.
States for a class are a compound
selector of the state attribute and the
class.
23. RULES FOR AUTHORING A
BLOCK’S STYLES
Key selector must target a single block or class,
or a pseudo-element of one.
Any number of states and pseudo-classes for
the target block or class are allowed.
Selectors may be contained within an
@-rule.
Combinators are forbidden except when the
context selector is one or more block-level
states.
!important is forbidden.
The rules are basically BEM’s
rules for selectors.
!important isn’t necessary.
These rules help minimize
coupling.
24. MARKUP RULES
You may not use a block class outside of the
root element's HTML subtree.
Two classes from the same block may not be
applied to the same HTML element.
While not required for
optimization, these rules exist to
keep the definitions of what is a
root, class or state clear.
25. BLOCK-SPECIFIC SYNTAX
@block-reference my-block from "subdir/my.block.css";
Special properties for .root:
implements: block-name, other-block;
extends: block-name;
block-name: preferred-name;
@block-global [state|is-loading];
.class { @is-block <block-name>[state|foo]; }
@block-debug <block-name> to (comment|stderr|stdout)
Some concepts for blocks
require specialized syntax.
This syntax is always removed
from the CSS output.
26. BLOCK-SPECIFIC SYNTAX
Are you ready? It's time to kill
the cascade.
To resolve property conflicts
across blocks we use the special
resolve() function.
"Override" Resolution
27. BLOCK-SPECIFIC SYNTAX
To resolve property conflicts
across blocks we use the special
resolve() function.
The source order of the resolve
declaration determines the
winner.
"Yield" Resolution
28. CONFLICT RESOLUTION
During analysis, two styles from different blocks
on the same block element setting the same
CSS property to different values is an error until
it is resolved.
Shorthand aware. Initial value aware.
Progressive-enhancement aware.
The ruleset generated by a resolve() is removed
by the optimizer and the rewriter will use the
correct class(es) even across optimizations that
merge declarations.
Resolve declarations are what
make CSS-Blocks work and what
makes it so optimizable.
29. CONFLICT RESOLUTION
Because resolution is property-based two
properties from the same rulesets can have
different resolution outcomes (override vs.
yield)
Wildcard syntax for resolving the same property
from different conflicting styles.
Flexibility the cascade can't
offer.
33. PREPROCESSOR INTEGRATION
Sass
• Read contents
CSS
• Optional pre-processesor if extension isn't
CSS
CSS
• Optional CSS Post Processor
So maybe you've heard of
Sass…
CSS-Blocks works with any
preprocessor.
34. TEMPLATE INTEGRATION
Current Implementations:
Handlebars/Glimmer
JSX (classnames-style)
Planned Implementations:
Handlebars/Ember
Vue
JSX (html-style)
CSS-Blocks can be adapted to
template and application
framework's conventions and
ethos to make it feel native to
that framework.
35. HANDLEBARS
A CSS block file is associated by convention
with each template and controller.
The block identifiers from @block-reference
directives in that block expose that name as a
block scope in the template.
Classes and states are set on
elements. The class attribute
uses a special style-if/style-
unless helpers.
36. JSX
Style expressions are function calls✻ to
a styles function that returns✻
optimized styles.
✻ it's actually rewritten during the build.
37. PASSING STYLES TO A
COMPONENT
A Block is used to style a component and the
relationships to its styles with other blocks are
analyzed within a component boundary.
A component parameter can accept a block
reference that inherits from or implements the
block.
The analysis of the base block can be applied to
the passed block.
Functional boundaries are
information boundaries.
The unit of style is a block –
that's what we pass.
38. HANDLING DYNAMISM
Output classes are modeled as
arbitrarily nested boolean
expressions over presence of
the class names set originally.
CSS classes
CSS-Block
Styles
Conditional
expressions
Optimized
Class names
39. HANDLEBARS
The optimizer gives instructions to
the rewriter for what classes should
be active and when according to
the dynamic behavior of the
element. The instructions
(opcodes) encode the conditional
expressions required to produce a
runtime class list.
<div class={{classnames 1 2 2 (isLoading) 1 1 "a" 0 "b" 1}}>
<aside class={{classnames 0 4 "g" 0 "h" 1 "c" 2 "d" 3}}>
</aside>
<article class={{classnames 1 3 0 isRecommended 1 2 1 1 "i" 0 "e" 1 "f" 2}}>
</article>
</div>
40. OptiCSS
Markup Analysis Driven Optimizer for CSS
Handles Generic CSS, does not require CSS-Blocks
Configurable and Extensible
42. THE 1ST CSS-BASED ALTERNATIVE TO CSS-IN-JS.
*whispers*
You can even co-locate your styles in your JS file if that’s your thing.
43. BENEFITS OF CSS-IN-JS
According to Mark
Scoped styles
Critical CSS
Smarter optimizations
Package management
Non-browser styling
I think there are more:
No meaningful cascade
Anonymous rule sets.
Determining styles for an element is
straightforward
Deleting an element deletes the styles.
44. CSS-Blocks + OptiCSS
According to Mark
Scoped Styles
Critical CSS
Smarter Optimizations
Package Management
✗ Non-browser Styling
I think there are more:
No meaningful (cross-component)
cascade
✗ Anonymous Rule Sets (?)
Determining styles for an element is
straightforward.
Deleting an element deletes its styles
(at build time)
45. STATIC ANALYSIS IS FOR
MORE THAN OPTIMIZATION
Override Analysis – Should a style stop setting a
property or set it to something else?
Dead Code Removal – list styles that are never
used, without running the app.
Correlation Analysis – should styles be
combined?
It unlocks a whole new world of
tools for design.
Are anonymous rulesets less
important when our design
abstractions are properly
factored?
46. WRAPPING UP
LinkedIn
Adam Miller (https://twitter.com/millea9)
http://css-blocks.com/ ☜ Sign up for Updates
Follow me on twitter: @chriseppstein
Love it or hate it, the cascade inspires strong feelings
This talk is about so much more than the cascade, but everything I’m going to talk about today is possible because of a new way to resolve conflicts between css selectors that set the same property on the same element. But I’m getting ahead of myself -- Let’s back up.
First talk in 2 years … I’m a little rusty.
First talk on this subject/project
The use of the word block derives from the notion of a Block in BEM.
CSS-Blocks is a Formalization of concepts found in BEM.
@block-reference – creates a local name that can be used to reference that block or classes/states within it.
Implements – Gives an error if the block does not have a selector that defines every state and class in the specified block.
Extends – causes the block to inherit styles from the base block.
@block-global – declares a block-level state to be global, allowing other blocks to use it in their context selectors.
@is-block declares that a block's class is always the root of another block (useful for sub-components)
Are your defaults right?
Should distinct concepts be merged because they’re used together?
Maybe our desire for anonymous rulesets will go away when our design abstractions are properly aligned with usage?