Slide 1

Slide 1 text

PostCSS: Build your own CSS processor morishi'er @ #nodefest

Slide 2

Slide 2 text

ࣗݾ঺հ .about-me { name: Masaaki Morishita; twitter: @morishitter_; github: morishitter; qiita: morishitter; specializing-in: CSS; member-of: 'PostCSS Team'; works-at: 'Increments, inc.'; }

Slide 3

Slide 3 text

Increments We are hiring! h"ps:/ /github.com/increments/job-descrip6ons

Slide 4

Slide 4 text

Do you use PostCSS?

Slide 5

Slide 5 text

Agenda 1. PostCSSͱ͸ 2. PostCSS੡πʔϧͷ঺հ 3. ϓϦϓϩηοαʔͱͯ͠ͷPostCSS 4. PostCSSͷAPI

Slide 6

Slide 6 text

Agenda 1. PostCSSͱ͸ 2. PostCSS੡πʔϧͷ঺հ 3. ϓϦϓϩηοαʔͱͯ͠ͷPostCSS 4. PostCSSͷAPI

Slide 7

Slide 7 text

PostCSSͱ͸ • Node੡ͷCSSύʔαʔ • CSSπʔϧΛ࡞ΔͨΊͷϑϨʔϜϫʔΫ • PostCSS੡πʔϧͷ࣮ߦ • by Andrey Sitnik

Slide 8

Slide 8 text

over 12K stars on GitHub !

Slide 9

Slide 9 text

4 million DL / month on npm

Slide 10

Slide 10 text

PostCSSͱ͸ • ޿͘࢖ΘΕΔΑ͏ʹͳͬͨ • CodePenͰ΋࢖͑Δ • Bootstrap v5͸PostCSS੡ʹͳΔ͔΋ʁ • ར༻اۀɾαʔϏε • Google, Facebook, GitHub, Wikipedia, Qiita, etc...

Slide 11

Slide 11 text

PostCSSͷॲཧͷྲྀΕ • PostCSSࣗମ͸ͨͩͷύʔαʔ • ASTΛૢ࡞͢ΔͨΊͷAPIΛఏڙ • ϓϥάΠϯ͕ASTΛม׵͢Δ

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

PostCSSͷΑ͏ͳπʔϧ͸ੲ͔Β͋ͬͨ

Slide 14

Slide 14 text

rework reworkcss / rework

Slide 15

Slide 15 text

Modular CSS preprocessing with rework by TJ Holowaychuk h"p:/ /tjholowaychuk.tumblr.com/post/ 44267035203/modular-css-preprocessing-with- rework

Slide 16

Slide 16 text

Custom CSS preprocessing by Nicolas Gallagher h"p:/ /nicolasgallagher.com/custom-css- preprocessing/

Slide 17

Slide 17 text

rework (in 2013) • Node੡ͷCSSύʔαʔ • ϓϥάΠϯΞʔΩςΫνϟʔ • Andrey͕rework-vendorsͱ͍͏ϓϥάΠϯΛ࡞੒ • ޙͷAutoprefixer • ʢҰํͦͷࠒ morishi:er͸YACPΛ։ൃʣ

Slide 18

Slide 18 text

rework (in 2013) • AutoprefixerΛ࡞Δ্Ͱɺrework͸ػೳෆ଍ • ౰࣌rework͸ϒϥ΢βϋοΫΛύʔεͰ͖ͳ͔ͬͨ • ʢ͋ͱTJʹAutoprefixerΛdisΒΕͨΓ…ʣ • AndreyʮΑࣗ͠෼Ͱ࡞Ζ͏ʂʯ

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

PostCSSʢʙ v1ʣ • 2013/11/4ʹ࠷ॳͷϦϦʔε • "PostCSS is a framework for CSS postprocessors" • AutoprefixerͷͨΊʹ࡞ΒΕͨ • ͜ͷࠒ͸CoffeeScriptͰॻ͔Εͯͨ

Slide 21

Slide 21 text

