Repository: tkh44/emotion
Pull request #197 (2017-07-28 21:56:32)

Prefix objects css props
<!--
Thanks for your interest in the project. I appreciate bugs filed and PRs submitted!

Please make sure that you are familiar with and follow the Code of Conduct for
this project (found in the CODE_OF_CONDUCT.md file).

Also, please make sure you're familiar with and follow the instructions in the
contributing guidelines (found in the CONTRIBUTING.md file).

If you're new to contributing to open source projects, you might find this free
video course helpful: http://kcd.im/pull-request

Please fill out the information below to expedite the review and (hopefully)
merge of your pull request!
-->

<!-- What changes are being made? (What feature/bug is being fixed here?) -->
**What**:

<!-- Why are these changes necessary? -->
**Why**:

<!-- How were these changes implemented? -->
**How**:

<!-- Have you done all of these things?  -->
**Checklist**:
<!-- add "N/A" to the end of each line that's irrelevant to your changes -->
<!-- to check an item, place an "x" in the box like so: "- [x] Documentation" -->
- [ ] Documentation
- [ ] Tests
- [ ] Code complete

<!-- feel free to add additional comments -->


codecov-io: # [Codecov](https://codecov.io/gh/tkh44/emotion/pull/197?src=pr&el=h1) Report
> :exclamation: No coverage uploaded for pull request base (`master@477c715`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference#section-missing-base-commit).
> The diff coverage is `60%`.

[![Impacted file tree graph](https://codecov.io/gh/tkh44/emotion/pull/197/graphs/tree.svg?src=pr&width=650&token=B1bWuYNE2t&height=150)](https://codecov.io/gh/tkh44/emotion/pull/197?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##             master     #197   +/-   ##
=========================================
  Coverage          ?   84.96%           
=========================================
  Files             ?       24           
  Lines             ?      931           
  Branches          ?      252           
=========================================
  Hits              ?      791           
  Misses            ?      104           
  Partials          ?       36
```


| [Impacted Files](https://codecov.io/gh/tkh44/emotion/pull/197?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/obj-parse.js](https://codecov.io/gh/tkh44/emotion/pull/197?src=pr&el=tree#diff-c3JjL29iai1wYXJzZS5qcw==) | `0% <0%> (ø)` | |
| [src/css-prop.js](https://codecov.io/gh/tkh44/emotion/pull/197?src=pr&el=tree#diff-c3JjL2Nzcy1wcm9wLmpz) | `100% <100%> (ø)` | |
| [src/babel.js](https://codecov.io/gh/tkh44/emotion/pull/197?src=pr&el=tree#diff-c3JjL2JhYmVsLmpz) | `97.93% <100%> (ø)` | |
| [src/parser.js](https://codecov.io/gh/tkh44/emotion/pull/197?src=pr&el=tree#diff-c3JjL3BhcnNlci5qcw==) | `96.87% <66.66%> (ø)` | |
| [src/ast-object.js](https://codecov.io/gh/tkh44/emotion/pull/197?src=pr&el=tree#diff-c3JjL2FzdC1vYmplY3QuanM=) | `87.59% <81.33%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/tkh44/emotion/pull/197?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/tkh44/emotion/pull/197?src=pr&el=footer). Last update [477c715...07c0cc3](https://codecov.io/gh/tkh44/emotion/pull/197?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/ast-object.js b/src/ast-object.js
index a19a58ce..773a7652 100644
--- a/src/ast-object.js
+++ b/src/ast-object.js
@@ -1,37 +1,99 @@
+import { expandCSSFallbacks, prefixer } from './parser'
 import { forEach, reduce } from './utils'
 
-export default class ASTObject {
-  obj: { [string]: any }
-  expressions: Array<any>
-  composesCount: number
-  t: any
+function prefixAst (args, t) {
+  function isLiteral (value) {
+    return (
+      t.isStringLiteral(value) ||
+      t.isNumericLiteral(value) ||
+      t.isBooleanLiteral(value)
+    )
+  }
 
-  constructor (obj, expressions, composesCount, t) {
-    this.obj = obj
-    this.expressions = expressions
-    this.composesCount = composesCount
-    this.t = t
+  if (Array.isArray(args)) {
+    return args.map(element => prefixAst(element, t))
   }
 
-  toAST () {
-    const { obj, t } = this
+  if (t.isObjectExpression(args)) {
+    let properties = []
+    args.properties.forEach(property => {
+      // nested objects
+      if (t.isObjectExpression(property.value)) {
+        const key = property.computed
+          ? property.key
+          : t.isStringLiteral(property.key)
+            ? t.stringLiteral(property.key.value)
+            : t.identifier(property.key.name)
 
-    const props = []
-    for (let key in obj) {
-      const rawValue = obj[key]
-      const { computed, composes, ast: keyAST } = this.objKeyToAst(key)
-
-      let valueAST
-      if (composes) {
-        // valueAST = t.arrayExpression(expressions.slice(0, composesCount))
-        continue
+        return properties.push(
+          t.objectProperty(key, prefixAst(property.value, t), property.computed)
+        )
+
+        // literal value or array of literal values
+      } else if (
+        isLiteral(property.value) ||
+        (t.isArrayExpression(property.value) &&
+          property.value.elements.every(isLiteral))
+      ) {
+        // bail on computed properties
+        if (property.computed) {
+          properties.push(property)
+          return
+        }
+
+        // handle array values: { display: ['flex', 'block'] }
+        const propertyValue = t.isArrayExpression(property.value)
+          ? property.value.elements.map(element => element.value)
+          : property.value.value
+
+        const style = { [property.key.name]: propertyValue }
+        const prefixedStyle = expandCSSFallbacks(prefixer(style))
+
+        for (let k in prefixedStyle) {
+          const key = t.isStringLiteral(property.key)
+            ? t.stringLiteral(k)
+            : t.identifier(k)
+          const val = prefixedStyle[k]
+          let value
+
+          if (typeof val === 'number') {
+            value = t.numericLiteral(val)
+          } else if (typeof val === 'string') {
+            value = t.stringLiteral(val)
+          } else if (Array.isArray(val)) {
+            value = t.arrayExpression(val.map(i => t.stringLiteral(i)))
+          }
+
+          properties.push(t.objectProperty(key, value))
+        }
+
+        // expressions
       } else {
-        valueAST = this.objValueToAst(rawValue)
+        properties.push(property)
       }
+    })
 
-      props.push(t.objectProperty(keyAST, valueAST, computed))
-    }
-    return t.objectExpression(props)
+    return t.objectExpression(properties)
+  }
+
+  if (t.isArrayExpression(args)) {
+    return t.arrayExpression(prefixAst(args.elements, t))
+  }
+
+  return args
+}
+
+export default class ASTObject {
+  props: Array<any>
+  expressions: Array<any>
+  composesCount: number
+  t: any
+
+  constructor (props, expressions, composesCount, t) {
+    this.props = props
+    this.expressions = expressions || []
+    this.composesCount = composesCount
+    this.t = t
   }
 
   objKeyToAst (key): { computed: boolean, ast: any, composes: boolean } {
@@ -54,21 +116,26 @@ export default class ASTObject {
   }
 
   objValueToAst (value) {
-    const { expressions, composesCount, t } = this
-
+    const { composesCount, t } = this
     if (typeof value === 'string') {
       const matches = this.getDynamicMatches(value)
       if (matches.length) {
         return this.replacePlaceholdersWithExpressions(matches, value)
       }
       return t.stringLiteral(value)
+    } else if (typeof value === 'number') {
+      return t.numericLiteral(value)
     } else if (Array.isArray(value)) {
       // should never hit here
+
+      if (value[0] && (value[0].key || value[0].value || value[0].spread)) {
+        return this.toAST(value)
+      }
+
       return t.arrayExpression(value.map(v => this.objValueToAst(v)))
     }
 
-    const obj = new this.constructor(value, expressions, composesCount, t)
-    return obj.toAST()
+    return ASTObject.fromJS(value, composesCount, t).toAST()
   }
 
   getDynamicMatches (str) {
@@ -109,7 +176,7 @@ export default class ASTObject {
       // }
 
       templateExpressions.push(
-        expressions
+        expressions.length
           ? expressions[p1 - composesCount]
           : t.identifier(`x${p1 - composesCount}`)
       )
@@ -131,6 +198,73 @@ export default class ASTObject {
     return t.templateLiteral(templateElements, templateExpressions)
   }
 
+  toAST (props = this.props) {
+    return this.t.objectExpression(
+      props.map(prop => {
+        if (this.t.isObjectProperty(prop)) {
+          return prop
+        }
+
+        // console.log(JSON.stringify(prop, null, 2))
+
+        const { property, key, value, spread, shorthand } = prop
+
+        if (spread || shorthand) {
+          return property
+        }
+
+        const { computed, ast: keyAST } = this.objKeyToAst(key)
+        const valueAST = this.objValueToAst(value)
+
+        return this.t.objectProperty(keyAST, valueAST, computed)
+      })
+    )
+  }
+
+  toJS (props = this.props) {
+    return props.reduce(
+      (
+        accum,
+        { property, key, value, computed: isComputedProperty, spread }
+      ) => {
+        if (spread) {
+          return accum
+        }
+
+        accum[key] = value
+        return accum
+      },
+      {}
+    )
+  }
+
+  static fromJS (jsObj, composesCount, t) {
+    const props = []
+    for (let key in jsObj) {
+      // console.log(key)
+      if (jsObj.hasOwnProperty(key)) {
+        let value
+        if (Object.prototype.toString.call(jsObj[key]) === '[object Object]') {
+          // console.log("what the fuck", jsObj[key])
+          // value = ASTObject.fromJS(jsObj[key], composesCount, t)
+          value = jsObj[key]
+        } else {
+          value = jsObj[key]
+        }
+
+        props.push({
+          key: key,
+          value: value,
+          computed: false,
+          spread: false,
+          property: null
+        })
+      }
+    }
+
+    return new ASTObject(props, [], composesCount, t)
+  }
+
   static fromAST (astObj, t) {
     function isLiteral (value) {
       return (
@@ -170,12 +304,27 @@ export default class ASTObject {
       return `xxx${expressions.length - 1}xxx`
     }
 
-    function toObj (astObj) {
-      let obj = {}
+    function convertAstToObj (astObj) {
+      const props = []
 
-      forEach(astObj.properties, property => {
-        // nested objects
+      forEach(astObj.properties, (property, i) => {
         let key
+        if (t.isSpreadProperty(property)) {
+          props.push({
+            key: null,
+            value: null,
+            computed: false,
+            shorthand: false,
+            spread: true,
+            property
+          })
+          return
+        }
+
+        if (property.shorthand) {
+          return property
+        }
+
         if (property.computed) {
           key = replaceExpressionsWithPlaceholders(property.key)
         } else {
@@ -183,19 +332,34 @@ export default class ASTObject {
             ? property.key.name
             : property.key.value
         }
+
+        // nested objects
         if (t.isObjectExpression(property.value)) {
-          obj[key] = toObj(property.value)
+          props.push({
+            key,
+            value: convertAstToObj(property.value),
+            computed: property.computed,
+            spread: false,
+            shorthand: false,
+            property
+          })
         } else {
-          obj[key] = replaceExpressionsWithPlaceholders(property.value)
+          props.push({
+            key,
+            value: replaceExpressionsWithPlaceholders(property.value),
+            computed: property.computed,
+            spread: false,
+            shorthand: false,
+            property
+          })
         }
       })
-
-      return obj
+      return props
     }
 
-    const obj = toObj(astObj)
+    const objectProperties = convertAstToObj(prefixAst(astObj, t))
     return new ASTObject(
-      obj,
+      objectProperties,
       expressions,
       0, // composesCount: we should support this,
       t
diff --git a/src/babel.js b/src/babel.js
index 150bff9b..dc843984 100644
--- a/src/babel.js
+++ b/src/babel.js
@@ -10,7 +10,9 @@ import cssProps from './css-prop'
 import ASTObject from './ast-object'
 
 function getFilename (path) {
-  return path.hub.file.opts.filename === 'unknown' ? '' : path.hub.file.opts.filename
+  return path.hub.file.opts.filename === 'unknown'
+    ? ''
+    : path.hub.file.opts.filename
 }
 
 export function replaceCssWithCallExpression (
@@ -61,7 +63,7 @@ export function replaceCssWithCallExpression (
           t.blockStatement([
             t.returnStatement(
               t.arrayExpression([
-                new ASTObject(styles, false, composesCount, t).toAST()
+                ASTObject.fromJS(styles, composesCount, t).toAST()
               ])
             )
           ])
@@ -115,7 +117,7 @@ export function buildStyledCallExpression (identifier, tag, path, state, t) {
       t.blockStatement([
         t.returnStatement(
           t.arrayExpression([
-            new ASTObject(styles, false, composesCount, t).toAST()
+            ASTObject.fromJS(styles, composesCount, t).toAST()
           ])
         )
       ])
@@ -131,16 +133,15 @@ export function buildStyledObjectCallExpression (path, identifier, t) {
     : t.stringLiteral(path.node.callee.property.name)
   return t.callExpression(identifier, [
     tag,
-    t.arrayExpression(buildProcessedStylesFromObjectAST(path.node.arguments, path, t))
+    t.arrayExpression(
+      buildProcessedStylesFromObjectAST(path.node.arguments, path, t)
+    )
   ])
 }
 
 function buildProcessedStylesFromObjectAST (objectAST, path, t) {
   if (t.isObjectExpression(objectAST)) {
-    const astObject = ASTObject.fromAST(objectAST, t)
-    const { styles } = parseCSS(astObject.obj, false, getFilename(path))
-    astObject.obj = styles
-    return astObject.toAST()
+    return ASTObject.fromAST(objectAST, t).toAST()
   }
   if (t.isArrayExpression(objectAST)) {
     return t.arrayExpression(
@@ -148,7 +149,9 @@ function buildProcessedStylesFromObjectAST (objectAST, path, t) {
     )
   }
   if (Array.isArray(objectAST)) {
-    return map(objectAST, obj => buildProcessedStylesFromObjectAST(obj, path, t))
+    return map(objectAST, obj =>
+      buildProcessedStylesFromObjectAST(obj, path, t)
+    )
   }
 
   return objectAST
@@ -207,7 +210,12 @@ export default function (babel) {
           if (state.cssPropIdentifier) {
             path.node.body.unshift(
               t.importDeclaration(
-                [t.importSpecifier(state.cssPropIdentifier, t.identifier('css'))],
+                [
+                  t.importSpecifier(
+                    state.cssPropIdentifier,
+                    t.identifier('css')
+                  )
+                ],
                 t.stringLiteral('emotion')
               )
             )
@@ -307,8 +315,16 @@ export default function (babel) {
               (name, hash, src) => src,
               true
             )
-          } else if (state.cssPropIdentifier && path.node.tag === state.cssPropIdentifier) {
-            replaceCssWithCallExpression(path, state.cssPropIdentifier, state, t)
+          } else if (
+            state.cssPropIdentifier &&
+            path.node.tag === state.cssPropIdentifier
+          ) {
+            replaceCssWithCallExpression(
+              path,
+              state.cssPropIdentifier,
+              state,
+              t
+            )
           }
         }
       }
diff --git a/src/css-prop.js b/src/css-prop.js
index 35aa34ab..800eb9f3 100644
--- a/src/css-prop.js
+++ b/src/css-prop.js
@@ -45,9 +45,7 @@ export default function (path, state, t) {
       )
     )
   } else {
-    throw path.buildCodeFrameError(
-      `${cssPropValue.value} is not a string or template literal`
-    )
+    cssTemplateExpression = t.callExpression(getCssIdentifer(), [cssPropValue])
   }
 
   if (!classNamesValue) {
@@ -90,14 +88,17 @@ export default function (path, state, t) {
     )
   }
 
-  function createCssTemplateExpression (templateLiteral) {
-    let identifier = t.identifier('css')
+  function getCssIdentifer () {
     if (state.opts.autoImportCssProp !== false) {
       if (!state.cssPropIdentifier) {
         state.cssPropIdentifier = path.scope.generateUidIdentifier('css')
       }
-      identifier = state.cssPropIdentifier
+      return state.cssPropIdentifier
+    } else {
+      return t.identifier('css')
     }
-    return t.taggedTemplateExpression(identifier, templateLiteral)
+  }
+  function createCssTemplateExpression (templateLiteral) {
+    return t.taggedTemplateExpression(getCssIdentifer(), templateLiteral)
   }
 }
diff --git a/src/obj-parse.js b/src/obj-parse.js
deleted file mode 100644
index 4d35c6d0..00000000
--- a/src/obj-parse.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import postcss from 'postcss'
-import isUnitlessNumber from './glamor/CSSPropertyOperations/CSSProperty'
-import { processStyleName } from './glamor/CSSPropertyOperations'
-
-function decl (parent, name, value) {
-  if (value === false || value === null) return
-
-  name = processStyleName(name)
-  if (typeof value === 'number') {
-    if (value === 0 || isUnitlessNumber[name] === 1) {
-      value = value.toString()
-    } else {
-      value += 'px'
-    }
-  }
-
-  if (name === 'css-float') name = 'float'
-
-  parent.push(postcss.decl({ prop: name, value: value }))
-}
-
-function atRule (parent, parts, value) {
-  var node = postcss.atRule({ name: parts[1], params: parts[3] || '' })
-  if (typeof value === 'object') {
-    node.nodes = []
-    parse(value, node)
-  }
-  parent.push(node)
-}
-
-function parse (obj, parent) {
-  var name, value, node, i
-  for (name in obj) {
-    if (obj.hasOwnProperty(name)) {
-      value = obj[name]
-      if (name[0] === '@') {
-        var parts = name.match(/@([^\s]+)(\s+([\w\W]*)\s*)?/)
-        if (Array.isArray(value)) {
-          for (i = 0; i < value.length; i++) {
-            atRule(parent, parts, value[i])
-          }
-        } else {
-          atRule(parent, parts, value)
-        }
-      } else if (Array.isArray(value)) {
-        for (i = 0; i < value.length; i++) {
-          decl(parent, name, value[i])
-        }
-      } else if (typeof value === 'object' && value !== null) {
-        node = postcss.rule({ selector: name })
-        parse(value, node)
-        parent.push(node)
-      } else {
-        decl(parent, name, value)
-      }
-    }
-  }
-}
-
-export default function (obj, opts) {
-  var root = postcss.root(opts)
-  parse(obj, root)
-  return root
-}
diff --git a/src/parser.js b/src/parser.js
index 0febea3c..ad28f816 100644
--- a/src/parser.js
+++ b/src/parser.js
@@ -5,9 +5,8 @@ import postcssJs from 'postcss-js'
 import objParse from 'postcss-js/parser'
 import autoprefixer from 'autoprefixer'
 import { processStyleName } from './glamor/CSSPropertyOperations'
-import { objStyle } from './index'
 
-const prefixer = postcssJs.sync([autoprefixer, postcssNested])
+export const prefixer = postcssJs.sync([autoprefixer, postcssNested])
 
 type Rule = {
   parent: { selector: string, nodes: Array<mixed> },
diff --git a/src/react/index.js b/src/react/index.js
index 1542fd03..773290c3 100644
--- a/src/react/index.js
+++ b/src/react/index.js
@@ -78,7 +78,9 @@ export default function (tag, objs, vars = [], content) {
             tag.__emotion_spec,
             (accum, spec) => {
               push(accum, spec.objs)
-              push(accum, spec.content.apply(null, map(spec.vars, getValue)))
+              if (spec.content) {
+                push(accum, spec.content.apply(null, map(spec.vars, getValue)))
+              }
               return accum
             },
             []
diff --git a/src/utils.js b/src/utils.js
index af5c99c3..5a924e99 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -80,7 +80,7 @@ export const assign: any =
   function (target) {
     let i = 1
     let length = arguments.length
-    for (;i < length; i++) {
+    for (; i < length; i++) {
       var source = arguments[i]
       for (var key in source) {
         if (Object.prototype.hasOwnProperty.call(source, key)) {


Test patch:
diff --git a/test/.babelrc b/test/.babelrc
index 9fbb976c..24b6795a 100644
--- a/test/.babelrc
+++ b/test/.babelrc
@@ -2,7 +2,8 @@
   "presets": [
     "flow",
     "env",
-    "react"
+    "react",
+    "stage-2"
   ],
   "plugins": [["./babel-plugin-emotion-test", { "inline": true }]]
 }
diff --git a/test/__snapshots__/css-prop.test.js.snap b/test/__snapshots__/css-prop.test.js.snap
index 5b5d1641..7d1d579c 100644
--- a/test/__snapshots__/css-prop.test.js.snap
+++ b/test/__snapshots__/css-prop.test.js.snap
@@ -78,6 +78,19 @@ exports[`css prop react kitchen sink 1`] = `
 </div>
 `;
 
+exports[`css prop react objects 1`] = `
+.glamor-0 {
+  color: red;
+  font-size: 1px;
+}
+
+<p
+  className="glamor-0"
+>
+  hello world
+</p>
+`;
+
 exports[`css prop react string expression 1`] = `
 .glamor-0 {
   color: red;
diff --git a/test/__snapshots__/css.test.js.snap b/test/__snapshots__/css.test.js.snap
index e9d05a07..fab7cd42 100644
--- a/test/__snapshots__/css.test.js.snap
+++ b/test/__snapshots__/css.test.js.snap
@@ -1,14 +1,8 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`css @supports 1`] = `
-@supports (display: grid) {
-  .glamor-0 {
-    display: grid;
-  }
-}
-
 <div
-  className="glamor-0"
+  className="css-1u5pet7"
 />
 `;
 
@@ -198,20 +192,6 @@ exports[`css nested at rules 1`] = `
   }
 }
 
-@supports (display: grid) {
-  .glamor-0 {
-    display: grid;
-  }
-}
-
-@supports (display: grid) and  ((display: -webkit-box) or (display: flex)) {
-  .glamor-0 {
-    display: -webkit-box;
-    display: -ms-flexbox;
-    display: flex;
-  }
-}
-
 <div
   className="glamor-0"
 />
diff --git a/test/__snapshots__/react.test.js.snap b/test/__snapshots__/react.test.js.snap
index 0a1233d6..81433ea3 100644
--- a/test/__snapshots__/react.test.js.snap
+++ b/test/__snapshots__/react.test.js.snap
@@ -14,12 +14,8 @@ exports[`styled basic render 1`] = `
 `;
 
 exports[`styled basic render with object as style 1`] = `
-.glamor-0 {
-  font-size: 20px;
-}
-
 <h1
-  className="glamor-0"
+  className="css-nil"
 >
   hello world
 </h1>
@@ -304,14 +300,6 @@ exports[`styled input placeholder 1`] = `
 `;
 
 exports[`styled input placeholder object 1`] = `
-.glamor-0::-webkit-input-placeholder {
-  background-color: green;
-}
-
-.glamor-0:-ms-input-placeholder {
-  background-color: green;
-}
-
 .glamor-0::placeholder {
   background-color: green;
 }
@@ -417,6 +405,18 @@ exports[`styled objects 1`] = `
 </h1>
 `;
 
+exports[`styled objects with spread properties 1`] = `
+.glamor-0 {
+  font-size: 20px;
+}
+
+<figure
+  className="glamor-0"
+>
+  hello world
+</figure>
+`;
+
 exports[`styled themes 1`] = `
 .glamor-0 {
   background-color: #ffd43b;
diff --git a/test/babel/__snapshots__/css-prop.test.js.snap b/test/babel/__snapshots__/css-prop.test.js.snap
index 53f4a69e..3d92f1dc 100644
--- a/test/babel/__snapshots__/css-prop.test.js.snap
+++ b/test/babel/__snapshots__/css-prop.test.js.snap
@@ -9,27 +9,32 @@ exports[`babel css prop StringLiteral css prop value 1`] = `
 })}></div>;"
 `;
 
-exports[`babel css prop basic 1`] = `
+exports[`babel css prop basic inline 1`] = `
+"import { css as _css } from \\"emotion\\";
+<div className={\\"a\\" + \\" \\" + /*#__PURE__*/_css([], [], function createEmotionStyledRules() {
+  return [{
+    \\"color\\": \\"brown\\"
+  }];
+})}></div>;"
+`;
+
+exports[`babel css prop basic object 1`] = `
+"import { css as _css } from \\"emotion\\";
+<div className={\\"a\\" + \\" \\" + _css({ color: 'brown' })}></div>;"
+`;
+
+exports[`babel css prop basic with extractStatic 1`] = `
 "import { css as _css } from \\"emotion\\";
 import \\"./css-prop.test.emotion.css\\";
 <div className={\\"a\\" + \\" \\" + \\"css-jf1v9l\\"}></div>;"
 `;
 
-exports[`babel css prop basic 2`] = `
+exports[`babel css prop basic with extractStatic 2`] = `
 ".css-jf1v9l {
     color: brown
 }"
 `;
 
-exports[`babel css prop basic inline 1`] = `
-"import { css as _css } from \\"emotion\\";
-<div className={\\"a\\" + \\" \\" + /*#__PURE__*/_css([], [], function createEmotionStyledRules() {
-  return [{
-    \\"color\\": \\"brown\\"
-  }];
-})}></div>;"
-`;
-
 exports[`babel css prop className as expression 1`] = `
 "import { css as _css } from \\"emotion\\";
 <div className={variable + \\" \\" + /*#__PURE__*/_css([], [], function createEmotionStyledRules() {
diff --git a/test/babel/__snapshots__/css.test.js.snap b/test/babel/__snapshots__/css.test.js.snap
index 0529276a..4eac4257 100644
--- a/test/babel/__snapshots__/css.test.js.snap
+++ b/test/babel/__snapshots__/css.test.js.snap
@@ -92,7 +92,7 @@ exports[`babel css extract dynamic property objects 1`] = `
 "
 css({
   'fontSize': '10px',
-  [\`w\${'idth'}\`]: '20px'
+  [\`w\${'idth'}\`]: 20
 });"
 `;
 
@@ -136,8 +136,6 @@ const cls2 = css([{
   'WebkitBoxFlex': '1',
   'msFlex': '1',
   'flex': '1',
-  'WebkitBoxAlign': \`\${'center'}\`,
-  'msFlexAlign': \`\${'center'}\`,
   'alignItems': \`\${'center'}\`
 }, {
   'WebkitBoxPack': 'start',
@@ -226,8 +224,6 @@ const cls2 = css({
   'WebkitBoxFlex': '1',
   'msFlex': '1',
   'flex': '1',
-  'WebkitBoxAlign': \`\${'center'}\`,
-  'msFlexAlign': \`\${'center'}\`,
   'alignItems': \`\${'center'}\`
 });"
 `;
diff --git a/test/babel/__snapshots__/styled.test.js.snap b/test/babel/__snapshots__/styled.test.js.snap
index 7740df45..6934e119 100644
--- a/test/babel/__snapshots__/styled.test.js.snap
+++ b/test/babel/__snapshots__/styled.test.js.snap
@@ -268,3 +268,26 @@ const H1 = styled(\\"h1\\", [{
   display: props.display
 })]);"
 `;
+
+exports[`babel styled component inline styled. objects with a multiple spread properties 1`] = `
+"var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+const defaultText = { fontSize: 20 };
+const Figure = styled(\\"figure\\", [_extends({}, defaultText, defaultFigure)]);"
+`;
+
+exports[`babel styled component inline styled. objects with a multiple spread properties and other keys 1`] = `
+"var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+const defaultText = { fontSize: 20 };
+const Figure = styled('figure', [_extends({}, defaultText, {
+  'fontSize': '20px'
+}, defaultFigure, defaultText2)]);"
+`;
+
+exports[`babel styled component inline styled. objects with a single spread property 1`] = `
+"var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+const defaultText = { fontSize: 20 };
+const Figure = styled(\\"figure\\", [_extends({}, defaultText)]);"
+`;
diff --git a/test/babel/css-prop.test.js b/test/babel/css-prop.test.js
index 4aee1ac4..5904df61 100644
--- a/test/babel/css-prop.test.js
+++ b/test/babel/css-prop.test.js
@@ -8,7 +8,7 @@ jest.mock('fs')
 fs.existsSync.mockReturnValue(true)
 
 describe('babel css prop', () => {
-  test('basic', () => {
+  test('basic with extractStatic', () => {
     const basic = '(<div className="a" css={`color: brown;`}></div>)'
     const { code } = babel.transform(basic, {
       plugins: [[plugin, { extractStatic: true }]],
@@ -28,6 +28,14 @@ describe('babel css prop', () => {
     expect(code).toMatchSnapshot()
   })
 
+  test('basic object', () => {
+    const basic = '(<div className="a" css={{ color: \'brown\' }}></div>)'
+    const {code} = babel.transform(basic, {
+      plugins: [plugin]
+    })
+    expect(code).toMatchSnapshot()
+  })
+
   test('dynamic inline', () => {
     const basic = `(<div className="a" css={\`color: $\{color};\`}></div>)`
     const { code } = babel.transform(basic, {
@@ -60,15 +68,6 @@ describe('babel css prop', () => {
     expect(code).toMatchSnapshot()
   })
 
-  test('wrong value type', () => {
-    const basic = '(<div css={5}></div>)'
-    expect(() =>
-      babel.transform(basic, {
-        plugins: [plugin]
-      })
-    ).toThrow()
-  })
-
   test('StringLiteral css prop value', () => {
     const basic = `<div css="color: brown;"></div>`
     const { code } = babel.transform(basic, {
diff --git a/test/babel/styled.test.js b/test/babel/styled.test.js
index 148865e7..204fb832 100644
--- a/test/babel/styled.test.js
+++ b/test/babel/styled.test.js
@@ -2,6 +2,7 @@
 /* eslint-env jest */
 import * as babel from 'babel-core'
 import plugin from '../../src/babel'
+import stage2 from 'babel-plugin-transform-object-rest-spread'
 import * as fs from 'fs'
 jest.mock('fs')
 
@@ -226,6 +227,46 @@ describe('babel styled component', () => {
       expect(code).toMatchSnapshot()
     })
 
+    test('styled. objects with a single spread property', () => {
+      const basic = `
+      const defaultText = { fontSize: 20 }
+      const Figure = styled.figure({
+        ...defaultText
+      })`
+      const { code } = babel.transform(basic, {
+        plugins: [plugin, stage2]
+      })
+      expect(code).toMatchSnapshot()
+    })
+
+    test('styled. objects with a multiple spread properties', () => {
+      const basic = `
+      const defaultText = { fontSize: 20 }
+      const Figure = styled.figure({
+        ...defaultText,
+        ...defaultFigure
+      })`
+      const {code} = babel.transform(basic, {
+        plugins: [plugin, stage2]
+      })
+      expect(code).toMatchSnapshot()
+    })
+
+    test('styled. objects with a multiple spread properties and other keys', () => {
+      const basic = `
+      const defaultText = { fontSize: 20 }
+      const Figure = styled.figure({
+        ...defaultText,
+        fontSize: '20px',
+        ...defaultFigure,
+        ...defaultText2
+      })`
+      const {code} = babel.transform(basic, {
+        plugins: [plugin, stage2]
+      })
+      expect(code).toMatchSnapshot()
+    })
+
     test('styled objects prefixed', () => {
       const basic = `
       const H1 = styled.h1({
diff --git a/test/css-prop.test.js b/test/css-prop.test.js
index aba22a93..317a300e 100644
--- a/test/css-prop.test.js
+++ b/test/css-prop.test.js
@@ -26,6 +26,15 @@ describe('css prop react', () => {
     expect(tree).toMatchSnapshot()
   })
 
+  test('objects', () => {
+    const fontSize = '1px'
+    const tree = renderer
+      .create(<p css={{ color: 'red', fontSize }}>hello world</p>)
+      .toJSON()
+
+    expect(tree).toMatchSnapshot()
+  })
+
   test('kitchen sink', () => {
     const props = { online: false, error: false, radius: 5 }
     const huge = 100
diff --git a/test/macro/__snapshots__/react.test.js.snap b/test/macro/__snapshots__/react.test.js.snap
index 00201709..0e9c0da8 100644
--- a/test/macro/__snapshots__/react.test.js.snap
+++ b/test/macro/__snapshots__/react.test.js.snap
@@ -13,12 +13,8 @@ exports[`styled basic render 1`] = `
 `;
 
 exports[`styled basic render with object as style 1`] = `
-.glamor-0 {
-  font-size: 20px;
-}
-
 <h1
-  className="glamor-0"
+  className="css-nil"
 >
   hello world
 </h1>
diff --git a/test/react.test.js b/test/react.test.js
index 346fb4ae..b0498dcd 100644
--- a/test/react.test.js
+++ b/test/react.test.js
@@ -428,6 +428,16 @@ describe('styled', () => {
     expect(tree).toMatchSnapshot()
   })
 
+  test('objects with spread properties', () => {
+    const defaultText = { fontSize: 20 }
+    const Figure = styled.figure({
+      ...defaultText
+    })
+    const tree = renderer.create(<Figure>hello world</Figure>).toJSON()
+
+    expect(tree).toMatchSnapshot()
+  })
+
   test('change theme', () => {
     const Div = styled.div`
       color: ${props => props.theme.primary}