Repository: tkh44/emotion
Pull request #31 (2017-06-28 01:13:34)

Add SSR/Critical CSS for inline mode
This ~~isn't done yet~~ is done, I just copied glamor's implementation. The tests are really simple ~~and I haven't tested it outside of jest yet~~ but it's a start and it ~~should theoretically~~ does work.

This will only work with inline mode since there will be imports to css files and node won't know what to do with that.

~~This also fixes the `fontFace` test.~~ Reverted to avoid merge conflicts with #39 


Patch:
diff --git a/package.json b/package.json
index 532fd997..3df2ff15 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,8 @@
     "src",
     "dist",
     "lib",
-    "styled.js"
+    "styled.js",
+    "server.js"
   ],
   "scripts": {
     "build": "babel src -d lib",
diff --git a/server.js b/server.js
new file mode 100644
index 00000000..c3f14437
--- /dev/null
+++ b/server.js
@@ -0,0 +1,1 @@
+module.exports = require('./lib/server')
diff --git a/src/index.js b/src/index.js
index 22e54ce8..40611c1f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -5,7 +5,7 @@ import hashArray from './hash'
 export const sheet = new StyleSheet()
 sheet.inject()
 
-let inserted: { [string]: boolean | void } = {}
+export let inserted: { [string]: boolean | void } = {}
 
 type inputVar = string | number
 
diff --git a/src/server.js b/src/server.js
new file mode 100644
index 00000000..a759f5cc
--- /dev/null
+++ b/src/server.js
@@ -0,0 +1,51 @@
+import { sheet, inserted } from './index'
+
+/** ** serverside stuff ****/
+
+// the api's copied from aphrodite, with 1 key difference
+// we include *all* the css generated by the app
+// to optimize to only include generated styles on the pages
+// use renderStaticOptimized
+export function renderStatic (fn) {
+  let html = fn()
+  if (html === undefined) {
+    throw new Error('did you forget to return from renderToString?')
+  }
+  let rules = sheet.sheet.cssRules
+  let css = rules.map(r => r.cssText).join('')
+  return { html, ids: Object.keys(inserted), css, rules }
+}
+
+export function renderStaticOptimized (fn) {
+  // parse out ids from html
+  // reconstruct css/rules/cache to pass
+  let html = fn()
+  if (html === undefined) {
+    throw new Error('did you forget to return from renderToString?')
+  }
+  let o = { html, ids: [], css: '', rules: [] }
+  let regex = /css-([a-zA-Z0-9]+)/gm
+  let match
+  let ids = {}
+  while ((match = regex.exec(html)) !== null) {
+    if (!ids[match[1] + '']) {
+      ids[match[1] + ''] = true
+    }
+  }
+
+  o.rules = sheet.sheet.cssRules.filter(x => {
+    let regex = /css-([a-zA-Z0-9]+)/gm
+    let match = regex.exec(x.cssText)
+    if (match && ids[match[1] + '']) {
+      return true
+    }
+    if (!match) {
+      return true
+    }
+    return false
+  })
+  o.ids = Object.keys(inserted).filter(id => !!ids[id + ''])
+  o.css = o.rules.map(x => x.cssText).join('')
+
+  return o
+}


Test patch:
diff --git a/test/__snapshots__/server.test.js.snap b/test/__snapshots__/server.test.js.snap
new file mode 100644
index 00000000..bf9b79ba
--- /dev/null
+++ b/test/__snapshots__/server.test.js.snap
@@ -0,0 +1,100 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`server renderStatic returns static css 1`] = `
+Object {
+  "css": ".css-unused-class-hlb84m-1kfvr9c { display: none; }.no-prefix { display: flex; justify-content: center; }.css-Main-ov1ktg-pvm7rn { display: -webkit-box; display: -moz-box; display: -ms-flexbox; display: -webkit-flex; display: flex; }.css-Image-z4ry8g-1qx25bk { border-radius: 50%;
+  height: 30;
+  width: 30;
+  background-color: red }.css-Image-z4ry8g-141jzk4 { border-radius: 50%;
+  height: 100;
+  width: 100;
+  background-color: red }.css-Image-z4ry8g-jpu7qc { border-radius: 50%;
+  height: 50pxpx;
+  width: 50pxpx;
+  background-color: red }",
+  "html": "<main class=\\"css-Main-ov1ktg-pvm7rn\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"1383946074\\"><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>",
+  "ids": Array [
+    "1kfvr9c",
+    "pvm7rn",
+    "1qx25bk",
+    "141jzk4",
+    "jpu7qc",
+  ],
+  "rules": Array [
+    Object {
+      "cssText": ".css-unused-class-hlb84m-1kfvr9c { display: none; }",
+    },
+    Object {
+      "cssText": ".no-prefix { display: flex; justify-content: center; }",
+    },
+    Object {
+      "cssText": ".css-Main-ov1ktg-pvm7rn { display: -webkit-box; display: -moz-box; display: -ms-flexbox; display: -webkit-flex; display: flex; }",
+    },
+    Object {
+      "cssText": ".css-Image-z4ry8g-1qx25bk { border-radius: 50%;
+  height: 30;
+  width: 30;
+  background-color: red }",
+    },
+    Object {
+      "cssText": ".css-Image-z4ry8g-141jzk4 { border-radius: 50%;
+  height: 100;
+  width: 100;
+  background-color: red }",
+    },
+    Object {
+      "cssText": ".css-Image-z4ry8g-jpu7qc { border-radius: 50%;
+  height: 50pxpx;
+  width: 50pxpx;
+  background-color: red }",
+    },
+  ],
+}
+`;
+
+exports[`server renderStatic throws if the fn does not return anything 1`] = `"did you forget to return from renderToString?"`;
+
+exports[`server renderStaticOptimized returns static css 1`] = `
+Object {
+  "css": ".no-prefix { display: flex; justify-content: center; }.css-Main-ov1ktg-pvm7rn { display: -webkit-box; display: -moz-box; display: -ms-flexbox; display: -webkit-flex; display: flex; }.css-Image-z4ry8g-1qx25bk { border-radius: 50%;
+  height: 30;
+  width: 30;
+  background-color: red }.css-Image-z4ry8g-141jzk4 { border-radius: 50%;
+  height: 100;
+  width: 100;
+  background-color: red }.css-Image-z4ry8g-jpu7qc { border-radius: 50%;
+  height: 50pxpx;
+  width: 50pxpx;
+  background-color: red }",
+  "html": "<main class=\\"css-Main-ov1ktg-pvm7rn\\" data-reactroot=\\"\\" data-reactid=\\"1\\" data-react-checksum=\\"1383946074\\"><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>",
+  "ids": Array [],
+  "rules": Array [
+    Object {
+      "cssText": ".no-prefix { display: flex; justify-content: center; }",
+    },
+    Object {
+      "cssText": ".css-Main-ov1ktg-pvm7rn { display: -webkit-box; display: -moz-box; display: -ms-flexbox; display: -webkit-flex; display: flex; }",
+    },
+    Object {
+      "cssText": ".css-Image-z4ry8g-1qx25bk { border-radius: 50%;
+  height: 30;
+  width: 30;
+  background-color: red }",
+    },
+    Object {
+      "cssText": ".css-Image-z4ry8g-141jzk4 { border-radius: 50%;
+  height: 100;
+  width: 100;
+  background-color: red }",
+    },
+    Object {
+      "cssText": ".css-Image-z4ry8g-jpu7qc { border-radius: 50%;
+  height: 50pxpx;
+  width: 50pxpx;
+  background-color: red }",
+    },
+  ],
+}
+`;
+
+exports[`server renderStaticOptimized throws if the fn does not return anything 1`] = `"did you forget to return from renderToString?"`;
diff --git a/test/server.test.js b/test/server.test.js
new file mode 100644
index 00000000..ccbca1a1
--- /dev/null
+++ b/test/server.test.js
@@ -0,0 +1,53 @@
+/**
+ * @jest-environment node
+*/
+/* eslint-env jest */
+import React from 'react'
+import { renderToString } from 'react-dom/server'
+import styled from '../src/styled'
+import { css, sheet } from '../src/index'
+import { renderStatic, renderStaticOptimized } from '../src/server'
+
+const color = 'red'
+
+const Main = styled.main`
+  display: flex;
+`
+
+const Image = styled.img`
+  border-radius: 50%;
+  height: attr(size px, 50px)
+  width: attr(size px, 50px)
+  background-color: ${color}
+`
+
+// this will be included in renderStatic but not in renderStaticOptimized 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
+
+sheet.insert('.no-prefix { display: flex; justify-content: center; }')
+
+const Page = () => <Main><Image size={30} /><Image size={100} /><Image /></Main>
+
+describe('server', () => {
+  describe('renderStatic', () => {
+    test('returns static css', () => {
+      expect(renderStatic(() => renderToString(<Page />))).toMatchSnapshot()
+    })
+    test('throws if the fn does not return anything', () => {
+      expect(() => renderStatic(() => {})).toThrowErrorMatchingSnapshot()
+    })
+  })
+  describe('renderStaticOptimized', () => {
+    test('returns static css', () => {
+      expect(renderStaticOptimized(() => renderToString(<Page />))).toMatchSnapshot()
+    })
+    test('throws if the fn does not return anything', () => {
+      expect(() => renderStaticOptimized(() => {})).toThrowErrorMatchingSnapshot()
+    })
+  })
+})