Repository: tkh44/emotion
Pull request #56 (2017-06-30 03:02:05)

Allow interpolation at other positions
<!--
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**:

This allows interpolation at other positions. e.g. selectors, media query params(not attr())

<!-- Why are these changes necessary? -->
**Why**:

#36 


<!-- 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 N/A
- [x] Tests
- [x] Code complete

<!-- feel free to add additional comments -->


codecov-io: # [Codecov](https://codecov.io/gh/tkh44/emotion/pull/56?src=pr&el=h1) Report
> Merging [#56](https://codecov.io/gh/tkh44/emotion/pull/56?src=pr&el=desc) into [master](https://codecov.io/gh/tkh44/emotion/commit/69a4f59b9d0f97c826f5e99adfcb44bf045660d7?src=pr&el=desc) will **decrease** coverage by `0.09%`.
> The diff coverage is `100%`.

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

```diff
@@            Coverage Diff            @@
##           master      #56     +/-   ##
=========================================
- Coverage   87.54%   87.45%   -0.1%     
=========================================
  Files          15       15             
  Lines         530      526      -4     
  Branches      107      106      -1     
=========================================
- Hits          464      460      -4     
  Misses         53       53             
  Partials       13       13
```


| [Impacted Files](https://codecov.io/gh/tkh44/emotion/pull/56?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/parser.js](https://codecov.io/gh/tkh44/emotion/pull/56?src=pr&el=tree#diff-c3JjL3BhcnNlci5qcw==) | `100% <100%> (ø)` | :arrow_up: |
| [src/inline.js](https://codecov.io/gh/tkh44/emotion/pull/56?src=pr&el=tree#diff-c3JjL2lubGluZS5qcw==) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/tkh44/emotion/pull/56?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/56?src=pr&el=footer). Last update [69a4f59...cedb328](https://codecov.io/gh/tkh44/emotion/pull/56?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index 9cf9bfa2..1b30bbb7 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ The default settings enable css extraction.
 This js file, `h1.js`
 
 ```jsx harmony
-import styled from 'emotion/styled'
+import styled from 'emotion/react'
 
 const H1 = styled('h1')`
   color: #ffd43b;
@@ -59,7 +59,7 @@ During babel compilation emotion will create `h1.emotion.css` and add `import '.
 
 ```jsx
 import './h1.emotion.css'
-import styled from 'emotion/styled'
+import styled from 'emotion/react'
 
 const H1 = styled('h1', 'css-H1-duiy4a')
 ```
@@ -82,7 +82,7 @@ Inline mode does **not** extract css into external files.
 This js file, `h1.js`
 
 ```jsx
-import styled from 'emotion/styled'
+import styled from 'emotion/react'
 
 const H1 = styled('h1')`
   color: #ffd43b;
@@ -93,7 +93,7 @@ const H1 = styled('h1')`
 
 ```jsx
 import './h1.emotion.css'
-import styled from 'emotion/styled'
+import styled from 'emotion/react'
 
 const H1 = styled('h1', 'css-H1-duiy4a', [], function createEmotionStyles() {
   return ['.css-H1-duiy4a {color:blue}']
@@ -107,7 +107,7 @@ const H1 = styled('h1', 'css-H1-duiy4a', [], function createEmotionStyles() {
 ### styled
 
 ```jsx
-import styled from 'emotion/styled'
+import styled from 'emotion/react'
 
 const H1 = styled('h1')`
   color: blue;
@@ -188,7 +188,7 @@ function SomeComponent (props) {
 
 ```jsx
 import { fragment, css } from 'emotion'
-import styled from 'emotion/styled'
+import styled from 'emotion/react'
 
 // You can define fragments which can be reused anywhere
 const flexCenter = fragment`
@@ -305,7 +305,7 @@ hydrate(ids)
 
 <script>
 import BoringComponent from './components/BoringComponent'
-// Import styled from emotion/vue instead of emotion/styled
+// Import styled from emotion/vue instead of emotion/react
 import styled from 'emotion/vue'
 
 // You can use styled.* just like with React
diff --git a/package.json b/package.json
index f21d4425..17650b36 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
     "src",
     "dist",
     "lib",
-    "styled.js",
+    "react.js",
     "server.js",
     "vue.js"
   ],
diff --git a/react.js b/react.js
new file mode 100644
index 00000000..8a86673d
--- /dev/null
+++ b/react.js
@@ -0,0 +1,1 @@
+module.exports = require('./lib/react')
diff --git a/src/babel.js b/src/babel.js
index 0be11b1b..a7535fbe 100644
--- a/src/babel.js
+++ b/src/babel.js
@@ -122,7 +122,7 @@ export default function (babel) {
         function buildCallExpression (identifier, tag, path) {
           const built = findAndReplaceAttrs(path, t)
 
-          let { hash, rules, name, hasVar, hasApply } = inline(
+          let { hash, rules, name, hasOtherMatch, hasApply } = inline(
             built,
             identifierName,
             'css',
@@ -138,7 +138,7 @@ export default function (babel) {
             t.stringLiteral(`${name}-${hash}`),
             t.arrayExpression(built.expressions)
           ]
-          if (!hasApply && !state.inline) {
+          if (!hasApply && !hasOtherMatch && !state.inline) {
             state.insertStaticRules(rules)
           } else {
             const expressions = built.expressions.map((x, i) =>
@@ -214,7 +214,7 @@ export default function (babel) {
           t.isIdentifier(path.node.tag) &&
           path.node.tag.name === 'css'
         ) {
-          const { hash, name, rules, hasApply, hasVar } = inline(
+          const { hash, name, rules, hasApply, hasVar, hasOtherMatch } = inline(
             path.node.quasi,
             identifierName,
             'css',
@@ -225,7 +225,7 @@ export default function (babel) {
             classNameStringLiteral,
             t.arrayExpression(path.node.quasi.expressions)
           ]
-          if (!hasApply && !state.inline) {
+          if (!hasApply && !hasOtherMatch && !state.inline) {
             state.insertStaticRules(rules)
             if (!hasVar) {
               return path.replaceWith(classNameStringLiteral)
@@ -250,13 +250,13 @@ export default function (babel) {
           t.isIdentifier(path.node.tag) &&
           path.node.tag.name === 'keyframes'
         ) {
-          const { hash, name, rules, hasApply, hasVar } = keyframes(
+          const { hash, name, rules, hasInterpolation } = keyframes(
             path.node.quasi,
             identifierName,
             'animation'
           )
           const animationName = `${name}-${hash}`
-          if (!hasApply && !hasVar && !state.inline) {
+          if (!hasInterpolation && !state.inline) {
             state.insertStaticRules([
               `@keyframes ${animationName} ${rules.join('')}`
             ])
@@ -275,11 +275,11 @@ export default function (babel) {
           t.isIdentifier(path.node.tag) &&
           path.node.tag.name === 'fontFace'
         ) {
-          const { rules, hasApply, hasVar } = fontFace(
+          const { rules, hasInterpolation } = fontFace(
             path.node.quasi,
             state.inline
           )
-          if (!hasApply && !hasVar && !state.inline) {
+          if (!hasInterpolation && !state.inline) {
             state.insertStaticRules(rules)
             if (t.isExpressionStatement(path.parent)) {
               path.parentPath.remove()
@@ -300,8 +300,8 @@ export default function (babel) {
           path.node.tag.name === 'injectGlobal' &&
           t.isTemplateLiteral(path.node.quasi)
         ) {
-          const { rules, hasApply, hasVar } = injectGlobal(path.node.quasi)
-          if (!hasApply && !hasVar && !state.inline) {
+          const { rules, hasInterpolation } = injectGlobal(path.node.quasi)
+          if (!hasInterpolation && !state.inline) {
             state.insertStaticRules(rules)
             if (t.isExpressionStatement(path.parent)) {
               path.parentPath.remove()
diff --git a/src/index.js b/src/index.js
index 203db1f1..676334c6 100644
--- a/src/index.js
+++ b/src/index.js
@@ -44,7 +44,7 @@ export function css (cls: string, vars: vars, content: () => string[]) {
         .map(r => r.replace(new RegExp(cls, 'gm'), `${cls}-${hash}`))
         .forEach(r => sheet.insert(r))
     }
-    return `${cls}-${hash}`
+    return `${cls}-${hash} ${cls}`
   }
   return cls + (vars && vars.length > 0 ? ' ' + values(cls, vars) : '')
 }
diff --git a/src/inline.js b/src/inline.js
index ee7b8b43..7ccd3cfb 100644
--- a/src/inline.js
+++ b/src/inline.js
@@ -25,15 +25,6 @@ function getName (
   return parts.join('-')
 }
 
-function replaceVarsWithPlaceholders (rules: string[]): string[] {
-  return rules.map(rule =>
-    rule.replace(
-      /var\(--[A-Za-z0-9-_]+-([0-9]+)\)/gm,
-      (match, p1) => `xxx${p1}xxx`
-    )
-  )
-}
-
 function replaceApplyWithPlaceholders (rules: string[]): string[] {
   return rules.map(rule =>
     rule.replace(
@@ -46,30 +37,29 @@ function replaceApplyWithPlaceholders (rules: string[]): string[] {
 function createSrc (
   strs: string[],
   name: string,
-  hash: string,
-  expressionLength: number
-): { src: string, hasApply: boolean, hasVar: boolean } {
+  hash: string
+): { src: string, hasApply: boolean, otherMatches: number } {
   let hasApply = false
-  let hasVar = false
+  let otherMatches = 0
   const src = strs
     .reduce((arr, str, i) => {
       arr.push(str)
-      if (i !== expressionLength) {
+      if (i !== strs.length - 1) {
         // todo - test for preceding @apply
         let applyMatch = /@apply\s*$/gm.exec(str)
         if (applyMatch) {
           hasApply = true
           arr.push(`--${name}-${hash}-${i}`)
         } else {
-          hasVar = true
-          arr.push(`var(--${name}-${hash}-${i})`)
+          otherMatches++
+          arr.push(`xxx${i}xxx`)
         }
       }
       return arr
     }, [])
     .join('')
     .trim()
-  return { src, hasApply, hasVar }
+  return { src, hasApply, otherMatches }
 }
 
 export function inline (
@@ -82,7 +72,8 @@ export function inline (
   name: string,
   rules: string[],
   hasApply: boolean,
-  hasVar: boolean
+  hasVar: boolean,
+  hasOtherMatch: boolean
 } {
   let strs = quasi.quasis.map(x => x.value.cooked)
   let hash = hashArray([...strs]) // todo - add current filename?
@@ -91,21 +82,19 @@ export function inline (
     identifierName,
     prefix
   )
-  let { src, hasApply, hasVar } = createSrc(
-    strs,
+  let { src, hasApply, otherMatches } = createSrc(strs, name, hash)
+
+  let { rules, hasVar, hasOtherMatch } = parseCSS(`.${name}-${hash} { ${src} }`, {
+    inlineMode: inlineMode || hasApply,
+    otherMatches,
     name,
-    hash,
-    quasi.expressions.length
-  )
+    hash
+  })
 
-  let rules = parseCSS(`.${name}-${hash} { ${src} }`)
-  if (inlineMode || hasApply) {
-    rules = replaceVarsWithPlaceholders(rules)
-  }
   if (hasApply) {
     rules = replaceApplyWithPlaceholders(rules)
   }
-  return { hash, name, rules, hasApply, hasVar }
+  return { hash, name, rules, hasApply, hasVar, hasOtherMatch }
 }
 
 export function keyframes (
@@ -116,8 +105,7 @@ export function keyframes (
   hash: string,
   name: string,
   rules: string[],
-  hasApply: boolean,
-  hasVar: boolean
+  hasInterpolation: boolean
 } {
   const strs = quasi.quasis.map(x => x.value.cooked)
   const hash = hashArray([...strs])
@@ -126,55 +114,40 @@ export function keyframes (
     identifierName,
     prefix
   )
-  const { src, hasApply, hasVar } = createSrc(
-    strs,
+  const { src, hasApply, otherMatches } = createSrc(strs, name, hash)
+  let { rules, hasVar, hasOtherMatch } = parseCSS(`{ ${src} }`, {
+    nested: false,
+    inlineMode: true,
+    otherMatches,
     name,
-    hash,
-    quasi.expressions.length
-  )
-  let rules = parseCSS(`{ ${src} }`, { nested: false })
+    hash
+  })
   if (hasApply) {
     rules = replaceApplyWithPlaceholders(rules)
   }
-  if (hasVar) {
-    rules = replaceVarsWithPlaceholders(rules)
-  }
-  return { hash, name, rules, hasApply, hasVar }
+  return { hash, name, rules, hasInterpolation: hasVar || hasApply || hasOtherMatch }
 }
 
 export function fontFace (
   quasi: any
-): { rules: string[], hasApply: boolean, hasVar: boolean } {
+): { rules: string[], hasInterpolation: boolean } {
   let strs = quasi.quasis.map(x => x.value.cooked)
-  const { src, hasApply, hasVar } = createSrc(
-    strs,
-    'name',
-    'hash',
-    quasi.expressions.length
-  )
-  let rules = parseCSS(`@font-face {${src}}`)
-  if (hasVar) {
-    rules = replaceVarsWithPlaceholders(rules)
+  const { src, hasApply, otherMatches } = createSrc(strs, 'name', 'hash')
+  let { rules, hasVar, hasOtherMatch } = parseCSS(`@font-face {${src}}`, { otherMatches, inlineMode: true, name: 'name', hash: 'hash' })
+  if (hasApply) {
+    rules = replaceApplyWithPlaceholders(rules)
   }
-  return { rules, hasApply, hasVar }
+  return { rules, hasInterpolation: hasVar || hasApply || hasOtherMatch }
 }
 
 export function injectGlobal (
   quasi: any
-): { rules: string[], hasApply: boolean, hasVar: boolean } {
+): { rules: string[], hasInterpolation: boolean } {
   let strs = quasi.quasis.map(x => x.value.cooked)
-  const { src, hasVar, hasApply } = createSrc(
-    strs,
-    'name',
-    'hash',
-    quasi.expressions.length
-  )
-  let rules = parseCSS(src)
+  const { src, otherMatches, hasApply } = createSrc(strs, 'name', 'hash')
+  let { rules, hasVar, hasOtherMatch } = parseCSS(src, { otherMatches, inlineMode: true, name: 'name', hash: 'hash' })
   if (hasApply) {
     rules = replaceApplyWithPlaceholders(rules)
   }
-  if (hasVar) {
-    rules = replaceVarsWithPlaceholders(rules)
-  }
-  return { rules, hasApply, hasVar }
+  return { rules, hasInterpolation: hasVar || hasApply || hasOtherMatch }
 }
diff --git a/src/parser.js b/src/parser.js
index 67304f3a..8f254106 100644
--- a/src/parser.js
+++ b/src/parser.js
@@ -6,20 +6,45 @@ import autoprefix from 'styled-components/lib/utils/autoprefix'
 
 export function parseCSS (
   css: string,
-  options: { nested: boolean } = {
-    nested: true
+  options: {
+    nested?: boolean,
+    inlineMode: boolean,
+    otherMatches: number,
+    name: string,
+    hash: string
+  } = {
+    nested: true,
+    inlineMode: true,
+    otherMatches: 0,
+    name: 'name',
+    hash: 'hash'
   }
-): string[] {
+): { rules: string[], hasOtherMatch: boolean, hasVar: boolean } {
   // todo - handle errors
   const root = parse(css)
   if (options.nested !== false) postcssNested(root)
-  autoprefix(root)
+
+  let vars = 0
 
   root.walkDecls(decl => {
     if (decl.prop === 'name') decl.remove()
+    if (!options.inlineMode) {
+      const re = /xxx(\S)xxx/gm
+      const match = re.exec(decl.value)
+      if (match) {
+        vars++
+        decl.value = decl.value.replace(match[0], `var(--${options.name}-${options.hash}-${match[1]})`)
+      }
+    }
   })
 
-  return stringifyCSSRoot(root)
+  autoprefix(root)
+
+  return {
+    rules: stringifyCSSRoot(root),
+    hasOtherMatch: vars !== options.otherMatches,
+    hasVar: !!vars || !!(options.inlineMode && options.otherMatches)
+  }
 }
 
 function stringifyCSSRoot (root) {
diff --git a/src/styled.js b/src/react.js
similarity index 88%
rename from src/styled.js
rename to src/react.js
index 2d38a804..d04e4bbd 100644
--- a/src/styled.js
+++ b/src/react.js
@@ -11,7 +11,7 @@ export default function (tag, cls, vars = [], content) {
   function Styled (props) {
     const className = css(
       cls,
-      vars.map(v => (v && typeof v === 'function' ? v(props) : v)),
+      vars.map(v => (v && typeof v === 'function' ? v.cls || v(props) : v)),
       content
     )
 
@@ -30,6 +30,6 @@ export default function (tag, cls, vars = [], content) {
   const debugName = name === 'css' ? '' : `.${name}`
   const componentTag = tag.displayName || tag.name || 'Component'
   Styled.displayName = `styled(${componentTag}${debugName})`
-
+  Styled.cls = '.' + cls
   return Styled
 }
diff --git a/styled.js b/styled.js
deleted file mode 100644
index d61724c7..00000000
--- a/styled.js
+++ /dev/null
@@ -1,1 +0,0 @@
-module.exports = require('./lib/styled')


Test patch:
diff --git a/test/__snapshots__/css-prop.test.js.snap b/test/__snapshots__/css-prop.test.js.snap
index 541b222a..dfb6a341 100644
--- a/test/__snapshots__/css-prop.test.js.snap
+++ b/test/__snapshots__/css-prop.test.js.snap
@@ -7,7 +7,7 @@ exports[`css prop react basic 1`] = `
 }
 
 <p
-  className="css-tree-7sri7n-1yz7ela"
+  className="css-tree-7sri7n-1yz7ela css-tree-7sri7n"
 >
   hello world
 </p>
@@ -64,25 +64,25 @@ exports[`css prop react kitchen sink 1`] = `
 }
 
 <div
-  className="css__legacy-stuff css-tree-1170hdn-1o62cat"
+  className="css__legacy-stuff css-tree-1170hdn-1o62cat css-tree-1170hdn"
 >
   <h1
-    className="css-tree-wjhr0q-imh2fa"
+    className="css-tree-wjhr0q-imh2fa css-tree-wjhr0q"
   >
     BOOM
   </h1>
   <p
-    className="test_class1 css-tree-14jcta0-1fj87hp"
+    className="test_class1 css-tree-14jcta0-1fj87hp css-tree-14jcta0"
   >
     Hello
   </p>
   <p
-    className="test_class1 test___class45 css-tree-1gdxe9e-1vltlgj"
+    className="test_class1 test___class45 css-tree-1gdxe9e-1vltlgj css-tree-1gdxe9e"
   >
     World
   </p>
   <p
-    className="css-tree-1g8ltio-i3faqz"
+    className="css-tree-1g8ltio-i3faqz css-tree-1g8ltio"
   >
     hello world
   </p>
@@ -97,7 +97,7 @@ exports[`css prop react string expression 1`] = `
 }
 
 <p
-  className="css-tree-7sqgip-8upfq4"
+  className="css-tree-7sqgip-8upfq4 css-tree-7sqgip"
 >
   hello world
 </p>
diff --git a/test/__snapshots__/keyframes.test.js.snap b/test/__snapshots__/keyframes.test.js.snap
index 6eaf4c91..832a5245 100644
--- a/test/__snapshots__/keyframes.test.js.snap
+++ b/test/__snapshots__/keyframes.test.js.snap
@@ -52,7 +52,7 @@ exports[`keyframes renders 1`] = `
 }
 
 <h1
-  className="css-H1-o0kcx2-fy8ana"
+  className="css-H1-o0kcx2-fy8ana css-H1-o0kcx2"
 >
   hello world
 </h1>
diff --git a/test/__snapshots__/parser.test.js.snap b/test/__snapshots__/parser.test.js.snap
index 9dc968e8..05cc0c2c 100644
--- a/test/__snapshots__/parser.test.js.snap
+++ b/test/__snapshots__/parser.test.js.snap
@@ -1,8 +1,11 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`parser basic 1`] = `
-Array [
-  ".thing {
+Object {
+  "hasOtherMatch": false,
+  "hasVar": false,
+  "rules": Array [
+    ".thing {
   display: -webkit-box;
   display: -moz-box;
   display: -ms-flexbox;
@@ -14,12 +17,16 @@ Array [
   justify-content: center;
   width: var(--css-hash-0);
 }",
-]
-`
+  ],
+}
+`;
 
 exports[`parser fancy 1`] = `
-Array [
-  ".some-selector {
+Object {
+  "hasOtherMatch": false,
+  "hasVar": false,
+  "rules": Array [
+    ".some-selector {
   display: -webkit-box;
   display: -moz-box;
   display: -ms-flexbox;
@@ -31,35 +38,40 @@ Array [
   justify-content: center;
   width: var(--css-hash-0);
 }",
-  ".some-selector:hover {
+    ".some-selector:hover {
   background-color: green;
 }",
-  "@media (max-width: 500px) {
+    "@media (max-width: 500px) {
   .some-selector {
     height: var(--css-hash-1);
     position: fixed;
   }
 }",
-  "@media print {
+    "@media print {
   .some-selector {
     display: none;
   }
 }",
-  ".some-selector::before {
+    ".some-selector::before {
   color: blue;
   width: 20px;
   height: 20px;
   content: 'pseudo'
 }",
-]
-`
+  ],
+}
+`;
 
 exports[`parser static 1`] = `
-Array [
-  ".thing {
+Object {
+  "hasOtherMatch": false,
+  "hasVar": false,
+  "rules": Array [
+    ".thing {
   display: block;
   height: 50px;
   width: 30px
 }",
-]
-`
+  ],
+}
+`;
diff --git a/test/__snapshots__/styled.test.js.snap b/test/__snapshots__/react.test.js.snap
similarity index 60%
rename from test/__snapshots__/styled.test.js.snap
rename to test/__snapshots__/react.test.js.snap
index 8a6c3f0a..a179f0c9 100644
--- a/test/__snapshots__/styled.test.js.snap
+++ b/test/__snapshots__/react.test.js.snap
@@ -7,7 +7,7 @@ exports[`styled attr 1`] = `
 }
 
 <h1
-  className="css-H1-15vt02v-1n5h3v0"
+  className="css-H1-15vt02v-1n5h3v0 css-H1-15vt02v"
   fontSize={48}
 />
 `;
@@ -18,7 +18,7 @@ exports[`styled basic render 1`] = `
 }
 
 <h1
-  className="css-H1-8xpzga-1e133rd"
+  className="css-H1-8xpzga-1e133rd css-H1-8xpzga"
 >
   hello world
 </h1>
@@ -30,12 +30,42 @@ exports[`styled call expression 1`] = `
 }
 
 <h1
-  className="css-H1-13wdnau-xy96vj legacy__class"
+  className="css-H1-13wdnau-xy96vj css-H1-13wdnau legacy__class"
 >
   hello world
 </h1>
 `;
 
+exports[`styled component as selector 1`] = `
+.css-H1-13wdnau-xy96vj {
+  font-size: 20px;
+}
+
+.css-Thing-1kdnbhf-384ekg {
+  display: -webkit-box;
+  display: -moz-box;
+  display: -ms-flexbox;
+  display: -webkit-flex;
+  display: flex;
+}
+
+.css-Thing-1kdnbhf-384ekg .css-H1-13wdnau {
+  color: green;
+}
+
+<div
+  className="css-Thing-1kdnbhf-384ekg css-Thing-1kdnbhf"
+>
+  hello 
+  <h1
+    className="css-H1-13wdnau-xy96vj css-H1-13wdnau"
+  >
+    This will be green
+  </h1>
+   world
+</div>
+`;
+
 exports[`styled composition 1`] = `
 .css-H1-13wdnau-xy96vj {
   font-size: 20px;
@@ -46,7 +76,7 @@ exports[`styled composition 1`] = `
 }
 
 <h1
-  className="css-H1-13wdnau-xy96vj css-H2-vxb7tq-1gn1gp5 legacy__class"
+  className="css-H1-13wdnau-xy96vj css-H1-13wdnau css-H2-vxb7tq-1gn1gp5 css-H2-vxb7tq legacy__class"
 >
   hello world
 </h1>
@@ -64,7 +94,7 @@ exports[`styled fragments 1`] = `
 }
 
 <h1
-  className="css-H1-1qd8mfb-1u8p6i5 css-H2-idm3bz-361gww legacy__class"
+  className="css-H1-1qd8mfb-1u8p6i5 css-H1-1qd8mfb css-H2-idm3bz-361gww css-H2-idm3bz legacy__class"
   scale={2}
 >
   hello world
@@ -81,7 +111,7 @@ exports[`styled function in expression 1`] = `
 }
 
 <h1
-  className="css-H1-13wdnau-xy96vj css-H2-vxb7tq-1wx98rf legacy__class"
+  className="css-H1-13wdnau-xy96vj css-H1-13wdnau css-H2-vxb7tq-1wx98rf css-H2-vxb7tq legacy__class"
   scale={2}
 >
   hello world
@@ -104,7 +134,7 @@ exports[`styled higher order component 1`] = `
 }
 
 <div
