Repository: styled-components/babel-plugin-styled-components
Pull request #15 (2016-12-17 17:43:02)

Minify css


Patch:
diff --git a/README.md b/README.md
index 9c1e34f..679b4ff 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,22 @@ If you don't need server-side rendering, you can disable it with the `ssr` optio
 }
 ```
 
+### Minify styles
+
+By default, plugin minifies styles in template literals. This operation may potentially break your styles in some rare cases, so we recommend to keep this option enabled in development if it's enabled in the production build. You will not see the effect of minification in generated style tags, it solely affects the presentation of styles inside js code.
+
+You can disable minification if you don't need it with minify option:
+
+```JSON
+{
+  "plugins": [
+    ["styled-components", {
+      "minify": false
+    }]
+  ]
+}
+```
+
 ## License
 
 Licensed under the MIT License, Copyright © 2016 Vladimir Danchenkov and Maximilian Stoiber.
diff --git a/src/index.js b/src/index.js
index 37c2958..3e93ffe 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,6 +1,7 @@
 import hash from './utils/hash'
 import getTarget from './utils/get-target'
 import getName from './utils/get-name'
+import minify from './utils/minify'
 
 const blockName = (file) => {
   return file.opts.basename !== 'index' ?
@@ -18,23 +19,39 @@ export default function({ types: t }) {
   return {
     visitor: {
       Program(path, state) {
-        // Default imported variable name to "styled", adjust based on import below
-        let importedVariableName = 'styled'
+        const importedVariableNames = {
+          default: 'styled',
+          css: 'css',
+          keyframes: 'keyframes'
+        }
+
+        const isStyled = (tag) => (
+          (t.isMemberExpression(tag) && tag.object.name === importedVariableNames.default) ||
+          (t.isCallExpression(tag) && tag.callee.name === importedVariableNames.default)
+        )
 
-        const isStyled = (tag) => (tag.object && tag.object.name === importedVariableName) || (tag.callee && tag.callee.name === importedVariableName)
+        const isHelper = (tag) => (
+          t.isIdentifier(tag) && (
+            tag.name === importedVariableNames.css ||
+            tag.name === importedVariableNames.keyframes
+          )
+        )
 
         path.traverse({
           ImportDeclaration(path) {
             // Is the styled-components import!
             if (path.node.source.value === 'styled-components') {
-              // If the default is imported it's at defaultImport[0], otherwise defaultImport is empty
-              const defaultImport = path.get('specifiers').find((specifier) => {
-                return specifier.isImportDefaultSpecifier() || specifier.isImportSpecifier() && specifier.node.imported.name === 'default'
+              path.get('specifiers').forEach((specifier) => {
+                let importedName
+                if (specifier.isImportDefaultSpecifier()) {
+                  importedName = 'default'
+                } else if (specifier.isImportSpecifier()) {
+                  importedName = specifier.node.imported.name
+                }
+                if (importedVariableNames.hasOwnProperty(importedName)) {
+                  importedVariableNames[importedName] = specifier.node.local.name
+                }
               })
-              if (defaultImport) {
-                // Save the imported name
-                importedVariableName = defaultImport.node.local.name
-              }
             }
           }
         })
@@ -43,17 +60,17 @@ export default function({ types: t }) {
           displayName: getOption(state.opts, 'displayName'),
           ssr: getOption(state.opts, 'ssr'),
           fileName: getOption(state.opts, 'fileName'),
-        }
-
-        if (!options.ssr && !options.displayName) {
-          return
+          minify: getOption(state.opts, 'minify')
         }
 
         path.traverse({
           TaggedTemplateExpression(path, { file }) {
             const tag = path.node.tag
+            if (options.minify && (isStyled(tag) || isHelper(tag))) {
+              minify(path.node.quasi)
+            }
 
-            if (!isStyled(tag)) return
+            if (!(isStyled(tag) && (options.ssr || options.displayName))) return
 
             // Get target
             const target = getTarget(path.node.tag)
@@ -81,7 +98,7 @@ export default function({ types: t }) {
             }
 
             const call = t.callExpression(
-              t.identifier(importedVariableName),
+              t.identifier(importedVariableNames.default),
               [ t.objectExpression(styledCallProps) ]
             )
 
diff --git a/src/utils/minify.js b/src/utils/minify.js
new file mode 100644
index 0000000..f5eaf2f
--- /dev/null
+++ b/src/utils/minify.js
@@ -0,0 +1,8 @@
+import * as t from 'babel-types'
+
+export default (teplateLiteral) => {
+  for (let element of teplateLiteral.quasis) {
+    element.value.raw = element.value.raw.replace(/(\\r|\\n|\r|\n)\s*/g, '')
+    element.value.cooked = element.value.cooked.replace(/[\r\n]\s*/g, '')
+  }
+}


Test patch:
diff --git a/test/fixtures/01-add-display-names/after.js b/test/fixtures/01-add-display-names/after.js
index bb30dc0..ee9029f 100644
--- a/test/fixtures/01-add-display-names/after.js
+++ b/test/fixtures/01-add-display-names/after.js
@@ -1,7 +1,7 @@
 const Test = styled({
   target: 'div',
   displayName: 'Test'
-})` width: 100%;`;
+})`width: 100%;`;
 const Test2 = styled({
   target: 'div',
   displayName: 'Test2'
diff --git a/test/fixtures/01-add-display-names/before.js b/test/fixtures/01-add-display-names/before.js
index f18524e..37b09b7 100644
--- a/test/fixtures/01-add-display-names/before.js
+++ b/test/fixtures/01-add-display-names/before.js
@@ -1,4 +1,4 @@
-const Test = styled.div` width: 100%;`;
+const Test = styled.div`width: 100%;`;
 const Test2 = styled('div')``;
 const Test3 = true ? styled.div`` : styled.div``;
 const styles = { One: styled.div`` }
diff --git a/test/fixtures/02-add-identifier/after.js b/test/fixtures/02-add-identifier/after.js
index daadc8b..37ba8e0 100644
--- a/test/fixtures/02-add-identifier/after.js
+++ b/test/fixtures/02-add-identifier/after.js
@@ -1,7 +1,7 @@
 const Test = styled({
   target: "div",
   identifier: "Test-137hlza"
-})` width: 100%;`;
+})`width: 100%;`;
 const Test2 = true ? styled({
   target: "div",
   identifier: "Test2-bjbly"
diff --git a/test/fixtures/02-add-identifier/before.js b/test/fixtures/02-add-identifier/before.js
index 76a1998..3f4b612 100644
--- a/test/fixtures/02-add-identifier/before.js
+++ b/test/fixtures/02-add-identifier/before.js
@@ -1,4 +1,4 @@
-const Test = styled.div` width: 100%;`;
+const Test = styled.div`width: 100%;`;
 const Test2 = true ? styled.div`` : styled.div``;
 const styles = { One: styled.div`` }
 let Component;
diff --git a/test/fixtures/03-add-identifier-and-display-name/after.js b/test/fixtures/03-add-identifier-and-display-name/after.js
index 66077f0..80097ff 100644
--- a/test/fixtures/03-add-identifier-and-display-name/after.js
+++ b/test/fixtures/03-add-identifier-and-display-name/after.js
@@ -2,7 +2,7 @@ const Test = styled({
   target: "div",
   displayName: "Test",
   identifier: "Test-18z24ew"
-})` width: 100%;`;
+})`width: 100%;`;
 const Test2 = true ? styled({
   target: "div",
   displayName: "Test2",
diff --git a/test/fixtures/03-add-identifier-and-display-name/before.js b/test/fixtures/03-add-identifier-and-display-name/before.js
index 76a1998..3f4b612 100644
--- a/test/fixtures/03-add-identifier-and-display-name/before.js
+++ b/test/fixtures/03-add-identifier-and-display-name/before.js
@@ -1,4 +1,4 @@
-const Test = styled.div` width: 100%;`;
+const Test = styled.div`width: 100%;`;
 const Test2 = true ? styled.div`` : styled.div``;
 const styles = { One: styled.div`` }
 let Component;
diff --git a/test/fixtures/04-track-the-imported-variable/after.js b/test/fixtures/04-track-the-imported-variable/after.js
index 7f623a0..cd590e5 100644
--- a/test/fixtures/04-track-the-imported-variable/after.js
+++ b/test/fixtures/04-track-the-imported-variable/after.js
@@ -4,7 +4,7 @@ const Test = s({
   target: "div",
   displayName: "Test",
   identifier: "Test-62sgar"
-})` width: 100%;`;
+})`width: 100%;`;
 const Test2 = true ? s({
   target: "div",
   displayName: "Test2",
diff --git a/test/fixtures/04-track-the-imported-variable/before.js b/test/fixtures/04-track-the-imported-variable/before.js
index 3ac31ab..9fe7111 100644
--- a/test/fixtures/04-track-the-imported-variable/before.js
+++ b/test/fixtures/04-track-the-imported-variable/before.js
@@ -1,6 +1,6 @@
 import s from "styled-components";
 
-const Test = s.div` width: 100%;`;
+const Test = s.div`width: 100%;`;
 const Test2 = true ? s.div`` : s.div``;
 const styles = { One: s.div`` }
 let Component;
diff --git a/test/fixtures/05-use-file-name/after.js b/test/fixtures/05-use-file-name/after.js
index bc1db03..00548e6 100644
--- a/test/fixtures/05-use-file-name/after.js
+++ b/test/fixtures/05-use-file-name/after.js
@@ -4,7 +4,7 @@ const Test = styled({
   target: "div",
   displayName: "before__Test",
   identifier: "before__Test-102oti6"
-})` color: red;`;
+})`color: red;`;
 styled({
   target: "div",
   displayName: "before",
diff --git a/test/fixtures/05-use-file-name/before.js b/test/fixtures/05-use-file-name/before.js
index 3e3a215..216ba4b 100644
--- a/test/fixtures/05-use-file-name/before.js
+++ b/test/fixtures/05-use-file-name/before.js
@@ -1,5 +1,5 @@
 import styled from "styled-components";
 
-const Test = styled.div` color: red;`;
+const Test = styled.div`color: red;`;
 styled.div``;
 export default styled.button``;
diff --git a/test/fixtures/06-work-with-hoisted-default-as-import/after.js b/test/fixtures/06-work-with-hoisted-default-as-import/after.js
index ab6562b..4c5a961 100644
--- a/test/fixtures/06-work-with-hoisted-default-as-import/after.js
+++ b/test/fixtures/06-work-with-hoisted-default-as-import/after.js
@@ -2,5 +2,5 @@ const Test = s({
   target: 'div',
   displayName: 'before__Test',
   identifier: 'before__Test-1ex1ta1'
-})` width: 100%;`;
+})`width: 100%;`;
 import { default as s, css } from 'styled-components';
diff --git a/test/fixtures/06-work-with-hoisted-default-as-import/before.js b/test/fixtures/06-work-with-hoisted-default-as-import/before.js
index e9c32b7..f48662e 100644
--- a/test/fixtures/06-work-with-hoisted-default-as-import/before.js
+++ b/test/fixtures/06-work-with-hoisted-default-as-import/before.js
@@ -1,2 +1,2 @@
-const Test = s.div` width: 100%;`;
+const Test = s.div`width: 100%;`;
 import { default as s, css } from 'styled-components';
diff --git a/test/fixtures/08-minify-css-to-use-with-transpilation/.babelrc b/test/fixtures/08-minify-css-to-use-with-transpilation/.babelrc
new file mode 100644
index 0000000..5db2615
--- /dev/null
+++ b/test/fixtures/08-minify-css-to-use-with-transpilation/.babelrc
@@ -0,0 +1,11 @@
+{
+  "presets": [
+    "es2015"
+  ],
+  "plugins": [
+    ["../../../src", {
+      "ssr": false,
+      "displayName": false
+    }]
+  ]
+}
diff --git a/test/fixtures/08-minify-css-to-use-with-transpilation/after.js b/test/fixtures/08-minify-css-to-use-with-transpilation/after.js
new file mode 100644
index 0000000..4e4f961
--- /dev/null
+++ b/test/fixtures/08-minify-css-to-use-with-transpilation/after.js
@@ -0,0 +1,26 @@
+'use strict';
+
+var _templateObject = _taggedTemplateLiteral(['width: 100%;'], ['width: 100%;']),
+    _templateObject2 = _taggedTemplateLiteral(['content: "  ', '  ";'], ['content: "  ', '  ";']),
+    _templateObject3 = _taggedTemplateLiteral(['content: "  ', '  ";color: red;'], ['content: "  ', '  ";color: red;']),
+    _templateObject4 = _taggedTemplateLiteral(['&:hover {color: blue;}'], ['&:hover {color: blue;}']);
+
+var _styledComponents = require('styled-components');
+
+var _styledComponents2 = _interopRequireDefault(_styledComponents);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
+
+var Simple = _styledComponents2.default.div(_templateObject);
+
+var Interpolation = _styledComponents2.default.div(_templateObject2, function (props) {
+  return props.text;
+});
+
+var SpecialCharacters = _styledComponents2.default.div(_templateObject3, function (props) {
+  return props.text;
+});
+
+var Parens = _styledComponents2.default.div(_templateObject4);
diff --git a/test/fixtures/08-minify-css-to-use-with-transpilation/before.js b/test/fixtures/08-minify-css-to-use-with-transpilation/before.js
new file mode 100644
index 0000000..71dc3a3
--- /dev/null
+++ b/test/fixtures/08-minify-css-to-use-with-transpilation/before.js
@@ -0,0 +1,19 @@
+import styled from 'styled-components';
+
+const Simple = styled.div`
+  width: 100%;
+`;
+
+const Interpolation = styled.div`
+  content: "  ${props => props.text}  ";
+`;
+
+const SpecialCharacters = styled.div`
+  content: "  ${props => props.text}  ";\n color: red;
+`;
+
+const Parens = styled.div`
+  &:hover {
+    color: blue;
+  }
+`;
diff --git a/test/fixtures/09-minify-css-to-use-without-transpilation/.babelrc b/test/fixtures/09-minify-css-to-use-without-transpilation/.babelrc
new file mode 100644
index 0000000..d4c9c82
--- /dev/null
+++ b/test/fixtures/09-minify-css-to-use-without-transpilation/.babelrc
@@ -0,0 +1,8 @@
+{
+  "plugins": [
+    ["../../../src", {
+      "ssr": false,
+      "displayName": false
+    }]
+  ]
+}
diff --git a/test/fixtures/09-minify-css-to-use-without-transpilation/after.js b/test/fixtures/09-minify-css-to-use-without-transpilation/after.js
new file mode 100644
index 0000000..c512356
--- /dev/null
+++ b/test/fixtures/09-minify-css-to-use-without-transpilation/after.js
@@ -0,0 +1,9 @@
+import styled from 'styled-components';
+
+const Simple = styled.div`width: 100%;`;
+
+const Interpolation = styled.div`content: "  ${ props => props.text }  ";`;
+
+const SpecialCharacters = styled.div`content: "  ${ props => props.text }  ";color: red;`;
+
+const Parens = styled.div`&:hover {color: blue;}color: red;`;
diff --git a/test/fixtures/09-minify-css-to-use-without-transpilation/before.js b/test/fixtures/09-minify-css-to-use-without-transpilation/before.js
new file mode 100644
index 0000000..f3de450
--- /dev/null
+++ b/test/fixtures/09-minify-css-to-use-without-transpilation/before.js
@@ -0,0 +1,20 @@
+import styled from 'styled-components';
+
+const Simple = styled.div`
+  width: 100%;
+`;
+
+const Interpolation = styled.div`
+  content: "  ${props => props.text}  ";
+`;
+
+const SpecialCharacters = styled.div`
+  content: "  ${props => props.text}  ";\n color: red;
+`;
+
+const Parens = styled.div`
+  &:hover {
+    color: blue;
+  }
+  color: red;
+`;
diff --git a/test/fixtures/10-minify-css-in-helpers/.babelrc b/test/fixtures/10-minify-css-in-helpers/.babelrc
new file mode 100644
index 0000000..d4c9c82
--- /dev/null
+++ b/test/fixtures/10-minify-css-in-helpers/.babelrc
@@ -0,0 +1,8 @@
+{
+  "plugins": [
+    ["../../../src", {
+      "ssr": false,
+      "displayName": false
+    }]
+  ]
+}
diff --git a/test/fixtures/10-minify-css-in-helpers/after.js b/test/fixtures/10-minify-css-in-helpers/after.js
new file mode 100644
index 0000000..6d95b1c
--- /dev/null
+++ b/test/fixtures/10-minify-css-in-helpers/after.js
@@ -0,0 +1,5 @@
+import { css, keyframes } from 'styled-components';
+
+const key = keyframes`to {transform: rotate(360deg);}`;
+
+const color = css`color: ${ theColor };`;
diff --git a/test/fixtures/10-minify-css-in-helpers/before.js b/test/fixtures/10-minify-css-in-helpers/before.js
new file mode 100644
index 0000000..0007afb
--- /dev/null
+++ b/test/fixtures/10-minify-css-in-helpers/before.js
@@ -0,0 +1,11 @@
+import { css, keyframes } from 'styled-components';
+
+const key = keyframes`
+  to {
+    transform: rotate(360deg);
+  }
+`;
+
+const color = css`
+  color: ${theColor};
+`;