Плагин к PostCSS для замены цвета в стилях

Владимир Кузнецов (Хантфлоу)

Плагин к PostCSS для замены цвета в стилях

Владимир Кузнецов
(Хантфлоу)

bit.ly/fc18-pp-src

PostCSS преобразует CSS в AST

PostCSS конвертирует CSS в AST и обратно

Плагины PostCSS изменяют AST

Плагины PostCSS меняют AST

Autoprefixer

Плагины PostCSS

  • postcss-nested
  • postcss-custom-properties
  • pixrem
  • postcss-unmq
  • postcss-hash-classname
  • postcss-namespace
  • postcss-assets-rebase
  • postcss-cachebuster
  • postcss-svg
  • postcss-at2x
  • postcss-bem-to-js
  • postcss-click

Плагины PostCSS

  • postcss-circle
  • postcss-triangle
  • postcss-clearfix
  • postcss-focus
  • postcss-aspect-ratio
  • postcss-zindex
  • postcss-conditionals
  • postcss-define-property
  • postcss-each
  • postcss-for
  • stylelint

Нет подходящего плагина?

  1. Смириться.

api.postcss.org

Обход дерева

Типы узлов

  • rule (div, .list)
  • declaration (color, background)
  • @rule (media, charset)
  • comment

Методы обхода

  • walkRules()
  • walkDecls()
  • walkAtRules()
  • walkComments()
  • walk()
        #main, div.sidebar {
          background: white;
          color: rgb(47, 79, 79);
        }
    
        { type: "root", nodes: [
          { type: "rule", selector: "#main, div.sidebar", nodes: [
            { type: "decl", prop: "background", value: "white" },
            { type: "decl", prop: "color", value: "rgb(47, 79, 79)" }
          ] }
        ] }
    

AST explorer

Постановка
задачи

postcss-alter-color

{ from: 'darkslategray', to: '#556832' }
p { color: darkslategray; }
a { color: #2F4F4F; }
.quote {
  border-left: 1px solid rgb(47, 79, 79);
  border-right: solid hsl(180, 25%, 25%) 1px;
}

postcss-alter-color

{ from: 'darkslategray', to: '#556832' }
p { color: darkslategray; }
a { color: #2F4F4F; }
.quote {
  border-left: 1px solid rgb(47, 79, 79);
  border-right: solid hsl(180, 25%, 25%) 1px;
}

postcss-alter-color

{ from: 'darkslategray', to: '#556832' }
p { color: #556832; }
a { color: #556832; }
.quote {
  border-left: 1px solid #556832;
  border-right: solid #556832 1px;
}
        function plugin(options) {
          return function (root) {
            root.walkDecls(function (decl) {
              if (decl.value === options.from)
                decl.value = options.to;
            });
          };
        }
        module.exports = postcss.plugin('alter-color', plugin);
    

Варианты записи цвета

Сплошные цвета

  • white
  • #333
  • #556832
  • rgb(47, 79, 79)
  • hsl(180, 25%, 25%)

parse-color

{ rgb: [ 255, 165, 0 ],
  hsl: [ 39, 100, 50 ],
  keyword: 'orange',
  hex: '#ffa500',
  rgba: [ 255, 165, 0, 1 ],
  hsla: [ 39, 100, 50, 1 ] }

Разбор значения узла decl

#main { border: 1px solid #2F4F4F }
{
  type: "decl",
  prop: "border",
  value: "1px solid #2F4F4F"
}
Идея!

CSSTree

        root.walkDecls(decl => {
          const parsedValue = cssTree.parse(
            decl.value,
            { context: 'value' }
          );
          // тут можно модифицировать parsedValue
          decl.value = cssTree.generate(parsedValue);
        });
    
Волчок
"1px solid #2F4F4F"
        { type: "Value", children: [
          { type: "Dimension", value: 1, unit: "px" },
          { type: "WhiteSpace", value: " " },
          { type: "Identifier", name: "solid" },
          { type: "WhiteSpace", value: " " },
          { type: "HexColor", value: "2F4F4F" }
        ] }
    
"darkslategray"
        { type: "Value", children: [
          { type: "Identifier", name: "darkslategray" }
        ] }
    
rgb(47, 79, 79)
        { type: "Value", children: [
          { type: "Function", name: "rgb", "children": [
            { type: "Number", value: "47"},
            { type: "Operator", value: ","},
            { type: "Number", value: "79"},
            { type: "Operator", value: ","},
            { type: "Number", value: "79"}
          ] }
        ] }
    

Типы узлов CSSTree, которые могут содержать цвет

Документация по обходу AST CSSTree

        function identifierVisitor(node, item, list) {
          if (node.name === 'darkslategray') {
            // изменяем node
          }
        }
        cssTree.walk(parsedValue, {
          visit: 'Identifier',
          enter: identifierVisitor
        });
    
        function identifierVisitor(node, item, list) {
          if (node.name === 'darkslategray') {
            const data = cssTree.fromPlainObject({
              type: 'HexColor',
              value: '556832'
            });
            list.replace(item, list.createItem(data));
          }
        }
    
        cssTree.walk(parsedValue, {
          visit: 'HexColor',
          enter: hexColorVisitor
        });
        cssTree.walk(parsedValue, {
          visit: 'Function',
          enter: functionVisitor
        });
    

rgb(), rgba(), hsl() и hsla()

        { type: "Value", children: [
          { type: "Function", name: "rgb", "children": [
            { type: "Number", value: "47"},
            { type: "Operator", value: ","},
            { type: "Number", value: "79"},
            { type: "Operator", value: ","},
            { type: "Number", value: "79"}
          ] }
        ] }
    
        { type: "Value", children: [
          { type: "Function", name: "rgb", "children": [
            { type: "Percentage", value: "18.4%"},
            { type: "Operator", value: ","},
            { type: "Percentage", value: "30.98%"},
            { type: "Operator", value: ","},
            { type: "Percentage", value: "30.98%"}
          ] }
        ] }
    

postcss-alter-color

  1. Познакомились с AST explorer.
  2. Разобрались с API PostCSS и CSSTree.
  3. Написали плагин, который делает больше, чем просто поиск и замена в текстовом редакторе.

Владимир Кузнецов

@mistakster (English)
@mista_k (больше про жизнь)

noteskeeper.ru

Слайды презентации: bit.ly/fc18-pp