-  className="css-Content-13wdnau-1z6hk6 css-onyx-wn422p-10htdr6"
+  className="css-Content-13wdnau-1z6hk6 css-Content-13wdnau css-onyx-wn422p-10htdr6 css-onyx-wn422p"
 />
 `;
 
@@ -114,7 +144,7 @@ exports[`styled innerRef 1`] = `
 }
 
 <h1
-  className="css-H1-ijh7uz-10q50rl"
+  className="css-H1-ijh7uz-10q50rl css-H1-ijh7uz"
   innerRef={[Function]}
 >
   hello world
@@ -127,7 +157,7 @@ exports[`styled name 1`] = `
 }
 
 <h1
-  className="css-FancyH1-1azfbv1-1fzd8kz"
+  className="css-FancyH1-1azfbv1-1fzd8kz css-FancyH1-1azfbv1"
 >
   hello world
 </h1>
@@ -139,7 +169,7 @@ exports[`styled no dynamic 1`] = `
 }
 
 <h1
-  className="css-H1-ijh7uz-10q50rl"
+  className="css-H1-ijh7uz-10q50rl css-H1-ijh7uz"
 >
   hello world
 </h1>
diff --git a/test/__snapshots__/server.test.js.snap b/test/__snapshots__/server.test.js.snap
index 65ca6a58..e7a784dc 100644
--- a/test/__snapshots__/server.test.js.snap
+++ b/test/__snapshots__/server.test.js.snap
@@ -12,7 +12,7 @@ Object {
   height: 50pxpx;
   width: 50pxpx;
   background-color: red }",
