Giter VIP home page Giter VIP logo

zaftig's Introduction

Zaftig npm size

Zaftig efficiently parses styles, generates a classname for them and inserts them a into a stylesheet in the head of the document.

Passing the same style string will return the same classname.

z`display flex` // .zjsdkk43-1
z`display flex` // .zjsdkk43-1

Highlights

  • ๐Ÿ’ธ Quick and lightweight
  • ๐Ÿ™‡ User defined helper functions z.helper({})
  • ๐Ÿ’ฏ Simplified CSS syntax with nested selector support z`:hover { c green }`
  • ๐Ÿ…ฐ๏ธ Initial based CSS property shorthands e.g. bc == background-color or d == display
  • โš™๏ธ Basic automatic vendor prefixing for: Chrome, Safari, Firefox, Edge, Opera (webkit)

Example:

// works just as well with React and other libraries/frameworks
import { render, h } from 'preact'
import z from 'zaftig'

z.setDot(false)

z.global`
  $btn-color #4444dd
  font-family sans-serif
  font-size 14
  margin 10
`

const btn = z`
  border none
  padding 0.75rem
  line-height 1.2rem
  color $btn-color
  border 0.5 solid $btn-color
  background-color white
  font-size 16
  cursor pointer
  transition transform 100ms
  outline none

  :active { transform scale(0.9) }
  &.primary { bc $btn-color; c white }
  &.rounded { border-radius 4 }
`

const btnGroup = z`.${btn} { margin 5 }`
const btnWarning = btn.z`$btn-color #ee5555`
const btnSuccess = btn.z`$btn-color green`

const App = () => (
  <main className={btnGroup}>
    <button className={btn.concat('primary', 'rounded')}>Primary</button>
    <button className={btnWarning.concat('rounded')}>Warning</button>
    <button className={btnSuccess}>Success</button>
    <button className={btnSuccess.concat('primary rounded')}>Success + Primary</button>
  </main>
)

render(<App />, document.body)

example

playground

Usage

Module:

import z from 'zaftig'

z.setDot(false) // if you're using React

z`color green; background red`

Script:

<!-- uses zaftig.es5.min.js -->
<script src="https://unpkg.com/zaftig"></script>
<script>
  z.setDot(false) // if you're using React

  z`color green; background red`
</script>

Or download the script and use it locally.

You can see all the options here.

Plugins and Tools

API

Quick links: z ~~ z.setDebug ~~ z.setDot ~~ z.global ~~ z.style ~~ z.concat ~~ z.anim ~~ z.helper ~~ z.getSheet ~~ z.new


z`<StyleString>`

Generates a className for the given StyleString and inserts it into a stylesheet in the head of the document.

It returns a Style object { class, className, concat, z }.

.concat allows you to combine the current className with others. See z.concat.

.z lets you extend/chain another StyleString, it calls .concat under the hood and is equivalent to z`...`.concat(z`...`)

const white = z`color white` // z1234-1
const whiteAndBlack = white.z`background-color black` // z1234-1 z1234-2
const extended = whiteAndBlack.concat(z`font-size 20px`, 'test') // z1234-1 z1234-2 z1234-3 test

When .toString() is called on a Style object the className will be returned.

When .valueOf() is called it will return the className with a dot prefixed. This allows you to directly concatenate style objects to tag names when using a hyperscript helper like mithril offers:

m('h1' + z`margin auto`) ==> m('h1.zfwe983')

Example expressions:

z`color green`
z`color green; background-color orange`
z`
  color green
  background-color orange
  :focus {
    margin 20px
    ::placeholder { c orange }
  }
  > span { p 10px }
`
z`c green;bc orange`

Styles are separated by ; or \n.

sel { /* rules */ } creates a nested style. Use & within the selector to reference the parent selector similar to how Less works.

There are a couple of special properties that can be used within a StyleString.

$name will prepend the given string to the generated class name to make it easier to read the DOM:

z`
  $name button
  border none
`
// generated classname will look something like: 'button-z2djkf2342-1'

$compose will make z generate a list of classnames rather than just the generated one:

z`
  $compose btn btn-primary
  border none
`
// .class will output something like: 'btn btn-primary z2djkf2342-1'

This allows you to easily combine zaftig generated classes with external css classes that you've manually written or that are provided by a css framework/library.

CSS variable handling:

You can create/reference css variables using the $ syntax. The normal css syntax using -- and var() also works.

z.global`
  $bg-color black
  $fg-color white
  $font-size 16px
`

z`
  color $fg-color
  background-color $bg-color
  font-size $font-size
`
/* generated css */

:root {
  --bg-color: black;
  --fg-color: white;
  --font-size: 16px;
}

.z2djkf2342-1 {
  color: var(--fg-color);
  background-color: var(--bg-color);
  font-size: var(--font-size);
}

