Make CSS easier and more maintainable by using JavaScript
TypeScript
Latest commit f6ca563 Dec 14, 2016 @blakeembrey v2.2.0

README.md

Free-Style

NPM version NPM downloads Build status Test coverage

Free-Style is designed to make CSS easier and more maintainable by using JavaScript.

Installation

npm install free-style --save

Why?

There's a great presentation by Christopher Chedeau you should check out.

Solved by using CSS in JS

  • No global variables (Where and what is .button? Why does it conflict?)
  • Built in dependency system (CommonJS, Require.js, <script />)
  • Dead code elimination (Automatically remove unused styles)
  • Minification (Minify JS with existing tools)
  • Shared constants and reusable styles (Using variables and objects)
  • Isolation (Every style is automatically namespaced)
  • Extensible (Just use JavaScript - everything from math to color manipulation already exists!)

Also solved with Free-Style

  • Working with legacy DOM components (You can nest .class-name in your styles)
  • Expose third-party and semantic hooks/theming through ordinary class names (.button)
  • Consistently generates styles and class names (Generates the exact same on client and server, and will magically merges duplicate styles)
  • Develop components alongside the style (No more hunting CSS files for estranged ul > li > a)
  • Create isomorphic applications by serving styles for only the components rendered (With third-parties, see React Free-Style)
  • Continue using CSS you already know ({ '&:hover': { ... } })
  • Automatically namespace @-rule styles ({ '@media (min-width: 500px)': { ... } })
  • Overload CSS properties using arrays ({ backgroundColor: ['red', 'linear-gradient(to right, red 0%, blue 100%)'] })
  • Integrates with any third-party system
  • Extremely small and powerful API that works with any ecosystem (~360 SLOC)

But How?

Free-Style generates a consistent hash from the style, after alphabetical property ordering and formatting, to use as the class name. This allows duplicate styles to automatically be merged on duplicate hashes. Every style is "registered" and assigned to a variable, which gets the most out of linters that will warn on unused variables and features like dead code minification. Using "register" returns the class name used for the Style instance and style instances (returned by create()) can be merged together at runtime to output only the styles on page (see React Free-Style). Styles should usually be created outside of the application run loop (E.g. render) so the CSS string and hashes are only generated once.

Ways to Use

  • stylin - The simplest abstraction, create styles, rules and keyframes, and the <style /> stays in sync.
  • easy-style - Light-weight singleton API for browsers and node
  • react-free-style - React implementation that renders styles used on the current page (universal use-case)
  • This module! - Create, compose and manipulate style instances

Usage

var FreeStyle = require('free-style')

// Create a stylesheet instance.
var Style = FreeStyle.create()

// Register a new style, returning a class name to use.
var backgroundStyle = Style.registerStyle({
  backgroundColor: 'red'
}) //=> "f14svl5e"

// Inject `<style>` into the `<head>`.
var styleElement = document.createElement('style')
styleElement.textContent = Style.getStyles()
document.head.appendChild(styleElement)

// Render the style by using the class name.
React.render(
  <div className={backgroundStyle}>Hello world!</div>,
  document.body
)

Styles

var buttonStyle = Style.registerStyle({
  backgroundColor: 'red',
  padding: 10
})

console.log(buttonStyle) //=> "f65pi0b"

Tip: The string returned by registerStyle is a unique hash of the content and should be used as a class in HTML.

Overload CSS Properties

Style.registerStyle({
  background: [
    'red',
    '-moz-linear-gradient(left, red 0%, blue 100%)',
    '-webkit-linear-gradient(left, red 0%, blue 100%)',
    '-o-linear-gradient(left, red 0%, blue 100%)',
    '-ms-linear-gradient(left, red 0%, blue 100%)',
    'linear-gradient(to right, red 0%, blue 100%)'
  ]
}) //=> "f1n85iiq"

Nest Rules

Style.registerStyle({
  color: 'red',
  '@media (min-width: 500px)': {
    color: 'blue'
  }
}) //=> "fk9tfor"

Nest Selectors

Style.registerStyle({
  '.classname': {
    color: 'blue'
  }
}) //=> "fc1zv17"

Parent Selector Reference

Style.registerStyle({
  '&:hover': {
    color: 'blue'
  }
}) //=> "f1h42yg6"

Tip: The ampersand (&) will be replaced by the parent selector at runtime. In this example, the result is .f1h42yg6:hover.

