GithubHelp home page GithubHelp logo

cssobj / cssobj Goto Github PK

View Code? Open in Web Editor NEW
269.0 12.0 17.0 671 KB

Runtime CSS manager, Turn CSS into dynamic JS module, Stylesheet CRUD (Create, Read, Update, Delete) in CSSOM, name space (local) class names

Home Page: https://cssobj.github.io/cssobj-demo/

License: MIT License

JavaScript 74.29% HTML 18.12% CSS 7.59%
css-in-js cssom dynamic-style crud cssobj stylesheet-crud css-crud stylesheets

cssobj's Introduction

cssobj logo

CSSOBJ Join the chat at https://gitter.im/css-in-js/cssobj

Runtime CSS manager, Turn CSS into dynamic JS module, Stylesheet CRUD (Create, Read, Update, Delete) in CSSOM, Solve common problems of CSS-in-JS.

Usage - Wiki - API - Demo - React - Babel

Build Status npm Coverage Status dependencies Status Standard - JavaScript Style Guide

Install:

npm

npm install cssobj  # the lib

# When use Babel
npm install babel-plugin-transform-cssobj

# When **NOT** use Babel, install the converter
npm install -g cssobj-converter

browser

<script src="https://unpkg.com/cssobj"></script>

Usage

First see this SIMPLE DEMO

In the example, cssobj will create <style> tag in HEAD, render CSS rules inside

import cssobj from 'cssobj'

const obj = {
  div: {
    backgroundColor: 'yellow',
    color: 'red',
    // simulate 50vh in CSS3
    height: () => window.innerHeight/2 + 'px'
  }
}
const result = cssobj(obj)

// dynamic update height when resize
window.onresize = () => result.update()

The rendered CSS (height is dynamically set to 50% of window height)

div { background-color: yellow; color: red; height: 600px; }

If you read the code, you've learned the API already:

Only One top level method: cssobj( obj, [config] ), all other things using result.someMethods, that's all, really.

Stylesheet CRUD

The power of cssobj is CSS CRUD (Create, Read, Update, Delete), dynamically change above CSS, see below:

1. Update property values

You want to change color to 'blue'

// using static value:
obj.div.color = 'blue'
result.update()  // color is now 'blue'


// using function as value:
obj.div.color = function(v){
  return randomColor()
}
result.update()  // color is now random

2. Delete/Remove properties

You want to remove backgroundColor

It's just work as you expected:

delete obj.div.backgroundColor
result.update()

3. Create/Add new properties

You want to add 'float' and 'clear'

It's just work as you expected:

obj.div.float = 'left'
obj.div.clear = 'both'
result.update()

4. Create/Add new rules

You want to add ':after' rule, and div span rule

obj.div['&:after'] = { fontSize:'10px', content:'"---"' }
obj.div.span = { fontSize: '18px' }
result.update()

5. Update/Replace rules

You want to replace the whole rule

obj.div.span = { color: 'green', fontSize: '20px' }
result.update()

All the above can use function instead

obj.div.span = function() {
  return { color: randomColor(), fontSize: currentSize + 'px' }
}
result.update()

6. Delete/Remove rules

You want to remove div span rule

delete obj.div.span
result.update()

7. Read a rule

Although cssobj can manage everything, you read the rule in stylesheet manually

const rule = result.root.children.div.omRule[0]
// => CSSStyleRule
rule.color = 'red'

8. Delete/Destroy cssobj

Currently, cssobj don't provide result.destroy() or similar method, you should manually destroy things:

// remove <style> tag
result.cssdom.parentNode.removeChild(el)
// GC result
result = null

Think of this: one cssobj instance === A <style> tag with rules + A manager from JS

At-Rules

All @-rules work as expected, and @media can be nested at any level:

cssobj({
  '.nav':{
    width: '1024px',
    '@media print': {
      display: 'none'
    }
  }
})

Above will hide .nav when print.

You can emit any @media rule by cssom.media option:

const result = cssobj({
  '.nav':{
    width: '1024px',
    '@media print': {
      color: 'red'
    }
  }
}, { cssom: { media:'' } })

result.config.cssom.media = 'print'
result.update()

