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

Ebea51dd17c435982c03bc9ae29dc3a6?s=128

Serg Hospodarets

September 15, 2016
Tweet

Transcript

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

    Hospodarets @malyw
  2. None
  3. <blink>No CSS</blink>

  4. 1996 year - CSS invented

  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 */ }
  6. CSS problems Absence of variables Absence of mixins Modules Nested

    rules are not supported Code duplication as result
  7. Preprocessors time

  8. Solved by preprocessors

  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; }
  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; }
  11. Nesting HTML SCSS <!-- menu --> <div class="nav"> <li> <a

    href="">link</a> <!-- submenu --> <ul> <li> <a href="">dropdown link</a> </li> </ul> </li> </div> // menu .nav { > li { > a:hover { background-color: red; } // submenu > ul { background-color: #fff; > li > a:hover { background-color: black; } } } }
  12. Modules

  13. What we use today

  14. Preprocessors problems

  15. Additional setup is needed to make a compiler work Any

    change require recompilation Compilation takes time ⏰
  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; }
  17. Source maps are required Debug might be hard (or buggy)

  18. We want Variables Mixins Nesting Modules Selector helpers, color functions

    We don't want Additional setup Compilation Not standardized syntax Hard debug
  19. Does CSS have all this today? Let's take a look

  20. What we want? Variables

  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; } }
  22. (a.k.a. CSS variables) CSS custom properties

  23. None
  24. Good news!

  25. Syntax /* declaration */ --VAR_NAME: <declaration-value>; /* usage */ var(--VAR_NAME)

    /* root element selector (global scope), e.g. <html> */ :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
  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; }
  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 */ }
  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*/ }
  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 */ }
  30. Usage example: emulating non existing CSS rule

  31. Scopes <div class="block"> My block is <div class="block block__highlight">awesome</div> </div>

    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); }
  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); }
  33. Scope examples /* Global scope (usually <html/>) */ :root {

    --bg: #f00; } /* var is reassigned when media query is applied */ @media screen and (min-width: 800px) { :root { --bg: #f00; } } /* and on body hover is reassigned for <body/> */ /* (but will be the same for <html/> as it's a parent scope) */ body:hover { --bg: #ff0; }
  34. Variables are alive

  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); }
  36. Generate colors from CSS custom properties

  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"}'; }
  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');
  39. None
  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'); }
  41. Why else CSS Custom Props are better?

  42. Color schema switcher based on CSS custom property values Cannot

    be done by a preprocessor without generating additional code
  43. What do we want? Mixins

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

    rule
  45. Custom Property: :root{ /* --property: value; */ --VAR: <declaration-value>; }

    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 ... */ }; }
  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
  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
  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; }
  49. (try in Chrome Canary) CSS Triangle Mixin

  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
  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%); }
  52. What we need? Nesting

  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; }
  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; } } } }
  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; }
  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 } }
  57. What we wish? Modules

  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!
  59. None
  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.
  61. You can easily apply media queries for different stylesheets. Advantages

    for free: Conditional loading /* Formal syntax */ @import [ <string> | <url> ] [<media-query-list>]?; @import url("print.css") print; @import "mobile.css" (max-width: 728px); The linked resources are loaded only when condition is met.
  62. We have Variables, Mixins Nesting and Modules What else we

    want? Selector helpers for complex cases Color functions
  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; }
  64. @custom-selector /* SYNTAX */ @custom-selector: <custom-selector> <selector-list>; 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 }
  65. Color functions /* SYNTAX */ color( <color> <color-adjuster>* ) 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 */ );
  66. None
  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
  68. Media Queries Level 4: Custom media queries /* SYNTAX */

    @custom-media --NAME <media-query-list>; How to use: @custom-media --tablet (min-width: 800px) and (max-width: 1024px); @media (--tablet){ .custom-media-queries{ background-color: red; } }
  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 */ }
  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?
  71. None
  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'] }) ] }, //...
  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
  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.
  75. Any live sites which use all these new CSS features?

    blog.hospodarets.com
  76. None
  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
  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
  79. Thank you! @malyw