Tip: The second argument to registerStyle and registerKeyframes is a "display name". The display name will be used as the class name prefix in development (process.env.NODE_ENV !== 'production').

Use JavaScript

var extend = require('xtend')

var ellipsisStyle = {
  whiteSpace: 'nowrap',
  overflow: 'hidden',
  textOverflow: 'ellipsis'
}

var redEllipsisStyle = Style.registerStyle(extend(
  {
    color: 'red'
  },
  ellipsisStyle
)) //=> "fvxl8qs"

Tip: This is a shallow extend example. There are modules on NPM for deep extending objects. You can also take advantage of new JavaScript features, such as const and computed properties:

const mediaQuery = '@media (min-width: 400px)'

const style = Style.registerStyle({
  backgroundColor: 'red',
  [mediaQuery]: {
    backgroundColor: 'pink'
  }
})

Unique Style Ouput

Sometimes you need to skip the default de-duping behaviour of free-style. For this, you can use IS_UNIQUE and enforce every style to be output separately:

Style.registerStyle({
  color: 'blue',
  '&::-webkit-input-placeholder': {
    color: `rgba(0, 0, 0, 0)`,
    [IS_UNIQUE]: true
  },
  '&::-moz-placeholder': {
    color: `rgba(0, 0, 0, 0)`,
    [IS_UNIQUE]: true
  },
  '&::-ms-input-placeholder': {
    color: `rgba(0, 0, 0, 0)`,
    [IS_UNIQUE]: true
  }
}) //=> "f13byakl"

Style.getStyles() //=> ".f13byakl{color:blue}.f13byakl::-webkit-input-placeholder{color:rgba(0, 0, 0, 0)}.f13byakl::-moz-placeholder{color:rgba(0, 0, 0, 0)}.f13byakl::-ms-input-placeholder{color:rgba(0, 0, 0, 0)}"

Keyframes

var colorAnimation = Style.registerKeyframes({
  from: { color: 'red' },
  to: { color: 'blue' }
}) //=> "h1j3ughx"

var style = Style.registerStyle({
  animationName: colorAnimation,
  animationDuration: '1s'
}) //=> "fibanyf"

Tip: The string returned by registerKeyframes the name of the animation, which is a hash of the rule (you can also add a "display name" in development!).

Rules

Style.registerRule('@font-face', {
  fontFamily: '"Bitstream Vera Serif Bold"',
  src: 'url("https://mdn.mozillademos.org/files/2468/VeraSeBd.ttf")'
})

Style.registerRule('@media print', {
  body: {
    color: 'red'
  }
})

Style.registerRule('body', {
  margin: 0,
  padding: 0
})

Tip: Interpolation of parent selectors (& => .generatedName) is not supported with registerRule.

CSS String

Style.getStyles() //=> ".f65pi0b{background-color:red;padding:10px}"

Useful Libraries

Custom Hash Algorithm

Initialize Free-Style with a custom CSS class hash algorithm (by default it uses a simple, internal string hash).

create(hashFunction) //=> `FreeStyle.FreeStyle`

Classes

FreeStyle.FreeStyle // Similar to writing a CSS file, holds styles and rules - returned from `create()`.
FreeStyle.Style // Styles hold the CSS string and a generate a consistent hash of their contents.
FreeStyle.Rule // Rules are lighter-weight containers that can be nested inside `FreeStyle` instances.
FreeStyle.Selector // Selectors hold the CSS selector and can be nested inside `Style` instances.
FreeStyle.Cache // `FreeStyle`, `Style` and `Rule` all extend the cache which maintains reference counts.

Other Properties and Methods

var ChildStyle = Style.create()

// The `changeId` property increments when a new style is inserted. The allows implementors to skip style
// element updates when styles are inserted twice, as the `changeId` property would remain the same.
ChildStyle.changeId

// Clones the style instance. This is useful when adding/removing or merging/unmerging styles. If you don't clone
// the instance beforehand, it's possible for a user to modify the style state with new styles/selectors, and the
// next time you unmerge/remove the instance will be inconsistent with the original.
ChildStyle.clone()

Style.merge(ChildStyle) // Merge the child styles into the current instance.
Style.unmerge(ChildStyle) // Unmerge the child styles from the current instance.

Legacy Browsers

To support legacy browsers (<= IE8) you'll need to polyfill some ES5 features, such as Object.keys, Array.isArray and Array.prototype.map.

License

MIT