Above will switch to print view, with below CSS:

nav {width: 1024px;}
nav {color: red;}

Then switch back:

result.config.cssom.media = ''
result.update()
cssobj({
  '@keyframes changeColor': {
    '0%': { backgroundColor: 'green' },
    '100%': { backgroundColor: 'yellow' }
  },
  '.nav': {
    backgroundColor: 'red',
    animation: '5s infinite changeColor'
  }
})

Notice above @keyframes, it have to be in top level of your source object, aka cannot be nested into .nav, that is different from @media rule, which allow nested at any level, or nested into another @media:

cssobj({
  h3:{
    color: 'blue',
    '@media (min-width: 400px)': {
      color: 'red',
      '@media (max-width: 500px)': {
          color: 'green'
      }
    },
    '@media (min-width: 500px)': {
      color: 'purple'
    }
  }
})

Above, what's the color will be? You can take a try and see what's the final CSS will be.

There's a hidden JS Bin...

Localize class names

Passing local: true as option, cssobj will add a random name space into all class names, this is called localize:

const result = cssobj(
  {
    '.nav': {color: 'red'}
  },
  { local: true }
)

Rendered CSS:

.nav_1lwyllh4_ {color: red;}

You can get this name space using result.space, or using below methods:

// As HTML class attribute
result.mapClass('nav active')  // [string] 'nav_1lwyllh4_ active_1lwyllh4_'

// As CSS selector
result.mapSel('.nav li.item')  // [string] '.nav_1lwyllh4_ li.item_1lwyllh4_'

React

You can use react-cssobj with React, like below:

import React from 'react'
import ReactCSS from 'react-cssobj'

const {css, mapClass} = ReactCSS({
  '.app': {
    background: 'red'
  }
})

export default class App extends React.Component {
  render(){
    return mapClass (<div className = 'app'>App</div>)
  }
}

Work Flow with Babel, See also Without Babel Version

If use Babel, recommended the babel-plugin-transform-cssobj

// create <style> in <head>, insert CSS rules, random namespace: _1jkhrb92_

// The babel-plugin only transform: CSSOBJ `text`

const result = CSSOBJ `
---
# cssobj config
local: true
plugins:
  - default-unit: px
---
// SCSS style (nested)
.nav {
  color: blue;
  height: 100;

  // font-size is a function
  .item { color: red; font-size: ${v => v.raw ? v.raw + 1 : 12} }

  // nested @media
  @media (max-width: 800px) {
    color: #333;
    // & = parent selector = .nav
    &:active {
      color: #666;
    }
  }

}
`
const html = result.mapClass(<ul class='nav'><li class='item active'>ITEM</li></ul>)
// <ul class="nav_1jkhrb92_"><li class="item_1jkhrb92_ active_1jkhrb92_"></li></ul>

Rendered result as below:

import cssobj from "cssobj";
import cssobj_plugin_default_unit from "cssobj-plugin-default-unit";
const result = cssobj({
  '.nav': {
    color: 'blue',
    height: 100,
    '.item': {
      color: 'red',
      fontSize: v => v.raw ? v.raw + 1 : 12
    },
    '@media (max-width: 800px)': {
      color: '#333',
      '&:active': {
        color: '#666'
      }
    }
  }
}, {
  local: true,
  plugins: [cssobj_plugin_default_unit('px')]
});

const html = <ul class={result.mapClass('nav')}><li class={result.mapClass('item active')}></li></ul>

For this first time render, all class names add a random suffix _1jkhrb92_, the font-size is 12px, the <style> tag which cssobj created now contains:

.nav_1jkhrb92_ { color: blue; height: 100px; }
.nav_1jkhrb92_ .item_1jkhrb92_ { color: red; font-size: 12px; }
@media (max-width: 800px) {
  .nav_1jkhrb92_ { color: rgb(51, 51, 51); }
  .nav_1jkhrb92_:active { color: rgb(102, 102, 102); }
}

Update CSS Value

Since we already have a function as the value:

fontSize: v => v.raw ? v.raw + 1 : 12

  • the value (===v.raw) initialised with 12 (default-unit plugin will add px when rendering, that is v.cooked === 12px)

  • each call of the function will increase font-size by 1

