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

PostCSS: Build your own CSS processor

PostCSS: Build your own CSS processor

at NodeFest 2016.

Masaaki Morishita

November 13, 2016
Tweet

More Decks by Masaaki Morishita

Other Decks in Technology

Transcript

  1. ࣗݾ঺հ .about-me { name: Masaaki Morishita; twitter: @morishitter_; github: morishitter;

    qiita: morishitter; specializing-in: CSS; member-of: 'PostCSS Team'; works-at: 'Increments, inc.'; }
  2. PostCSSʢʙ v1ʣ • 2013/11/4ʹ࠷ॳͷϦϦʔε • "PostCSS is a framework for

    CSS postprocessors" • AutoprefixerͷͨΊʹ࡞ΒΕͨ • ͜ͷࠒ͸CoffeeScriptͰॻ͔Εͯͨ
  3. PostCSS (v2 ʙ v4) • ϓϥάΠϯ͕େྔʹ࡞ΒΕ͍ͯ͘ • ݴޠ֦ுͷͨΊͷϓϥάΠϯ΋࡞ΒΕ͍ͯ͘ • cssnext,

    PreCSS, AtCSS • ʮpostprocessorʯͱ͍͏୯ޠΛ࢖Θͳ͘ͳͬͨ • ʢAPI͕͜Ζ͜ΖมΘͬͨ…ʣ
  4. /* input */ .example { display: flex; user-select: none; -webkit-border-radius:

    3px; border-radius: 3px; } /* output */ .example { display: -ms-flexbox; display: flex; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; border-radius: 3px; }
  5. AutoprefixerͷΦϓγϣϯ • αϙʔτϒϥ΢βࢦఆ (ai / browserslist) autoprefixer({ browsers: [ "ie

    >= 11", "last 2 Edge versions", "last 2 Firefox versions", "last 2 Chrome versions", "last 2 Safari versions", "last 2 Opera versions", "last 2 iOS versions", "last 2 ChromeAndroid versions" ] })
  6. ྨࣅπʔϧͱͷύϑΥʔϚϯεൺֱ Autoprefixer: 44 ms Stylecow: 200 ms (4.5 times slower)

    nib: 340 ms (7.7 times slower) Compass: 2417 ms (54.9 times slower) h"ps:/ /github.com/postcss/benchmark#prefixers
  7. Stylelint • ϧʔϧ਺͸໿170ݸ • CSSLint: 38, scss-lint: 62, CSSComb: 26

    • શͯͷϧʔϧ͸PostCSSϓϥάΠϯͱ࣮ͯ͠૷ • ಠࣗϧʔϧ΋࡞ΕΔ
  8. in Facebook "Improving CSS quality at Facebook and beyond" h"ps:/

    /code.facebook.com/posts/ 879890885467584/improving-css-quality-at- facebook-and-beyond/
  9. StylelintͷઃఆϑΝΠϧ { "rules": { "at-rule-no-vendor-prefix": true, "block-no-empty": true, "color-hex-case": "lower",

    "color-hex-length": "short", "color-no-invalid-hex": true, "comment-no-empty": true, "custom-property-no-outside-root": true, "declaration-block-no-duplicate-properties": true, "declaration-block-no-ignored-properties": true, "declaration-block-no-shorthand-property-overrides": true, "declaration-no-important": true, "selector-descendant-combinator-no-non-space": true, "selector-max-empty-lines": 0, "selector-max-specificity": "0,5,0", "selector-no-vendor-prefix": true, ...
  10. ઃఆϑΝΠϧूͱΧελϚΠζ • stylelint-config-* • standard, suitcss, primer, qiita, etc... {

    "extends": "stylelint-config-primer", "rules": { "selector-max-specificity": "0,6,0" } }
  11. stylefmt // input @mixin clearfix () { &::before, &::after{content:" ";display

    : table; } &::after {clear: both;}} // output @mixin clearfix () { &::before, &::after { content: " "; display: table; } &::after { clear: both; } }
  12. CSS in JS • ʮReact: CSS in JSʯby Christopher Chedeau

    render() { const style = { display: 'inline-block', padding: '8px 12px', backgroundColor: '#0099ff', color: '#fff', fontSize: '14px', textAlign: 'center' }; return ( <button style={style}>ૹ৴</button> ); } h"ps:/ /speakerdeck.com/vjeux/react-css-in-js
  13. CSS Modules • webpackΛ࢖͏͜ͱ͕લఏ • css-loader, style-loader, (postcss-loader) • σϑΥϧτͰϩʔΧϧείʔϓ

    • ηϨΫλ໊ΛʹϋογϡΛ෇͚ͯিಥΛճආ • composes ͰଞͷίϯϙʔωϯτͷελΠϧΛ࠶ར༻ • ͜ΕΒͷػೳΛPostCSSͰ࣮૷
  14. // SubmitButton.js import style from './SubmitButton.css'; export default class SubmitButton

    extends React.Component { render() { return ( <button className={style.default}>ૹ৴</button>; ); } }; /* SubmitButton.css */ .button { display: inline-block; padding: 6px 12px; font-size: 14px; } .default { composes: button; /* other properties */ }
  15. Custom Proper,es input: :root { --fontSize-m: 18px; } p {

    font-size: var(--fontSize-m); } output: p { font-size: 18px; }
  16. Custom media queries input: @custom-media --small-viewport (max-width: 600px); @media (--small-viewport)

    { /* styles for small viewport */ } output: @media (max-width: 600px) { /* styles for small viewport */ }
  17. Custom selectors input: @custom-selector :--heading h1, h2, h3; article :--heading

    { margin-bottom: 2em; } output: article h1, article h2, article h3 { margin-bottom: 2em; }
  18. @apply input: :root { --truncate: { white-space: nowrap; overflow: hidden;

    text-overflow: ellipsis; } } p { @apply --truncate; } output: p { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  19. @extend input: .error { color: red; } .serious-error { @extend

    .error; } output: .error, .serious-error { color: red; }
  20. ύϑΥʔϚϯε cssnext: 45 ms PreCSS: 64 ms (1.4 times slower)

    node-sass: 65 ms (1.4 times slower) Rework: 83 ms (1.8 times slower) Less: 146 ms (3.2 times slower) Stylus: 219 ms (4.7 times slower) Ruby Sass: 1207 ms (26.2 times slower) h"ps:/ /github.com/postcss/benchmark#preprocessors
  21. ͜͏͍͏ม׵Λ͢ΔϓϥάΠϯΛॻ͍ͯΈΔ /* input */ .foo { font-size: 12px; } .bar

    { @ref .foo, font-size; color: #333; } /* expected */ .foo { font-size: 12px; } .bar { font-size: 12px; color: #333; }
  22. ASTͷϊʔυʢҰ෦ൈਮʣ Root { type: 'root', nodes: [ Rule { type:

    'rule', nodes: [ Declaration { type: 'decl', prop: 'font-size', value: '12px' } ], selector: '.foo' }, Rule { type: 'rule', nodes: [ AtRule { type: 'atrule', name: 'ref', params: '.foo, font-size' }, Declaration { type: 'decl', prop: 'color', value: '#333' } ], selector: '.bar' } ] }
  23. postcss.plugin(name, initializer) module.exports = postcss.plugin('postcss-ref', function (opts) { opts =

    opts || {}; return function (root) { // ASTΛม׵͢Δॲཧ }; });
  24. container.walkAtRule([name], callback) /* * @param {string|RegExp} [name] * @param {childIterator}

    callback * @return {false|undefined} */ • Container: ਌ϊʔυʢࢠϊʔυΛ࣋ͭ͜ͱ͕Ͱ͖ΔʣΛੜ੒ ͢ΔΫϥε
  25. container.walkAtRule([name], callback) module.exports = postcss.plugin('postcss-ref', function (opts) { opts =

    opts || {}; return function (root) { root.walkAtRules('ref', function (atrule) { // `@ref`ͷAtRuleϊʔυʹରͯ͠ͷॲཧ } }; });
  26. container.walkRule([selector], callback) root.walkAtRules('ref', function (atrule) { var selector; // =

    '.foo' root.walkRules(selector, function (rule) { // .foo ηϨΫλͷRuleϊʔυʹରͯ͠ͷॲཧ }); }
  27. container.walkDecls([prop], callback) root.walkAtRules('ref', function (atrule) { var selector; // =

    '.foo' var refedProperty; // = 'font-size' var newValue; root.walkRules(selector, function (rule) { rule.walkDecls(refedProperty, function (decl) { newValue = decl.value; // = '12px' }); }); }
  28. container.insertBefore(exist, add) root.walkAtRules('ref', function (atrule) { root.walkRules(selector, function (rule) {

    // }); if (atrule.parent.type === 'rule') { atrule.parent.insertBefore(atrule, { prop: newProperty, value: newValue }); } }
  29. container.remove() /* * @return {Node} */ root.walkAtRules('ref', function (atrule) {

    if (atrule.parent.type === 'rule') { atrule.parent.insertBefore(atrule, { prop: newProperty, value: newValue }); } atrule.remove(); }
  30. Custom Syntaxes • postcss-scss, postcss-less • SCSS, Less༻ͷPostCSS Syntaxes •

    PostCSSͰSCSS, LessϑΝΠϧΛCSSʹίϯύΠϧͰ͖ΔΑ͏ ʹͳΔΘ͚Ͱ͸ͳ͍ • SCSS, Lessίʔυʹରͯ͠ɺม׵ॲཧΛ͢Δ͜ͱ͕Ͱ͖Δ
  31. Custom Syntaxes • postcss-js • CSS in JSͷͨΊͷ΋ͷ • JSΦϒδΣΫτΛPostCSS

    ASTʹม׵ • Radium౳Λ࢖͍ͬͯͯ΋PostCSSͷϓϥάΠϯΛ࢖͑Δ
  32. Custom Syntaxes • Φϓγϣϯͷ௥Ճ • parser: ύʔαʔͷࢦఆ • stringifier: Ξ΢τϓοτͷ࢓ํ

    • syntax: parser + stringifier var scss = postcss-scss; postcss.process([ plugins]).process(source, { syntax: scss });
  33. 4. PostCSSͷAPI • ASTͷߏ଄ • Root, AtRule, Rule, Declara1on, Comment

    • ASTૢ࡞ͷAPI • h8p:/ /api.postcss.org • Custom Syntaxes • CSSҎ֎ͷߏจ΋PostCSS ASTʹม׵
  34. CSSपลπʔϧɺΞʔΩςΫνϟʔ • PostCSS, rework • cssnext, PreCSS, Sass, Less, Stylus,

    ... • Radium, react-style, styled-components, css-modules, ... • Stylelint, SCSS Lint, CSS Lint, CSSComb, Stylefmt, ... • OOCSS, SMACSS, ITCSS, BEM, ECSS, Atomic CSS, ... • Bootstrap, FoundaGon, BASSCSS, Tachyons, ...