├── .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 | Looks like a button, is a link
28 |
29 | // new
30 | Looks like a button, is a link
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 | A nice-looking button
11 | A nice-looking button that is blue
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 | Base Button
76 | Yellow Button
77 |
78 | );
79 | ```
80 |
81 | Rendering ` ` produces this markup:
82 |
83 | ```html
84 |
85 | Base Button
86 | Yellow Button
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 `` above, it would be
94 |
95 | - Easy to change text color via the `color` prop
96 | - Easy to change background color via the `bg` prop
97 | - Hard to change other styles without totally rewriting the `base` prop
98 |
99 | ### A more flexible Button
100 |
101 | By using more style props, we can make a more flexible button:
102 |
103 | ```jsx
104 | import nanostyled from 'nanostyled';
105 | import 'tachyons/css/tachyons.css';
106 |
107 | const FlexibleButton = nanostyled('button', {
108 | color: 'white', // white text
109 | bg: 'bg-blue', // blue background
110 | weight: 'fw7', // bold font
111 | radius: 'br3', // round corners
112 | padding: 'pa2', // some padding
113 | typeface: 'sans-serif', // sans-serif font
114 | fontSize: 'f4', // font size #4 in the Tachyons font scale
115 | base: 'bn input-reset', // remove border and appearance artifacts
116 | });
117 | ```
118 |
119 | Rendering a stock ` ` will produce the same markup as its
120 | simpler relative. But it's much easier to render alternate styles:
121 |
122 | ```jsx
123 |
124 | Button with a green background, black text, heavier font, and rounder corners
125 |
126 | ```
127 |
128 | When you need a variation that you didn't plan for in your style props, you can
129 | still use the `className` prop:
130 |
131 | ```jsx
132 |
133 | A button that dims on hover and sets the cursor to 'pointer'
134 |
135 | ```
136 |
137 | ### A full starter UI Kit
138 |
139 | Here's a [proof-of-concept UI kit on CodeSandbox][codesandbox].
140 |
141 | ## Use in Preact and other React-like libraries
142 |
143 | Under the hood, nanostyled is built as a library-agnostic factory function.
144 | To use it in Preact without a compatibility layer, import the factory directly:
145 |
146 | ```javascript
147 | import { h } from 'preact';
148 | import nanoFactory from 'nanostyled/factory';
149 | const nanostyled = nanoFactory(h);
150 |
151 | const Button = nanostyled('button', {
152 | bg: 'bg-blue',
153 | color: 'white',
154 | });
155 | ```
156 |
157 | ## API Reference
158 |
159 | `nanostyled(tag, styleProps)`
160 |
161 | The `nanostyled` function takes two arguments:
162 |
163 | 1. tag (String) - the name of an HTML element
164 | 2. styleProps (Object) - an object that maps component props to CSS class names
165 |
166 | ```jsx
167 | const Paragraph = nanostyled('p', {
168 | font: 'serif',
169 | size: 'f4',
170 | });
171 | ```
172 |
173 | `nanostyled` returns a component, which will render styleProps into the
174 | HTML `class` attribute, and pass all other props directly to the rendered
175 | element, _with one exception_:
176 |
177 | You can use the special `as` prop to change the HTML element rendered by a
178 | nanostyled component. If, say, you've made a nanostyled `button`, but you want
179 | it to render as an `a` tag sometimes, do this:
180 |
181 | ```jsx
182 | const Button = nanostyled('button', { color: 'white', bg: 'black' });
183 |
184 | A button
185 | Looks like a button, is a link
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 |
--------------------------------------------------------------------------------