PostCSS (v2 ʙ v4) • ϓϥάΠϯ͕େྔʹ࡞ΒΕ͍ͯ͘ • ݴޠ֦ுͷͨΊͷϓϥάΠϯ΋࡞ΒΕ͍ͯ͘ • cssnext, PreCSS, AtCSS • ʮpostprocessorʯͱ͍͏୯ޠΛ࢖Θͳ͘ͳͬͨ • ʢAPI͕͜Ζ͜ΖมΘͬͨ…ʣ

Slide 22

Slide 22 text

PostCSS v5 (current) released on August 20, 2015

Slide 23

Slide 23 text

PostCSS v5 • ࠓ·ͰͰҰ൪େ͖͍มߋ • Custom Syntaxes • ύʔαʔ෦෼ΛΧελϚΠζͰ͖ΔΑ͏ʹʢ͋ͱͰઆ໌ʣ • 2016೥ݱࡏɺ޿͘࢖ΘΕΔΑ͏ʹ • ʢrework͸׬શʹΦϫίϯʹʣ

Slide 24

Slide 24 text

1. PostCSSͱ͸ • PostCSS͸ϓϥάΠϯΞʔΩςΫνϟʔͳCSSύʔαʔ • ASTૢ࡞ͷͨΊͷAPIΛఏڙ • ϓϥάΠϯΛॻ͘͜ͱͰCSSʹಠࣗͷॲཧΛ͓͜ͳ͏ • PostCSSͷྺ࢙ • PostCSS͸rework͕ݩʹͳ͍ͬͯΔ

Slide 25

Slide 25 text

Agenda 1. PostCSSͱ͸ 2. PostCSS੡πʔϧͷ঺հ 3. ϓϦϓϩηοαʔͱͯ͠ͷPostCSS 4. PostCSSͷAPI

Slide 26

Slide 26 text

2. PostCSS੡πʔϧͷ঺հ • Autoprefixer • Stylelint • CSS Modules

Slide 27

Slide 27 text

Autoprefixer • postcss / autoprefixer • ʮCan I Useʯͱ͍͏αΠτͷσʔλΛ ݩʹࣗಈͰϕϯμʔϓϦϑΟοΫεΛ ෇༩͢Δπʔϧ

Slide 28

Slide 28 text