Automatic px:

Zaftig will automatically append px to any numeric value provided to a css property that accepts px values.

z`
  margin 10
  padding 100
  width 5
  height 50
  box-shadow 0 2 4 0 rgba(0, 0, 0, 0.1)
  opacity 0.5
`
/* generated css */

.z2djkf2342-1 {
  margin: 10px;
  padding: 100px;
  width: 5px;
  height: 50px;
  box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
  opacity: 0.5;
}

You can specify px manually if you prefer to, but zaftig will add it for you if you don't.


z.setDebug(bool)

Enable/disable debug mode.

In debug mode Zaftig will insert styles using textContent and will log to the console when an unknown css property is encountered.

textContent is much less efficient than the non debug CSSOM method, but it allows you to modify the styles using chrome dev tools.

NOTE: make sure to call setDebug before inserting any styles


z.setDot(bool)

Controls how Style objects behave when valueOf() is invoked.

The default for dot is true, meaning that a dot will be prepended to classNames, this is useful when using a hyperscript helper such as microh or Mithril's m function.

Under normal circumstances this should not interfere with usage of zaftig, but depending on how some libraries process component props you may want to disable the dot.


z.global`<StyleString>`

Appends global styles. Multiple calls with the same string will result in the style being appended multiple times.

z.global`
  html, body {
    font-family sans-serif
    margin 0
  }
`

z.style`<StyleString>`

Parses given style string and returns string of css rules. Useful for styles that change frequently to prevent generating too many classNames. Resulting string can be assigned to the style attribute of a DOM element.

z.style`color ${fgColor};background-color ${bgColor}`

// returns
;('color: #444; background-color: #fff;')

Nested selectors will be ignored, only simple styles will be returned.


z.concat(...(string | Style | Falsy))

Processes all the given arguments into a final className wrapped in a Style object for further chaining.

Falsy arguments are ignored which means arguments can be conditionally included.

z.concat('hello', cond && 'world') // 'hello' or 'hello world' depending on cond

// you can use either strings or zaftig styles
z.concat('btn btn-large', z`c green`) // 'btn btn-large z1234-1'

// concat can be used statically
z.concat('one', 'two') // 'one two'

// or chained of an existing style
z`c green`.concat('one', 'two') // 'z1234-1 one two'

// you can continue chaining after calling concat
z.concat('one').concat('two').concat('three').z`c green` // 'one two three z1234-1'

z.anim`<StyleString>`

Generates an @keyframes for the given StyleString. It returns the generated name.

const grow = z.anim`
  0% { scale(0.2) }
  100% { scale(1) }
`
// then to use it
z`animation ${grow} 1s ease`

z.helper({ helperName: StyleString | Function, ... })

Register helpers functions to be called from style strings.

A helper can be a StyleString or a function that returns a StyleString.

If the helper is a function it will receive arguments, as seen in the size example below. If it is a StyleString it will have arguments appended to it.

z.helper({
  mx: x => `margin-left ${x}; margin-right ${x}`,
  size: (h, w) => `h ${h}; w ${w}`,
  shadow: 'box-shadow 0 2 4 2 rgba(0,0,0,0.5)',
  smooth: 'transition' // since args are appended, this can act as an alias
})

z`
  mx 10
  size 50 100
  shadow
  smooth color 1s
`
/* generated css */

.z2djkf2342-1 {
  margin-left: 10px;
  margin-right: 10px;
  height: 50px;
  width: 100px;
  box-shadow: 0px 2px 4px 2px rgba(0, 0, 0, 0.5);
}

String based helpers will have any arguments passed automatically appended. This allows you to easily create your own css property shorthands.

z.helper({
  bo: 'border',
  tra: 'transition'
})

z`
  bo 4 solid red
  tra 500ms
`
/* generated css */

.zx5cc6j6obc9-1 {
  border: 4px solid red;
  transition: 500ms;
}

Helpers can also be used in selectors, mainly useful for media queries.

const breakpoints = { sm: '640px', md: '768px', lg: '1024px', xl: '1280px' }

z.helper({
  // can be function with arguments like normal helpers
  '@med': x => `@media (min-width: ${breakpoints[x]})`,
  // or simply a string
  '@lg': '@media (min-width: 1024px)'
})

z`
  c blue
  @med md { c orange }
  @lg { c red }
`
/* generated css */
.z2djkf2342-1 {
  color: blue;
}

@media (min-width: 768px) {
  .z2djkf2342-1 {
    color: orange;
  }
}

@media (min-width: 1024px) {
  .z2djkf2342-1 {
    color: red;
  }
}

z.getSheet()

Returns the DOM node representing the stylesheet.

You can read the stylesheet from it in a couple ways depending on the whether you're in debug mode or not:

// in debug mode you can look for the stylesheet in document.head in DOM or like this:
z.getSheet().textContent
// in prod mode you can use the following code to get the stylesheet:
[...z.getSheet().sheet.cssRules].map(x => x.cssText).join('\n')

z.new(config)

Creates a new instance of zaftig, with a separate style element, helpers and rule/parser cache.

The new instance will have all the same methods as the default one.

config is an optional object parameter that looks something like this:

const newZ = z.new({
  // if you pass a style elem, you need to append it to the doc yourself
  // if you don't pass one then zaftig will create and append one for you
  style: document.head.appendChild(document.createElement('style')),

  // override the prefix for all classes generated by zaftig
  // a random one will be generated by default
  id: 'custom-id',

  // preload the instance with some helpers
  // defults to {}, add more with z.helper()
  helpers: { mx: x => `ml ${x}; mr ${x}` },

  // overrides auto-px behavior, if property accepts px, then automatically
  // this unit to numbers
  unit: 'rem',

  // debug flag, default is false
  debug: true,

  // whether or not to add dot to className when valueOf() is called, default is true
  dot: true
})

Creating a new instance is useful when you want to you ensure you get a private/non global version of zaftig, or to render styles into a shadow DOM.

Credits

Heavily inspired by bss by Rasmus Porsager.

zaftig's People

Contributors

fuzetsu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

zaftig's Issues

Select where the style sheet goes

Useful for shadow dom or iframes.

Off topic: is generation of inline styles just for file size or are there other performance benefits?

"theming" implementation

First of all, what I mean by "theming" is:

  1. being able to define variables which influence the style
  2. allow components to be notified (for e.g. re-rendering) when those variables change
  3. when the active theme's variable change, this effect is propagated automatically to all the interested parties

So going by that concept, by reading the README, I've understood this library does not offer a theming solution.

I've tried to implement it in React

Interesting files

constants.ts

defines the variables for the theme [1st item].

theme.ts

useThemeVariable subscribes to themeChangedCallbacks [2nd item]; due to the fact that it's a hook leveraging useState, the 3rd item is also satisfied.

Button.tsx

Due to useThemeVariable, this components re-renders everytime the theme changes.

What motivated me to do this, initially, was wondering how I'd use Sass' helper functions (darken, lighten, saturate, etc) outside of SCSS files, since that's not directly available with CSS-in-JS. I later found there's the color package for that, so you'd just need to post-process the variable's value after every theme change.


If you feel interested in bringing similar ideas into the library somehow, let me know! And thank you for this project.

better error handling

Currently (particularly in prod mode) if one rule has a css syntax error in it the whole style will fail to generate and nothing will be applied, also the syntax error in unclear, because we spit out the whole style string instead of just the rule that failed.

Would be nice to add a try catch to insertRule to log which specific rules failed, and prevent the whole style from failing just because of that one rule.

remove parser options setting

Not particularly useful, hurts compatibility with helper libraries, and adds a bit of bloat to the code base (pretty minimal though).

improve documentation

Would be nice to have a section detailing the different coding styles possible with zaftig.

  • classic: most styles go inline inside view, directly on the element the style is for
  • defining single top level class outside of component and assigning to root element, use nested styles / classes to define styles beyond that
  • etc

automatic prefixing for pseudo selectors (and values)

For example ::placeholder is not supported in Edge and causes a syntax error (flems).

Would be nice if we handled this automatically, and replaced with prefixed versions automatically like we do with css properties.

This might be the time to look into prefixing special css values as well.

helpers vs. inlinining the CSS directly into the templated string?

when I was using this library for the first time, I thought the z.helper API was neat

however, can't the same be done by interpolating expressions directly into the string?

e.g., instead of

z.helper({
  bo: 'border'
})

z`
  bo 4 solid red
`

do

z`
  ${bo(4, 'solid', 'red')}
`

collateral benefit: also results in straightforward type-checking if you're using e.g. TypeScript

this could also be z.style in the "post-processing" phase for effects which depend on updated values (after a hook has triggered an update, for instance)


Is there some advantage I'm failing to see in there? My current impression is that those custom functions are more flexible and also would help in reducing the API.

handle `@import`

Currently zaftig doesn't support @import statements, would it make sense to do so? They only work if they're declared as the first rules in the stylesheet.

So would they only work in z.global? I feel like it probably doesn't make sense to implement, since links work fine, and there's less uncertainty about how they can be included..
Including an external stylesheet kind of goes against CSS in JS anyway...

https://developer.mozilla.org/en-US/docs/Web/CSS/@import

name change?

The name zaftig was chosen initially because it was fairly unique, and justified the use of z but it's not particularly meaningful to what it does, and a bit hard to type (based on some reports).

Currently considering:

  • zafty

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.