├── .gitignore ├── .travis.yml ├── package.json ├── LICENSE ├── src ├── free-style.js └── index.js ├── test.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6.6.0" 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stylin", 3 | "version": "5.1.3", 4 | "description": "Javascript to CSS: A convenience wrapper around free-style", 5 | "repository": "ajoslin/stylin", 6 | "main": "src/index.js", 7 | "scripts": { 8 | "test": "standard && tape test.js" 9 | }, 10 | "keywords": [ 11 | "css", 12 | "javascript", 13 | "style" 14 | ], 15 | "author": "Andrew Joslin ", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "proxyquire": "^1.7.11", 19 | "standard": "^6.0.8", 20 | "tape": "^4.5.1" 21 | }, 22 | "dependencies": { 23 | "free-style": "~2.0.0", 24 | "global": "^4.3.1", 25 | "inline-style-prefixer": "^3.0.0", 26 | "insert-styles": "~1.2.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Andrew Joslin (ajoslin.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/free-style.js: -------------------------------------------------------------------------------- 1 | var FreeStyle = require('free-style') 2 | var insertStyles = require('insert-styles') 3 | var window = require('global/window') 4 | 5 | var styleInstancesById = {} 6 | var DEFAULT_STYLE_ID = '__stylin__' 7 | 8 | // Support multiple instances of the module in the same document. 9 | module.exports = window.__stylin_data__ = window.__stylin_data__ || { 10 | STYLE_ID: DEFAULT_STYLE_ID, 11 | getStyles: getStyles, 12 | reset: reset, 13 | registerStyle: createStyleFunction('registerStyle', 1), 14 | registerKeyframes: createStyleFunction('registerKeyframes', 1), 15 | registerRule: createStyleFunction('registerRule', 2) 16 | } 17 | 18 | function getStyles (styleId) { 19 | var byId = styleInstancesById[styleId || DEFAULT_STYLE_ID] 20 | return byId && byId.Style ? byId.Style.getStyles() : '' 21 | } 22 | 23 | function reset () { 24 | styleInstancesById = {} 25 | } 26 | 27 | function createStyleFunction (methodName, arity) { 28 | return function styleFn () { 29 | var args = Array.prototype.slice.call(arguments) 30 | var opts = args.length > arity ? args.pop() : null 31 | var styleId = opts && opts.styleId ? opts.styleId : DEFAULT_STYLE_ID 32 | 33 | var byId = styleInstancesById[styleId] = styleInstancesById[styleId] || { 34 | Style: FreeStyle.create(), 35 | changeId: null 36 | } 37 | var Style = byId.Style 38 | var changeId = byId.changeId 39 | 40 | var result = Style[methodName].apply(Style, args) 41 | if (Style.changeId !== changeId) { 42 | insertStyles(Style.getStyles(), {id: styleId}) 43 | byId.changeId = Style.changeId 44 | } 45 | 46 | return result 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var prefixAll = require('inline-style-prefixer/static') 2 | var freeStyle = require('./free-style') 3 | 4 | var hasOwnProperty = Object.prototype.hasOwnProperty 5 | 6 | module.exports = exports.default = css 7 | 8 | css.unimportant = unimportant 9 | css.keyframes = keyframes 10 | css.rule = rule 11 | css.getCss = freeStyle.getStyles 12 | css.STYLE_ID = freeStyle.STYLE_ID 13 | css.reset = freeStyle.reset 14 | 15 | function css (style, opts) { 16 | if (!style) throw new TypeError('stylin: css style object expected') 17 | 18 | return freeStyle.registerStyle(prepareStyle(true, style), opts) 19 | } 20 | 21 | function unimportant (style, opts) { 22 | if (!style) throw new TypeError('stylin.unimportant: css style object expected') 23 | 24 | return freeStyle.registerStyle(prepareStyle(false, style), opts) 25 | } 26 | 27 | function keyframes (style, opts) { 28 | if (!style) throw new TypeError('stylin.keyframes: css style object expected') 29 | 30 | return freeStyle.registerKeyframes(prepareStyle(false, style), opts) 31 | } 32 | 33 | function rule (key, style, opts) { 34 | if (!key || !style) throw new TypeError('stylin.rule: parameters (key, style) expected') 35 | 36 | return freeStyle.registerRule(key, prepareStyle(false, style), opts) 37 | } 38 | 39 | function prepareStyle (addImportant, styles) { 40 | prefixAll(styles) 41 | 42 | if (addImportant) { 43 | important(styles) 44 | } 45 | 46 | return styles 47 | } 48 | 49 | function important (style) { 50 | for (var key in style) { 51 | if (!hasOwnProperty.call(style, key)) continue 52 | 53 | if (style && typeof style[key] === 'object') { 54 | style[key] = important(style[key]) 55 | } else if (String(style[key]).indexOf('!important') === -1) { 56 | style[key] += ' !important' 57 | } 58 | } 59 | 60 | return style 61 | } 62 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var stylin = require('./') 3 | 4 | test('stylin', function (t) { 5 | var className = stylin({ 6 | color: 'green', 7 | '.foo': { 8 | transition: 'color 1s' 9 | } 10 | }) 11 | 12 | t.equal(stylin.getCss(), `.${className}{color:green !important}.${className} .foo{-moz-transition:color 1s !important;-webkit-transition:color 1s !important;transition:color 1s !important}`) 13 | 14 | var className2 = stylin({ 15 | color: 'red' 16 | }) 17 | t.equal(stylin.getCss(), `.${className}{color:green !important}.${className} .foo{-moz-transition:color 1s !important;-webkit-transition:color 1s !important;transition:color 1s !important}.${className2}{color:red !important}`) 18 | t.end() 19 | }) 20 | 21 | test('stylin.reset()', function (t) { 22 | t.ok(stylin.getCss()) 23 | stylin.reset() 24 | t.notOk(stylin.getCss()) 25 | t.end() 26 | }) 27 | 28 | test('stylin.unimportant', function (t) { 29 | var className = stylin.unimportant( 30 | {color: 'red'}, 31 | {styleId: 'test-unimportant'} 32 | ) 33 | t.equal(stylin.getCss('test-unimportant'), `.${className}{color:red}`) 34 | t.end() 35 | }) 36 | 37 | test('stylin.rule', function (t) { 38 | stylin.rule( 39 | '@media print', 40 | {backgroundColor: 'teal'}, 41 | {styleId: 'test-rule'} 42 | ) 43 | t.ok(stylin.getCss('test-rule'), '@media print{background-color:teal}') 44 | t.end() 45 | }) 46 | 47 | test('stylin.keyframes', function (t) { 48 | var className = stylin.keyframes( 49 | { 50 | from: {opacity: 0}, 51 | to: {opacity: 1} 52 | }, 53 | {styleId: 'test-keyframes'} 54 | ) 55 | t.equal(stylin.getCss('test-keyframes'), `@keyframes ${className}{from{opacity:0}to{opacity:1}}`) 56 | t.end() 57 | }) 58 | 59 | test('errors', function (t) { 60 | t.throws(function () { 61 | stylin.css() 62 | }, TypeError) 63 | t.throws(function () { 64 | stylin.rule('foo') 65 | }, TypeError) 66 | t.throws(function () { 67 | stylin.keyframes() 68 | }, TypeError) 69 | 70 | t.end() 71 | }) 72 | 73 | test('constant: style id', function (t) { 74 | t.equal(typeof stylin.STYLE_ID, 'string') 75 | t.end() 76 | }) 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stylin [![Build Status](https://travis-ci.org/ajoslin/stylin.svg?branch=master)](https://travis-ci.org/ajoslin/stylin) 2 | 3 | Javascript to CSS: Convert a style object into css and insert it into the DOM at runtime. 4 | 5 | This library is a convenience wrapper around [free-style](https://github.com/blakeembrey/free-style) that provides auto-prefixing and an easier API. 6 | 7 | Total size is less than 6KB gzipped. 8 | 9 | ``` 10 | npm install --save stylin 11 | ``` 12 | 13 | ### Example 14 | 15 | ```js 16 | var css = require('stylin') 17 | var h = require('virtual-dom/h') 18 | 19 | function render (color) { 20 | var style = { 21 | color: color, 22 | '&:hover': { 23 | backgroundColor: color 24 | } 25 | } 26 | return h('div', {className: css(style)}) 27 | } 28 | 29 | render('red') // generates new stylesheet and className 30 | render('red') // re-uses stylesheet and className 31 | render('blue') // generates new stylesheet and className 32 | ``` 33 | 34 | ### API 35 | 36 | All styles are set to `!important` by default, to avoid the pains of style-priority. 37 | 38 | stylin **will mutate passed in style objects** to add browser prefixes and `!important` the first time they are passed in. We mutate objects for two performance increases: 39 | 40 | - We only need to add prefixes and `!important` once per style object 41 | - We don't thrash the garbage collector with a deluge of new objects 42 | 43 | ### `stylin(style, [styleOptions]) -> className` 44 | 45 | Returns a string className for the given style object. All style values will be marked as `!important`. Use `stylin.unimportant` when this is not wanted. 46 | 47 | ##### style 48 | 49 | *Type*: Object (*required*) 50 | 51 | An object of style key/value pairs, which will be prefixed and passed to [FreeStyle#registerStyle](https://github.com/blakeembrey/free-style#styles). 52 | 53 | ##### styleOptions 54 | 55 | *Type*: Object 56 | 57 | ###### styleOptions.styleId 58 | 59 | If passed in, use a FreeStyle instance and stylesheet matching `styleId` for these styles. By default, all styles are shared between one FreeStyle instance and ` 127 | 128 | ... everything else ... 129 | ` 130 | } 131 | ``` 132 | 133 | ## License 134 | 135 | MIT © [Andrew Joslin](http://ajoslin.com) 136 | 137 | MIT © [Blake Embrey](http://blakeembrey.me), [free-style](https://github.com/blakeembrey/free-style) 138 | --------------------------------------------------------------------------------