Repository: styled-components/babel-plugin-styled-components
Pull request #21 (2016-12-26 10:59:44)

Transpile template literals
Another feature for those who care about minification. @mxstbr Please put attention on docs and suggest/make changes if something there is difficult to comprehend.


Patch:
diff --git a/README.md b/README.md
index 679b4ff..19eba38 100644
--- a/README.md
+++ b/README.md
@@ -78,6 +78,55 @@ You can disable minification if you don't need it with minify option:
 }
 ```
 
+### Transpile template literals
+
+Template literals are not supported yet by some browsers. You'll probably transpile your code with some preset that includes `babel-plugin-transform-es2015-template-literals` to make it work in older browsers, but there is one tiny caveat. Output of that plugin is quite wordy. It's done this way to meet specification requirements.
+
+```JS
+// processed with babel-preset-latest
+
+var _templateObject = _taggedTemplateLiteral(['width: 100%;'], ['width: 100%;']);
+function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
+var Simple = _styledComponents2.default.div(_templateObject);
+```   
+
+Styled-components do not require full spec compatibility. So in order to reduce bundle size this plugin will  transpile template literals attached to styled-component to the form that works in older browsers but have smaller footprint.
+
+```JS
+// processed with babel-preset-latest
+// and babel-plugin-styled-components with { transpileTemplateLiterals: true } option
+
+var Simple = _styledComponents2.default.div(['width: 100%;']);
+```
+
+Take a note that it will keep other template literals not related to styled-components as is.
+
+```JS
+// following will be converted:
+styled.div``
+keyframe``
+css``
+
+// But this will not be converted and will arise syntax error in IE:
+`some text`
+
+// In next example outer template literal will be converted because it's attached to component factory,
+// but inner template literals will not be touched and will generate syntax error in older browsers.
+styled.div`color: ${ light ? `white` : `black`};`
+```
+
+You can disable this feature with `transpileTemplateLiterals` option:
+
+```JSON
+{
+  "plugins": [
+    ["styled-components", {
+      "transpileTemplateLiterals": 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 fc1c309..676e892 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,5 +1,6 @@
 import minify from './visitors/minify'
 import displayNameAndId from './visitors/displayNameAndId'
+import transpileTemplateLiterals from './visitors/transpileTemplateLiterals'
 
 export default function({ types: t }) {
   return {
@@ -7,6 +8,7 @@ export default function({ types: t }) {
       TaggedTemplateExpression(path, state) {
         minify(path, state)
         displayNameAndId(path, state)
+        transpileTemplateLiterals(path, state)
       }
     }
   }
diff --git a/src/utils/detectors.js b/src/utils/detectors.js
index a6b0974..4f5fde2 100644
--- a/src/utils/detectors.js
+++ b/src/utils/detectors.js
@@ -13,18 +13,25 @@ const importLocalName = (name, state) => {
   return name === 'default' ? 'styled' : name
 }
 
-export const isStyled = (node, state) => {
-  const { tag } = node
+export const isStyled = (tag, state) => {
   return (
     (t.isMemberExpression(tag) && tag.object.name === importLocalName('default', state)) ||
     (t.isCallExpression(tag) && tag.callee.name === importLocalName('default', state))
   )
 }
 
-export const isHelper = (node, state) => {
-  const { tag } = node
+export const isHelper = (tag, state) => {
   return t.isIdentifier(tag) && (
     tag.name === importLocalName('css', state) ||
     tag.name === importLocalName('keyframes', state)
   )
 }
+
+export const isConfiguredStyled = (tag, state) => {
+  return (
+    t.isCallExpression(tag) &&
+    t.isMemberExpression(tag.callee) &&
+    tag.callee.property.name === 'withConfig' &&
+    isStyled(tag.callee.object, state)
+  )
+}
diff --git a/src/utils/options.js b/src/utils/options.js
index 852af06..0b516fd 100644
--- a/src/utils/options.js
+++ b/src/utils/options.js
@@ -6,3 +6,4 @@ export const useDisplayName = (state) => getOption(state, 'displayName')
 export const useSSR = (state) =>  getOption(state, 'ssr')
 export const useFileName = (state) =>getOption(state, 'fileName')
 export const useMinify = (state) => getOption(state, 'minify')
+export const useTranspileTemplateLiterals = (state) => getOption(state, 'transpileTemplateLiterals')
diff --git a/src/visitors/displayNameAndId.js b/src/visitors/displayNameAndId.js
index d525ddb..9aff0fa 100644
--- a/src/visitors/displayNameAndId.js
+++ b/src/visitors/displayNameAndId.js
@@ -47,8 +47,8 @@ const getComponentId = (displayName) => {
   return `${displayName || 's'}-${hash(`${id}${displayName}`)}`
 }
 
-export default (path, state, detector) => {
-  if (isStyled(path.node, state)) {
+export default (path, state) => {
+  if (isStyled(path.node.tag, state)) {
     const displayName = getDisplayName(path, useFileName(state) && state.file)
     id++
     addConfig(
diff --git a/src/visitors/minify.js b/src/visitors/minify.js
index c547121..4bb1b4c 100644
--- a/src/visitors/minify.js
+++ b/src/visitors/minify.js
@@ -2,8 +2,8 @@ import * as t from 'babel-types'
 import { useMinify } from '../utils/options'
 import { isStyled, isHelper } from '../utils/detectors'
 
-export default (path, state, detector) => {
-  if (useMinify(state) && (isStyled(path.node, state) || isHelper(path.node, state))) {
+export default (path, state) => {
+  if (useMinify(state) && (isStyled(path.node.tag, state) || isHelper(path.node.tag, state))) {
     const templateLiteral = path.node.quasi
     for (let element of templateLiteral.quasis) {
       element.value.raw = element.value.raw.replace(/(\\r|\\n|\r|\n)\s*/g, '')
diff --git a/src/visitors/transpileTemplateLiterals.js b/src/visitors/transpileTemplateLiterals.js
new file mode 100644
index 0000000..0093b95
--- /dev/null
+++ b/src/visitors/transpileTemplateLiterals.js
@@ -0,0 +1,15 @@
+import * as t from 'babel-types'
+import { useTranspileTemplateLiterals } from '../utils/options'
+import { isStyled, isHelper, isConfiguredStyled } from '../utils/detectors'
+
+export default (path, state) => {
+  if (useTranspileTemplateLiterals(state) && (
+    isStyled(path.node.tag, state) ||
+    isHelper(path.node.tag, state) ||
+    isConfiguredStyled(path.node.tag, state)
+  )) {
+    const { tag: callee, quasi: { quasis, expressions }} = path.node
+    const values = t.arrayExpression(quasis.map(quasi => t.stringLiteral(quasi.value.cooked)))
+    path.replaceWith(t.callExpression(callee, [ values, ...expressions ]))
+  }
+}


Test patch:
diff --git a/test/fixtures/01-add-display-names/.babelrc b/test/fixtures/01-add-display-names/.babelrc
index 6651216..1d038d8 100644
--- a/test/fixtures/01-add-display-names/.babelrc
+++ b/test/fixtures/01-add-display-names/.babelrc
@@ -2,7 +2,8 @@
   "plugins": [
     ["../../../src", {
       "ssr": false,
-      "fileName": false
+      "fileName": false,
+      "transpileTemplateLiterals": false
     }]
   ]
 }
diff --git a/test/fixtures/02-add-identifier/.babelrc b/test/fixtures/02-add-identifier/.babelrc
index acd4be9..203a200 100644
--- a/test/fixtures/02-add-identifier/.babelrc
+++ b/test/fixtures/02-add-identifier/.babelrc
@@ -2,7 +2,8 @@
   "plugins": [
     ["../../../src", {
       "displayName": false,
-      "fileName": false
+      "fileName": false,
+      "transpileTemplateLiterals": false
     }]
   ]
 }
diff --git a/test/fixtures/03-add-identifier-and-display-name/.babelrc b/test/fixtures/03-add-identifier-and-display-name/.babelrc
index ce47411..242cad8 100644
--- a/test/fixtures/03-add-identifier-and-display-name/.babelrc
+++ b/test/fixtures/03-add-identifier-and-display-name/.babelrc
@@ -1,7 +1,8 @@
 {
   "plugins": [
     ["../../../src", {
-      "fileName": false
+      "fileName": false,
+      "transpileTemplateLiterals": false
     }]
   ]
 }
diff --git a/test/fixtures/04-track-the-imported-variable/.babelrc b/test/fixtures/04-track-the-imported-variable/.babelrc
index ce47411..242cad8 100644
--- a/test/fixtures/04-track-the-imported-variable/.babelrc
+++ b/test/fixtures/04-track-the-imported-variable/.babelrc
@@ -1,7 +1,8 @@
 {
   "plugins": [
     ["../../../src", {
-      "fileName": false
+      "fileName": false,
+      "transpileTemplateLiterals": false
     }]
   ]
 }
diff --git a/test/fixtures/05-use-file-name/.babelrc b/test/fixtures/05-use-file-name/.babelrc
index fb02be5..290e0a8 100644
--- a/test/fixtures/05-use-file-name/.babelrc
+++ b/test/fixtures/05-use-file-name/.babelrc
@@ -1,5 +1,7 @@
 {
   "plugins": [
-    "../../../src"
+    ["../../../src", {
+      "transpileTemplateLiterals": false
+    }]
   ]
 }
diff --git a/test/fixtures/06-work-with-hoisted-default-as-import/.babelrc b/test/fixtures/06-work-with-hoisted-default-as-import/.babelrc
index fb02be5..290e0a8 100644
--- a/test/fixtures/06-work-with-hoisted-default-as-import/.babelrc
+++ b/test/fixtures/06-work-with-hoisted-default-as-import/.babelrc
@@ -1,5 +1,7 @@
 {
   "plugins": [
-    "../../../src"
+    ["../../../src", {
+      "transpileTemplateLiterals": false
+    }]
   ]
 }
diff --git a/test/fixtures/07-not-use-private-api-if-not-required/.babelrc b/test/fixtures/07-not-use-private-api-if-not-required/.babelrc
index d4c9c82..40aebbc 100644
--- a/test/fixtures/07-not-use-private-api-if-not-required/.babelrc
+++ b/test/fixtures/07-not-use-private-api-if-not-required/.babelrc
@@ -2,7 +2,8 @@
   "plugins": [
     ["../../../src", {
       "ssr": false,
-      "displayName": false
+      "displayName": false,
+      "transpileTemplateLiterals": false
     }]
   ]
 }
diff --git a/test/fixtures/08-minify-css-to-use-with-transpilation/.babelrc b/test/fixtures/08-minify-css-to-use-with-transpilation/.babelrc
index 5db2615..91a8569 100644
--- a/test/fixtures/08-minify-css-to-use-with-transpilation/.babelrc
+++ b/test/fixtures/08-minify-css-to-use-with-transpilation/.babelrc
@@ -5,7 +5,8 @@
   "plugins": [
     ["../../../src", {
       "ssr": false,
-      "displayName": false
+      "displayName": false,
+      "transpileTemplateLiterals": false
     }]
   ]
 }
diff --git a/test/fixtures/09-minify-css-to-use-without-transpilation/.babelrc b/test/fixtures/09-minify-css-to-use-without-transpilation/.babelrc
index d4c9c82..40aebbc 100644
--- a/test/fixtures/09-minify-css-to-use-without-transpilation/.babelrc
+++ b/test/fixtures/09-minify-css-to-use-without-transpilation/.babelrc
@@ -2,7 +2,8 @@
   "plugins": [
     ["../../../src", {
       "ssr": false,
-      "displayName": false
+      "displayName": false,
+      "transpileTemplateLiterals": false
     }]
   ]
 }
diff --git a/test/fixtures/10-minify-css-in-helpers/.babelrc b/test/fixtures/10-minify-css-in-helpers/.babelrc
index d4c9c82..40aebbc 100644
--- a/test/fixtures/10-minify-css-in-helpers/.babelrc
+++ b/test/fixtures/10-minify-css-in-helpers/.babelrc
@@ -2,7 +2,8 @@
   "plugins": [
     ["../../../src", {
       "ssr": false,
-      "displayName": false
+      "displayName": false,
+      "transpileTemplateLiterals": false
     }]
   ]
 }
diff --git a/test/fixtures/11-transpile-template-literals-with-config/.babelrc b/test/fixtures/11-transpile-template-literals-with-config/.babelrc
new file mode 100644
index 0000000..7a25d31
--- /dev/null
+++ b/test/fixtures/11-transpile-template-literals-with-config/.babelrc
@@ -0,0 +1,12 @@
+{
+  "presets": [
+    "es2015"
+  ],
+  "plugins": [
+    ["../../../src", {
+      "ssr": false,
+      "displayName": true,
+      "transpileTemplateLiterals": true
+    }]
+  ]
+}
diff --git a/test/fixtures/11-transpile-template-literals-with-config/after.js b/test/fixtures/11-transpile-template-literals-with-config/after.js
new file mode 100644
index 0000000..ddd7f5e
--- /dev/null
+++ b/test/fixtures/11-transpile-template-literals-with-config/after.js
@@ -0,0 +1,21 @@
+'use strict';
+
+var _styledComponents = require('styled-components');
+
+var _styledComponents2 = _interopRequireDefault(_styledComponents);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var Named = _styledComponents2.default.div.withConfig({
+  displayName: 'before__Named'
+})(['width: 100%;']);
+
+var NamedWithInterpolation = _styledComponents2.default.div.withConfig({
+  displayName: 'before__NamedWithInterpolation'
+})(['color: ', ';'], function (color) {
+  return props.color;
+});
+
+var Wrapped = (0, _styledComponents2.default)(Inner).withConfig({
+  displayName: 'before__Wrapped'
+})(['color: red;']);
diff --git a/test/fixtures/11-transpile-template-literals-with-config/before.js b/test/fixtures/11-transpile-template-literals-with-config/before.js
new file mode 100644
index 0000000..1e94882
--- /dev/null
+++ b/test/fixtures/11-transpile-template-literals-with-config/before.js
@@ -0,0 +1,11 @@
+import styled from 'styled-components';
+
+const Named = styled.div`
+  width: 100%;
+`;
+
+const NamedWithInterpolation = styled.div`
+  color: ${color => props.color};
+`;
+
+const Wrapped = styled(Inner)`color: red;`;
diff --git a/test/fixtures/12-transpile-template-literals-without-config/.babelrc b/test/fixtures/12-transpile-template-literals-without-config/.babelrc
new file mode 100644
index 0000000..bf118ed
--- /dev/null
+++ b/test/fixtures/12-transpile-template-literals-without-config/.babelrc
@@ -0,0 +1,12 @@
+{
+  "presets": [
+    "es2015"
+  ],
+  "plugins": [
+    ["../../../src", {
+      "ssr": false,
+      "displayName": false,
+      "transpileTemplateLiterals": true
+    }]
+  ]
+}
diff --git a/test/fixtures/12-transpile-template-literals-without-config/after.js b/test/fixtures/12-transpile-template-literals-without-config/after.js
new file mode 100644
index 0000000..b23b1e1
--- /dev/null
+++ b/test/fixtures/12-transpile-template-literals-without-config/after.js
@@ -0,0 +1,15 @@
+'use strict';
+
+var _styledComponents = require('styled-components');
+
+var _styledComponents2 = _interopRequireDefault(_styledComponents);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var Named = _styledComponents2.default.div(['width: 100%;']);
+
+var NamedWithInterpolation = _styledComponents2.default.div(['color: ', ';'], function (color) {
+  return props.color;
+});
+
+var Wrapped = (0, _styledComponents2.default)(Inner)(['color: red;']);
diff --git a/test/fixtures/12-transpile-template-literals-without-config/before.js b/test/fixtures/12-transpile-template-literals-without-config/before.js
new file mode 100644
index 0000000..1e94882
--- /dev/null
+++ b/test/fixtures/12-transpile-template-literals-without-config/before.js
@@ -0,0 +1,11 @@
+import styled from 'styled-components';
+
+const Named = styled.div`
+  width: 100%;
+`;
+
+const NamedWithInterpolation = styled.div`
+  color: ${color => props.color};
+`;
+
+const Wrapped = styled(Inner)`color: red;`;