So, just need call result.update, the function invoked, stylesheet updated, automatically:

result.update()
// font-size  ->  13px

result.update()
// font-size  ->  14px

Above, only font-size changed, all other things keep untouched

CRUD (Create, Read, Update, Delete) stylesheet from JS

When the source JS Object (first arg of cssobj()) have no changes, result.update only invoke the value function (here, the above font-size function),

Otherwise, it will look into the source JS Object, find which part have been changed (diff), and update stylesheet accordingly. See below:

// result.obj === reference of the source js object

// change a css property
result.obj['.nav'].color = 'orange'

// remove a css property
delete result.obj['.nav'].height

// add a new css property
result.obj['.nav'].width = 200

// add a new rule
result.obj['.nav'].a = { color: 'blue', '&:hover': {textDecoration: 'none'} }

// delete a rule
delete result.obj['.nav']['.item']

result.update()

// color      ->  'orange' (PROP CHANGED)
// height     ->   (PROP REMOVED)
// width      ->   200px (PROP ADDED)
// a, a:hover ->   (RULE ADDED)
// .item      ->   (RULE REMOVED)

Above, only diffed part updated, other rules and props will keep untouched

Now, the stylesheet becomes:

.nav_1jkhrb92_ { color: orange; width: 200px; }
@media (max-width: 800px) {
  .nav_1jkhrb92_ { color: #333; }
  .nav_1jkhrb92_:active { color: #666; }
}
.nav_1jkhrb92_ a { color: blue; }
.nav_1jkhrb92_ a:hover { text-decoration: none; }

Diff with NEW JS Object

const newObj = { '.nav': { width: 100, a: { color: 'blue' } } }
result.update(newObj)
// cssobj will DIFF with old obj, keep same part, change diffed part in stylesheet!
// .nav, .nav a   rules keeped
// width -> 100px, drop all other rules/props

Now, the stylesheet becomes:

/* below 2 rules keeped */
.nav_1jkhrb92_ { width: 100px; }
.nav_1jkhrb92_ a { color: blue; }

/* other rules gone */

That's it, see more Usage & Example

Work Flow (Without Babel)

First install cssobj-converter

npm install -g cssobj-converter
  • Step 1

Write your CSS as normal (e.g. index.css)

// file: index.css
.nav { color: blue; font-size: 12px; }
  • Step 2

Turn it into JS module, from cssobj-converter CLI

# in command line, run cssobj-converter
cssobj index.css -o index.css.js

The result

// file: index.css.js
module.exports = {
  '.nav': { color: 'blue', fontSize: '12px' }
}
  • Step 3

Let's rock:

// import your css module
const obj = require('./index.css')

// create <style> tag in <head>, with rules in obj.
// `local: true` will put class names into local space
const result = cssobj(obj, {local: true})

result.mapClass(<JSX>)  // with Babel
result.mapClass('classA')  // without Babel

// update some rule
obj['.nav'].color = 'red'
obj['.nav'].fontSize = v => parseInt(v.cooked) + 1  // increase font-size by 1
result.update()

More to read:

How it worked?

  1. cssobj first parse js object into Virtual CSSOM middle format.

  2. The internal cssom plugin will create stylesheet dom, and apply rules from middle format.

  3. When the js object changed, cssobj will diff CSSOM rules (add/delete/change) accordingly. (see demo)

Tools

Convert existing style sheet into cssobj:

  • CLI Converter Recommended CLI tools to convert CSS. Run npm -g cssobj-converter

  • Online Converter It's free node server, slow, and unstalbe, not recommended

Debug

Plugins

About writing a plugin, See: plugin-guide

Helpers

Demos

Test

Using phantom 2.0 to test with CSSOM. Please see test/ folder.

Remark

cssobj is wrapper for cssobj-core, plugin-localize and plugin-cssom.

License

MIT

cssobj's People

Contributors

amilajack avatar futurist avatar mithriljs-cn avatar

Stargazers

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

Watchers

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

cssobj's Issues

[poll] remove intros feature

Please see the API and docs:

https://github.com/cssobj/cssobj/blob/master/docs/api.md#config-object

https://github.com/cssobj/cssobj/blob/master/docs/merge-multiple-object.md

intros is a syntax sugar for Object.assign, see below:

cssobj(yourObj, { intros: [ {p:{color: 'red'}} ] })

cssobj will turn above internally into (roughly):

FinalSourceObj = Object.assign(
    {},
    {p:{color: 'red'}},
    yourObj
)

Thus you can combine multiple objects in runtime.

But how do you use this feature before? Or do you think it's little use that can be removed?

The related code is below, totally 5-6 lines also (saving 150 bytes when removed)

https://github.com/cssobj/cssobj-core/blob/master/lib/cssobj-core.js#L368

https://github.com/cssobj/cssobj-core/blob/master/lib/cssobj-core.js#L381-L385

After removed, you can also use Object.assign your source object before passing into cssobj.

And the old intros, like:

https://github.com/cssobj/cssobj-intro-clearfix

Will be renamed into css-object-clearfix, and can be add many more code snippet like this.

PS: I'm plan to remove intros API in cssobj 2.0, how do you think of it?

poll: should drop IE8 support?

Should cssobj drop old IE support? (IE<=8), mainly the @media hook

That's only 2 benefit I can see to remove old IE @media hook:

  1. Smaller size ( before 4.2K min.gz, after 3.9K min.gz

  2. More maintainable code, the CSSOM plugin will be smaller, and more clearly.

The question:

  1. do you use this feature now?

  2. targeting IE>8, is that OK for you?

Whether allow node.prop accept invalid CSS if it's start with $

See PR cssobj/cssobj-core#1

Think below code:

cssobj({
  p: { $test: true, font: 123, color: null }
})

The $test prop will be dropped in node.prop object, since true cannot pass the condition:

if( isValidCSSValue(val) ) ...

So the resulting v-node is:

{
  ...
  rawVal: { $test:[true], font: [123], color: [null] },
  prop: { font: [123] }
  ...
}

User can check the node.rawVal object to get the $test value.

The Different between rawVal and prop is: the first is before value plugins, the latter is value after all value plugins, in addition, ensure it's Valid CSS

This branch is allow node.prop accept invalid CSS values if it's start with "$":

https://github.com/cssobj/cssobj-core/blob/prop-allow-all-%24/lib/cssobj-core.js#L287

This will allow above code resulting as below:

{
  ...
  rawVal: { $test:[true], font: [123], color: [null] },
  prop: { $test:[true], font: [123] }
  ...
}

Does this make sense? Is there any side effects?

Duplicate identifier 'lastVal'.

Getting an error in the index.d.ts file:
Duplicate identifier 'lastVal'.

    interface Node {
      children: Object;
      key: string;
      obj: Object;
      parent: Object;
      diff?: Object;
      omRule?: Object[];
      parentRule?: any;
      rawVal?: Object;
      lastVal?: Object;   // ONE
      lastRaw?: Object;
      lastVal?: Object;   // TWO
      prevVal?: any;
      prop?: Object;
      selPart?: string[];
      selText?: string;
      selTextPart?: string;
      groupText?: string;
      at?: string;
      type?: string;
    }

https://github.com/cssobj/cssobj/blob/master/index.d.ts

Does it work with iframe?

I wonder if this works with iframe content given that it loads the content from the same origin (domain)?
With jquery I can call .css method on an element inside iframe like this

$('#iframe').contents().find('#widget-1').css('color', 'red;);

Comparison table to be updated

Your comparison table is interesting, but as I work on a comparison of several libraries to make a choice, I feel that some entries might be wrong.
For example, I think that Glamor do support IE8's media queries via some hook (not tested). And Fela supports changing rules / style at runtime, they even wrote an article on the topic: https://medium.com/@rofrischmann/styles-as-functions-of-state-1885627a63f7
Etc. I plan to gather more information as I test them, so I will propose some changes.

cssobj terminology (glossary, naming)

I'm revising the docs, as a preparing, please suggesting below terminology (glossary, naming):

Sample Object

var obj = {
  div: {
    $test: true,
    font: 123,
    color: function(){ return 'red' },
    p: {
      height: 10
    }
  }
}

Please suggesting below naming:

word candidates remark
obj source object
div selector
$test directive
font property name
123 property value
function ()
{ return 'red' }
value function
p nested selector

More use case and naming suggests are welcome!

Class name clash

I contribute to the CSS-in-JS perf tests project, and CSSObj is part of the tested libraries.
In my current branch I simulate the case of a component with its own styling, with class names voluntarily clashing with class names used in the application itself. Ie. I use elements with container and button class names in the app, and the same names in a component used in the app.
After all, that's the main goal of the CSS-in-JS libraries: to avoid name clashing and to isolate CSS of components from the other CSS.

I probably use your library in a wrong way, because the generated names are the same in the app and in the component, so the display is bad...
The relevant code is at https://github.com/PhiLhoSoft/css-in-js-perf-tests/blob/complex-test-case/src/complex-test/cases/cssobj.js

I initialize the library with:

const cssobjOptions = {
    local: true,
    plugins: [
        cssobjPluginLocalize(),
        cssobjPluginGencss({ indent: '\t', newLine: '\n' }),
    ]
};
const cssobj = cssobjCore(cssobjOptions);

I generate the styles as:

const cssObjectA = cssobj(styleSheetA); // For the app
const cssObjectC = cssobj(styleSheetC); // For the component

For the record, I also tried to create two cssobj instances, but I had the same results.

I get the mapped names via cssObjectA.mapClass and cssObjectC.mapClass functions.

The generated code looks like:

<div class=" container_13fcj6h1_">
    <section class=" itemContainer_13fcj6h1_">
        <header class=" title_13fcj6h1_">
            <h2>Main app</h2>
        </header>
<div class=" container_13fcj6h1_ regular">
    <span>Item A</span>
    <input type="checkbox" value="a" >Disable</input>
    <button class=" button_13fcj6h1_">x</button>
</div>
[....]
<div class=" container_13fcj6h1_ regular">
    <span>Item E</span>
    <input type="checkbox" value="e" >Disable</input>
    <button class=" button_13fcj6h1_">x</button>
</div>
        <button class=" button_13fcj6h1_">Update</button>
    </section>
[...]
</div>

Notice the container_13fcj6h1_ class on the external div, and the same name on the div wrapping the components. Likewise, the global button is named button_13fcj6h1_ and so are the component buttons.
Other libraries doesn't have this problem...

So, did I miss something in the setup or in the way to use the API?
Thank you for any insight.

Note; I saw I can workaround the issue by using custom decorations, but I wanted to know why the default suffixes aren't truly random as said in the docs.

CSSObj seems slow

I contribute to the https://github.com/hellofresh/css-in-js-perf-tests/ project, which aims to test the performance, and to examine the API and the results, of several CSS-in-JS projects, focusing on those view-agnostic and working on server side, among other things.
I added CSSObj to the project, and it seems it is a bit slow: https://github.com/hellofresh/css-in-js-perf-tests/issues/23
It is probably because I didn't made optimal usage of the API, or perhaps the library is not performing well in the context of the tests (the benchmark code calls the test case lot of times to do its stats).
For example, the https://engineering.hellofresh.com/the-css-in-js-battle-89c34a7a83ea article explains that JSS perf wasn't good because the global CSS in the library instance was growing, so they added a reset / flush call in each test case.
Perhaps your library has a similar problem, but I haven't found such function to clean up the CSS after rendering.
Do you have any tip to improve the performance?

result.update() does not work with an !important

cssobj.update() does not work with an !important
To work around this error, it is necessary to use the following code:

var save_s_obj = result.obj[selector];
delete result.obj[selector];
result.update();
result.obj[selector] = save_s_obj;
result.obj[selector][prop] = val + " !important";
result.update();

cssobj 2.0 wishlist

I'm thinking of cssobj 2.0, the wish lists:

  • bug fix (e.g. $extend not updating)
  • drop IE 8
  • drop intros
  • add $assign
  • allow at-rules nest into any level
  • performance improvement
  • more modern JS feature using IE9+

Related issue: #14

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.