/* 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; }

Slide 29

Slide 29 text

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" ] })

Slide 30

Slide 30 text

ྨࣅπʔϧͱͷύϑΥʔϚϯεൺֱ 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

Slide 31

Slide 31 text

Stylelint • stylelint / stylelint • ϞμϯͳCSSϦϯλʔ • ΊͪΌͪ͘ΌΧελϚΠζͰ͖Δ • SCSS, Less, CSSͷ৽͍͠ه๏ʹ΋࢖ ͑Δ

Slide 32

Slide 32 text

Stylelint • ϧʔϧ਺͸໿170ݸ • CSSLint: 38, scss-lint: 62, CSSComb: 26 • શͯͷϧʔϧ͸PostCSSϓϥάΠϯͱ࣮ͯ͠૷ • ಠࣗϧʔϧ΋࡞ΕΔ

Slide 33

Slide 33 text

in Facebook "Improving CSS quality at Facebook and beyond" h"ps:/ /code.facebook.com/posts/ 879890885467584/improving-css-quality-at- facebook-and-beyond/

Slide 34

Slide 34 text

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, ...

Slide 35

Slide 35 text

ઃఆϑΝΠϧूͱΧελϚΠζ • stylelint-config-* • standard, suitcss, primer, qiita, etc... { "extends": "stylelint-config-primer", "rules": { "selector-max-specificity": "0,6,0" } }

Slide 36

Slide 36 text

StylelintͷઃఆϑΝΠϧ • ϑΥʔϚοτʹؔ͢Δ΋ͷ͕େ൒ • color-hex-case • length-zero-no-unit • declaration-block-semicolon-newline-after • block-closing-brace-empty-line-before • "ܯࠂ"Ͱ͸ͳ͘ɺࣗಈमਖ਼ͯ͠΄͍͠

Slide 37

Slide 37 text

Stylefmt • morishi(er / stylefmt • StylelintͷઃఆϑΝΠϧΛݩʹࣗಈͰ ϑΥʔϚοτ • શͯͷϧʔϧʹରԠͰ͖ͯ͸͍ͳ͍

Slide 38

Slide 38 text

stylefmt // input @mixin clearfix () { &::before, &::after{content:" ";display : table; } &::after {clear: both;}} // output @mixin clearfix () { &::before, &::after { content: " "; display: table; } &::after { clear: both; } }

Slide 39

Slide 39 text

CSS Modules • css-modules / css-modules • CSSͷϞδϡʔϧԽͷΞϓϩʔνͷ1ͭ • άϩʔόϧείʔϓ໰୊Λղܾ͢Δ ͨΊͷ΋ͷ

Slide 40

Slide 40 text

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 ( ૹ৴ ); } h"ps:/ /speakerdeck.com/vjeux/react-css-in-js

Slide 41

Slide 41 text

CSS in JSܥπʔϧઓ૪ • Radium, react-style, styling, etc... • CSS ModulesνʔϜʮCSS͸CSSϑΝΠϧʹॻ͖͍ͨʯ

Slide 42

Slide 42 text

CSS Modules • webpackΛ࢖͏͜ͱ͕લఏ • css-loader, style-loader, (postcss-loader) • σϑΥϧτͰϩʔΧϧείʔϓ • ηϨΫλ໊ΛʹϋογϡΛ෇͚ͯিಥΛճආ • composes ͰଞͷίϯϙʔωϯτͷελΠϧΛ࠶ར༻ • ͜ΕΒͷػೳΛPostCSSͰ࣮૷

Slide 43

Slide 43 text

// SubmitButton.js import style from './SubmitButton.css'; export default class SubmitButton extends React.Component { render() { return ( ૹ৴; ); } }; /* SubmitButton.css */ .button { display: inline-block; padding: 6px 12px; font-size: 14px; } .default { composes: button; /* other properties */ }

Slide 44

Slide 44 text

PostCSS.parts h"p:/ /postcss.parts

Slide 45

Slide 45 text

2. PostCSS੡πʔϧͷ঺հ • Autoprefixer • ϕϯμʔϓϦϑΟοΫεͷࣗಈ෇༩ • Stylelint • ESLintͷΑ͏ʹΧελϚΠζੑͷߴ͍CSSϦϯλʔ • CSS Modules • CSSʹϩʔΧϧείʔϓΛ

Slide 46

Slide 46 text

Agenda 1. PostCSSͱ͸ 2. PostCSS੡πʔϧͷ঺հ 3. ϓϦϓϩηοαʔͱͯ͠ͷPostCSS 4. PostCSSͷAPI

Slide 47

Slide 47 text

ϓϦϓϩηοαʔͱͯ͠ͷCSS • ݴޠ֦ு༻ϓϥάΠϯ • cssnext, precss, atcss, etc

Slide 48

Slide 48 text

cssnext • MoOx / postcss-cssnext • ະདྷͷCSSͷߏจΛࠓͷϒϥ΢β͕ղ ऍͰ͖ΔΑ͏ʹτϥϯεύΠϧ͢Δ PostCSSϓϥάΠϯू

Slide 49

Slide 49 text

Custom Proper,es input: :root { --fontSize-m: 18px; } p { font-size: var(--fontSize-m); } output: p { font-size: 18px; }

Slide 50

Slide 50 text

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 */ }

Slide 51

Slide 51 text

Custom selectors input: @custom-selector :--heading h1, h2, h3; article :--heading { margin-bottom: 2em; } output: article h1, article h2, article h3 { margin-bottom: 2em; }

Slide 52

Slide 52 text

@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; }

Slide 53

Slide 53 text

@extend input: .error { color: red; } .serious-error { @extend .error; } output: .error, .serious-error { color: red; }

Slide 54

Slide 54 text

ϓϦϓϩηοαʔͱͯ͠PostCSSΛ࢖͏ϝϦοτ • ඞཁͳػೳ͚ͩΛબ୒Ͱ͖Δ • ΧελϚΠζ͕༰қ • ύϑΥʔϚϯε

