Upgrade to Pro — share decks privately, control downloads, hide ads and more …

You might not need a CSS preprocessor

You might not need a CSS preprocessor

📹 Video: https://www.youtube.com/watch?v=o2an6ON6i3g
🔥 Live slides + Demos: https://blog.hospodarets.com/you-might-not-need-a-css-preprocessor

Presented at Topconf Tallinn 2016.

Having native CSS variables, which work in all modern browsers, also mixins and nesting, we can consider switching from preprocessors to native CSS and look for a path to migration.

In the presentation, there is a description of the modern abilities of CSS, which specs are in progress, demos how this already works in browsers and how to use polyfills to make these CSS goodness work everywhere.

You will know:
1) That mostly everything you need in preprocessors currently is available in pure CSS
2) How to make it work cross-browser
3) The real examples of already migrated sites

Serg Hospodarets

September 15, 2016
Tweet

More Decks by Serg Hospodarets

Other Decks in Programming

Transcript

  1. You might not need a
    CSS preprocessor
    DevDay 2016
    Serg Hospodarets
    @malyw

    View Slide

  2. View Slide

  3. No CSS

    View Slide

  4. 1996 year - CSS invented

    View Slide

  5. Content and presentation are separated
    .page-header {
    /* shorthand */
    border-bottom: 1px solid #eee;
    }
    p {
    font-family: Arial; /* font */
    font-size: 14px; /* font */
    color: #333; /* color */
    margin: 10px 0; /* layout */
    }
    .btn-outline {
    color: #563d7c; /* color */
    border-color: #563d7c; /* color */
    }

    View Slide

  6. CSS problems
    Absence of variables
    Absence of mixins
    Modules
    Nested rules are not
    supported
    Code duplication as result

    View Slide

  7. Preprocessors time

    View Slide

  8. Solved by
    preprocessors

    View Slide

  9. Sass:
    Variables and Operators (+, -, *, /, %)
    $font-size: 10px;
    $font-family: Helvetica, sans-serif;
    body {
    font: $font-size $font-family;
    }
    .mark{
    font-size: 1.5 * $font-size;
    }

    View Slide

  10. Mixins
    @mixin clearfix {
    &:after {
    display: block;
    content: '';
    clear: both;
    }
    }
    .sidebar{
    @include clearfix;
    }
    .main{
    @include clearfix;
    }
    .sidebar:after {
    display: block;
    content: '';
    clear: both;
    }
    .main:after {
    display: block;
    content: '';
    clear: both;
    }

    View Slide

  11. Nesting
    HTML SCSS



    link



    dropdown link




    // menu
    .nav {
    > li {
    > a:hover {
    background-color: red;
    }
    // submenu
    > ul {
    background-color: #fff;
    > li > a:hover {
    background-color: black;
    }
    }
    }
    }

    View Slide

  12. Modules

    View Slide

  13. What we use today

    View Slide

  14. Preprocessors
    problems

    View Slide

  15. Additional setup is needed to make a compiler work
    Any change require recompilation
    Compilation takes time ⏰

    View Slide

  16. Each has own syntax!
    // Sass
    $color: #f00;
    $images: "../img";
    @mixin clearfix {
    &:after {
    content: " ";
    display: block;
    clear: both;
    }
    }
    body {
    color: $color;
    background: url("#{img}/1.png");
    @include clearfix;
    }
    // Less
    @color: #f00;
    @images: "../img";
    .clearfix() {
    &:after {
    content: " ";
    display: block;
    clear: both;
    }
    }
    body {
    color: @color;
    background: url("@{img}/1.png");
    .clearfix;
    }

    View Slide

  17. Source maps are required
    Debug might be hard (or buggy)

    View Slide

  18. We want
    Variables
    Mixins
    Nesting
    Modules
    Selector helpers, color functions
    We don't want
    Additional setup
    Compilation
    Not standardized syntax
    Hard debug

    View Slide

  19. Does CSS have all this
    today?
    Let's take a look

    View Slide

  20. What we want?
    Variables

    View Slide

  21. First CSS variable currentColor
    :root { color: red; }
    i {border: 1px solid currentColor;}
    :root { color: red; }
    i {border: 1px solid red;}
    Other "variables": em, rem
    html { font-size: 1em; }
    h1 { font-size: 2.074em; }
    @media (min-width: 1400px) {
    html { font-size: 1.25em; }
    }
    html { font-size: 16px; }
    h1 { font-size: 33px; }
    @media (min-width: 1400px) {
    html { font-size: 20px; }
    h1 { font-size: 41px; }
    }

    View Slide

  22. (a.k.a. CSS variables)
    CSS custom properties

    View Slide

  23. View Slide

  24. Good news!

    View Slide

  25. Syntax
    /* declaration */
    --VAR_NAME: ;
    /* usage */
    var(--VAR_NAME)
    /* root element selector (global scope), e.g. */
    :root {
    /* CSS variables declarations */
    --main-color: #ff00ff;
    --main-bg: rgb(200, 255, 255);
    }
    body {
    /* use the variable */
    color: var(--main-color);
    }
    Didn't expect the "--"? There was .
    a reason

    View Slide

  26. Variable examples
    :root{
    --main-color: #4d4e53;
    --main-bg: rgb(255, 255, 255);
    --logo-border-color: rebeccapurple;
    --header-height: 68px;
    --content-padding: 10px 20px;
    --base-line-height: 1.428571429;
    --transition-duration: .35s;
    --external-link: "external link";
    --margin-top: calc(2vh + 20px);
    }
    And even..
    :root{
    --foo: if(x > 5) this.width = 10;
    }

    View Slide

  27. Variable Defaults
    If the variable has already been assigned to
    - it won’t be re-assigned
    p {
    --p-margin: 5px;
    margin: var(--p-margin, 0 0 10px); /* 5px */
    }
    If it doesn’t have a value yet
    - it will be given one.
    p {
    margin: var(--p-margin, 0 0 10px);/* 0 0 10px */
    }

    View Slide

  28. Reassign vars from others
    .block {
    --block-text: 'This is my block';
    --block-highlight-text: var(--block-text)' with highlight';
    }
    .block__highlight:before {
    content: var(--block-highlight-text);
    /*This is my block with highlight*/
    }

    View Slide

  29. Reset/inherit values
    As for any other CSS property, you can apply "initial" and
    "inherit" values
    .with-reset {
    --bgcolor: initial;/* RESETS THE VALUE for the scope */
    --color: green;/* CHANGES THE VALUE */
    --border: inherit;/* INHERITS THE VALUE for the scope */
    }

    View Slide

  30. Usage example: emulating non existing CSS rule

    View Slide

  31. Scopes

    My block is
    awesome

    Follow usual CSS cascade rules:
    :root{
    --global-var: 1em;
    /* --global-var is available globally */
    }
    .block {
    --block-var: 1.5em;
    /* --global-var and --block-var are available */
    }
    .block__highlight {
    --block-highlight-var: 2rem;
    /* --global-var , --block-var and --block-highlight-var */
    font-size: var(--block-highlight-font-size);
    }

    View Slide

  32. CSS / preprocessors scopes are different
    /* SCSS: scope depends on
    the selectors structure
    in the code */
    $font-size: 20px;
    .block{
    $font-size: 42px;
    }
    .block__highlight{
    font-size: $font-size;
    }
    /* CSS: scope depends
    on the selectors structure
    in the DOM */
    :root{
    --font-size: 20px;
    }
    .block{
    --font-size: 42px;
    }
    .block__highlight{
    font-size: var(--font-size);
    }

    View Slide

  33. Scope examples
    /* Global scope (usually

    View Slide

  34. Variables are alive

    View Slide

  35. Operators and calculations
    :root {
    --block-font-size: 1rem;
    }
    .block__highlight {
    /* DOESN'T WORK */
    font-size: var(--block-font-size)*1.5;
    }
    CSS calc( ) to the rescue (for values)!
    :root {
    --block-font-size: 1rem;
    }
    .block__highlight {
    /* WORKS */
    font-size: calc(var(--block-font-size)*1.5);
    }

    View Slide

  36. Generate colors from CSS custom properties

    View Slide

  37. CSS to JS: without
    To pass variables from CSS to JS we used to use
    to write JSON in CSS
    workarounds or hacks
    .breakpoints-data {
    font-family: '{"phone":"480px","tablet":"800px"}';
    }

    View Slide

  38. CSS to JS: with
    .breakpoints-data {
    --phone: 480px;
    --tablet: 800px;
    }
    JS
    const breakpointsData = document.querySelector('.breakpoints-data');
    // GET
    const phone = getComputedStyle(breakpointsData)
    .getPropertyValue('--phone');
    // SET
    breakpointsData.style
    .setProperty('--phone', 'custom');

    View Slide

  39. View Slide

  40. Check if supported
    CSS
    @supports ( (--a: 0)) {
    /* supported */
    }
    @supports ( not (--a: 0)) {
    /* not supported */
    }
    JS
    const isSupported = window.CSS && window.CSS.supports &&
    window.CSS.supports('--a', 0);
    /* e.g. load a CSS file generated by a preprocessor */
    if(!isSupported){
    removeCss('css-custom-properties.css')
    loadCss('without-css-custom-properties.css');
    }

    View Slide

  41. Why else CSS Custom Props are
    better?

    View Slide

  42. Color schema switcher based on CSS custom property
    values
    Cannot be done by a preprocessor without generating additional code

    View Slide

  43. What do we want?
    Mixins

    View Slide

  44. (a.k.a. CSS mixins)
    CSS Custom Sets of Properties and
    @apply rule

    View Slide

  45. Custom Property:
    :root{
    /* --property: value; */
    --VAR: ;
    }
    Custom properties can hold more than just values- they can
    also be used to hold sets of declarations:
    :root{
    /*
    --property: {
    property1: value1;
    property...: value...;
    }
    */
    --MIXIN: {
    /* style declaration 1 */
    /* style declaration ... */
    };
    }

    View Slide

  46. Syntax
    :root {
    --pink-schema: {
    color: #6A8759;
    background-color: #F64778;
    }
    }
    body{
    @apply --pink-schema;
    }
    @apply rule takes these sets of declarations and inlines
    them in another style rule

    View Slide

  47. Reasons to use
    Put reusable bunches of styles into separate entities
    Avoid code duplication
    Apply changes in a central place
    Behavior
    Everything form CSS variables (scopes, usage from JS etc.) is
    applicable for Custom Sets of Properties

    View Slide

  48. Examples
    :root {
    --clearfix: {
    display: table;
    clear: both;
    content: '';
    };
    }
    .clearfix:after{
    @apply --clearfix;
    }
    :root {
    --overflow-ellipsis: {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    };
    }
    .overflow-box{
    @apply --overflow-ellipsis;
    }

    View Slide

  49. (try in Chrome Canary)
    CSS Triangle Mixin

    View Slide

  50. Problem- variables cannot be passed
    :root {
    --triangle-to-bottom-size: 50px;
    --triangle-to-bottom: {
    /* STYLES */
    border-bottom-width: var(--triangle-to-bottom-size);
    };
    }
    .triangle-to-bottom {
    --triangle-to-bottom-size: 8px;
    @apply --triangle-to-bottom;
    /* but still 50px size is applied for border-bottom-width */
    }
    Discussion to change this behavior is in progress

    View Slide

  51. Mixins for CSS vendor prefixes?
    Every time you need clip-path:
    Lea Verou's idea:
    * {/* has zero specificity */
    /* prevents the property inheritance from outer scopes */
    --clip-path: initial;
    -webkit-clip-path: var(--clip-path);
    clip-path: var(--clip-path);
    }
    header {/* any selector like this overrides the "*" */
    /* assign the prop value for the scope */
    --clip-path: polygon(0% 0%, 100% 0%, 100% 100%%, 0% 100%);
    }

    View Slide

  52. What we need?
    Nesting

    View Slide

  53. What we want to avoid?
    CSS code duplication in selectors
    Bad readability of the code
    table.colortable td {
    text-align:center;
    }
    table.colortable td.upper {
    text-transform:uppercase;
    }
    table.colortable td:first-child,
    table.colortable td:first-child+td {
    border:1px solid #000;
    }

    View Slide

  54. Tab Atkins' CSS Nesting spec
    proposal
    Syntax is close to preprocessors
    /* Dropdown menu on hover */
    ul {
    /* direct nesting (& MUST be the first part of selector)*/
    & > li {
    color: #000;
    & > ul { display: none; }
    &:hover {
    color: #f00;
    & > ul { display: block; }
    }
    }
    }

    View Slide

  55. The Nesting At-Rule: '@nest' for complex cases
    @nest < selector (MUST CONTAIN a nesting selector '&') >
    .foo {
    color: black;
    @nest body.loading & {
    opacity: 0.5;
    }
    @nest :not(&) {
    color: white;
    }
    }
    .foo {
    color: black;
    }
    body.loading .foo {
    opacity: 0.5;
    }
    :not(.foo) {
    color: white;
    }

    View Slide

  56. Migration is easy
    Nesting selector syntax is very close to Sass
    Complex nesting can be done using @nest at-rule
    media expression etc. are nested:
    a {
    @media (min-width: 30em) {
    color: yellow;
    }
    }
    @media (min-width: 30em) {
    a {
    color: yellow
    }
    }

    View Slide

  57. What we wish?
    Modules

    View Slide

  58. The @import CSS at-rule is used to import style rules from
    other style sheets.
    It is available in all browsers since IE 5.5!

    View Slide

  59. View Slide

  60. Why we didn't use this before?
    Bugs in old browsers with the order of inclusion
    Before requests didn't go in parallel
    For HTTP1x good practice is file concatenation.
    With coming of HTTP2 rules will be cnanged.

    View Slide

  61. You can easily apply media queries for different stylesheets.
    Advantages for free:
    Conditional loading
    /* Formal syntax */
    @import [ | ] []?;
    @import url("print.css") print;
    @import "mobile.css" (max-width: 728px);
    The linked resources are loaded only when condition is met.

    View Slide

  62. We have
    Variables, Mixins
    Nesting and Modules
    What else we want?
    Selector helpers for complex cases
    Color functions

    View Slide

  63. pseudo-class
    :matches
    /* SYNTAX */
    :matches( selector[, selector]* )
    A functional pseudo-class taking a selector list as its
    argument.
    .nav:matches(.side,.top) .links:matches(:hover, :focus) {
    color: #BADA55;
    }
    /* Same thing as this... */
    .nav.side .links:hover,
    .nav.top .links:hover,
    .nav.side .links:focus,
    .nav.top .links:focus {
    color: #BADA55;
    }

    View Slide

  64. @custom-selector
    /* SYNTAX */
    @custom-selector: ;
    Example:
    @custom-selector :--text-inputs input[type="text"],
    input[type="password"];
    :--text-inputs.disabled,
    :--text-inputs[disabled] {
    opacity: 0.5
    }
    Same as:
    input[type="text"].disabled,
    input[type="password"].disabled,
    input[type="text"][disabled],
    input[type="password"][disabled] {
    opacity: 0.5
    }

    View Slide

  65. Color functions
    /* SYNTAX */
    color( * )
    some adjusters have shortcuts
    adjusters can be pipped
    color(
    red /* from red */
    blackness(+25%) /* to 25% more black than red */
    blackness(+25%) /* to 50% more black than red */
    blackness(-50%) /* to red again */
    hue(+ 30deg) /* to orange */
    hue(- 30deg) /* to red again */
    );

    View Slide

  66. View Slide

  67. Better media queries!
    Problem
    The syntax is too long and they cannot be reused easily
    Need
    A simple way to set breakpoints and reuse them

    View Slide

  68. Media Queries Level 4:
    Custom media queries
    /* SYNTAX */
    @custom-media --NAME ;
    How to use:
    @custom-media --tablet (min-width: 800px) and (max-width: 1024px);
    @media (--tablet){
    .custom-media-queries{
    background-color: red;
    }
    }

    View Slide

  69. Instead of:
    Use:
    Media queries ranges
    @media (min-width: 800px) and (max-width: 1024px) {
    .media-queries-range{
    background-color: red;
    }
    }
    @media (width >= 800px) and (width <= 1024px) {
    .media-queries-range{ background-color: red; }
    }
    With custom media queries:
    @custom-media --tablet (width >= 800px) and (width <= 1024px);
    @media (--tablet){
    /* STYLES */
    }

    View Slide

  70. Can we Use it now?
    Current situation
    CSS variables are supported in all the modern browsers
    except EDGE
    CSS mixins work in Chrome Canary
    Some of other specs are integrated in various browsers,
    sometimes it's in beta/dev versions
    Should we wait for another couple years?

    View Slide

  71. View Slide

  72. PostCSS based
    Includes other PostCSS plugins (vars, mixins etc.)
    If you already use Autoprefixer- change is straign forward
    Build setup:
    //...
    postcss: {
    options: {
    processors: [
    require('autoprefixer')({
    browsers: ['last 2 versions']
    })
    ]
    },
    //...
    //...
    postcss: {
    options: {
    processors: [
    require('postcss-cssnext')({
    browsers: ['last 2 versions']
    })
    ]
    },
    //...

    View Slide

  73. How to migrate
    1) Preprocessor
    Add PostCSS and cssnext
    2) Preprocessor+PostCSS+ability to use new CSS features
    Change vars, mixins, media queries, colors and selectors to the CSS ones
    Disable a preprocessor
    3) PostCSS + all the CSS additions power
    Disable cssnext and PostCss when the browsers support everything
    4) You have pure CSS with everything

    View Slide

  74. You still can combine preprocessor and all the mentioned
    CSS additions (or process them via PostCSS) to use the
    strongest parts of the both.

    View Slide

  75. Any live sites which use all these
    new CSS features?
    blog.hospodarets.com

    View Slide

  76. View Slide

  77. Conclusions
    Today we have mostly everything we need from
    preprocessors in pure CSS
    variables, mixins
    modules, nesting
    complex selectors and color functions
    We have polyfills to make it work until it's supported in all
    browsers
    We have the real examples of migrated applications

    View Slide

  78. How can I help / stay tuned?
    Spec, dra s, proposals
    CSS Working Group (WG) Editor Dra s
    Tab Atkins spec proposals
    Other
    Subscribe to CSS Working Group mailing list / RSS etc.
    cssnext issues

    View Slide

  79. Thank you!
    @malyw

    View Slide