-  "html": "<main class=\\"css-Main-17vxl0k-1g2c78b\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"52123375\\"><img size=\\"30\\" class=\\"css-Image-z4ry8g-1qx25bk\\" data-reactid=\\"2\\"/><img size=\\"100\\" class=\\"css-Image-z4ry8g-141jzk4\\" data-reactid=\\"3\\"/><img class=\\"css-Image-z4ry8g-jpu7qc\\" data-reactid=\\"4\\"/></main>",
+  "html": "<main class=\\"css-Main-17vxl0k-1g2c78b css-Main-17vxl0k\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"-987402706\\"><img size=\\"30\\" class=\\"css-Image-z4ry8g-1qx25bk css-Image-z4ry8g\\" data-reactid=\\"2\\"/><img size=\\"100\\" class=\\"css-Image-z4ry8g-141jzk4 css-Image-z4ry8g\\" data-reactid=\\"3\\"/><img class=\\"css-Image-z4ry8g-jpu7qc css-Image-z4ry8g\\" data-reactid=\\"4\\"/></main>",
   "ids": Array [
     "1kfvr9c",
     "1g2c78b",
@@ -66,7 +66,7 @@ Object {
   height: 50pxpx;
   width: 50pxpx;
   background-color: red }",
-  "html": "<main class=\\"css-Main-17vxl0k-1g2c78b\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"52123375\\"><img size=\\"30\\" class=\\"css-Image-z4ry8g-1qx25bk\\" data-reactid=\\"2\\"/><img size=\\"100\\" class=\\"css-Image-z4ry8g-141jzk4\\" data-reactid=\\"3\\"/><img class=\\"css-Image-z4ry8g-jpu7qc\\" data-reactid=\\"4\\"/></main>",
+  "html": "<main class=\\"css-Main-17vxl0k-1g2c78b css-Main-17vxl0k\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"-987402706\\"><img size=\\"30\\" class=\\"css-Image-z4ry8g-1qx25bk css-Image-z4ry8g\\" data-reactid=\\"2\\"/><img size=\\"100\\" class=\\"css-Image-z4ry8g-141jzk4 css-Image-z4ry8g\\" data-reactid=\\"3\\"/><img class=\\"css-Image-z4ry8g-jpu7qc css-Image-z4ry8g\\" data-reactid=\\"4\\"/></main>",
   "ids": Array [],
   "rules": Array [
     Object {
diff --git a/test/__snapshots__/vue.test.js.snap b/test/__snapshots__/vue.test.js.snap
index 78e18d92..8efc97ba 100644
--- a/test/__snapshots__/vue.test.js.snap
+++ b/test/__snapshots__/vue.test.js.snap
@@ -2,13 +2,13 @@
 
 exports[`vue styled based on props 1`] = `
 "
-      <div id=\\"app\\"><h1 weight=\\"bold\\" class=\\"css-BasedOnProps-csm9t8-1qo3zsu\\"></h1></div>
+      <div id=\\"app\\"><h1 weight=\\"bold\\" class=\\"css-BasedOnProps-csm9t8-1qo3zsu css-BasedOnProps-csm9t8\\"></h1></div>
     "
 `;
 
 exports[`vue styled basic 1`] = `
 "
-      <div id=\\"app\\"><div class=\\"css-StyledComponent-1brvt4i-ulwf5d\\"></div></div>
+      <div id=\\"app\\"><div class=\\"css-StyledComponent-1brvt4i-ulwf5d css-StyledComponent-1brvt4i\\"></div></div>
     "
 `;
 
@@ -28,24 +28,24 @@ exports[`vue styled creates the correct styles 1`] = `
 
 exports[`vue styled custom external component 1`] = `
 "
-      <div id=\\"app\\"><div class=\\"css-NonHtmlComponent-15287sz-1wq2ikh\\"><div></div><div></div><h1>some text</h1><div></div></div></div>
+      <div id=\\"app\\"><div class=\\"css-NonHtmlComponent-15287sz-1wq2ikh css-NonHtmlComponent-15287sz\\"><div></div><div></div><h1>some text</h1><div></div></div></div>
     "
 `;
 
 exports[`vue styled with class 1`] = `
 "
-      <div id=\\"app\\"><div class=\\"some-class css-StyledComponent-1brvt4i-ulwf5d\\"></div></div>
+      <div id=\\"app\\"><div class=\\"some-class css-StyledComponent-1brvt4i-ulwf5d css-StyledComponent-1brvt4i\\"></div></div>
     "
 `;
 
 exports[`vue styled with properties 1`] = `
 "
-      <div id=\\"app\\"><div aria-label=\\"label\\" class=\\"css-StyledComponent-1brvt4i-ulwf5d\\"></div></div>
+      <div id=\\"app\\"><div aria-label=\\"label\\" class=\\"css-StyledComponent-1brvt4i-ulwf5d css-StyledComponent-1brvt4i\\"></div></div>
     "
 `;
 
 exports[`vue styled with style 1`] = `
 "
-      <div id=\\"app\\"><div class=\\"css-StyledComponent-1brvt4i-ulwf5d\\" style=\\"display: none;\\"></div></div>
+      <div id=\\"app\\"><div class=\\"css-StyledComponent-1brvt4i-ulwf5d css-StyledComponent-1brvt4i\\" style=\\"display: none;\\"></div></div>
     "
 `;
diff --git a/test/babel/__snapshots__/css.test.js.snap b/test/babel/__snapshots__/css.test.js.snap
index bc3dfd6a..2e95e135 100644
--- a/test/babel/__snapshots__/css.test.js.snap
+++ b/test/babel/__snapshots__/css.test.js.snap
@@ -33,6 +33,14 @@ const cls2 = css(\\"css-cls2-1b4c3x0\\", [frag, fragB], function createEmotionRu
 });"
 `;
 
+exports[`babel css extract interpolation in selector 1`] = `
+"
+const cls2 = css(\\"css-cls2-16o34vq\\", [className], function createEmotionRules(x0) {
+  return [\`.css-cls2-16o34vq { margin: 12px 48px;
+        color: #ffffff; }\`, \`.css-cls2-16o34vq .\${x0} { display: none; }\`];
+});"
+`;
+
 exports[`babel css inline css basic 1`] = `
 "
 css(\\"css-cu78iu\\", [widthVar], function createEmotionRules(x0) {
@@ -65,3 +73,11 @@ const cls2 = css(\\"css-cls2-1b4c3x0\\", [frag, fragB], function createEmotionRu
         color: #ffffff; }\`];
 });"
 `;
+
+exports[`babel css inline interpolation in selector 1`] = `
+"
+const cls2 = css(\\"css-cls2-16o34vq\\", [className], function createEmotionRules(x0) {
+  return [\`.css-cls2-16o34vq { margin: 12px 48px;
+        color: #ffffff; }\`, \`.css-cls2-16o34vq .\${x0} { display: none; }\`];
+});"
+`;
diff --git a/test/babel/__snapshots__/font-face.test.js.snap b/test/babel/__snapshots__/font-face.test.js.snap
index 3f286407..ace54cfd 100644
--- a/test/babel/__snapshots__/font-face.test.js.snap
+++ b/test/babel/__snapshots__/font-face.test.js.snap
@@ -42,6 +42,16 @@ fontFace([\`@font-face {font-family: MyHelvetica;
           font-weight: bold;}\`]);"
 `;
 
+exports[`fontFace babel inline fragment 1`] = `
+"
+const frag = \`font-family: \${fontFamilyName};
+          src: local(\\"Helvetica Neue Bold\\"),
+               local(\\"HelveticaNeue-Bold\\"),
+               url(MgOpenModernaBold.ttf);
+          font-weight: bold;\`;
+fontFace([\`@font-face {\${frag}}\`]);"
+`;
+
 exports[`fontFace babel inline interpolation 1`] = `
 "
 fontFace([\`@font-face {font-family: \${fontFamilyName};
diff --git a/test/babel/__snapshots__/styled.test.js.snap b/test/babel/__snapshots__/styled.test.js.snap
index da119ba5..1625b3e2 100644
--- a/test/babel/__snapshots__/styled.test.js.snap
+++ b/test/babel/__snapshots__/styled.test.js.snap
@@ -133,4 +133,12 @@ exports[`babel styled component inline no dynamic 1`] = `
 });"
 `;
 
+exports[`babel styled component inline no dynamic 2`] = `
+"
+const SomeComponent = \\"div\\";
+styled(\\"h1\\", \\"css-byjn67\\", [SomeComponent], function createEmotionStyledRules(x0) {
+  return [\`.css-byjn67 { color: blue; }\`, \`.css-byjn67 \${x0} { color: green; }\`];
+});"
+`;
+
 exports[`babel styled component inline no use 1`] = `"\\"h1\\";"`;
diff --git a/test/babel/css.test.js b/test/babel/css.test.js
index e57d7955..68c991c4 100644
--- a/test/babel/css.test.js
+++ b/test/babel/css.test.js
@@ -41,6 +41,21 @@ describe('babel css', () => {
       })
       expect(code).toMatchSnapshot()
     })
+    test('interpolation in selector', () => {
+      const basic = `
+        const cls2 = css\`
+        margin: 12px 48px;
+        color: #ffffff;
+        .\${className} {
+          display: none;
+        }
+        \`
+      `
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]]
+      })
+      expect(code).toMatchSnapshot()
+    })
   })
   describe('extract', () => {
     test('css basic', () => {
@@ -82,5 +97,24 @@ describe('babel css', () => {
       expect(code).toMatchSnapshot()
       expect(fs.writeFileSync).toHaveBeenCalledTimes(1)
     })
+
+    test('interpolation in selector', () => {
+      const basic = `
+        const cls2 = css\`
+        margin: 12px 48px;
+        color: #ffffff;
+        .\${className} {
+          display: none;
+        }
+        \`
+      `
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]],
+        filename: __filename,
+        babelrc: false
+      })
+      expect(code).toMatchSnapshot()
+      expect(fs.writeFileSync).toHaveBeenCalledTimes(1)
+    })
   })
 })