Slide 55

Slide 55 text

ඞཁͳػೳ͚ͩΛબ୒ • ʮSass࢖ͬͯΔ͚Ͳ@importͱม਺͚ͩ͋Ε͹͍͍Θʔʯ • ʮmixinͱ͔extend͸؅ཧͰ͖ͳ͍͔Β࢖ͬͯͳ͍ʯ • PostCSSͳΒඞཁͳϓϥάΠϯ͚ͩΛ௥ՃͰ͖Δ

Slide 56

Slide 56 text

ΧελϚΠζ͕༰қ • 1ͭ1ͭͷϓϥάΠϯ͸খ͍͞ͷͰίϯτϦϏϡʔτ΋͠΍͍͢ • ϓϥάΠϯΛॻ͘͜ͱͰಠࣗͷΧελϚΠζ͕Մೳ

Slide 57

Slide 57 text

ύϑΥʔϚϯε 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

Slide 58

Slide 58 text

ϓϦϓϩηοαʔͱͯ͠PostCSSΛ࢖͏ͱ͖ͷ஫ҙ఺ • cssnext • ະདྷͷCSSΛઌऔΓ Ͱ͸ͳ͍ • ϙϦϑΟϧ Ͱ͸ͳ͍ • γϯλοΫεͷॻ͖৺஍͸Θ͔Δ • PreCSS • node-sass࢖͓ʁ

Slide 59

Slide 59 text

ϓϦϓϩηοαʔͱͯ͠PostCSSΛ࢖͏ͱ͖ͷ஫ҙ఺ • ϓϥάΠϯΛબΜͰ࢖͏ͱ͍͍ • ͦͷϓϥάΠϯʹԿ͔͋ͬͨͱ͖͸ͳ͓֮͢ޛ • ϓϥάΠϯಉ࢜ʹ΋ґଘؔ܎͕͋Δ • ʮpostcss-mixins͸postcss-nestedΑΓઌʹಡΈࠐΈ·͠ΐ ͏ʯ

Slide 60

Slide 60 text

3. ϓϦϓϩηοαʔͱͯ͠ͷPostCSS • cssnext: ະདྷͷߏจΛ࢖͑ΔCSSϓϦϓϩηοαʔ • ϝϦοτ • ඞཁͳػೳ͚ͩɺΧελϚΠζੑɺύϑΥʔϚϯε • ஫ҙ఺ • cssnext͸ϙϦϑΟϧ͡Όͳ͍ • ϓϥάΠϯબ୒ͷ೉қ౓͕ߴ͍

Slide 61

Slide 61 text

Agenda 1. PostCSSͱ͸ 2. PostCSS੡πʔϧͷ঺հ 3. ϓϦϓϩηοαʔͱͯ͠ͷPostCSS 4. PostCSSͷAPI

Slide 62

Slide 62 text

PostCSSͷAPI • ASTͷߏ଄ • ϓϥάΠϯΛ࡞ΔͨΊͷ͍͔ͭ͘ͷAPIΛ঺հ • Custom Syntaxes h"p:/ /api.postcss.org/

Slide 63

Slide 63 text

