From 4ead519766002f8a4e4dedb1550770bbb6dca6f8 Mon Sep 17 00:00:00 2001 From: Kye Hohenberger Date: Mon, 21 Aug 2017 22:16:46 -0600 Subject: [PATCH 01/12] Add dynamic value support for extract static in the styled function. --- package.json | 2 +- packages/babel-plugin-emotion/src/index.js | 27 +++++++++++-- packages/babel-plugin-emotion/src/parser.js | 10 ++--- .../test/__snapshots__/styled.test.js.snap | 39 ++++++++++++++++++- .../babel-plugin-emotion/test/styled.test.js | 14 +++++++ .../__snapshots__/extract.test.js.snap | 30 ++++++++++---- .../test/extract/extract.test.emotion.css | 3 ++ packages/emotion/test/extract/extract.test.js | 8 ++++ packages/react-emotion/src/index.js | 35 +++++++++++++++-- 9 files changed, 144 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 23a4d0f26..ac5dee3d0 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "module-alias": "^2.0.1", "npm-run-all": "^4.0.2", "polished": "^1.2.1", - "prettier-eslint-cli": "^4.0.3", + "prettier-eslint-cli": "^4.2.1", "react": "^15.5.4", "react-addons-test-utils": "^15.5.1", "react-dom": "^15.5.4", diff --git a/packages/babel-plugin-emotion/src/index.js b/packages/babel-plugin-emotion/src/index.js index d76a30784..9a98dd898 100644 --- a/packages/babel-plugin-emotion/src/index.js +++ b/packages/babel-plugin-emotion/src/index.js @@ -144,7 +144,7 @@ const getComponentId = (state, prefix: string = 'css') => { export function buildStyledCallExpression(identifier, tag, path, state, t) { const identifierName = getIdentifierName(path, t) - if (state.extractStatic && !path.node.quasi.expressions.length) { + if (state.extractStatic) { const { name, hash, src } = inline( path.node.quasi, identifierName, @@ -152,13 +152,32 @@ export function buildStyledCallExpression(identifier, tag, path, state, t) { ) const cssText = `.${name}-${hash} { ${src} }` - const { staticCSSRules } = parseCSS(cssText, true, getFilename(path)) + const { staticCSSRules, composesCount } = parseCSS( + cssText, + true, + getFilename(path) + ) + + // Help Needed: + // We should read the browser preferences for postcss + // and turn this on only if css vars are supported in their browser targets. + const objs = path.node.quasi.expressions.slice(0, composesCount) + + if (objs.length) { + throw new Error('Cannot use composes in extractStatic mode.') + } - state.insertStaticRules(staticCSSRules) + const vars = path.node.quasi.expressions.slice(composesCount) + state.insertStaticRules( + staticCSSRules.map(ruleText => + ruleText.replace(/xxx(\d+)xxx/gm, `var(--${name}-${hash}-$1)`) + ) + ) return t.callExpression(identifier, [ tag, t.stringLiteral(getComponentId(state, name)), - t.arrayExpression([t.stringLiteral(`${name}-${hash}`)]) + t.arrayExpression([t.stringLiteral(`${name}-${hash}`)]), + t.arrayExpression(vars) ]) } diff --git a/packages/babel-plugin-emotion/src/parser.js b/packages/babel-plugin-emotion/src/parser.js index 33b9b5453..ad7a8a653 100644 --- a/packages/babel-plugin-emotion/src/parser.js +++ b/packages/babel-plugin-emotion/src/parser.js @@ -35,7 +35,7 @@ export function parseCSS( } else { root = parse(css, { from: filename }) } - let vars = 0 + let composes: number = 0 root.walkDecls((decl: Decl): void => { @@ -52,7 +52,6 @@ export function parseCSS( const composeMatches = decl.value.match(/xxx(\d+)xxx/gm) const numOfComposes: number = !composeMatches ? 0 : composeMatches.length composes += numOfComposes - vars += numOfComposes decl.remove() } }) @@ -63,10 +62,9 @@ export function parseCSS( return { styles, - staticCSSRules: - vars === 0 && extractStatic - ? stringifyCSSRoot(postcssJs.parse(styles)) - : [], + staticCSSRules: extractStatic + ? stringifyCSSRoot(postcssJs.parse(styles)) + : [], composesCount: composes } } diff --git a/packages/babel-plugin-emotion/test/__snapshots__/styled.test.js.snap b/packages/babel-plugin-emotion/test/__snapshots__/styled.test.js.snap index 921012811..67fdf18f3 100644 --- a/packages/babel-plugin-emotion/test/__snapshots__/styled.test.js.snap +++ b/packages/babel-plugin-emotion/test/__snapshots__/styled.test.js.snap @@ -2,7 +2,7 @@ exports[`babel styled component extract basic 1`] = ` "import \\"./styled.test.emotion.css\\"; -const H1 = styled(\\"h1\\", \\"styled-H1-1qcxcvu0\\", [\\"styled-H1-10x82eg\\"]);" +const H1 = styled(\\"h1\\", \\"styled-H1-1qcxcvu0\\", [\\"styled-H1-10x82eg\\"], []);" `; exports[`babel styled component extract basic 2`] = ` @@ -37,11 +37,46 @@ exports[`babel styled component extract basic 2`] = ` exports[`babel styled component extract no use 1`] = ` "import \\"./styled.test.emotion.css\\"; -styled(\\"h1\\", \\"styled-yoisj70\\", [\\"styled-0\\"]);" +styled(\\"h1\\", \\"styled-yoisj70\\", [\\"styled-0\\"], []);" `; exports[`babel styled component extract no use 2`] = `".styled-0 {}"`; +exports[`babel styled component extract with interpolation 1`] = ` +"import \\"./styled.test.emotion.css\\"; +const H1 = styled(\\"h1\\", \\"styled-H1-i6cu680\\", [\\"styled-H1-9acweo\\"], [p => p.justify]);" +`; + +exports[`babel styled component extract with interpolation 2`] = ` +".styled-H1-9acweo { + display: -webkit-box; display: -ms-flexbox; display: flex; + -webkit-box-pack: var(--styled-H1-9acweo-0); + -ms-flex-pack: var(--styled-H1-9acweo-0); + justify-content: var(--styled-H1-9acweo-0); + width: var(--css-hash-0) +} +.styled-H1-9acweo:hover { + background-color: green +} +@media (max-width: 500px) { + .styled-H1-9acweo { + height: var(--css-hash-1); + position: fixed + } +} +@media print { + .styled-H1-9acweo { + display: none + } +} +.styled-H1-9acweo::before { + color: blue; + width: 20px; + height: 20px; + content: 'pseudo' +}" +`; + exports[`babel styled component inline basic 1`] = ` "const H1 = /*#__PURE__*/styled('h1', 'css-H1-uutogn0', [], [fontSize + 'px'], function createEmotionStyledRules(x0) { return { diff --git a/packages/babel-plugin-emotion/test/styled.test.js b/packages/babel-plugin-emotion/test/styled.test.js index cb3e586ce..918ea4805 100644 --- a/packages/babel-plugin-emotion/test/styled.test.js +++ b/packages/babel-plugin-emotion/test/styled.test.js @@ -353,5 +353,19 @@ describe('babel styled component', () => { expect(fs.writeFileSync).toHaveBeenCalledTimes(2) expect(fs.writeFileSync.mock.calls[1][1]).toMatchSnapshot() }) + + test('with interpolation', () => { + const basic = + "const H1 = styled.h1`display: flex; justify-content: ${p => p.justify}; width: var(--css-hash-0); &:hover { background-color: green; } @media (max-width: 500px) { height: var(--css-hash-1); position: fixed; } @media print { display: none; } &::before { color: blue; width: 20px; height: 20px; content: 'pseudo' }`" + const {code} = babel.transform(basic, { + plugins: [[plugin, {extractStatic: true}]], + filename: __filename, + babelrc: false + }) + + expect(code).toMatchSnapshot() + expect(fs.writeFileSync).toHaveBeenCalledTimes(3) + expect(fs.writeFileSync.mock.calls[2][1]).toMatchSnapshot() + }) }) }) diff --git a/packages/emotion/test/extract/__snapshots__/extract.test.js.snap b/packages/emotion/test/extract/__snapshots__/extract.test.js.snap index ba74b1ed9..8a9ec93b8 100644 --- a/packages/emotion/test/extract/__snapshots__/extract.test.js.snap +++ b/packages/emotion/test/extract/__snapshots__/extract.test.js.snap @@ -2,27 +2,40 @@ exports[`styled basic render nested 1`] = `

hello world

-`; +` exports[`styled name 1`] = `

hello world

-`; +` exports[`styled no dynamic 1`] = `

hello world

-`; +` + +exports[`styled with expressions 1`] = ` +

+ hello world +

+` exports[`styled writes the correct css 1`] = ` ".styled-H1-ijh7uz { @@ -40,6 +53,9 @@ exports[`styled writes the correct css 1`] = ` .styled-H1-1f87tx4 span:hover:after { content: \\"after\\" } +.styled-H1-1t8i2zo { + font-size: var(--styled-H1-1t8i2zo-0) +} .styled-H1-131pfth { name: FancyH1; font-size: 38px @@ -52,4 +68,4 @@ html { color: yellow; background-color: purple }" -`; +` diff --git a/packages/emotion/test/extract/extract.test.emotion.css b/packages/emotion/test/extract/extract.test.emotion.css index 8485abdd6..73e550196 100644 --- a/packages/emotion/test/extract/extract.test.emotion.css +++ b/packages/emotion/test/extract/extract.test.emotion.css @@ -13,6 +13,9 @@ .styled-H1-1f87tx4 span:hover:after { content: "after" } +.styled-H1-1t8i2zo { + font-size: var(--styled-H1-1t8i2zo-0) +} .styled-H1-131pfth { name: FancyH1; font-size: 38px diff --git a/packages/emotion/test/extract/extract.test.js b/packages/emotion/test/extract/extract.test.js index 193cb2fc7..242ecc54c 100644 --- a/packages/emotion/test/extract/extract.test.js +++ b/packages/emotion/test/extract/extract.test.js @@ -36,6 +36,14 @@ describe('styled', () => { expect(tree).toMatchSnapshot() }) + test('with expressions', () => { + const H1 = styled.h1`font-size: ${p => p.fontSize};` + + const tree = renderer.create(

hello world

).toJSON() + + expect(tree).toMatchSnapshot() + }) + test('name', () => { const H1 = styled.h1` name: FancyH1; diff --git a/packages/react-emotion/src/index.js b/packages/react-emotion/src/index.js index 6ab52d84c..b67807c92 100644 --- a/packages/react-emotion/src/index.js +++ b/packages/react-emotion/src/index.js @@ -11,6 +11,11 @@ const reactPropsRegex = new RegExp(propsRegexString) const testOmitPropsOnStringTag = key => reactPropsRegex.test(key) const testOmitPropsOnComponent = key => key !== 'theme' && key !== 'innerRef' +let css45 = (c, v = []) => ({ + className: c, + style: v.reduce((o, v, i) => ((o[`--${c}-${i}`] = v), o), {}) +}) + export default function(tag, cls, objs, vars = [], content) { if (!tag) { throw new Error( @@ -18,6 +23,8 @@ export default function(tag, cls, objs, vars = [], content) { ) } + const FAST_PATH = vars && vars.length && !content + const componentTag = tag.displayName || tag.name || 'Component' const spec = { vars, @@ -73,10 +80,30 @@ export default function(tag, cls, objs, vars = [], content) { return h( localTag, omit( - assign({}, props, { - ref: props.innerRef, - className - }), + assign( + {}, + props, + { + ref: props.innerRef, + className + }, + FAST_PATH && { + style: assign( + {}, + props.style, + FAST_PATH + ? reduce( + map(vars, getValue), + (accum, value, i) => { + accum[`${objs[0]}-${i}`] = value + return accum + }, + {} + ) + : null + ) + } + ), omitFn ) ) From a956255028e3424bf1a51f760c93316d3bf3ee31 Mon Sep 17 00:00:00 2001 From: Kye Hohenberger Date: Mon, 21 Aug 2017 22:32:28 -0600 Subject: [PATCH 02/12] Remove sketch code --- packages/react-emotion/src/index.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/react-emotion/src/index.js b/packages/react-emotion/src/index.js index b67807c92..bf6069423 100644 --- a/packages/react-emotion/src/index.js +++ b/packages/react-emotion/src/index.js @@ -11,11 +11,6 @@ const reactPropsRegex = new RegExp(propsRegexString) const testOmitPropsOnStringTag = key => reactPropsRegex.test(key) const testOmitPropsOnComponent = key => key !== 'theme' && key !== 'innerRef' -let css45 = (c, v = []) => ({ - className: c, - style: v.reduce((o, v, i) => ((o[`--${c}-${i}`] = v), o), {}) -}) - export default function(tag, cls, objs, vars = [], content) { if (!tag) { throw new Error( From 770cfcb2189c3f47a9c7c76915010d8387b3b9d4 Mon Sep 17 00:00:00 2001 From: Kye Hohenberger Date: Tue, 22 Aug 2017 09:38:10 -0600 Subject: [PATCH 03/12] short circuit the css call entirely --- packages/react-emotion/src/index.js | 53 ++++++++++++++++------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/packages/react-emotion/src/index.js b/packages/react-emotion/src/index.js index bf6069423..e281bb1d5 100644 --- a/packages/react-emotion/src/index.js +++ b/packages/react-emotion/src/index.js @@ -48,6 +48,31 @@ export default function(tag, cls, objs, vars = [], content) { return v } + if (FAST_PATH) { + return h( + localTag, + omit( + assign({}, props, { + ref: props.innerRef, + className: objs[0], + style: assign( + {}, + props.style, + reduce( + map(vars, getValue), + (accum, value, i) => { + accum[`${objs[0]}-${i}`] = value + return accum + }, + {} + ) + ) + }), + omitFn + ) + ) + } + let finalObjs = [] push( @@ -75,30 +100,10 @@ export default function(tag, cls, objs, vars = [], content) { return h( localTag, omit( - assign( - {}, - props, - { - ref: props.innerRef, - className - }, - FAST_PATH && { - style: assign( - {}, - props.style, - FAST_PATH - ? reduce( - map(vars, getValue), - (accum, value, i) => { - accum[`${objs[0]}-${i}`] = value - return accum - }, - {} - ) - : null - ) - } - ), + assign({}, props, { + ref: props.innerRef, + className + }), omitFn ) ) From cfdb9ab5ae26c4cafaa1e9914a7ddeb7239d54b5 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Thu, 24 Aug 2017 19:23:36 +1000 Subject: [PATCH 04/12] Update test --- .../babel-plugin-emotion/test/styled.test.js | 61 ++++++++++++++++--- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/packages/babel-plugin-emotion/test/styled.test.js b/packages/babel-plugin-emotion/test/styled.test.js index 918ea4805..0d01baa04 100644 --- a/packages/babel-plugin-emotion/test/styled.test.js +++ b/packages/babel-plugin-emotion/test/styled.test.js @@ -328,6 +328,9 @@ describe('babel styled component', () => { }) describe('extract', () => { + afterEach(() => { + fs.writeFileSync.clearMock() + }) test('no use', () => { const basic = 'styled.h1``' const { code } = babel.transform(basic, { @@ -341,8 +344,27 @@ describe('babel styled component', () => { }) test('basic', () => { - const basic = - "const H1 = styled.h1`display: flex; justify-content: center; width: var(--css-hash-0); &:hover { background-color: green; } @media (max-width: 500px) { height: var(--css-hash-1); position: fixed; } @media print { display: none; } &::before { color: blue; width: 20px; height: 20px; content: 'pseudo' }`" + const basic = `const H1 = styled.h1\` + display: flex; + justify-content: center; + width: var(--css-hash-0); + &:hover { + background-color: green; + } + @media (max-width: 500px) { + height: var(--css-hash-1); + position: fixed; + } + @media print { + display: none; + } + &::before { + color: blue; + width: 20px; + height: 20px; + content: 'pseudo'; + } + \`` const { code } = babel.transform(basic, { plugins: [[plugin, { extractStatic: true }]], filename: __filename, @@ -350,22 +372,41 @@ describe('babel styled component', () => { }) expect(code).toMatchSnapshot() - expect(fs.writeFileSync).toHaveBeenCalledTimes(2) - expect(fs.writeFileSync.mock.calls[1][1]).toMatchSnapshot() + expect(fs.writeFileSync).toHaveBeenCalledTimes(1) + expect(fs.writeFileSync.mock.calls[0][1]).toMatchSnapshot() }) test('with interpolation', () => { - const basic = - "const H1 = styled.h1`display: flex; justify-content: ${p => p.justify}; width: var(--css-hash-0); &:hover { background-color: green; } @media (max-width: 500px) { height: var(--css-hash-1); position: fixed; } @media print { display: none; } &::before { color: blue; width: 20px; height: 20px; content: 'pseudo' }`" - const {code} = babel.transform(basic, { - plugins: [[plugin, {extractStatic: true}]], + const basic = `const H1 = styled.h1\` + display: flex; + justify-content: \${p => p.justify}; + width: var(--css-hash-0); + &:hover { + background-color: green; + } + @media (max-width: 500px) { + height: var(--css-hash-1); + position: fixed; + } + @media print { + display: none; + } + &::before { + color: blue; + width: 20px; + height: 20px; + content: 'pseudo'; + } + \`` + const { code } = babel.transform(basic, { + plugins: [[plugin, { extractStatic: true }]], filename: __filename, babelrc: false }) expect(code).toMatchSnapshot() - expect(fs.writeFileSync).toHaveBeenCalledTimes(3) - expect(fs.writeFileSync.mock.calls[2][1]).toMatchSnapshot() + expect(fs.writeFileSync).toHaveBeenCalledTimes(1) + expect(fs.writeFileSync.mock.calls[0][1]).toMatchSnapshot() }) }) }) From ce748351c8fbffd27d571fce5afac65e373e1f9c Mon Sep 17 00:00:00 2001 From: Kye Hohenberger Date: Thu, 24 Aug 2017 13:39:08 -0600 Subject: [PATCH 05/12] Use the sheet for the custom property values. --- packages/emotion/src/index.js | 34 ++++++++++++++++++- .../__snapshots__/extract.test.js.snap | 21 ++++++------ packages/react-emotion/src/index.js | 33 ++++-------------- 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/packages/emotion/src/index.js b/packages/emotion/src/index.js index c5515e95b..8bdaef197 100644 --- a/packages/emotion/src/index.js +++ b/packages/emotion/src/index.js @@ -9,6 +9,7 @@ import { clean, createMarkupForStyles, hashString as hash, + hashArray, hashObject } from 'emotion-utils' @@ -82,7 +83,38 @@ export function css(objs: any, vars: Array, content: () => Array) { return computedClassName.trim() } -function insertRawRule(css: string) { +type inputVar = string | number + +export function customProperties(baseClassName: string, vars: Array) { + const hash = hashArray([baseClassName, ...vars]) + const varCls = `css-vars-${hash}` + if (inserted[hash]) { + return varCls + } + + let src = '' + forEach(vars, (val: inputVar, i: number) => { + src && (src += '; ') + src += `--${baseClassName}-${i}: ${val}` + }) + + let spec = { + id: hash, + css: `.${varCls} {${src}}`, + type: 'raw' + } + + register(spec) + + if (!inserted[spec.id]) { + sheet.insert(spec.css) + inserted[spec.id] = true + } + + return varCls +} + +export function insertRawRule(css: string) { let spec = { id: hash(css), css, diff --git a/packages/emotion/test/extract/__snapshots__/extract.test.js.snap b/packages/emotion/test/extract/__snapshots__/extract.test.js.snap index 8a9ec93b8..21fcd510a 100644 --- a/packages/emotion/test/extract/__snapshots__/extract.test.js.snap +++ b/packages/emotion/test/extract/__snapshots__/extract.test.js.snap @@ -6,7 +6,7 @@ exports[`styled basic render nested 1`] = ` > hello world -` +`; exports[`styled name 1`] = `

hello world

-` +`; exports[`styled no dynamic 1`] = `

hello world

-` +`; exports[`styled with expressions 1`] = ` +.glamor-0 { + --styled-H1-1t8i2zo-0: 24; +} +

hello world

-` +`; exports[`styled writes the correct css 1`] = ` ".styled-H1-ijh7uz { @@ -68,4 +67,4 @@ html { color: yellow; background-color: purple }" -` +`; diff --git a/packages/react-emotion/src/index.js b/packages/react-emotion/src/index.js index e281bb1d5..7891e556f 100644 --- a/packages/react-emotion/src/index.js +++ b/packages/react-emotion/src/index.js @@ -1,5 +1,5 @@ import { createElement as h } from 'react' -import { css } from 'emotion' +import { css, customProperties } from 'emotion' import { map, reduce, assign, omit } from 'emotion-utils' import propsRegexString from /* preval */ './props' @@ -18,7 +18,7 @@ export default function(tag, cls, objs, vars = [], content) { ) } - const FAST_PATH = vars && vars.length && !content + const EXTRACTED_DYNAMIC = vars && vars.length && !content const componentTag = tag.displayName || tag.name || 'Component' const spec = { @@ -48,31 +48,6 @@ export default function(tag, cls, objs, vars = [], content) { return v } - if (FAST_PATH) { - return h( - localTag, - omit( - assign({}, props, { - ref: props.innerRef, - className: objs[0], - style: assign( - {}, - props.style, - reduce( - map(vars, getValue), - (accum, value, i) => { - accum[`${objs[0]}-${i}`] = value - return accum - }, - {} - ) - ) - }), - omitFn - ) - ) - } - let finalObjs = [] push( @@ -83,6 +58,10 @@ export default function(tag, cls, objs, vars = [], content) { push(accum, spec.objs) if (spec.content) { accum.push(spec.content.apply(null, map(spec.vars, getValue))) + } else if (spec.vars.length) { + // Dynamic properties for extracted css will have variables + // but no content function + accum.push(customProperties(spec.objs[0], map(spec.vars, getValue))) } accum.push(spec.cls) return accum From 0e84412965e912cdc696e571dc00f47de639f6d6 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 25 Aug 2017 06:48:11 +1000 Subject: [PATCH 06/12] Add test for SSR with extractStatic --- .../extract/__snapshots__/server.test.js.snap | 46 +++++++ .../test/extract/server.test.emotion.css | 82 +++++++++++++ packages/emotion/test/extract/server.test.js | 115 ++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 packages/emotion/test/extract/__snapshots__/server.test.js.snap create mode 100644 packages/emotion/test/extract/server.test.emotion.css create mode 100644 packages/emotion/test/extract/server.test.js diff --git a/packages/emotion/test/extract/__snapshots__/server.test.js.snap b/packages/emotion/test/extract/__snapshots__/server.test.js.snap new file mode 100644 index 000000000..f79ec6fb6 --- /dev/null +++ b/packages/emotion/test/extract/__snapshots__/server.test.js.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`extractCritical returns static css 1`] = `Object {}`; + +exports[`extractCritical returns static css 2`] = ` +Object { + "css": ".css-vars-1py8q97 {--styled-Image-157rves-0: css-bounce-h7fy2z; --styled-Image-157rves-1: red}", + "html": "
", + "ids": Array [ + "1py8q97", + ], + "rules": Array [ + Object { + "cssText": ".css-vars-1py8q97 {--styled-Image-157rves-0: css-bounce-h7fy2z; --styled-Image-157rves-1: red}", + }, + ], +} +`; + +exports[`extractCritical returns static css 3`] = ` +Object { + "css": "", + "html": "
Hello
", + "ids": Array [ + "1py8q97", + ], + "rules": Array [], +} +`; + +exports[`hydration only rules that are not in the critical css are inserted 1`] = ` +Object { + "css": ".css-vars-1py8q97 {--styled-Image-157rves-0: css-bounce-h7fy2z; --styled-Image-157rves-1: red}", + "html": "
", + "ids": Array [ + "1py8q97", + ], + "rules": Array [ + Object { + "cssText": ".css-vars-1py8q97 {--styled-Image-157rves-0: css-bounce-h7fy2z; --styled-Image-157rves-1: red}", + }, + ], +} +`; + +exports[`hydration only rules that are not in the critical css are inserted 2`] = `Array []`; diff --git a/packages/emotion/test/extract/server.test.emotion.css b/packages/emotion/test/extract/server.test.emotion.css new file mode 100644 index 000000000..e71669ec3 --- /dev/null +++ b/packages/emotion/test/extract/server.test.emotion.css @@ -0,0 +1,82 @@ +@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 css-bounce-h7fy2z { + 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 css-bounce-h7fy2z { + 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) + } +} +.styled-Main-noat5a { + color: hotpink +} +.styled-Main-noat5a: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 +} +.styled-Image-157rves { + -webkit-animation: var(--styled-Image-157rves-0); + animation: var(--styled-Image-157rves-0); + border-radius: 50%; + height: 50px; + width: 50px; + background-color: var(--styled-Image-157rves-1) +} +.css-getComponents-k26awr { + display: none +} +.no-prefix { + display: -webkit-box; display: -ms-flexbox; display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center +} \ No newline at end of file diff --git a/packages/emotion/test/extract/server.test.js b/packages/emotion/test/extract/server.test.js new file mode 100644 index 000000000..69678a77b --- /dev/null +++ b/packages/emotion/test/extract/server.test.js @@ -0,0 +1,115 @@ +/** + * @jest-environment node +*/ +import React from 'react' +import { renderToString } from 'react-dom/server' +import styled from 'react-emotion' +import { + css, + injectGlobal, + keyframes, + flush, + hydrate, + fontFace, + sheet +} from 'emotion' +import { extractCritical } from 'emotion-server' + +const getComponents = () => { + const color = 'red' + + 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 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); + } + + 40%, 43% { + animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + transform: translate3d(0, -30px, 0); + } + + 70% { + animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + transform: translate3d(0, -15px, 0); + } + + 90% { + transform: translate3d(0,-4px,0); + } + ` + + const Main = styled.main` + color: hotpink; + &:hover { + color: white; + background-color: lightgray; + border-color: aqua; + box-shadow: -15px -15px 0 0 aqua, -30px -30px 0 0 cornflowerblue; + } + ` + + 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; + ` + + // this will be included in both because it doesn't have the css- prefix + + injectGlobal` + .no-prefix { + display: flex; + justify-content: center; + } + ` + + const Page1 = () => +
+ + + +
+ + const Page2 = () => +
+
Hello
+
+ return { Page1, Page2 } +} + +describe('extractCritical', () => { + test('returns static css', () => { + const { Page1, Page2 } = getComponents() + expect(sheet.registered).toMatchSnapshot() + expect(extractCritical(renderToString())).toMatchSnapshot() + expect(extractCritical(renderToString())).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()) + expect({ html, ids, css, rules }).toMatchSnapshot() + flush() + hydrate(ids) + const { Page1: NewPage1 } = getComponents() + renderToString() + expect(sheet.sheet.cssRules).toMatchSnapshot() + }) +}) From 4c8eee73e3e2637eb21f09972a862282f6796e47 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 25 Aug 2017 08:46:11 +1000 Subject: [PATCH 07/12] Remove unused variable --- packages/react-emotion/src/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/react-emotion/src/index.js b/packages/react-emotion/src/index.js index 7891e556f..7d870724b 100644 --- a/packages/react-emotion/src/index.js +++ b/packages/react-emotion/src/index.js @@ -18,8 +18,6 @@ export default function(tag, cls, objs, vars = [], content) { ) } - const EXTRACTED_DYNAMIC = vars && vars.length && !content - const componentTag = tag.displayName || tag.name || 'Component' const spec = { vars, From 07be9eaf24bcfa948f7d0848440c5d1106bf1308 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 25 Aug 2017 09:24:28 +1000 Subject: [PATCH 08/12] Fallback to inline when styled can't be extracted --- packages/babel-plugin-emotion/src/index.js | 33 +++---- packages/babel-plugin-emotion/src/parser.js | 36 +++++--- .../test/__snapshots__/parser.test.js.snap | 3 + .../test/__snapshots__/styled.test.js.snap | 92 ++++++++++++++++--- .../babel-plugin-emotion/test/styled.test.js | 78 +++++++++++++++- 5 files changed, 197 insertions(+), 45 deletions(-) diff --git a/packages/babel-plugin-emotion/src/index.js b/packages/babel-plugin-emotion/src/index.js index 9a98dd898..3ac9a32b4 100644 --- a/packages/babel-plugin-emotion/src/index.js +++ b/packages/babel-plugin-emotion/src/index.js @@ -152,7 +152,7 @@ export function buildStyledCallExpression(identifier, tag, path, state, t) { ) const cssText = `.${name}-${hash} { ${src} }` - const { staticCSSRules, composesCount } = parseCSS( + const { staticCSSRules, varCount } = parseCSS( cssText, true, getFilename(path) @@ -161,24 +161,25 @@ export function buildStyledCallExpression(identifier, tag, path, state, t) { // Help Needed: // We should read the browser preferences for postcss // and turn this on only if css vars are supported in their browser targets. - const objs = path.node.quasi.expressions.slice(0, composesCount) - if (objs.length) { - throw new Error('Cannot use composes in extractStatic mode.') - } + let canUseCssVariables = true - const vars = path.node.quasi.expressions.slice(composesCount) - state.insertStaticRules( - staticCSSRules.map(ruleText => - ruleText.replace(/xxx(\d+)xxx/gm, `var(--${name}-${hash}-$1)`) + if ( + (varCount === path.node.quasi.expressions.length && canUseCssVariables) || + path.node.quasi.expressions.length === 0 + ) { + state.insertStaticRules( + staticCSSRules.map(ruleText => + ruleText.replace(/xxx(\d+)xxx/gm, `var(--${name}-${hash}-$1)`) + ) ) - ) - return t.callExpression(identifier, [ - tag, - t.stringLiteral(getComponentId(state, name)), - t.arrayExpression([t.stringLiteral(`${name}-${hash}`)]), - t.arrayExpression(vars) - ]) + return t.callExpression(identifier, [ + tag, + t.stringLiteral(getComponentId(state, name)), + t.arrayExpression([t.stringLiteral(`${name}-${hash}`)]), + t.arrayExpression(path.node.quasi.expressions) + ]) + } } const { src, name } = inline(path.node.quasi, identifierName, 'css') diff --git a/packages/babel-plugin-emotion/src/parser.js b/packages/babel-plugin-emotion/src/parser.js index ad7a8a653..80b331f26 100644 --- a/packages/babel-plugin-emotion/src/parser.js +++ b/packages/babel-plugin-emotion/src/parser.js @@ -37,22 +37,31 @@ export function parseCSS( } let composes: number = 0 - + let varCount = 0 root.walkDecls((decl: Decl): void => { if (decl.prop === 'composes') { - if (!/xxx(\d+)xxx/gm.exec(decl.value)) { - throw new Error('composes must be a interpolation') - } - if (decl.parent.nodes[0] !== decl) { - throw new Error('composes must be the first rule') + if (!extractStatic) { + if (!/xxx(\d+)xxx/gm.exec(decl.value)) { + throw new Error('composes must be a interpolation') + } + if (decl.parent.nodes[0] !== decl) { + throw new Error('composes must be the first rule') + } + if (decl.parent.type !== 'root') { + throw new Error('composes cannot be on nested selectors') + } + const composeMatches = decl.value.match(/xxx(\d+)xxx/gm) + const numOfComposes: number = !composeMatches + ? 0 + : composeMatches.length + composes += numOfComposes + decl.remove() } - if (decl.parent.type !== 'root') { - throw new Error('composes cannot be on nested selectors') + } else { + const match = decl.value.match(/xxx(?:\d+)xxx/gm) + if (match && decl.prop[0] !== '$') { + varCount += match.length } - const composeMatches = decl.value.match(/xxx(\d+)xxx/gm) - const numOfComposes: number = !composeMatches ? 0 : composeMatches.length - composes += numOfComposes - decl.remove() } }) @@ -65,7 +74,8 @@ export function parseCSS( staticCSSRules: extractStatic ? stringifyCSSRoot(postcssJs.parse(styles)) : [], - composesCount: composes + composesCount: composes, + varCount } } diff --git a/packages/babel-plugin-emotion/test/__snapshots__/parser.test.js.snap b/packages/babel-plugin-emotion/test/__snapshots__/parser.test.js.snap index c585e421e..39abe5f4f 100644 --- a/packages/babel-plugin-emotion/test/__snapshots__/parser.test.js.snap +++ b/packages/babel-plugin-emotion/test/__snapshots__/parser.test.js.snap @@ -13,6 +13,7 @@ Object { "width": "var(--css-hash-0)", }, }, + "varCount": 0, } `; @@ -45,6 +46,7 @@ Object { "width": "var(--css-hash-0)", }, }, + "varCount": 0, } `; @@ -65,5 +67,6 @@ Object { "width": "30px", }, }, + "varCount": 0, } `; diff --git a/packages/babel-plugin-emotion/test/__snapshots__/styled.test.js.snap b/packages/babel-plugin-emotion/test/__snapshots__/styled.test.js.snap index 67fdf18f3..640e32635 100644 --- a/packages/babel-plugin-emotion/test/__snapshots__/styled.test.js.snap +++ b/packages/babel-plugin-emotion/test/__snapshots__/styled.test.js.snap @@ -2,32 +2,32 @@ exports[`babel styled component extract basic 1`] = ` "import \\"./styled.test.emotion.css\\"; -const H1 = styled(\\"h1\\", \\"styled-H1-1qcxcvu0\\", [\\"styled-H1-10x82eg\\"], []);" +const H1 = styled(\\"h1\\", \\"styled-H1-1wk00ht0\\", [\\"styled-H1-xlr0g1\\"], []);" `; exports[`babel styled component extract basic 2`] = ` -".styled-H1-10x82eg { +".styled-H1-xlr0g1 { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; width: var(--css-hash-0) } -.styled-H1-10x82eg:hover { +.styled-H1-xlr0g1:hover { background-color: green } @media (max-width: 500px) { - .styled-H1-10x82eg { + .styled-H1-xlr0g1 { height: var(--css-hash-1); position: fixed } } @media print { - .styled-H1-10x82eg { + .styled-H1-xlr0g1 { display: none } } -.styled-H1-10x82eg::before { +.styled-H1-xlr0g1::before { color: blue; width: 20px; height: 20px; @@ -42,34 +42,42 @@ styled(\\"h1\\", \\"styled-yoisj70\\", [\\"styled-0\\"], []);" exports[`babel styled component extract no use 2`] = `".styled-0 {}"`; +exports[`babel styled component extract with composes 1`] = ` +"const H1 = /*#__PURE__*/styled(\\"h1\\", \\"css-H1-14eg5ys0\\", [something], [], function createEmotionStyledRules() { + return { + \\"display\\": \\"-webkit-box; display: -ms-flexbox; display: flex\\" + }; +});" +`; + exports[`babel styled component extract with interpolation 1`] = ` "import \\"./styled.test.emotion.css\\"; -const H1 = styled(\\"h1\\", \\"styled-H1-i6cu680\\", [\\"styled-H1-9acweo\\"], [p => p.justify]);" +const H1 = styled(\\"h1\\", \\"styled-H1-j7p59z0\\", [\\"styled-H1-chhlbr\\"], [p => p.justify]);" `; exports[`babel styled component extract with interpolation 2`] = ` -".styled-H1-9acweo { +".styled-H1-chhlbr { display: -webkit-box; display: -ms-flexbox; display: flex; - -webkit-box-pack: var(--styled-H1-9acweo-0); - -ms-flex-pack: var(--styled-H1-9acweo-0); - justify-content: var(--styled-H1-9acweo-0); + -webkit-box-pack: var(--styled-H1-chhlbr-0); + -ms-flex-pack: var(--styled-H1-chhlbr-0); + justify-content: var(--styled-H1-chhlbr-0); width: var(--css-hash-0) } -.styled-H1-9acweo:hover { +.styled-H1-chhlbr:hover { background-color: green } @media (max-width: 500px) { - .styled-H1-9acweo { + .styled-H1-chhlbr { height: var(--css-hash-1); position: fixed } } @media print { - .styled-H1-9acweo { + .styled-H1-chhlbr { display: none } } -.styled-H1-9acweo::before { +.styled-H1-chhlbr::before { color: blue; width: 20px; height: 20px; @@ -77,6 +85,60 @@ exports[`babel styled component extract with interpolation 2`] = ` }" `; +exports[`babel styled component extract with lots of interpolations 1`] = ` +"import './styled.test.emotion.css'; +const H1 = styled('h1', 'styled-H1-16fsv4k0', ['styled-H1-1ghdihd'], [p => p.justify, 'something', 'fixed']);" +`; + +exports[`babel styled component extract with lots of interpolations 2`] = ` +".styled-H1-1ghdihd { + display: -webkit-box; display: -ms-flexbox; display: flex; + -webkit-box-pack: var(--styled-H1-1ghdihd-0) var(--styled-H1-1ghdihd-1); + -ms-flex-pack: var(--styled-H1-1ghdihd-0) var(--styled-H1-1ghdihd-1); + justify-content: var(--styled-H1-1ghdihd-0) var(--styled-H1-1ghdihd-1); + width: var(--css-hash-0) +} +.styled-H1-1ghdihd:hover { + background-color: green +} +@media (max-width: 500px) { + .styled-H1-1ghdihd { + height: var(--css-hash-1); + position: var(--styled-H1-1ghdihd-2) + } +} +@media print { + .styled-H1-1ghdihd { + display: none + } +} +.styled-H1-1ghdihd::before { + color: blue; + width: 20px; + height: 20px; + content: 'pseudo' +}" +`; + +exports[`babel styled component extract with random interpolation 1`] = ` +"const H1 = /*#__PURE__*/styled(\\"h1\\", \\"css-H1-1v04vms0\\", [], [something], function createEmotionStyledRules(x0) { + return { + \\"$0\\": x0, + \\"display\\": \\"-webkit-box; display: -ms-flexbox; display: flex\\" + }; +});" +`; + +exports[`babel styled component extract with selector interpolation 1`] = ` +"const H1 = /*#__PURE__*/styled(\\"h1\\", \\"css-H1-mse9rr0\\", [], [something], function createEmotionStyledRules(x0) { + return { + [x0]: { + \\"display\\": \\"-webkit-box; display: -ms-flexbox; display: flex\\" + } + }; +});" +`; + exports[`babel styled component inline basic 1`] = ` "const H1 = /*#__PURE__*/styled('h1', 'css-H1-uutogn0', [], [fontSize + 'px'], function createEmotionStyledRules(x0) { return { diff --git a/packages/babel-plugin-emotion/test/styled.test.js b/packages/babel-plugin-emotion/test/styled.test.js index 0d01baa04..e67347483 100644 --- a/packages/babel-plugin-emotion/test/styled.test.js +++ b/packages/babel-plugin-emotion/test/styled.test.js @@ -329,7 +329,7 @@ describe('babel styled component', () => { describe('extract', () => { afterEach(() => { - fs.writeFileSync.clearMock() + fs.writeFileSync.mockClear() }) test('no use', () => { const basic = 'styled.h1``' @@ -408,5 +408,81 @@ describe('babel styled component', () => { expect(fs.writeFileSync).toHaveBeenCalledTimes(1) expect(fs.writeFileSync.mock.calls[0][1]).toMatchSnapshot() }) + + test('with lots of interpolations', () => { + const basic = `const H1 = styled.h1\` + display: flex; + justify-content: \${p => p.justify} \${'something'}; + width: var(--css-hash-0); + &:hover { + background-color: green; + } + @media (max-width: 500px) { + height: var(--css-hash-1); + position: \${'fixed'}; + } + @media print { + display: none; + } + &::before { + color: blue; + width: 20px; + height: 20px; + content: 'pseudo'; + } + \`` + const { code } = babel.transform(basic, { + plugins: [[plugin, { extractStatic: true }]], + filename: __filename, + babelrc: false + }) + + expect(code).toMatchSnapshot() + expect(fs.writeFileSync).toHaveBeenCalledTimes(1) + expect(fs.writeFileSync.mock.calls[0][1]).toMatchSnapshot() + }) + test('with composes', () => { + const basic = `const H1 = styled.h1\` + composes: \${something}; + display: flex; + \`` + const { code } = babel.transform(basic, { + plugins: [[plugin, { extractStatic: true }]], + filename: __filename, + babelrc: false + }) + + expect(code).toMatchSnapshot() + expect(fs.writeFileSync).not.toHaveBeenCalled() + }) + test('with random interpolation', () => { + const basic = `const H1 = styled.h1\` + \${something}; + display: flex; + \`` + const { code } = babel.transform(basic, { + plugins: [[plugin, { extractStatic: true }]], + filename: __filename, + babelrc: false + }) + + expect(code).toMatchSnapshot() + expect(fs.writeFileSync).not.toHaveBeenCalled() + }) + test('with selector interpolation', () => { + const basic = `const H1 = styled.h1\` + \${something} { + display: flex; + } + \`` + const { code } = babel.transform(basic, { + plugins: [[plugin, { extractStatic: true }]], + filename: __filename, + babelrc: false + }) + + expect(code).toMatchSnapshot() + expect(fs.writeFileSync).not.toHaveBeenCalled() + }) }) }) From e330c976dc8317ceb15bc71cb76d7dedc8157ffa Mon Sep 17 00:00:00 2001 From: Kye Hohenberger Date: Thu, 24 Aug 2017 19:27:05 -0600 Subject: [PATCH 09/12] Progress on css version. Not sure how to give the function a baseClassName though. --- packages/babel-plugin-emotion/src/index.js | 29 +++++++++++++-- .../test/__snapshots__/css.test.js.snap | 23 ++++++++++++ .../test/__snapshots__/keyframes.test.js.snap | 37 +++++++++++++------ .../babel-plugin-emotion/test/css.test.js | 17 +++++++++ .../test/keyframes.test.js | 3 +- packages/emotion/src/index.js | 9 ++++- .../__snapshots__/extract.test.js.snap | 21 +++++++++++ .../test/extract/extract.test.emotion.css | 15 ++++++++ packages/emotion/test/extract/extract.test.js | 36 ++++++++++++++++++ 9 files changed, 172 insertions(+), 18 deletions(-) diff --git a/packages/babel-plugin-emotion/src/index.js b/packages/babel-plugin-emotion/src/index.js index 3ac9a32b4..213f04d78 100644 --- a/packages/babel-plugin-emotion/src/index.js +++ b/packages/babel-plugin-emotion/src/index.js @@ -35,11 +35,34 @@ export function replaceCssWithCallExpression( getIdentifierName(path, t), 'css' ) - if (state.extractStatic && !path.node.quasi.expressions.length) { + if (state.extractStatic) { const cssText = staticCSSTextCreator(name, hash, src) - const { staticCSSRules } = parseCSS(cssText, true, getFilename(path)) + const { staticCSSRules, composesCount } = parseCSS( + cssText, + true, + getFilename(path) + ) + + state.insertStaticRules( + staticCSSRules.map(ruleText => + ruleText.replace(/xxx(\d+)xxx/gm, `var(--${name}-${hash}-$1)`) + ) + ) + + if (path.node.quasi.expressions.length) { + const composeValues = path.node.quasi.expressions.slice( + 0, + composesCount + ) + const vars = path.node.quasi.expressions.slice(composesCount) + return path.replaceWith( + t.callExpression(identifier, [ + t.arrayExpression(composeValues), + t.arrayExpression(vars) + ]) + ) + } - state.insertStaticRules(staticCSSRules) if (!removePath) { return path.replaceWith(t.stringLiteral(`${name}-${hash}`)) } diff --git a/packages/babel-plugin-emotion/test/__snapshots__/css.test.js.snap b/packages/babel-plugin-emotion/test/__snapshots__/css.test.js.snap index a3a9ad18a..33d0ab9d5 100644 --- a/packages/babel-plugin-emotion/test/__snapshots__/css.test.js.snap +++ b/packages/babel-plugin-emotion/test/__snapshots__/css.test.js.snap @@ -86,6 +86,29 @@ exports[`babel css extract css basic 2`] = ` }" `; +exports[`babel css extract css basic 3`] = ` +"import \\"./css.test.emotion.css\\"; + +\\"css-153l48f\\";" +`; + +exports[`babel css extract css with dynamic values 1`] = ` +"import \\"./css.test.emotion.css\\"; + +\\"css-64popv\\";" +`; + +exports[`babel css extract css with dynamic values 2`] = ` +".css-153l48f { + margin: 12px 48px; + color: #ffffff; color: blue; + display: -webkit-box; display: -ms-flexbox; display: flex; + -webkit-box-flex: 1; + -ms-flex: 1 0 auto; + flex: 1 0 auto +}" +`; + exports[`babel css extract dynamic property objects 1`] = ` " css({ diff --git a/packages/babel-plugin-emotion/test/__snapshots__/keyframes.test.js.snap b/packages/babel-plugin-emotion/test/__snapshots__/keyframes.test.js.snap index adb87d248..e81f313be 100644 --- a/packages/babel-plugin-emotion/test/__snapshots__/keyframes.test.js.snap +++ b/packages/babel-plugin-emotion/test/__snapshots__/keyframes.test.js.snap @@ -30,19 +30,32 @@ exports[`babel keyframes extract keyframes basic 2`] = ` `; exports[`babel keyframes extract keyframes with interpolation 1`] = ` -" -const rotate360 = /*#__PURE__*/keyframes([], [endingRotation], function createEmotionStyledRules(x0) { - return [{ - \\"from\\": { - \\"WebkitTransform\\": \\"rotate(0deg)\\", - \\"transform\\": \\"rotate(0deg)\\" - }, - \\"to\\": { - \\"WebkitTransform\\": \`rotate(\${x0})\`, - \\"transform\\": \`rotate(\${x0})\` +"import \\"./keyframes.test.emotion.css\\"; + +const rotate360 = keyframes([], [endingRotation]);" +`; + +exports[`babel keyframes extract keyframes with interpolation 2`] = ` +"@-webkit-keyframes css-rotate360-9bsj7q { + from { + -webkit-transform: rotate(0deg); + transform: rotate(0deg) } - }]; -});" + to { + -webkit-transform: rotate(var(--css-rotate360-9bsj7q-0)); + transform: rotate(var(--css-rotate360-9bsj7q-0)) + } +} +@keyframes css-rotate360-9bsj7q { + from { + -webkit-transform: rotate(0deg); + transform: rotate(0deg) + } + to { + -webkit-transform: rotate(var(--css-rotate360-9bsj7q-0)); + transform: rotate(var(--css-rotate360-9bsj7q-0)) + } +}" `; exports[`babel keyframes inline keyframes basic 1`] = ` diff --git a/packages/babel-plugin-emotion/test/css.test.js b/packages/babel-plugin-emotion/test/css.test.js index 7b02c4746..d3aa816ce 100644 --- a/packages/babel-plugin-emotion/test/css.test.js +++ b/packages/babel-plugin-emotion/test/css.test.js @@ -292,6 +292,23 @@ describe('babel css', () => { expect(fs.writeFileSync.mock.calls[0][1]).toMatchSnapshot() }) + test('css with dynamic values', () => { + const color = '#ffffff' + const basic = ` + css\` + margin: 12px 48px; + color: ${color}; + \`` + const { code } = babel.transform(basic, { + plugins: [[plugin, { extractStatic: true }]], + filename: __filename, + babelrc: false + }) + expect(code).toMatchSnapshot() + expect(fs.writeFileSync).toHaveBeenCalledTimes(2) + expect(fs.writeFileSync.mock.calls[0][1]).toMatchSnapshot() + }) + test('composes', () => { const basic = ` const cls1 = css\` diff --git a/packages/babel-plugin-emotion/test/keyframes.test.js b/packages/babel-plugin-emotion/test/keyframes.test.js index 5f7593c59..17e660363 100644 --- a/packages/babel-plugin-emotion/test/keyframes.test.js +++ b/packages/babel-plugin-emotion/test/keyframes.test.js @@ -77,7 +77,8 @@ describe('babel keyframes', () => { filename: __filename }) expect(code).toMatchSnapshot() - expect(fs.writeFileSync).toHaveBeenCalledTimes(1) + expect(fs.writeFileSync).toHaveBeenCalledTimes(2) + expect(fs.writeFileSync.mock.calls[1][1]).toMatchSnapshot() }) }) }) diff --git a/packages/emotion/src/index.js b/packages/emotion/src/index.js index 8bdaef197..b5895a7b3 100644 --- a/packages/emotion/src/index.js +++ b/packages/emotion/src/index.js @@ -74,7 +74,9 @@ export function css(objs: any, vars: Array, content: () => Array) { } let { computedClassName = '', objectStyles = [] } = buildStyles( - content ? objs.concat(content.apply(null, vars)) : objs + content + ? objs.concat(content.apply(null, vars)) + : vars && vars.length ? [customProperties(objs[0], vars)] : objs ) if (objectStyles.length) { computedClassName += ' ' + objStyle.apply(null, objectStyles).toString() @@ -85,7 +87,10 @@ export function css(objs: any, vars: Array, content: () => Array) { type inputVar = string | number -export function customProperties(baseClassName: string, vars: Array) { +export function customProperties( + baseClassName: string, + vars: Array +): string { const hash = hashArray([baseClassName, ...vars]) const varCls = `css-vars-${hash}` if (inserted[hash]) { diff --git a/packages/emotion/test/extract/__snapshots__/extract.test.js.snap b/packages/emotion/test/extract/__snapshots__/extract.test.js.snap index 21fcd510a..90b79e072 100644 --- a/packages/emotion/test/extract/__snapshots__/extract.test.js.snap +++ b/packages/emotion/test/extract/__snapshots__/extract.test.js.snap @@ -8,6 +8,27 @@ exports[`styled basic render nested 1`] = ` `; +exports[`styled css with dynamic values 1`] = ` +.glamor-0 { + color: yellow; + height: 15px; +} + +

+ hello world +

+`; + +exports[`styled injectGlobal with dynamic values 1`] = ` +

+ hello world +

+`; + exports[`styled name 1`] = `

div { + display: none +} +html { + background: green +} .css-14yvnlz { font-family: sans-serif; color: yellow; background-color: purple +} +.css-cls-um1go4 { + color: var(--css-cls-um1go4-0); + height: var(--css-cls-um1go4-1) } \ No newline at end of file diff --git a/packages/emotion/test/extract/extract.test.js b/packages/emotion/test/extract/extract.test.js index 242ecc54c..7c16cefb0 100644 --- a/packages/emotion/test/extract/extract.test.js +++ b/packages/emotion/test/extract/extract.test.js @@ -61,6 +61,28 @@ describe('styled', () => { } ` }) + + test('injectGlobal with dynamic values', () => { + const display = 'flex' + const cls = injectGlobal` + body { + margin: 0; + padding: 0; + display: ${display}; + & > div { + display: none; + } + } + html { + background: green; + } + ` + + const tree = renderer.create(

hello world

).toJSON() + + expect(tree).toMatchSnapshot() + }) + test('css', () => { expect(css` font-family: sans-serif; @@ -68,6 +90,20 @@ describe('styled', () => { background-color: purple; `) }) + + test('css with dynamic values', () => { + const color = 'yellow' + const height = '15rem' + const cls = css` + color: ${color}; + height: ${height}; + ` + + const tree = renderer.create(

hello world

).toJSON() + + expect(tree).toMatchSnapshot() + }) + test('writes the correct css', () => { const filenameArr = basename(__filename).split('.') filenameArr.pop() From 3a701dbcb48a394cff5a91416cc685701e1f83a2 Mon Sep 17 00:00:00 2001 From: Kye Hohenberger Date: Thu, 24 Aug 2017 19:32:13 -0600 Subject: [PATCH 10/12] give css function a name to work with --- packages/babel-plugin-emotion/src/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/babel-plugin-emotion/src/index.js b/packages/babel-plugin-emotion/src/index.js index 213f04d78..aeb3680eb 100644 --- a/packages/babel-plugin-emotion/src/index.js +++ b/packages/babel-plugin-emotion/src/index.js @@ -57,7 +57,9 @@ export function replaceCssWithCallExpression( const vars = path.node.quasi.expressions.slice(composesCount) return path.replaceWith( t.callExpression(identifier, [ - t.arrayExpression(composeValues), + t.arrayExpression( + [t.stringLiteral(`${name}-${hash}`)].concat(composeValues) + ), t.arrayExpression(vars) ]) ) From 503ecb6a1d9d472d32aa53e6ba6f0c441079110a Mon Sep 17 00:00:00 2001 From: Kye Hohenberger Date: Thu, 24 Aug 2017 21:53:51 -0600 Subject: [PATCH 11/12] add pure comment to function --- packages/babel-plugin-emotion/src/index.js | 4 ++++ .../test/__snapshots__/css.test.js.snap | 11 +--------- .../__snapshots__/extract.test.js.snap | 8 ++++---- .../test/extract/extract.test.emotion.css | 2 +- packages/emotion/test/extract/extract.test.js | 20 +++++++++---------- 5 files changed, 20 insertions(+), 25 deletions(-) diff --git a/packages/babel-plugin-emotion/src/index.js b/packages/babel-plugin-emotion/src/index.js index aeb3680eb..2eb58eb46 100644 --- a/packages/babel-plugin-emotion/src/index.js +++ b/packages/babel-plugin-emotion/src/index.js @@ -50,6 +50,10 @@ export function replaceCssWithCallExpression( ) if (path.node.quasi.expressions.length) { + if (!removePath) { + path.addComment('leading', '#__PURE__') + } + const composeValues = path.node.quasi.expressions.slice( 0, composesCount diff --git a/packages/babel-plugin-emotion/test/__snapshots__/css.test.js.snap b/packages/babel-plugin-emotion/test/__snapshots__/css.test.js.snap index 33d0ab9d5..748ad3fae 100644 --- a/packages/babel-plugin-emotion/test/__snapshots__/css.test.js.snap +++ b/packages/babel-plugin-emotion/test/__snapshots__/css.test.js.snap @@ -32,16 +32,7 @@ exports[`babel css extract composes no dynamic 1`] = ` "import './css.test.emotion.css'; const cls1 = 'css-cls1-1q8jsgx'; -const cls2 = /*#__PURE__*/css(['one-class', 'another-class', cls1], [], function createEmotionStyledRules() { - return [{ - 'WebkitBoxPack': 'center', - 'msFlexPack': 'center', - 'justifyContent': 'center', - 'WebkitBoxAlign': 'center', - 'msFlexAlign': 'center', - 'alignItems': 'center' - }]; -});" +const cls2 = /*#__PURE__*/css(['css-cls2-1k023jk'], ['one-class', 'another-class', cls1]);" `; exports[`babel css extract composes no dynamic 2`] = ` diff --git a/packages/emotion/test/extract/__snapshots__/extract.test.js.snap b/packages/emotion/test/extract/__snapshots__/extract.test.js.snap index 90b79e072..56f0497e2 100644 --- a/packages/emotion/test/extract/__snapshots__/extract.test.js.snap +++ b/packages/emotion/test/extract/__snapshots__/extract.test.js.snap @@ -2,7 +2,7 @@ exports[`styled basic render nested 1`] = `

hello world

@@ -31,7 +31,7 @@ exports[`styled injectGlobal with dynamic values 1`] = ` exports[`styled name 1`] = `

hello world

@@ -39,7 +39,7 @@ exports[`styled name 1`] = ` exports[`styled no dynamic 1`] = `

hello world

@@ -51,7 +51,7 @@ exports[`styled with expressions 1`] = ` }

hello world

diff --git a/packages/emotion/test/extract/extract.test.emotion.css b/packages/emotion/test/extract/extract.test.emotion.css index d78c5086d..2f0e5b022 100644 --- a/packages/emotion/test/extract/extract.test.emotion.css +++ b/packages/emotion/test/extract/extract.test.emotion.css @@ -26,7 +26,7 @@ html { body { margin: 0; padding: 0; - display: var(--css-cls-1cs3vvh-0) + display: var(--css-cls-y4yfy5-0) } body > div { display: none diff --git a/packages/emotion/test/extract/extract.test.js b/packages/emotion/test/extract/extract.test.js index 7c16cefb0..61e28e742 100644 --- a/packages/emotion/test/extract/extract.test.js +++ b/packages/emotion/test/extract/extract.test.js @@ -65,17 +65,17 @@ describe('styled', () => { test('injectGlobal with dynamic values', () => { const display = 'flex' const cls = injectGlobal` - body { - margin: 0; - padding: 0; - display: ${display}; - & > div { - display: none; - } - } - html { - background: green; + body { + margin: 0; + padding: 0; + display: ${display}; + & > div { + display: none; } + } + html { + background: green; + } ` const tree = renderer.create(

hello world

).toJSON() From 5b3f20ef59a313bacfe5a51af2400ecdf5243666 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 25 Aug 2017 21:58:44 +1000 Subject: [PATCH 12/12] It mostly works --- packages/babel-plugin-emotion/src/index.js | 79 +++++++++++-------- .../test/__snapshots__/css.test.js.snap | 33 +++----- .../test/__snapshots__/keyframes.test.js.snap | 37 +++------ .../babel-plugin-emotion/test/css.test.js | 25 +++--- .../test/keyframes.test.js | 6 +- packages/emotion/src/index.js | 12 +-- .../__snapshots__/extract.test.js.snap | 16 ++-- .../extract/__snapshots__/server.test.js.snap | 8 +- .../test/extract/extract.test.emotion.css | 17 +--- 9 files changed, 112 insertions(+), 121 deletions(-) diff --git a/packages/babel-plugin-emotion/src/index.js b/packages/babel-plugin-emotion/src/index.js index 2eb58eb46..92e88ed5b 100644 --- a/packages/babel-plugin-emotion/src/index.js +++ b/packages/babel-plugin-emotion/src/index.js @@ -30,49 +30,60 @@ export function replaceCssWithCallExpression( removePath = false ) { try { - const { name, hash, src } = inline( - path.node.quasi, - getIdentifierName(path, t), - 'css' - ) + const identifierName = getIdentifierName(path, t) + const { name, hash, src } = inline(path.node.quasi, identifierName, 'css') if (state.extractStatic) { - const cssText = staticCSSTextCreator(name, hash, src) - const { staticCSSRules, composesCount } = parseCSS( - cssText, - true, - getFilename(path) - ) + if ( + // we should probably change this + staticCSSTextCreator('css', 'hash', 'thing') === '.css-hash { thing }' + ) { + const { name, hash, src } = inline( + path.node.quasi, + identifierName, + 'vars' // we don't want these styles to be merged in css`` + ) - state.insertStaticRules( - staticCSSRules.map(ruleText => - ruleText.replace(/xxx(\d+)xxx/gm, `var(--${name}-${hash}-$1)`) + const cssText = `.${name}-${hash} { ${src} }` + const { staticCSSRules, varCount } = parseCSS( + cssText, + true, + getFilename(path) ) - ) - if (path.node.quasi.expressions.length) { - if (!removePath) { + // Help Needed: + // We should read the browser preferences for postcss + // and turn this on only if css vars are supported in their browser targets. + let canUseCssVariables = true + + if ( + varCount === path.node.quasi.expressions.length && + canUseCssVariables && + path.node.quasi.expressions.length !== 0 + ) { path.addComment('leading', '#__PURE__') + state.insertStaticRules( + staticCSSRules.map(ruleText => + ruleText.replace(/xxx(\d+)xxx/gm, `var(--${name}-${hash}-$1)`) + ) + ) + return path.replaceWith( + t.callExpression(identifier, [ + t.arrayExpression([t.stringLiteral(`${name}-${hash}`)]), + t.arrayExpression(path.node.quasi.expressions) + ]) + ) } - - const composeValues = path.node.quasi.expressions.slice( - 0, - composesCount - ) - const vars = path.node.quasi.expressions.slice(composesCount) - return path.replaceWith( - t.callExpression(identifier, [ - t.arrayExpression( - [t.stringLiteral(`${name}-${hash}`)].concat(composeValues) - ), - t.arrayExpression(vars) - ]) - ) } + if (path.node.quasi.expressions.length === 0) { + const cssText = staticCSSTextCreator(name, hash, src) + const { staticCSSRules } = parseCSS(cssText, true, getFilename(path)) - if (!removePath) { - return path.replaceWith(t.stringLiteral(`${name}-${hash}`)) + state.insertStaticRules(staticCSSRules) + if (!removePath) { + return path.replaceWith(t.stringLiteral(`${name}-${hash}`)) + } + return path.replaceWith(t.identifier('undefined')) } - return path.replaceWith(t.identifier('undefined')) } const { styles, composesCount } = parseCSS(src, false, getFilename(path)) diff --git a/packages/babel-plugin-emotion/test/__snapshots__/css.test.js.snap b/packages/babel-plugin-emotion/test/__snapshots__/css.test.js.snap index 748ad3fae..2de67a153 100644 --- a/packages/babel-plugin-emotion/test/__snapshots__/css.test.js.snap +++ b/packages/babel-plugin-emotion/test/__snapshots__/css.test.js.snap @@ -32,7 +32,16 @@ exports[`babel css extract composes no dynamic 1`] = ` "import './css.test.emotion.css'; const cls1 = 'css-cls1-1q8jsgx'; -const cls2 = /*#__PURE__*/css(['css-cls2-1k023jk'], ['one-class', 'another-class', cls1]);" +const cls2 = /*#__PURE__*/css(['one-class', 'another-class', cls1], [], function createEmotionStyledRules() { + return [{ + 'WebkitBoxPack': 'center', + 'msFlexPack': 'center', + 'justifyContent': 'center', + 'WebkitBoxAlign': 'center', + 'msFlexAlign': 'center', + 'alignItems': 'center' + }]; +});" `; exports[`babel css extract composes no dynamic 2`] = ` @@ -54,12 +63,6 @@ const cls2 = /*#__PURE__*/css([cls1], [], function createEmotionStyledRules() { });" `; -exports[`babel css extract composes with objects 2`] = ` -".css-cls1-1q8jsgx { - display: -webkit-box; display: -ms-flexbox; display: flex -}" -`; - exports[`babel css extract css basic 1`] = ` "import \\"./css.test.emotion.css\\"; @@ -77,26 +80,16 @@ exports[`babel css extract css basic 2`] = ` }" `; -exports[`babel css extract css basic 3`] = ` -"import \\"./css.test.emotion.css\\"; - -\\"css-153l48f\\";" -`; - exports[`babel css extract css with dynamic values 1`] = ` "import \\"./css.test.emotion.css\\"; -\\"css-64popv\\";" +/*#__PURE__*/css([\\"vars-bnojx5\\"], [color]);" `; exports[`babel css extract css with dynamic values 2`] = ` -".css-153l48f { +".vars-bnojx5 { margin: 12px 48px; - color: #ffffff; color: blue; - display: -webkit-box; display: -ms-flexbox; display: flex; - -webkit-box-flex: 1; - -ms-flex: 1 0 auto; - flex: 1 0 auto + color: var(--vars-bnojx5-0) }" `; diff --git a/packages/babel-plugin-emotion/test/__snapshots__/keyframes.test.js.snap b/packages/babel-plugin-emotion/test/__snapshots__/keyframes.test.js.snap index e81f313be..adb87d248 100644 --- a/packages/babel-plugin-emotion/test/__snapshots__/keyframes.test.js.snap +++ b/packages/babel-plugin-emotion/test/__snapshots__/keyframes.test.js.snap @@ -30,32 +30,19 @@ exports[`babel keyframes extract keyframes basic 2`] = ` `; exports[`babel keyframes extract keyframes with interpolation 1`] = ` -"import \\"./keyframes.test.emotion.css\\"; - -const rotate360 = keyframes([], [endingRotation]);" -`; - -exports[`babel keyframes extract keyframes with interpolation 2`] = ` -"@-webkit-keyframes css-rotate360-9bsj7q { - from { - -webkit-transform: rotate(0deg); - transform: rotate(0deg) - } - to { - -webkit-transform: rotate(var(--css-rotate360-9bsj7q-0)); - transform: rotate(var(--css-rotate360-9bsj7q-0)) - } -} -@keyframes css-rotate360-9bsj7q { - from { - -webkit-transform: rotate(0deg); - transform: rotate(0deg) - } - to { - -webkit-transform: rotate(var(--css-rotate360-9bsj7q-0)); - transform: rotate(var(--css-rotate360-9bsj7q-0)) +" +const rotate360 = /*#__PURE__*/keyframes([], [endingRotation], function createEmotionStyledRules(x0) { + return [{ + \\"from\\": { + \\"WebkitTransform\\": \\"rotate(0deg)\\", + \\"transform\\": \\"rotate(0deg)\\" + }, + \\"to\\": { + \\"WebkitTransform\\": \`rotate(\${x0})\`, + \\"transform\\": \`rotate(\${x0})\` } -}" + }]; +});" `; exports[`babel keyframes inline keyframes basic 1`] = ` diff --git a/packages/babel-plugin-emotion/test/css.test.js b/packages/babel-plugin-emotion/test/css.test.js index d3aa816ce..45c0cae16 100644 --- a/packages/babel-plugin-emotion/test/css.test.js +++ b/packages/babel-plugin-emotion/test/css.test.js @@ -273,6 +273,9 @@ describe('babel css', () => { }) }) describe('extract', () => { + afterEach(() => { + fs.writeFileSync.mockClear() + }) test('css basic', () => { const basic = ` css\` @@ -288,16 +291,15 @@ describe('babel css', () => { babelrc: false }) expect(code).toMatchSnapshot() - expect(fs.writeFileSync).toHaveBeenCalledTimes(1) + expect(fs.writeFileSync).toHaveBeenCalled() expect(fs.writeFileSync.mock.calls[0][1]).toMatchSnapshot() }) test('css with dynamic values', () => { - const color = '#ffffff' const basic = ` css\` margin: 12px 48px; - color: ${color}; + color: \${color}; \`` const { code } = babel.transform(basic, { plugins: [[plugin, { extractStatic: true }]], @@ -305,7 +307,7 @@ describe('babel css', () => { babelrc: false }) expect(code).toMatchSnapshot() - expect(fs.writeFileSync).toHaveBeenCalledTimes(2) + expect(fs.writeFileSync).toHaveBeenCalled() expect(fs.writeFileSync.mock.calls[0][1]).toMatchSnapshot() }) @@ -326,8 +328,8 @@ describe('babel css', () => { babelrc: false }) expect(code).toMatchSnapshot() - expect(fs.writeFileSync).toHaveBeenCalledTimes(2) - expect(fs.writeFileSync.mock.calls[1][1]).toMatchSnapshot() + expect(fs.writeFileSync).toHaveBeenCalled() + expect(fs.writeFileSync.mock.calls[0][1]).toMatchSnapshot() }) test('composes no dynamic', () => { const basic = ` @@ -346,8 +348,8 @@ describe('babel css', () => { babelrc: false }) expect(code).toMatchSnapshot() - expect(fs.writeFileSync).toHaveBeenCalledTimes(3) - expect(fs.writeFileSync.mock.calls[2][1]).toMatchSnapshot() + expect(fs.writeFileSync).toHaveBeenCalled() + expect(fs.writeFileSync.mock.calls[0][1]).toMatchSnapshot() }) test('basic object support', () => { @@ -356,6 +358,7 @@ describe('babel css', () => { plugins: [[plugin]] }) expect(code).toMatchSnapshot() + expect(fs.writeFileSync).not.toHaveBeenCalled() }) test('prefixed objects', () => { @@ -374,6 +377,7 @@ describe('babel css', () => { plugins: [[plugin]] }) expect(code).toMatchSnapshot() + expect(fs.writeFileSync).not.toHaveBeenCalled() }) test('dynamic property objects', () => { @@ -387,6 +391,7 @@ describe('babel css', () => { plugins: [[plugin]] }) expect(code).toMatchSnapshot() + expect(fs.writeFileSync).not.toHaveBeenCalled() }) test('prefixed array of objects', () => { @@ -406,6 +411,7 @@ describe('babel css', () => { plugins: [[plugin]] }) expect(code).toMatchSnapshot() + expect(fs.writeFileSync).not.toHaveBeenCalled() }) test('composes with objects', () => { @@ -424,8 +430,7 @@ describe('babel css', () => { }) expect(code).toMatchSnapshot() - expect(fs.writeFileSync).toHaveBeenCalledTimes(3) - expect(fs.writeFileSync.mock.calls[2][1]).toMatchSnapshot() + expect(fs.writeFileSync).not.toHaveBeenCalled() }) }) }) diff --git a/packages/babel-plugin-emotion/test/keyframes.test.js b/packages/babel-plugin-emotion/test/keyframes.test.js index 17e660363..147c28e34 100644 --- a/packages/babel-plugin-emotion/test/keyframes.test.js +++ b/packages/babel-plugin-emotion/test/keyframes.test.js @@ -40,6 +40,9 @@ describe('babel keyframes', () => { }) }) describe('extract', () => { + afterEach(() => { + fs.writeFileSync.mockClear() + }) test('keyframes basic', () => { const basic = ` const rotate360 = keyframes\` @@ -77,8 +80,7 @@ describe('babel keyframes', () => { filename: __filename }) expect(code).toMatchSnapshot() - expect(fs.writeFileSync).toHaveBeenCalledTimes(2) - expect(fs.writeFileSync.mock.calls[1][1]).toMatchSnapshot() + expect(fs.writeFileSync).not.toHaveBeenCalled() }) }) }) diff --git a/packages/emotion/src/index.js b/packages/emotion/src/index.js index b5895a7b3..e5ef63e4d 100644 --- a/packages/emotion/src/index.js +++ b/packages/emotion/src/index.js @@ -69,14 +69,15 @@ function buildStyles(objs) { } export function css(objs: any, vars: Array, content: () => Array) { + if (content === undefined && vars !== undefined) { + return `${customProperties(objs[0], vars)} ${objs[0]}` + } if (!Array.isArray(objs)) { objs = [objs] } let { computedClassName = '', objectStyles = [] } = buildStyles( - content - ? objs.concat(content.apply(null, vars)) - : vars && vars.length ? [customProperties(objs[0], vars)] : objs + content ? objs.concat(content.apply(null, vars)) : objs ) if (objectStyles.length) { computedClassName += ' ' + objStyle.apply(null, objectStyles).toString() @@ -99,13 +100,12 @@ export function customProperties( let src = '' forEach(vars, (val: inputVar, i: number) => { - src && (src += '; ') - src += `--${baseClassName}-${i}: ${val}` + src += `--${baseClassName}-${i}:${val};` }) let spec = { id: hash, - css: `.${varCls} {${src}}`, + css: `.${varCls}{${src}}`, type: 'raw' } diff --git a/packages/emotion/test/extract/__snapshots__/extract.test.js.snap b/packages/emotion/test/extract/__snapshots__/extract.test.js.snap index 56f0497e2..a84f75854 100644 --- a/packages/emotion/test/extract/__snapshots__/extract.test.js.snap +++ b/packages/emotion/test/extract/__snapshots__/extract.test.js.snap @@ -2,7 +2,7 @@ exports[`styled basic render nested 1`] = `

hello world

@@ -10,12 +10,12 @@ exports[`styled basic render nested 1`] = ` exports[`styled css with dynamic values 1`] = ` .glamor-0 { - color: yellow; - height: 15px; + --vars-cls-um1go4-0: yellow; + --vars-cls-um1go4-1: 15rem; }

hello world

@@ -31,7 +31,7 @@ exports[`styled injectGlobal with dynamic values 1`] = ` exports[`styled name 1`] = `

hello world

@@ -51,7 +51,7 @@ exports[`styled with expressions 1`] = ` }

hello world

@@ -87,5 +87,9 @@ html { font-family: sans-serif; color: yellow; background-color: purple +} +.vars-cls-um1go4 { + color: var(--vars-cls-um1go4-0); + height: var(--vars-cls-um1go4-1) }" `; diff --git a/packages/emotion/test/extract/__snapshots__/server.test.js.snap b/packages/emotion/test/extract/__snapshots__/server.test.js.snap index f79ec6fb6..7c9f949d5 100644 --- a/packages/emotion/test/extract/__snapshots__/server.test.js.snap +++ b/packages/emotion/test/extract/__snapshots__/server.test.js.snap @@ -4,14 +4,14 @@ exports[`extractCritical returns static css 1`] = `Object {}`; exports[`extractCritical returns static css 2`] = ` Object { - "css": ".css-vars-1py8q97 {--styled-Image-157rves-0: css-bounce-h7fy2z; --styled-Image-157rves-1: red}", + "css": ".css-vars-1py8q97{--styled-Image-157rves-0:css-bounce-h7fy2z;--styled-Image-157rves-1:red;}", "html": "
", "ids": Array [ "1py8q97", ], "rules": Array [ Object { - "cssText": ".css-vars-1py8q97 {--styled-Image-157rves-0: css-bounce-h7fy2z; --styled-Image-157rves-1: red}", + "cssText": ".css-vars-1py8q97{--styled-Image-157rves-0:css-bounce-h7fy2z;--styled-Image-157rves-1:red;}", }, ], } @@ -30,14 +30,14 @@ Object { exports[`hydration only rules that are not in the critical css are inserted 1`] = ` Object { - "css": ".css-vars-1py8q97 {--styled-Image-157rves-0: css-bounce-h7fy2z; --styled-Image-157rves-1: red}", + "css": ".css-vars-1py8q97{--styled-Image-157rves-0:css-bounce-h7fy2z;--styled-Image-157rves-1:red;}", "html": "
", "ids": Array [ "1py8q97", ], "rules": Array [ Object { - "cssText": ".css-vars-1py8q97 {--styled-Image-157rves-0: css-bounce-h7fy2z; --styled-Image-157rves-1: red}", + "cssText": ".css-vars-1py8q97{--styled-Image-157rves-0:css-bounce-h7fy2z;--styled-Image-157rves-1:red;}", }, ], } diff --git a/packages/emotion/test/extract/extract.test.emotion.css b/packages/emotion/test/extract/extract.test.emotion.css index 2f0e5b022..c85a8108f 100644 --- a/packages/emotion/test/extract/extract.test.emotion.css +++ b/packages/emotion/test/extract/extract.test.emotion.css @@ -23,23 +23,12 @@ html { background: pink } -body { - margin: 0; - padding: 0; - display: var(--css-cls-y4yfy5-0) -} -body > div { - display: none -} -html { - background: green -} .css-14yvnlz { font-family: sans-serif; color: yellow; background-color: purple } -.css-cls-um1go4 { - color: var(--css-cls-um1go4-0); - height: var(--css-cls-um1go4-1) +.vars-cls-um1go4 { + color: var(--vars-cls-um1go4-0); + height: var(--vars-cls-um1go4-1) } \ No newline at end of file