/red-perfume

Experimental CSS Atomizer

Primary LanguageCSSMIT LicenseMIT

Red Perfume

Build Status Coverage Badge Lint Coverage: 100% Code of Conduct: No Ideologies MIT Licensed

Feedback

Leave feedback as an issue or a response on Twitter.

Star and Watch this repo for updates.

Or follow me on Twitter if that's easier.

Experimental CSS Atomizer (WIP)

This is a library for a build tool that helps to drastically reduce the total amount of CSS that is shipped for your project. Facebook adopted this atomized CSS approach and it reduced their homepage CSS by 80%. Twitter also atomizes their CSS.

With red-perfume you write your CSS however you like (semantic class names, BEM, utility classes, whatever). Then reference them in your HTML normally.

Example:

.cow,
.cat {
    font-size: 12px;
    padding: 8px;
}
.dog {
    font-size: 12px;
    background: #F00;
    padding: 8px;
}
<!DOCTYPE html>
<html>
  <head>
    <title>Test</title>
  </head>
  <body>
    <p class="cool cow moo">
      Hi there!
    </p>
    <!--
      <span class="dog">comments are skipped</span>
    -->
    <h1 class="cool cat nice wow">
      Meow
    </h1>
    <h2 class="dog">
      Woof
    </h2>
  </body>
</html>

Then red-perfume atomizes the styling into atomic classes, and replaces the references to them:

.rp__font-size__--COLON12px {
  font-size: 12px;
}
.rp__padding__--COLON8px {
  padding: 8px;
}
.rp__background__--COLON__--OCTOTHORPF00 {
  background: #F00;
}
<!DOCTYPE html>
<html>
  <head>
    <title>Test</title>
  </head>
  <body>
    <p class="cool moo rp__font-size__--COLON12px rp__padding__--COLON8px">
      Hi there!
    </p>
    <!--
      <span class="dog">comments are skipped</span>
    -->
    <h1 class="cool nice wow rp__font-size__--COLON12px rp__padding__--COLON8px">
      Meow
    </h1>
    <h2 class="rp__font-size__--COLON12px rp__background__--COLON__--OCTOTHORPF00 rp__padding__--COLON8px">
      Woof
    </h2>
  </body>
</html>

This output isn't as pretty to read, but it's a build step, not your source code, so it doesn't really matter. Note: Uglification of classnames is on the TODO list.

This above example already works as a proof of concept with the current code. However, the library needs a lot more work to be usable in most projects. Look at the issues page to see what work is left to be done and how you can help!

API (subject to change before v1.0.0)

This is my first draft of the API.

const redPerfume = require('red-perfume');

redPerfume.atomize({
  verbose: true,
  customLogger: function (message, err) {
    console.log(message, err);
  },
  tasks: [
    {
      uglify: false,
      styles: {
        in: [
          './styles/file.css',
          './styles/style.css'
        ],
        // The two above files will be atomized and combined into this output
        out: './dist/styles/styles.css'
      },
      // The output markup will be a copy of the input but modified to have the class names replaced to match the new atomized styles from styles.out
      markup: [
        {
          in: './index.html',
          out: './dist/index.html'
        },
        {
          in: './contact.html',
          out: './dist/contact.html'
        }
      ],
      scripts: {
        out: './dist/atomic-styles.json'
      }
    },
    {
      uglify: true,
      styles: {
        data: '.example { padding: 10px; margin: 10px; }',
        result: function (result, err) {
          // .rp__a { padding: 10px } .rp__b { margin: 10px }
          console.log(result);
        }
      },
      markup: [
        {
          data: '<!DOCTYPE html><html><body><div class="example"></div></body></html>',
          result: function (result, err) {
            // '<!DOCTYPE html><html><body><div class="rp__a rp__b"></div></body></html>'
            console.log(result);
          }
        }
      ],
      scripts: {
        result: function (result, err) {
          // { '.example': [ '.rp__a', '.rp__b' ] }
          console.log(result);
        }
      }
    }
  ]
});
Key Type Allowed Default Description
verbose Boolean true, false true If true, consoles out helpful warnings and errors using customLogger or console.error.
customLogger Function Any function console.error You can pass in your own custom function to log errors/warnings to. When called the function will receive a message string for the first argument and sometimes an error object for the second argument. This can be useful in scenarios like adding in custom wrappers or colors in a command line/terminal. This function may be called multiple times before all tasks complete. Only called if verbose is true.
tasks Array Array of objects undefined An array of task objects. Each represents the settings for an atomization task to be performed.

Tasks API:

Key Type Default Description
uglify Boolean false If false the atomized classes, and all references to them, are long (.rp__padding__--COLOR12px). If true they are short (.rp__b5p).
styles Object undefined CSS settings. API below
markup Array undefined HTML settings. An array of objects with their API defined below
scripts Object undefined JS settings. API below

Styles Task API:

Key Type Default Description
in Array undefined An array of strings to valid paths for CSS files. All files will remain untouched. A new atomized string is produced for out/result.
out String undefined A string file path output. If file exists it will be overwritten with the atomized styles from in and/or data
data String undefined A string of CSS to be atomized. Atomized styles are passed to out and result.
result Function undefined A function, if supplied, will be called with the combined atomized results from data and the files form in as the first argument, and an error for the second argument. You can create a promise in this function to be returned if you like.

Markup Task API:

Key Type Default Description
in String undefined Path to an HTML file.
out String undefined Path where the modified version of the in file will be stored. If file already exists, it will be overwritten.
data String undefined A string of markup to be processed. This is ignored there is a valid HTML file passed to in
result Function undefined A function, if supplied, will be called with the processed string version of the markup from in or data as the first argument, and an error for the second argument. You can create a promise in this function to be returned if you like.

Scripts Task API:

Key Type Default Description
out String undefined Path where a JSON object will be stored. The object contains keys (selectors) and values (array of strings of atomized class names). If file already exists, it will be overwritten.
result Function undefined A function, if supplied, will be called with the JSON object as first argument, and an error for the second argument. You can create a promise in this function to be returned if you like.

Running locally to see the proof of concept or contribute

  1. Install Node.js & npm
  2. Download or fork or clone the repo
  3. npm install
  4. node index.js

Why is it called "Red Perfume"

This library takes in any CSS and breaks it down to pure Atomic CSS. This is a process called "CSS Atomization", and libraries that do this process are called "CSS Atomizers".

Outside of our industry jargon, "Atomizer" already exists as a word.

Atomizer (NOUN)

  1. A device for emitting water, perfume, or other liquids as a fine spray.

- Oxford English Dictionary

Though actual atomizers themselves have no consistent size, design, color, or shape. So there is no iconic image that represents them.

Example of several atomizers of differnt size, shape, color and design

And though perfume bottles can also come in many shapes, colors, sizes and designs, they are still recognizable as perfume bottles.