͜͏͍͏ม׵Λ͢ΔϓϥάΠϯΛॻ͍ͯΈΔ /* input */ .foo { font-size: 12px; } .bar { @ref .foo, font-size; color: #333; } /* expected */ .foo { font-size: 12px; } .bar { font-size: 12px; color: #333; }

Slide 64

Slide 64 text

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' } ] }

Slide 65

Slide 65 text

ASTͷϊʔυ

Slide 66

Slide 66 text

ASTૢ࡞ͷAPI • postcss.plugin() • container.walk(Rule|AtRule|Decls)() • container.insert(Before|After)() • container.remove()

Slide 67

Slide 67 text

postcss.plugin(name, initializer) /* * @param {string} name * @param {function} initializer * @return {Plugin} PostCSS plugin */

Slide 68

Slide 68 text

postcss.plugin(name, initializer) module.exports = postcss.plugin('postcss-ref', function (opts) { opts = opts || {}; return function (root) { // ASTΛม׵͢Δॲཧ }; });

Slide 69

Slide 69 text

container.walkAtRule([name], callback) /* * @param {string|RegExp} [name] * @param {childIterator} callback * @return {false|undefined} */ • Container: ਌ϊʔυʢࢠϊʔυΛ࣋ͭ͜ͱ͕Ͱ͖ΔʣΛੜ੒ ͢ΔΫϥε

Slide 70

Slide 70 text

container.walkAtRule([name], callback) module.exports = postcss.plugin('postcss-ref', function (opts) { opts = opts || {}; return function (root) { root.walkAtRules('ref', function (atrule) { // `@ref`ͷAtRuleϊʔυʹରͯ͠ͷॲཧ } }; });

Slide 71

Slide 71 text

container.walkRule([selector], callback) /* * @param {string|RegExp} [selector] * @param {childIterator} callback * @return {false|undefined} */

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

container.walkDecls([prop], callback) /* * @param {string|RegExp} [prop] * @param {childIterator} callback * @return {false|undefined} */

Slide 74

Slide 74 text

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' }); }); }

Slide 75

Slide 75 text

container.insertBefore(exist, add) /* * @param {Node|number} exist * @param {Node|object|string|Node[]} * @return {Node} */

Slide 76

Slide 76 text

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 }); } }

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

׬੒ morishi'er / postcss-ref

Slide 79

Slide 79 text

Custom Syntaxes • PostCSS v5ʙͷػೳ • จࣈྻ͔ΒPostCSSͷASTΛ૊ΈཱͯΔϓϩάϥϜ • CSSҎ֎ͷߏจͷύʔεՄೳʹ • PostCSSͷASTʹ͑͞ม׵Ͱ͖Ε͹طଘͷϓϥάΠϯΛద༻Ͱ͖ Δ

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

Custom Syntaxes • postcss-scss, postcss-less • SCSS, Less༻ͷPostCSS Syntaxes • PostCSSͰSCSS, LessϑΝΠϧΛCSSʹίϯύΠϧͰ͖ΔΑ͏ ʹͳΔΘ͚Ͱ͸ͳ͍ • SCSS, Lessίʔυʹରͯ͠ɺม׵ॲཧΛ͢Δ͜ͱ͕Ͱ͖Δ

Slide 82

Slide 82 text

Custom Syntaxes • postcss-js • CSS in JSͷͨΊͷ΋ͷ • JSΦϒδΣΫτΛPostCSS ASTʹม׵ • Radium౳Λ࢖͍ͬͯͯ΋PostCSSͷϓϥάΠϯΛ࢖͑Δ

Slide 83

Slide 83 text

Custom Syntaxes • Φϓγϣϯͷ௥Ճ • parser: ύʔαʔͷࢦఆ • stringifier: Ξ΢τϓοτͷ࢓ํ • syntax: parser + stringifier var scss = postcss-scss; postcss.process([ plugins]).process(source, { syntax: scss });

Slide 84

Slide 84 text

4. PostCSSͷAPI • ASTͷߏ଄ • Root, AtRule, Rule, Declara1on, Comment • ASTૢ࡞ͷAPI • h8p:/ /api.postcss.org • Custom Syntaxes • CSSҎ֎ͷߏจ΋PostCSS ASTʹม׵

Slide 85

Slide 85 text

Agenda 1. PostCSSͱ͸ 2. PostCSS੡πʔϧͷ঺հ 3. ϓϦϓϩηοαʔͱͯ͠ͷPostCSS 4. PostCSSͷAPI

Slide 86

Slide 86 text

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, ...

Slide 87

Slide 87 text

ղܾ͍ͨ͠໰୊͸͍ͭ΋ಉ͡ ಓ۩ʹৼΓճ͞ΕΔͳ

Slide 88

Slide 88 text

PostCSS͸ΤίγεςϜͱͯ͠ྑ͘Ͱ͖͍ͯΔ PostCSS is Awesome!

Slide 89

Slide 89 text

Thanks :D @morishi(er_