├── .gitignore ├── .prettierignore ├── CHANGELOG.md ├── README.md ├── example.html ├── factory.js ├── index.js ├── index.umd.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── factory.js └── index.js └── test ├── benchmark.js └── nanostyled.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .cache 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## 0.3 - 2019-04-21 8 | 9 | ### Added 10 | - Support Preact without `preact/compat` via `nanostyled/factory` 11 | ```javascript 12 | import { h } from 'preact'; 13 | import nanoFactory from 'nanostyled/factory'; 14 | const nanostyled = nanoFactory(h); 15 | 16 | const Button = nanostyled('button', { 17 | bg: 'bg-blue', 18 | color: 'white', 19 | }); 20 | ``` 21 | 22 | ### Changed 23 | - Rename `props.tag` to `props.as`, to match Styled Components and Emotion. 24 | To upgrade, use the new prop name on components that override the default tag: 25 | ```jsx 26 | // old 27 | 28 | 29 | // new 30 | 31 | ``` 32 | 33 | 34 | ### Fixed 35 | - Trim README 36 | - Support IE without polyfills or Babel transforms 37 | 38 | ## 0.2 - 2018-10-29 39 | First public release 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nanostyled 2 | 3 | Nanostyled is a tiny library (< 1 Kb) for building styled UI elements in React 4 | and Preact. 5 | 6 | Like a CSS-in-JS library, nanostyled encapsulates complex styles into simple, 7 | tweakable components: 8 | 9 | ```jsx 10 | 11 | 12 | ``` 13 | 14 | Unlike a CSS-in-JS library, nanostyled doesn't parse CSS in JS, which makes your 15 | bundle smaller, your components faster, and your server-side-rendering a breeze. 16 | 17 | | | Low overhead | Props-controlled, component-based API | Zero-config SSR | 18 | | ---------- | ------------ | ------------------------------------- | --------------- | 19 | | nanostyled | ✅ | ✅ | ✅ | 20 | | CSS-in-JS | ❌ | ✅ | ❌ | 21 | | Plain CSS | ✅ | ❌ | ✅ | 22 | 23 | --- 24 | 25 | 26 | 27 | * [Install](#install) 28 | * [Use](#use) 29 | * [A nanostyled Button](#a-nanostyled-button) 30 | * [A more flexible Button](#a-more-flexible-button) 31 | * [A full starter UI Kit](#a-full-starter-ui-kit) 32 | * [Use in Preact and other React-like libraries](#use-in-preact-and-other-react-like-libraries) 33 | * [API Reference](#api-reference) 34 | * [Performance](#performance) 35 | * [Server-Side Rendering](#server-side-rendering) 36 | * [Related Projects](#related-projects) 37 | * [Browser Support](#browser-support) 38 | * [Contributing](#contributing) 39 | * [Bugs](#bugs) 40 | * [Pull requests](#pull-requests) 41 | * [License](#license) 42 | 43 | 44 | 45 | --- 46 | 47 | ## Install 48 | 49 | ``` 50 | npm install nanostyled 51 | ``` 52 | 53 | ## Use 54 | 55 | Nanostyled works by mapping **style props** onto class names from your 56 | [atomic CSS framework][adam-wathan] of choice, like [Tachyons][tachyons] or 57 | [Tailwind][tailwind]. 58 | 59 | ### A nanostyled Button 60 | 61 | ```jsx 62 | import nanostyled from 'nanostyled'; 63 | // This example uses CSS classes from Tachyons 64 | import 'tachyons/css/tachyons.css'; 65 | 66 | // A Button via three style props: 67 | const Button = nanostyled('button', { 68 | color: 'white', 69 | bg: 'bg-blue', 70 | base: 'fw7 br3 pa2 sans-serif f4 bn input-reset', 71 | }); 72 | 73 | const App = () => ( 74 |
75 | 76 | 77 |
78 | ); 79 | ``` 80 | 81 | Rendering `` produces this markup: 82 | 83 | ```html 84 |
85 | 86 | 87 |
88 | ``` 89 | 90 | When a nanostyled component renders, it consumes its style props and merges them 91 | into an HTML class string, as per above. 92 | 93 | Which style props to use is up to you. In the ` 185 | 186 | ``` 187 | 188 | ## Performance 189 | 190 | In a rudimentary benchmark (`test/benchmark.js`), a nanostyled Button renders ~ 191 | 2x more quickly than a similar Button built with styled-components. 192 | 193 | In addition to rendering components more quickly, nanostyled is also almost two 194 | orders of magnitude smaller than styled-components over the wire: 195 | 196 | | | nanostyled | styled-components | 197 | | ----------------- | ---------- | ----------------- | 198 | | size (min + gzip) | 0.4 kB | 15.3 kB | 199 | | 3G download time | 12ms | 305ms | 200 | 201 | ## Server-Side Rendering 202 | 203 | When rendering on a server, just use nanostyled normally. 204 | 205 | ## Related Projects 206 | 207 | - [Tachyons][tachyons] 208 | - [Tailwind][tailwind] 209 | - [Styled Components][styled-components] 210 | - [Tachyons Components](https://github.com/jxnblk/tachyons-components) 211 | 212 | ## Browser Support 213 | Nanostyled aims to run in any browser that implements ES5, including IE 9+. 214 | if you discover otherwise, please file an issue. 215 | 216 | ## Contributing 217 | 218 | ### Bugs 219 | 220 | Please open [an issue](https://github.com/chrisfrank/nanostyled/issues) on 221 | Github. 222 | 223 | ### Pull requests 224 | 225 | PRs are welcome. Please include tests! See `test/nanostyled.test.js` for the 226 | format to follow. 227 | 228 | ## License 229 | 230 | MIT 231 | 232 | [styled-components]: https://www.styled-components.com/ 233 | [adam-wathan]: 234 | https://adamwathan.me/css-utility-classes-and-separation-of-concerns/ 235 | [tachyons]: http://tachyons.io/ 236 | [tailwind]: https://tailwindcss.com/ 237 | [unpkg]: https://unpkg.com/ 238 | [codesandbox]: https://codesandbox.io/s/3r8l4rr8p1 239 | [intro]: https://dev.to/chrisfrank/introducing-nanostyled-2p6k 240 | [basscss]: http://basscss.com 241 | -------------------------------------------------------------------------------- /example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Nanostyled Example 5 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /factory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // wrap nanostyled in a factory that can accept any React-compatible 4 | // +createElement+ function. Works with React, Preact, Inferno, etc 5 | function factory(createElement) { 6 | return function nanostyled(tag, styleProps) { 7 | var Component = function(props) { 8 | var separatedProps = Object.keys(styleProps).reduce( 9 | function(memo, key) { 10 | var style = props[key] === undefined ? styleProps[key] : props[key]; 11 | if (style) memo.styles.push(style); 12 | delete memo.notStyles[key]; 13 | return memo; 14 | }, 15 | { 16 | styles: [props.className].filter(Boolean), 17 | notStyles: assign({}, props), 18 | } 19 | ); 20 | 21 | var passedProps = assign(separatedProps.notStyles, { 22 | className: separatedProps.styles.join(' '), 23 | as: undefined, 24 | }); 25 | 26 | return createElement(props.as || tag, passedProps); 27 | }; 28 | 29 | Component.displayName = 'nanostyled-'.concat(tag); 30 | return Component; 31 | }; 32 | } 33 | 34 | // borrow IE-compatible Object.assign from @developit/preact 35 | function assign(obj, props) { 36 | for (var i in props) obj[i] = props[i]; 37 | return obj; 38 | } 39 | 40 | module.exports = factory; 41 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } 4 | 5 | var React = _interopDefault(require('react')); 6 | 7 | // wrap nanostyled in a factory that can accept any React-compatible 8 | // +createElement+ function. Works with React, Preact, Inferno, etc 9 | function factory(createElement) { 10 | return function nanostyled(tag, styleProps) { 11 | var Component = function(props) { 12 | var separatedProps = Object.keys(styleProps).reduce( 13 | function(memo, key) { 14 | var style = props[key] === undefined ? styleProps[key] : props[key]; 15 | if (style) memo.styles.push(style); 16 | delete memo.notStyles[key]; 17 | return memo; 18 | }, 19 | { 20 | styles: [props.className].filter(Boolean), 21 | notStyles: assign({}, props), 22 | } 23 | ); 24 | 25 | var passedProps = assign(separatedProps.notStyles, { 26 | className: separatedProps.styles.join(' '), 27 | as: undefined, 28 | }); 29 | 30 | return createElement(props.as || tag, passedProps); 31 | }; 32 | 33 | Component.displayName = 'nanostyled-'.concat(tag); 34 | return Component; 35 | }; 36 | } 37 | 38 | // borrow IE-compatible Object.assign from @developit/preact 39 | function assign(obj, props) { 40 | for (var i in props) obj[i] = props[i]; 41 | return obj; 42 | } 43 | 44 | var nanostyled = factory(React.createElement); 45 | 46 | module.exports = nanostyled; 47 | -------------------------------------------------------------------------------- /index.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('react')) : 3 | typeof define === 'function' && define.amd ? define(['react'], factory) : 4 | (global = global || self, global.nanostyled = factory(global.React)); 5 | }(this, function (React) { 'use strict'; 6 | 7 | React = React && React.hasOwnProperty('default') ? React['default'] : React; 8 | 9 | // wrap nanostyled in a factory that can accept any React-compatible 10 | // +createElement+ function. Works with React, Preact, Inferno, etc 11 | function factory(createElement) { 12 | return function nanostyled(tag, styleProps) { 13 | var Component = function(props) { 14 | var separatedProps = Object.keys(styleProps).reduce( 15 | function(memo, key) { 16 | var style = props[key] === undefined ? styleProps[key] : props[key]; 17 | if (style) memo.styles.push(style); 18 | delete memo.notStyles[key]; 19 | return memo; 20 | }, 21 | { 22 | styles: [props.className].filter(Boolean), 23 | notStyles: assign({}, props), 24 | } 25 | ); 26 | 27 | var passedProps = assign(separatedProps.notStyles, { 28 | className: separatedProps.styles.join(' '), 29 | as: undefined, 30 | }); 31 | 32 | return createElement(props.as || tag, passedProps); 33 | }; 34 | 35 | Component.displayName = 'nanostyled-'.concat(tag); 36 | return Component; 37 | }; 38 | } 39 | 40 | // borrow IE-compatible Object.assign from @developit/preact 41 | function assign(obj, props) { 42 | for (var i in props) obj[i] = props[i]; 43 | return obj; 44 | } 45 | 46 | var nanostyled = factory(React.createElement); 47 | 48 | return nanostyled; 49 | 50 | })); 51 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nanostyled", 3 | "version": "0.3.1-0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/helper-annotate-as-pure": { 8 | "version": "7.0.0", 9 | "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", 10 | "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", 11 | "dev": true, 12 | "requires": { 13 | "@babel/types": "^7.0.0" 14 | } 15 | }, 16 | "@babel/helper-module-imports": { 17 | "version": "7.0.0", 18 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", 19 | "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", 20 | "dev": true, 21 | "requires": { 22 | "@babel/types": "^7.0.0" 23 | } 24 | }, 25 | "@babel/types": { 26 | "version": "7.4.0", 27 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz", 28 | "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==", 29 | "dev": true, 30 | "requires": { 31 | "esutils": "^2.0.2", 32 | "lodash": "^4.17.11", 33 | "to-fast-properties": "^2.0.0" 34 | } 35 | }, 36 | "@emotion/is-prop-valid": { 37 | "version": "0.7.3", 38 | "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.7.3.tgz", 39 | "integrity": "sha512-uxJqm/sqwXw3YPA5GXX365OBcJGFtxUVkB6WyezqFHlNe9jqUWH5ur2O2M8dGBz61kn1g3ZBlzUunFQXQIClhA==", 40 | "dev": true, 41 | "requires": { 42 | "@emotion/memoize": "0.7.1" 43 | } 44 | }, 45 | "@emotion/memoize": { 46 | "version": "0.7.1", 47 | "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.1.tgz", 48 | "integrity": "sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==", 49 | "dev": true 50 | }, 51 | "@emotion/unitless": { 52 | "version": "0.7.3", 53 | "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.3.tgz", 54 | "integrity": "sha512-4zAPlpDEh2VwXswwr/t8xGNDGg8RQiPxtxZ3qQEXyQsBV39ptTdESCjuBvGze1nLMVrxmTIKmnO/nAV8Tqjjzg==", 55 | "dev": true 56 | }, 57 | "@types/estree": { 58 | "version": "0.0.39", 59 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", 60 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", 61 | "dev": true 62 | }, 63 | "@types/node": { 64 | "version": "11.13.5", 65 | "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.5.tgz", 66 | "integrity": "sha512-/OMMBnjVtDuwX1tg2pkYVSqRIDSmNTnvVvmvP/2xiMAAWf4a5+JozrApCrO4WCAILmXVxfNoQ3E+0HJbNpFVGg==", 67 | "dev": true 68 | }, 69 | "babel-plugin-styled-components": { 70 | "version": "1.10.0", 71 | "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.0.tgz", 72 | "integrity": "sha512-sQVKG8irFXx14ZfaK1bBePirfkacl3j8nZwSZK+ZjsbnadRHKQTbhXbe/RB1vT6Vgkz45E+V95LBq4KqdhZUNw==", 73 | "dev": true, 74 | "requires": { 75 | "@babel/helper-annotate-as-pure": "^7.0.0", 76 | "@babel/helper-module-imports": "^7.0.0", 77 | "babel-plugin-syntax-jsx": "^6.18.0", 78 | "lodash": "^4.17.10" 79 | } 80 | }, 81 | "babel-plugin-syntax-jsx": { 82 | "version": "6.18.0", 83 | "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", 84 | "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", 85 | "dev": true 86 | }, 87 | "benchmark": { 88 | "version": "2.1.4", 89 | "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", 90 | "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", 91 | "dev": true, 92 | "requires": { 93 | "lodash": "^4.17.4", 94 | "platform": "^1.3.3" 95 | } 96 | }, 97 | "brace-expansion": { 98 | "version": "1.1.11", 99 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 100 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 101 | "dev": true, 102 | "requires": { 103 | "balanced-match": "^1.0.0", 104 | "concat-map": "0.0.1" 105 | }, 106 | "dependencies": { 107 | "balanced-match": { 108 | "version": "1.0.0", 109 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 110 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 111 | "dev": true 112 | } 113 | } 114 | }, 115 | "camelize": { 116 | "version": "1.0.0", 117 | "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", 118 | "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=", 119 | "dev": true 120 | }, 121 | "concat-map": { 122 | "version": "0.0.1", 123 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 124 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 125 | "dev": true 126 | }, 127 | "css-color-keywords": { 128 | "version": "1.0.0", 129 | "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", 130 | "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=", 131 | "dev": true 132 | }, 133 | "css-to-react-native": { 134 | "version": "2.3.0", 135 | "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-2.3.0.tgz", 136 | "integrity": "sha512-IhR7bNIrCFwbJbKZOAjNDZdwpsbjTN6f1agXeELHDqg1wHPA8c2QLruttKOW7hgMGetkfraRJCIEMrptifBfVw==", 137 | "dev": true, 138 | "requires": { 139 | "camelize": "^1.0.0", 140 | "css-color-keywords": "^1.0.0", 141 | "postcss-value-parser": "^3.3.0" 142 | } 143 | }, 144 | "esutils": { 145 | "version": "2.0.2", 146 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 147 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 148 | "dev": true 149 | }, 150 | "fs.realpath": { 151 | "version": "1.0.0", 152 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 153 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 154 | "dev": true 155 | }, 156 | "glob": { 157 | "version": "7.1.3", 158 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 159 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 160 | "dev": true, 161 | "requires": { 162 | "fs.realpath": "^1.0.0", 163 | "inflight": "^1.0.4", 164 | "inherits": "2", 165 | "minimatch": "^3.0.4", 166 | "once": "^1.3.0", 167 | "path-is-absolute": "^1.0.0" 168 | } 169 | }, 170 | "has-flag": { 171 | "version": "3.0.0", 172 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 173 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 174 | "dev": true 175 | }, 176 | "inflight": { 177 | "version": "1.0.6", 178 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 179 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 180 | "dev": true, 181 | "requires": { 182 | "once": "^1.3.0", 183 | "wrappy": "1" 184 | } 185 | }, 186 | "inherits": { 187 | "version": "2.0.3", 188 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 189 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 190 | "dev": true 191 | }, 192 | "jasmine": { 193 | "version": "3.2.0", 194 | "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.2.0.tgz", 195 | "integrity": "sha512-qv6TZ32r+slrQz8fbx2EhGbD9zlJo3NwPrpLK1nE8inILtZO9Fap52pyHk7mNTh4tG50a+1+tOiWVT3jO5I0Sg==", 196 | "dev": true, 197 | "requires": { 198 | "glob": "^7.0.6", 199 | "jasmine-core": "~3.2.0" 200 | } 201 | }, 202 | "jasmine-core": { 203 | "version": "3.2.1", 204 | "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.2.1.tgz", 205 | "integrity": "sha512-pa9tbBWgU0EE4SWgc85T4sa886ufuQdsgruQANhECYjwqgV4z7Vw/499aCaP8ZH79JDS4vhm8doDG9HO4+e4sA==", 206 | "dev": true 207 | }, 208 | "js-tokens": { 209 | "version": "4.0.0", 210 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 211 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 212 | "dev": true 213 | }, 214 | "lodash": { 215 | "version": "4.17.11", 216 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 217 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", 218 | "dev": true 219 | }, 220 | "loose-envify": { 221 | "version": "1.4.0", 222 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 223 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 224 | "dev": true, 225 | "requires": { 226 | "js-tokens": "^3.0.0 || ^4.0.0" 227 | } 228 | }, 229 | "memoize-one": { 230 | "version": "5.0.4", 231 | "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.4.tgz", 232 | "integrity": "sha512-P0z5IeAH6qHHGkJIXWw0xC2HNEgkx/9uWWBQw64FJj3/ol14VYdfVGWWr0fXfjhhv3TKVIqUq65os6O4GUNksA==", 233 | "dev": true 234 | }, 235 | "minimatch": { 236 | "version": "3.0.4", 237 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 238 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 239 | "dev": true, 240 | "requires": { 241 | "brace-expansion": "^1.1.7" 242 | } 243 | }, 244 | "object-assign": { 245 | "version": "4.1.1", 246 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 247 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 248 | "dev": true 249 | }, 250 | "once": { 251 | "version": "1.4.0", 252 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 253 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 254 | "dev": true, 255 | "requires": { 256 | "wrappy": "1" 257 | } 258 | }, 259 | "path-is-absolute": { 260 | "version": "1.0.1", 261 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 262 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 263 | "dev": true 264 | }, 265 | "platform": { 266 | "version": "1.3.5", 267 | "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", 268 | "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", 269 | "dev": true 270 | }, 271 | "postcss-value-parser": { 272 | "version": "3.3.1", 273 | "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", 274 | "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", 275 | "dev": true 276 | }, 277 | "prettier": { 278 | "version": "1.14.3", 279 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.14.3.tgz", 280 | "integrity": "sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg==", 281 | "dev": true 282 | }, 283 | "prop-types": { 284 | "version": "15.6.2", 285 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", 286 | "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", 287 | "dev": true, 288 | "requires": { 289 | "loose-envify": "^1.3.1", 290 | "object-assign": "^4.1.1" 291 | } 292 | }, 293 | "react": { 294 | "version": "16.5.0", 295 | "resolved": "https://registry.npmjs.org/react/-/react-16.5.0.tgz", 296 | "integrity": "sha512-nw/yB/L51kA9PsAy17T1JrzzGRk+BlFCJwFF7p+pwVxgqwPjYNeZEkkH7LXn9dmflolrYMXLWMTkQ77suKPTNQ==", 297 | "dev": true, 298 | "requires": { 299 | "loose-envify": "^1.1.0", 300 | "object-assign": "^4.1.1", 301 | "prop-types": "^15.6.2", 302 | "schedule": "^0.3.0" 303 | } 304 | }, 305 | "react-dom": { 306 | "version": "16.5.0", 307 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.5.0.tgz", 308 | "integrity": "sha512-qgsQdjFH54pQ1AGLCBKsqjPxib4Pnp+cOsNxGPlkHn5YnsSt43sBvHSif6FheY7NMMS6HPeSJOxXf6ECanjacA==", 309 | "dev": true, 310 | "requires": { 311 | "loose-envify": "^1.1.0", 312 | "object-assign": "^4.1.1", 313 | "prop-types": "^15.6.2", 314 | "schedule": "^0.3.0" 315 | } 316 | }, 317 | "react-is": { 318 | "version": "16.5.1", 319 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.5.1.tgz", 320 | "integrity": "sha512-Q7S+9y2lJA9oJCMqLt045f+kLRhsMLA1wW2DAGXA6b7wcTQRHnUDMc5oR49tn0Z4swvnfV+/t8iZFXY74IQmpA==", 321 | "dev": true 322 | }, 323 | "react-test-renderer": { 324 | "version": "16.5.1", 325 | "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.5.1.tgz", 326 | "integrity": "sha512-NOOtwdN5DNGFqsOyU4zqO2+N82wBt7Z5gNOW+ZY/Aui1K/ohmW7uqd5yUW6UYN0Y64Ci/m9CbwrytTHYIMIWtQ==", 327 | "dev": true, 328 | "requires": { 329 | "object-assign": "^4.1.1", 330 | "prop-types": "^15.6.2", 331 | "react-is": "^16.5.1", 332 | "schedule": "^0.4.0" 333 | }, 334 | "dependencies": { 335 | "schedule": { 336 | "version": "0.4.0", 337 | "resolved": "https://registry.npmjs.org/schedule/-/schedule-0.4.0.tgz", 338 | "integrity": "sha512-hYjmoaEMojiMkWCxKr6ue+LYcZ29u29+AamWYmzwT2VOO9ws5UJp/wNhsVUPiUeNh+EdRfZm7nDeB40ffTfMhA==", 339 | "dev": true, 340 | "requires": { 341 | "object-assign": "^4.1.1" 342 | } 343 | } 344 | } 345 | }, 346 | "rollup": { 347 | "version": "1.10.1", 348 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.10.1.tgz", 349 | "integrity": "sha512-pW353tmBE7QP622ITkGxtqF0d5gSRCVPD9xqM+fcPjudeZfoXMFW2sCzsTe2TU/zU1xamIjiS9xuFCPVT9fESw==", 350 | "dev": true, 351 | "requires": { 352 | "@types/estree": "0.0.39", 353 | "@types/node": "^11.13.5", 354 | "acorn": "^6.1.1" 355 | }, 356 | "dependencies": { 357 | "acorn": { 358 | "version": "6.1.1", 359 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", 360 | "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", 361 | "dev": true 362 | } 363 | } 364 | }, 365 | "schedule": { 366 | "version": "0.3.0", 367 | "resolved": "https://registry.npmjs.org/schedule/-/schedule-0.3.0.tgz", 368 | "integrity": "sha512-20+1KVo517sR7Nt+bYBN8a+bEJDKLPEx7Ohtts1kX05E4/HY53YUNuhfkVNItmWAnBYHcpG9vsd2/CJxG+aPCQ==", 369 | "dev": true, 370 | "requires": { 371 | "object-assign": "^4.1.1" 372 | } 373 | }, 374 | "styled-components": { 375 | "version": "4.2.0", 376 | "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-4.2.0.tgz", 377 | "integrity": "sha512-L/LzkL3ZbBhqIVHdR7DbYujy4tqvTNRfc+4JWDCYyhTatI+8CRRQUmdaR0+ARl03DWsfKLhjewll5uNLrqrl4A==", 378 | "dev": true, 379 | "requires": { 380 | "@babel/helper-module-imports": "^7.0.0", 381 | "@emotion/is-prop-valid": "^0.7.3", 382 | "@emotion/unitless": "^0.7.0", 383 | "babel-plugin-styled-components": ">= 1", 384 | "css-to-react-native": "^2.2.2", 385 | "memoize-one": "^5.0.0", 386 | "prop-types": "^15.5.4", 387 | "react-is": "^16.6.0", 388 | "stylis": "^3.5.0", 389 | "stylis-rule-sheet": "^0.0.10", 390 | "supports-color": "^5.5.0" 391 | }, 392 | "dependencies": { 393 | "react-is": { 394 | "version": "16.8.6", 395 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", 396 | "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", 397 | "dev": true 398 | } 399 | } 400 | }, 401 | "stylis": { 402 | "version": "3.5.4", 403 | "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", 404 | "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==", 405 | "dev": true 406 | }, 407 | "stylis-rule-sheet": { 408 | "version": "0.0.10", 409 | "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", 410 | "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==", 411 | "dev": true 412 | }, 413 | "supports-color": { 414 | "version": "5.5.0", 415 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 416 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 417 | "dev": true, 418 | "requires": { 419 | "has-flag": "^3.0.0" 420 | } 421 | }, 422 | "to-fast-properties": { 423 | "version": "2.0.0", 424 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 425 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", 426 | "dev": true 427 | }, 428 | "wrappy": { 429 | "version": "1.0.2", 430 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 431 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 432 | "dev": true 433 | } 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nanostyled", 3 | "version": "0.3.1", 4 | "description": "Style React components as if with CSS-in-JS, without CSS-in-JS", 5 | "main": "index.js", 6 | "browser": "index.umd.js", 7 | "module": "src/index.js", 8 | "scripts": { 9 | "bench": "NODE_ENV=production node test/benchmark.js", 10 | "precommit": "npm run build && npm run test && pretty-quick --staged", 11 | "test": "jasmine test/nanostyled.test.js", 12 | "prebuild": "prettier src/**/*.js --write", 13 | "build": "rollup -c" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://www.github.com/chrisfrank/nanostyled" 18 | }, 19 | "keywords": [ 20 | "react", 21 | "preact", 22 | "CSS", 23 | "CSS-in-JS", 24 | "styled-components", 25 | "atomic CSS", 26 | "utility css", 27 | "functional CSS", 28 | "tachyons", 29 | "tailwind" 30 | ], 31 | "author": "Chris Frank", 32 | "license": "MIT", 33 | "devDependencies": { 34 | "benchmark": "^2", 35 | "jasmine": "^3", 36 | "prettier": "^1", 37 | "react": "^16", 38 | "react-dom": "^16", 39 | "react-test-renderer": "^16", 40 | "rollup": "^1", 41 | "styled-components": "^4.2" 42 | }, 43 | "prettier": { 44 | "proseWrap": "always", 45 | "singleQuote": true, 46 | "trailingComma": "es5" 47 | }, 48 | "dependencies": {} 49 | } 50 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import pkg from './package.json'; 2 | 3 | export default [ 4 | // browser-friendly UMD build 5 | { 6 | input: 'src/index.js', 7 | output: { 8 | name: pkg.name, 9 | file: pkg.browser, 10 | format: 'umd', 11 | globals: { 12 | 'react': 'React', 13 | }, 14 | }, 15 | external: ['react'], 16 | }, 17 | // CommonJS (for Node) and ES module (for bundlers) build. 18 | // (We could have three entries in the configuration array 19 | // instead of two, but it's quicker to generate multiple 20 | // builds from a single configuration where possible, using 21 | // an array for the `output` option, where we can specify 22 | // `file` and `format` for each target) 23 | { 24 | input: 'src/index.js', 25 | external: ['react'], 26 | output: [ 27 | { file: pkg.main, format: 'cjs' }, 28 | ], 29 | }, 30 | { 31 | input: 'src/factory.js', 32 | output: [ 33 | { file: 'factory.js', format: 'cjs' }, 34 | ], 35 | }, 36 | ]; 37 | -------------------------------------------------------------------------------- /src/factory.js: -------------------------------------------------------------------------------- 1 | // wrap nanostyled in a factory that can accept any React-compatible 2 | // +createElement+ function. Works with React, Preact, Inferno, etc 3 | export default function factory(createElement) { 4 | return function nanostyled(tag, styleProps) { 5 | var Component = function(props) { 6 | var separatedProps = Object.keys(styleProps).reduce( 7 | function(memo, key) { 8 | var style = props[key] === undefined ? styleProps[key] : props[key]; 9 | if (style) memo.styles.push(style); 10 | delete memo.notStyles[key]; 11 | return memo; 12 | }, 13 | { 14 | styles: [props.className].filter(Boolean), 15 | notStyles: assign({}, props), 16 | } 17 | ); 18 | 19 | var passedProps = assign(separatedProps.notStyles, { 20 | className: separatedProps.styles.join(' '), 21 | as: undefined, 22 | }); 23 | 24 | return createElement(props.as || tag, passedProps); 25 | }; 26 | 27 | Component.displayName = 'nanostyled-'.concat(tag); 28 | return Component; 29 | }; 30 | } 31 | 32 | // borrow IE-compatible Object.assign from @developit/preact 33 | function assign(obj, props) { 34 | for (var i in props) obj[i] = props[i]; 35 | return obj; 36 | } 37 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styleFactory from './factory'; 3 | var nanostyled = styleFactory(React.createElement); 4 | export default nanostyled; 5 | -------------------------------------------------------------------------------- /test/benchmark.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const TestRenderer = require('react-test-renderer'); 3 | const bm = require('benchmark'); 4 | const styled = require('styled-components'); 5 | const nanostyled = require('../index.js'); 6 | 7 | const NanoButton = nanostyled('button', { 8 | padding: 'ph3', 9 | margin: 'ma2', 10 | display: 'db', 11 | color: 'bg-black white hover-red', 12 | }); 13 | 14 | const StyledButton = styled.default.button({ 15 | padding: '1em', 16 | margin: '1em', 17 | display: 'block', 18 | color: 'white', 19 | background: 'black', 20 | '&:hover': { 21 | color: 'red', 22 | }, 23 | }); 24 | 25 | const renderButton = button => 26 | TestRenderer.create(React.createElement(button, { children: 'Click Me' })); 27 | 28 | let suite = new bm.Suite(); 29 | 30 | suite.add('nanostyled', () => renderButton(NanoButton)); 31 | 32 | suite.add('sc', () => renderButton(StyledButton)); 33 | 34 | suite.on('cycle', function(event) { 35 | console.log(String(event.target)); 36 | }); 37 | 38 | suite.on('complete', function() { 39 | console.log('Fastest is ' + this.filter('fastest').map('name')); 40 | }); 41 | 42 | suite.run({ async: true }); 43 | -------------------------------------------------------------------------------- /test/nanostyled.test.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const TestRenderer = require('react-test-renderer'); 3 | const nanostyled = require('../index.js'); 4 | 5 | const renderJSON = (Component, props) => 6 | TestRenderer.create(React.createElement(Component, props)).toJSON(); 7 | 8 | // nanostyled works with plain elements and with full React classes 9 | class TestComponent extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | this.extras = () => ({ extra: 'props' }); 13 | } 14 | 15 | render() { 16 | return React.createElement( 17 | 'button', 18 | Object.assign({}, this.props, this.extras()) 19 | ); 20 | } 21 | } 22 | 23 | function FunctionComponent(props) { 24 | const extras = { testing: 'a function component' }; 25 | 26 | return React.createElement('button', Object.assign({}, props, extras)); 27 | } 28 | 29 | [TestComponent, FunctionComponent, 'button'].forEach(elem => { 30 | describe('a button', () => { 31 | let Button = nanostyled(elem, { 32 | margin: 'ma3', 33 | padding: 'ph3', 34 | }); 35 | 36 | it('renders style props as className', () => { 37 | let res = renderJSON(Button); 38 | expect(res.props.className).toEqual('ma3 ph3'); 39 | }); 40 | 41 | it('has a displayname', () => { 42 | expect(Button.displayName).toEqual(`nanostyled-${elem}`); 43 | }); 44 | 45 | it('filters style props from rendered output', () => { 46 | let res = renderJSON(Button); 47 | expect(res.props.margin).toEqual(undefined); 48 | }); 49 | 50 | describe('with custom style props', () => { 51 | it('overwrites styleprops with truthy custom props', () => { 52 | let res = renderJSON(Button, { margin: 'ma1', padding: 'pa1' }); 53 | expect(res.props.className).toEqual('ma1 pa1'); 54 | }); 55 | 56 | it('overwrites styleprops with falsy custom props', () => { 57 | let res = renderJSON(Button, { margin: null, padding: 'pa1' }); 58 | expect(res.props.className).toEqual('pa1'); 59 | }); 60 | }); 61 | 62 | it('passes props.className', () => { 63 | let res = renderJSON(Button, { className: 'custom' }); 64 | expect(res.props.className).toEqual('custom ma3 ph3'); 65 | }); 66 | 67 | it('passes props.style', () => { 68 | let style = { whiteSpace: 'nowrap' }; 69 | let res = renderJSON(Button, { style }); 70 | expect(res.props.style).toEqual(style); 71 | }); 72 | 73 | describe('with an +as+ prop', () => { 74 | it('renders the custom tag instead of the default', () => { 75 | let res = renderJSON(Button, { as: 'a' }); 76 | expect(res.type).toEqual('a'); 77 | }); 78 | 79 | it('filters the +as+ prop from DOM attributes', () => { 80 | let res = renderJSON(Button, { as: 'a' }); 81 | expect(res.props.as).toEqual(undefined); 82 | }); 83 | }); 84 | }); 85 | }); 86 | --------------------------------------------------------------------------------