diff --git a/test/babel/font-face.test.js b/test/babel/font-face.test.js
index 79cd3d88..39cd5514 100644
--- a/test/babel/font-face.test.js
+++ b/test/babel/font-face.test.js
@@ -36,6 +36,23 @@ describe('fontFace babel', () => {
       })
       expect(code).toMatchSnapshot()
     })
+    test('fragment', () => {
+      const basic = `
+        const frag = fragment\`
+          font-family: \${fontFamilyName};
+          src: local("Helvetica Neue Bold"),
+               local("HelveticaNeue-Bold"),
+               url(MgOpenModernaBold.ttf);
+          font-weight: bold;
+        \`
+        fontFace\`
+          @apply \${frag}
+      \`;`
+      const { code } = babel.transform(basic, {
+        plugins: [[plugin]]
+      })
+      expect(code).toMatchSnapshot()
+    })
   })
   describe('extract', () => {
     test('basic', () => {
diff --git a/test/babel/styled.test.js b/test/babel/styled.test.js
index 92488e75..6eea5ba8 100644
--- a/test/babel/styled.test.js
+++ b/test/babel/styled.test.js
@@ -25,6 +25,21 @@ describe('babel styled component', () => {
       expect(code).toMatchSnapshot()
     })
 
+    test('no dynamic', () => {
+      const basic = `
+      const SomeComponent = styled.div\`\`
+      styled.h1\`
+        color:blue;
+        \${SomeComponent} {
+          color: green;
+        }
+      \``
+      const { code } = babel.transform(basic, {
+        plugins: [plugin]
+      })
+      expect(code).toMatchSnapshot()
+    })
+
     test('basic', () => {
       const basic = "const H1 = styled.h1`font-size: ${fontSize + 'px'};`"
       const { code } = babel.transform(basic, {
diff --git a/test/extract/__snapshots__/extract.test.js.snap b/test/extract/__snapshots__/extract.test.js.snap
index aaa515e2..fcdf78e5 100644
--- a/test/extract/__snapshots__/extract.test.js.snap
+++ b/test/extract/__snapshots__/extract.test.js.snap
@@ -60,7 +60,7 @@ exports[`styled fragments 1`] = `
 }
 
 <h1
-  className="css-H1-1qd8mfb-1u8p6i5 css-H2-idm3bz legacy__class"
+  className="css-H1-1qd8mfb-1u8p6i5 css-H1-1qd8mfb css-H2-idm3bz legacy__class"
   scale={2}
 >
   hello world
@@ -100,7 +100,7 @@ exports[`styled higher order component 1`] = `
 }
 
 <div
-  className="css-Content-13wdnau vars-1x8swp css-onyx-wn422p-10htdr6"
+  className="css-Content-13wdnau vars-1x8swp css-onyx-wn422p-10htdr6 css-onyx-wn422p"
 />
 `;
 
diff --git a/test/extract/extract.test.js b/test/extract/extract.test.js
index 365ee3ba..0ec3a3b1 100644
--- a/test/extract/extract.test.js
+++ b/test/extract/extract.test.js
@@ -5,7 +5,7 @@ import renderer from 'react-test-renderer'
 import { basename } from 'path'
 import { matcher, serializer } from '../../jest-utils'
 import { fragment, injectGlobal, css } from '../../src/index'
-import styled from '../../src/styled'
+import styled from '../../src/react'
 
 expect.addSnapshotSerializer(serializer)
 expect.extend(matcher)
diff --git a/test/keyframes.test.js b/test/keyframes.test.js
index 547fb81c..569d3b08 100644
--- a/test/keyframes.test.js
+++ b/test/keyframes.test.js
@@ -3,7 +3,7 @@ import React from 'react'
 import renderer from 'react-test-renderer'
 import { matcher, serializer } from '../jest-utils'
 import { keyframes, sheet } from '../src/index'
-import styled from '../src/styled'
+import styled from '../src/react'
 
 expect.addSnapshotSerializer(serializer)
 expect.extend(matcher)
diff --git a/test/styled.test.js b/test/react.test.js
similarity index 90%
rename from test/styled.test.js
rename to test/react.test.js
index 51f7c4bf..711ff57f 100644
--- a/test/styled.test.js
+++ b/test/react.test.js
@@ -6,7 +6,7 @@ import { matcher, serializer } from '../jest-utils'
 
 // eslint-disable-next-line no-unused-vars
 import css, { fragment } from '../src/index'
-import styled from '../src/styled'
+import styled from '../src/react'
 
 expect.addSnapshotSerializer(serializer)
 expect.extend(matcher)
@@ -88,6 +88,26 @@ describe('styled', () => {
     expect(tree).toMatchSnapshotWithEmotion()
   })
 
+  test('component as selector', () => {
+    const fontSize = 20
+    const H1 = styled.h1`
+      font-size: ${fontSize}px;
+    `
+
+    const Thing = styled.div`
+      display: flex;
+      ${H1} {
+        color: green;
+      }
+    `
+
+    const tree = renderer
+      .create(<Thing>hello <H1>This will be green</H1> world</Thing>)
+      .toJSON()
+
+    expect(tree).toMatchSnapshotWithEmotion()
+  })
+
   test('function in expression', () => {
     const fontSize = 20
     const H1 = styled('h1')`
diff --git a/test/server.test.js b/test/server.test.js
index 620ffa57..9daee917 100644
--- a/test/server.test.js
+++ b/test/server.test.js
@@ -4,7 +4,7 @@
 /* eslint-env jest */
 import React from 'react'
 import { renderToString } from 'react-dom/server'
-import styled from '../src/styled'
+import styled from '../src/react'
 import { css, sheet } from '../src/index'
 import { renderStatic, renderStaticOptimized } from '../src/server'