Repository: tkh44/emotion (js)

Issue:
SSR + hyrdate causes two sets of <style> tags to be created
See example repo and follow the steps to install: https://github.com/zeit/next.js/tree/master/examples/with-emotion

- `emotion` version (same as that in the example repo, but also tried `6.0.3` and `7.0.4`)
- `react` version (`15.6.1` per the example repo)

What happened:

You can see below that there are two sets of `<style>` tags injected and it appears emotion is not respecting the IDs that were passed in with `hydrate`.

6.0.3:

![image](https://user-images.githubusercontent.com/626664/28800181-eaf58a6a-768e-11e7-8873-24acf1d2ce35.png)

7.0.4:

![image](https://user-images.githubusercontent.com/626664/28800259-40d211b0-768f-11e7-989c-f0601673f486.png)

I've also had this with my project using emotion but thought it would be easier to replicate by referring to the example repo.

Pull request description:
Fix SSR hydration
<!--
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**:

Fix SSR hydration for `keyframes`, `fontFace` and `injectGlobal`

I also added some tests for hydration to make sure styles don't get reinserted

Also, raw rules had hashes that were longer numbers so I changed the hashString export to call `.toString(36)` on the hash.

<!-- Why are these changes necessary? -->
**Why**:

Because otherwise rules get reinserted on the client.

<!-- How were these changes implemented? -->
**How**:

Don't filter out rules with the type `raw` or `keyframes` in `extractCritical`

<!-- 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 -->

Closes #216 

I found an interesting problem while I was doing this which I'm not entirely sure how to solve.

Let's say you have a component which composes a class.

```jsx
const cls1 = css`
  color: hotpink;
`
const SomeComponent = styled.div`
  composes: ${cls1};
  background-color: yellow;
`
```

`cls1` won't be in the html so it won't be included in the `css` from `extractCritical` so it's inserted on the client.

Maybe we could solve this by inserting rules in `toString` so if they're not used as strings they're not inserted?

I'm not sure if this really matters that much since `insertRule` is so fast but in a large app with lots of `composes` lots of rules will be inserted that are never actually used.

@threepointone Would this also be a problem in glamor? if not how does glamor solve it?
```jsx
const cls1 = css({ color: 'hotpink' })
const classThatWillBeInHtml = css({ backgroundColor: 'yellow' }, cls1)
```


Patch:
diff --git a/src/hash.js b/src/hash.js
index b2fbbfad..7d00ac63 100644
--- a/src/hash.js
+++ b/src/hash.js
@@ -3,7 +3,7 @@
 
 export function hashArray (arr: string[]): string {
   let str = arr.join(',')
-  return hashString(str, str.length).toString(36)
+  return hashString(str)
 }
 
 export function hashObject (...objs: Array<{ [string]: any }>): string {
@@ -11,10 +11,14 @@ export function hashObject (...objs: Array<{ [string]: any }>): string {
   for (let i = 0; i < objs.length; i++) {
     str += JSON.stringify(objs[i])
   }
-  return hashString(str, str.length).toString(36)
+  return hashString(str)
 }
 
-export function hashString (str: string, seed: number) {
+export function hashString (str: string) {
+  return hash(str, str.length).toString(36)
+}
+
+function hash (str: string, seed: number) {
   let m = 0x5bd1e995
   let r = 24
   let h = seed ^ str.length
diff --git a/src/index.js b/src/index.js
index 937ba016..f647a2c1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -77,7 +77,7 @@ export function css (objs: any, vars: Array<any>, content: () => Array<any>) {
 
 function insertRawRule (css: string) {
   let spec = {
-    id: hash(css, css.length),
+    id: hash(css),
     css,
     type: 'raw'
   }
diff --git a/src/server.js b/src/server.js
index 8d653b12..da3bfc7c 100644
--- a/src/server.js
+++ b/src/server.js
@@ -26,13 +26,18 @@ export function extractCritical (html) {
   }
 
   o.rules = sheet.sheet.cssRules.slice().filter(x => {
+    RGX.lastIndex = 0
     let match = RGX.exec(x.cssText)
     const ret = match == null || ids[match[1]] || false
-    RGX.lastIndex = 0
     return ret
   })
 
-  o.ids = keys(inserted).filter(id => !!ids[id])
+  o.ids = keys(inserted).filter(
+    id =>
+      !!ids[id] ||
+      sheet.registered[id].type === 'raw' ||
+      sheet.registered[id].type === 'keyframes'
+  )
 
   let css = ''
   forEach(o.rules, x => (css += x.cssText))


Test patch:
diff --git a/test/__snapshots__/server.test.js.snap b/test/__snapshots__/server.test.js.snap
index 0bd3ce14..aa81c4a9 100644
--- a/test/__snapshots__/server.test.js.snap
+++ b/test/__snapshots__/server.test.js.snap
@@ -2,21 +2,36 @@
 
 exports[`extractCritical returns static css 1`] = `
 Object {
-  "css": ".no-prefix {display:-webkit-box; display: -ms-flexbox; display: flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.css-71uu79{display:-webkit-box; display: -ms-flexbox; display: flex;}.css-l2qi8{border-radius:50%;height:50px;width:50px;background-color:red;}",
-  "html": "<main class=\\"css-13bzyir0 css-71uu79\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"452416722\\"><img size=\\"30\\" class=\\"css-13bzyir1 css-l2qi8\\" data-reactid=\\"2\\"/><img size=\\"100\\" class=\\"css-13bzyir1 css-l2qi8\\" data-reactid=\\"3\\"/><img class=\\"css-13bzyir1 css-l2qi8\\" data-reactid=\\"4\\"/></main>",
+  "css": "@font-face{font-family:'Patrick Hand SC';font-style:normal;font-weight:400;src:local('Patrick Hand SC'), local('PatrickHandSC-Regular'), url(https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE.woff2) format('woff2');unicode-range:U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;}@-webkit-keyframes animation_7jdctn{from, 20%, 53%, 80%, to {-webkit-animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}40%, 43% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -30px, 0);transform:translate3d(0, -30px, 0);}70% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -15px, 0);transform:translate3d(0, -15px, 0);}90% {-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0);}}@keyframes animation_7jdctn{from, 20%, 53%, 80%, to {-webkit-animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}40%, 43% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -30px, 0);transform:translate3d(0, -30px, 0);}70% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -15px, 0);transform:translate3d(0, -15px, 0);}90% {-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0);}}.no-prefix {display:-webkit-box; display: -ms-flexbox; display: flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.css-1kjabrg{color:hotpink;display:-webkit-box; display: -ms-flexbox; display: flex;}.css-1kjabrg:hover{color:white;background-color:lightgray;border-color:aqua;-webkit-box-shadow:-15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;box-shadow:-15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;}.css-1j6ha19{-webkit-animation:animation_7jdctn;animation:animation_7jdctn;border-radius:50%;height:50px;width:50px;background-color:red;}",
+  "html": "<main class=\\"css-fcaqzd0 css-1kjabrg\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"-997632446\\"><img size=\\"30\\" class=\\"css-fcaqzd1 css-1j6ha19\\" data-reactid=\\"2\\"/><img size=\\"100\\" class=\\"css-fcaqzd1 css-1j6ha19\\" data-reactid=\\"3\\"/><img class=\\"css-fcaqzd1 css-1j6ha19\\" data-reactid=\\"4\\"/></main>",
   "ids": Array [
-    "71uu79",
-    "l2qi8",
+    "1vx6krt",
+    "7jdctn",
+    "1tybtvv",
+    "1kjabrg",
+    "1j6ha19",
   ],
   "rules": Array [
+    Object {
+      "cssText": "@font-face{font-family:'Patrick Hand SC';font-style:normal;font-weight:400;src:local('Patrick Hand SC'), local('PatrickHandSC-Regular'), url(https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE.woff2) format('woff2');unicode-range:U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;}",
+    },
+    Object {
+      "cssText": "@-webkit-keyframes animation_7jdctn{from, 20%, 53%, 80%, to {-webkit-animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}40%, 43% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -30px, 0);transform:translate3d(0, -30px, 0);}70% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -15px, 0);transform:translate3d(0, -15px, 0);}90% {-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0);}}",
+    },
+    Object {
+      "cssText": "@keyframes animation_7jdctn{from, 20%, 53%, 80%, to {-webkit-animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}40%, 43% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -30px, 0);transform:translate3d(0, -30px, 0);}70% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -15px, 0);transform:translate3d(0, -15px, 0);}90% {-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0);}}",
+    },
     Object {
       "cssText": ".no-prefix {display:-webkit-box; display: -ms-flexbox; display: flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}",
     },
     Object {
-      "cssText": ".css-71uu79{display:-webkit-box; display: -ms-flexbox; display: flex;}",
+      "cssText": ".css-1kjabrg{color:hotpink;display:-webkit-box; display: -ms-flexbox; display: flex;}",
     },
     Object {
-      "cssText": ".css-l2qi8{border-radius:50%;height:50px;width:50px;background-color:red;}",
+      "cssText": ".css-1kjabrg:hover{color:white;background-color:lightgray;border-color:aqua;-webkit-box-shadow:-15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;box-shadow:-15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;}",
+    },
+    Object {
+      "cssText": ".css-1j6ha19{-webkit-animation:animation_7jdctn;animation:animation_7jdctn;border-radius:50%;height:50px;width:50px;background-color:red;}",
     },
   ],
 }
@@ -24,18 +39,84 @@ Object {
 
 exports[`extractCritical returns static css 2`] = `
 Object {
-  "css": ".no-prefix {display:-webkit-box; display: -ms-flexbox; display: flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.css-71uu79{display:-webkit-box; display: -ms-flexbox; display: flex;}",
-  "html": "<main class=\\"css-13bzyir0 css-71uu79\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"1740055930\\"><div data-reactid=\\"2\\">Hello</div></main>",
+  "css": "@font-face{font-family:'Patrick Hand SC';font-style:normal;font-weight:400;src:local('Patrick Hand SC'), local('PatrickHandSC-Regular'), url(https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE.woff2) format('woff2');unicode-range:U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;}@-webkit-keyframes animation_7jdctn{from, 20%, 53%, 80%, to {-webkit-animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}40%, 43% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -30px, 0);transform:translate3d(0, -30px, 0);}70% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -15px, 0);transform:translate3d(0, -15px, 0);}90% {-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0);}}@keyframes animation_7jdctn{from, 20%, 53%, 80%, to {-webkit-animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}40%, 43% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -30px, 0);transform:translate3d(0, -30px, 0);}70% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -15px, 0);transform:translate3d(0, -15px, 0);}90% {-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0);}}.no-prefix {display:-webkit-box; display: -ms-flexbox; display: flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.css-1kjabrg{color:hotpink;display:-webkit-box; display: -ms-flexbox; display: flex;}.css-1kjabrg:hover{color:white;background-color:lightgray;border-color:aqua;-webkit-box-shadow:-15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;box-shadow:-15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;}",
+  "html": "<main class=\\"css-fcaqzd0 css-1kjabrg\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"-1456331201\\"><div data-reactid=\\"2\\">Hello</div></main>",
   "ids": Array [
-    "71uu79",
+    "1vx6krt",
+    "7jdctn",
+    "1tybtvv",
+    "1kjabrg",
   ],
   "rules": Array [
+    Object {
+      "cssText": "@font-face{font-family:'Patrick Hand SC';font-style:normal;font-weight:400;src:local('Patrick Hand SC'), local('PatrickHandSC-Regular'), url(https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE.woff2) format('woff2');unicode-range:U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;}",
+    },
+    Object {
+      "cssText": "@-webkit-keyframes animation_7jdctn{from, 20%, 53%, 80%, to {-webkit-animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}40%, 43% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -30px, 0);transform:translate3d(0, -30px, 0);}70% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -15px, 0);transform:translate3d(0, -15px, 0);}90% {-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0);}}",
+    },
+    Object {
+      "cssText": "@keyframes animation_7jdctn{from, 20%, 53%, 80%, to {-webkit-animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}40%, 43% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -30px, 0);transform:translate3d(0, -30px, 0);}70% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -15px, 0);transform:translate3d(0, -15px, 0);}90% {-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0);}}",
+    },
     Object {
       "cssText": ".no-prefix {display:-webkit-box; display: -ms-flexbox; display: flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}",
     },
     Object {
-      "cssText": ".css-71uu79{display:-webkit-box; display: -ms-flexbox; display: flex;}",
+      "cssText": ".css-1kjabrg{color:hotpink;display:-webkit-box; display: -ms-flexbox; display: flex;}",
+    },
+    Object {
+      "cssText": ".css-1kjabrg:hover{color:white;background-color:lightgray;border-color:aqua;-webkit-box-shadow:-15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;box-shadow:-15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;}",
     },
   ],
 }
 `;
+
+exports[`hydration only rules that are not in the critical css are inserted 1`] = `
+Object {
+  "css": "@font-face{font-family:'Patrick Hand SC';font-style:normal;font-weight:400;src:local('Patrick Hand SC'), local('PatrickHandSC-Regular'), url(https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE.woff2) format('woff2');unicode-range:U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;}@-webkit-keyframes animation_7jdctn{from, 20%, 53%, 80%, to {-webkit-animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}40%, 43% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -30px, 0);transform:translate3d(0, -30px, 0);}70% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -15px, 0);transform:translate3d(0, -15px, 0);}90% {-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0);}}@keyframes animation_7jdctn{from, 20%, 53%, 80%, to {-webkit-animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}40%, 43% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -30px, 0);transform:translate3d(0, -30px, 0);}70% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -15px, 0);transform:translate3d(0, -15px, 0);}90% {-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0);}}.no-prefix {display:-webkit-box; display: -ms-flexbox; display: flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.css-1kjabrg{color:hotpink;display:-webkit-box; display: -ms-flexbox; display: flex;}.css-1kjabrg:hover{color:white;background-color:lightgray;border-color:aqua;-webkit-box-shadow:-15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;box-shadow:-15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;}.css-1j6ha19{-webkit-animation:animation_7jdctn;animation:animation_7jdctn;border-radius:50%;height:50px;width:50px;background-color:red;}",
+  "html": "<main class=\\"css-fcaqzd0 css-1kjabrg\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"-997632446\\"><img size=\\"30\\" class=\\"css-fcaqzd1 css-1j6ha19\\" data-reactid=\\"2\\"/><img size=\\"100\\" class=\\"css-fcaqzd1 css-1j6ha19\\" data-reactid=\\"3\\"/><img class=\\"css-fcaqzd1 css-1j6ha19\\" data-reactid=\\"4\\"/></main>",
+  "ids": Array [
+    "1vx6krt",
+    "7jdctn",
+    "1tybtvv",
+    "1kjabrg",
+    "1j6ha19",
+  ],
+  "rules": Array [
+    Object {
+      "cssText": "@font-face{font-family:'Patrick Hand SC';font-style:normal;font-weight:400;src:local('Patrick Hand SC'), local('PatrickHandSC-Regular'), url(https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE.woff2) format('woff2');unicode-range:U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;}",
+    },
+    Object {
+      "cssText": "@-webkit-keyframes animation_7jdctn{from, 20%, 53%, 80%, to {-webkit-animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}40%, 43% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -30px, 0);transform:translate3d(0, -30px, 0);}70% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -15px, 0);transform:translate3d(0, -15px, 0);}90% {-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0);}}",
+    },
+    Object {
+      "cssText": "@keyframes animation_7jdctn{from, 20%, 53%, 80%, to {-webkit-animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);animation-timing-function:cubic-bezier(0.215, 0.610, 0.355, 1.000);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}40%, 43% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -30px, 0);transform:translate3d(0, -30px, 0);}70% {-webkit-animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);animation-timing-function:cubic-bezier(0.755, 0.050, 0.855, 0.060);-webkit-transform:translate3d(0, -15px, 0);transform:translate3d(0, -15px, 0);}90% {-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0);}}",
+    },
+    Object {
+      "cssText": ".no-prefix {display:-webkit-box; display: -ms-flexbox; display: flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}",
+    },
+    Object {
+      "cssText": ".css-1kjabrg{color:hotpink;display:-webkit-box; display: -ms-flexbox; display: flex;}",
+    },
+    Object {
+      "cssText": ".css-1kjabrg:hover{color:white;background-color:lightgray;border-color:aqua;-webkit-box-shadow:-15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;box-shadow:-15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;}",
+    },
+    Object {
+      "cssText": ".css-1j6ha19{-webkit-animation:animation_7jdctn;animation:animation_7jdctn;border-radius:50%;height:50px;width:50px;background-color:red;}",
+    },
+  ],
+}
+`;
+
+exports[`hydration only rules that are not in the critical css are inserted 2`] = `
+Array [
+  Object {
+    "cssText": ".css-hjkr8p{color:hotpink;}",
+  },
+  Object {
+    "cssText": ".css-hjkr8p:hover{color:white;background-color:lightgray;border-color:aqua;-webkit-box-shadow:-15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;box-shadow:-15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;}",
+  },
+  Object {
+    "cssText": ".css-trriyv{display:none;name:unused-class;}",
+  },
+]
+`;
diff --git a/test/server.test.js b/test/server.test.js
index 7b193996..ca043fcb 100644
--- a/test/server.test.js
+++ b/test/server.test.js
@@ -5,50 +5,109 @@
 import React from 'react'
 import { renderToString } from 'react-dom/server'
 import styled from '../src/react'
-import { css, injectGlobal } from '../src/index'
+import { css, injectGlobal, keyframes, flush, hydrate, fontFace, sheet } from '../src/index'
 import { extractCritical } from '../src/server'
 
-const color = 'red'
+const getComponents = () => {
+  const color = 'red'
 
-const Main = styled.main`display: flex;`
+  fontFace`
+    font-family: 'Patrick Hand SC';
+    font-style: normal;
+    font-weight: 400;
+    src: local('Patrick Hand SC'), local('PatrickHandSC-Regular'), url(https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE.woff2) format('woff2');
+    unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
+  `
 
-const Image = styled.img`
-  border-radius: 50%;
-  height: 50px;
-  width: 50px;
-  background-color: ${color};
-`
+  const bounce = keyframes`
+    from, 20%, 53%, 80%, to {
+      animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+      transform: translate3d(0,0,0);
+    }
 
-// this will not be included since it's not used
-css`
-  display: none;
-  name: unused-class;
-`
+    40%, 43% {
+      animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+      transform: translate3d(0, -30px, 0);
+    }
 
-// this will be included in both because it doesn't have the css- prefix
+    70% {
+      animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+      transform: translate3d(0, -15px, 0);
+    }
 
-injectGlobal`
-  .no-prefix {
+    90% {
+      transform: translate3d(0,-4px,0);
+    }
+  `
+
+  const hoverStyles = css`
+    color: hotpink;
+    &:hover {
+      color: white;
+      background-color: lightgray;
+      border-color: aqua;
+      box-shadow: -15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue;
+    }
+  `
+
+  const Main = styled.main`
+    composes: ${hoverStyles};
     display: flex;
-    justify-content: center;
-  }
-`
-
-const Page1 = () =>
-  <Main>
-    <Image size={30} />
-    <Image size={100} />
-    <Image />
-  </Main>
-
-const Page2 = () =>
-  <Main>
-    <div>Hello</div>
-  </Main>
+  `
+
+  const Image = styled.img`
+    animation: ${bounce};
+    border-radius: 50%;
+    height: 50px;
+    width: 50px;
+    background-color: ${color};
+  `
+
+  // this will not be included since it's not used
+  css`
+    display: none;
+    name: unused-class;
+  `
+
+  // this will be included in both because it doesn't have the css- prefix
+
+  injectGlobal`
+    .no-prefix {
+      display: flex;
+      justify-content: center;
+    }
+  `
+
+  const Page1 = () =>
+    <Main>
+      <Image size={30} />
+      <Image size={100} />
+      <Image />
+    </Main>
+
+  const Page2 = () =>
+    <Main>
+      <div>Hello</div>
+    </Main>
+  return { Page1, Page2 }
+}
 
 describe('extractCritical', () => {
   test('returns static css', () => {
+    const { Page1, Page2 } = getComponents()
     expect(extractCritical(renderToString(<Page1 />))).toMatchSnapshot()
     expect(extractCritical(renderToString(<Page2 />))).toMatchSnapshot()
   })
 })
+describe('hydration', () => {
+  test('only rules that are not in the critical css are inserted', () => {
+    const { Page1 } = getComponents()
+    const { html, ids, css, rules } = extractCritical(renderToString(<Page1 />))
+    expect({ html, ids, css, rules }).toMatchSnapshot()
+    flush()
+    hydrate(ids)
+    const { Page1: NewPage1 } = getComponents()
+    renderToString(<NewPage1 />)
+    expect(sheet.sheet.cssRules).toMatchSnapshot()
+  })
+})