Repository: styled-components/babel-plugin-styled-components
Pull request #5 (2016-11-24 15:42:35)

Rewrite to the new, private API
With the new private API, all the IIFEs are unnecessary since we can just pass an object to the styled() calls. This commit updates the plugin and the tests to reflect the new result. Need to wait for styled-components/styled-components#227 (which introduces the new API) to be merged before releasing.

----

Please review the code @vdanchenkov, I'm not very familiar with writing babel plugins, so if you see things that could be done easier please let me know!

@geelen please review the result of the conversion in the `after.js` files of the fixtures!


Patch:
diff --git a/src/index.js b/src/index.js
index 4b2fa97..564bc44 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,14 +1,16 @@
 import template from 'babel-template'
 
 import hash from './utils/hash'
+import getTarget from './utils/get-target'
 
-const buildNodeWithDisplayNameAndIdentifier = template(`(function() { var c = VALUE;  c.identifier = IDENTIFIER;  c.displayName = DISPLAYNAME; return c })()`)
-const buildNodeWithDisplayName = template(`(function() { var c = VALUE;  c.displayName = DISPLAYNAME; return c })()`)
-const buildNodeWithIdentifier = template(`(function() { var c = VALUE;  c.identifier = IDENTIFIER; return c })()`)
+const buildStyledCall = template(`styled({
+  target: TARGET,
+  displayName: DISPLAYNAME,
+  identifier: IDENTIFIER
+})`)
 
 const isStyled = (tag) => (tag.object && tag.object.name == 'styled') || (tag.callee && tag.callee.name == 'styled')
 
-
 let id = 0
 
 export default function({ types: t }) {
@@ -17,76 +19,60 @@ export default function({ types: t }) {
       TaggedTemplateExpression: {
         enter(path, { opts }) {
           const addDisplayName = (opts.displayName === undefined || opts.displayName === null) ? true : opts.displayName
-					const addIdentifier = (opts.ssr === undefined || opts.ssr === null) ? true : opts.ssr
+          const addIdentifier = (opts.ssr === undefined || opts.ssr === null) ? true : opts.ssr
           const tag = path.node.tag
 
           if (!isStyled(tag)) return
-          if (path.node._styledComponentsSeen) {
-            return
-          }
 
           let displayName
 
-          if (addDisplayName) {
-            path.find((path) => {
-              if (path.isAssignmentExpression()) {
-                displayName = path.node.left
-              } else if (path.isObjectProperty()) {
-                displayName = path.node.key
-              } else if (path.isVariableDeclarator()) {
-                displayName = path.node.id
-              } else if (path.isStatement()) {
-                // we've hit a statement, we should stop crawling up
-                return true
-              }
-
-              // we've got an displayName! no need to continue
-              if (displayName) return true
-            })
-
-            // ensure that we have an displayName we can inherit from
-            if (!displayName) return
-
-            // foo.bar -> bar
-            if (t.isMemberExpression(displayName)) {
-              displayName = displayName.property
+          path.find((path) => {
+            // const X = styled
+            if (path.isAssignmentExpression()) {
+              displayName = path.node.left
+            // const X = { Y: styled }
+            } else if (path.isObjectProperty()) {
+              displayName = path.node.key
+            // let X; X = styled
+            } else if (path.isVariableDeclarator()) {
+              displayName = path.node.id
+            } else if (path.isStatement()) {
+              // we've hit a statement, we should stop crawling up
+              return true
             }
 
-            // identifiers are the only thing we can reliably get a name from
-            if (!t.isIdentifier(displayName)) {
-              displayName = undefined
-            } else {
-              displayName = displayName.name
-            }
+            // we've got an displayName (if we need it) no need to continue
+            if (displayName) return true
+          })
+
+          // foo.bar -> bar
+          if (t.isMemberExpression(displayName)) {
+            displayName = displayName.property
           }
 
-          let newNode
-          if (addIdentifier) {
-            id++
-            // Prefix the identifier with a character if no displayName exists because CSS classes cannot start with a number
-            const identifier = `${displayName || 's'}-${hash(`${id}${displayName}`)}`
-            if (!addDisplayName) {
-              newNode = buildNodeWithIdentifier({
-                VALUE: path.node,
-                IDENTIFIER: t.stringLiteral(identifier),
-              })
-            } else {
-              newNode = buildNodeWithDisplayNameAndIdentifier({
-                VALUE: path.node,
-                DISPLAYNAME: t.stringLiteral(displayName),
-                IDENTIFIER: t.stringLiteral(identifier),
-              })
-            }
-          } else if (addDisplayName) {
-            newNode = buildNodeWithDisplayName({
-              VALUE: path.node,
-              DISPLAYNAME: t.stringLiteral(displayName),
-            })
+          // Get target
+          const target = getTarget(path.node.tag)
+
+          // identifiers are the only thing we can reliably get a name from
+          if (!t.isIdentifier(displayName)) {
+            displayName = undefined
+          } else {
+            displayName = displayName.name
           }
-          path.node._styledComponentsSeen = true
 
-          if (!newNode) return
-          path.replaceWith(newNode)
+          id++
+          // Prefix the identifier with a character if no displayName exists because CSS classes cannot start with a number
+          const identifier = `${displayName || 's'}-${hash(`${id}${displayName}`)}`
+          // Put together the final code again
+          // Create the styled({ }) call
+          const call = buildStyledCall({
+            TARGET: target,
+            DISPLAYNAME: (addDisplayName && t.stringLiteral(displayName)) || t.identifier('undefined'),
+            IDENTIFIER: (addIdentifier && t.stringLiteral(identifier)) || t.identifier('undefined')
+          })
+          // Put together the styled call with the template literal
+          // to get the finished styled({ })`` form! 🎉
+        	path.node.tag = call.expression
         }
       }
     }
diff --git a/src/utils/get-target.js b/src/utils/get-target.js
new file mode 100644
index 0000000..f98ebbf
--- /dev/null
+++ b/src/utils/get-target.js
@@ -0,0 +1,21 @@
+import * as t from 'babel-types'
+
+/**
+ * Get the target (i.e. tagname or component variable) from a styled call
+ *
+ * @param  {Node} node
+ *
+ * @return {String}   The target
+ */
+const getTarget = (node) => {
+  // styled.div`` => "div"
+  if (t.isMemberExpression(node)) {
+    return t.stringLiteral(node.property.name)
+  }
+  // styled(Bla) => Bla
+  if (t.isCallExpression(node)) {
+    return node.arguments[0]
+  }
+}
+
+export default getTarget


Test patch:
diff --git a/test/fixtures/add-display-names/after.js b/test/fixtures/add-display-names/after.js
index 7994cda..a2ff3cc 100644
--- a/test/fixtures/add-display-names/after.js
+++ b/test/fixtures/add-display-names/after.js
@@ -1,30 +1,35 @@
-const Test = function () {
-  var c = styled.div` width: 100%;`;
-  c.displayName = "Test";
-  return c;
-}();
-const Test2 = true ? function () {
-  var c = styled.div``;
-  c.displayName = "Test2";
-  return c;
-}() : function () {
-  var c = styled.div``;
-  c.displayName = "Test2";
-  return c;
-}();
-const styles = { One: function () {
-    var c = styled.div``;
-    c.displayName = "One";
-    return c;
-  }() };
+const Test = styled({
+  target: 'div',
+  displayName: 'Test',
+  identifier: undefined
+})` width: 100%;`;
+const Test2 = styled({
+  target: 'div',
+  displayName: 'Test2',
+  identifier: undefined
+})``;
+const Test3 = true ? styled({
+  target: 'div',
+  displayName: 'Test3',
+  identifier: undefined
+})`` : styled({
+  target: 'div',
+  displayName: 'Test3',
+  identifier: undefined
+})``;
+const styles = { One: styled({
+    target: 'div',
+    displayName: 'One',
+    identifier: undefined
+  })`` };
 let Component;
-Component = function () {
-  var c = styled.div``;
-  c.displayName = "Component";
-  return c;
-}();
-const WrappedComponent = function () {
-  var c = styled(Inner)``;
-  c.displayName = "WrappedComponent";
-  return c;
-}();
+Component = styled({
+  target: 'div',
+  displayName: 'Component',
+  identifier: undefined
+})``;
+const WrappedComponent = styled({
+  target: Inner,
+  displayName: 'WrappedComponent',
+  identifier: undefined
+})``;
diff --git a/test/fixtures/add-display-names/before.js b/test/fixtures/add-display-names/before.js
index 76a1998..f18524e 100644
--- a/test/fixtures/add-display-names/before.js
+++ b/test/fixtures/add-display-names/before.js
@@ -1,5 +1,6 @@
 const Test = styled.div` width: 100%;`;
-const Test2 = true ? styled.div`` : styled.div``;
+const Test2 = styled('div')``;
+const Test3 = true ? styled.div`` : styled.div``;
 const styles = { One: styled.div`` }
 let Component;
 Component = styled.div``;
diff --git a/test/fixtures/add-identifier-and-display-name/after.js b/test/fixtures/add-identifier-and-display-name/after.js
index ecfcef0..66077f0 100644
--- a/test/fixtures/add-identifier-and-display-name/after.js
+++ b/test/fixtures/add-identifier-and-display-name/after.js
@@ -1,36 +1,30 @@
-const Test = function () {
-  var c = styled.div` width: 100%;`;
-  c.identifier = "Test-1s574it";
-  c.displayName = "Test";
-  return c;
-}();
-const Test2 = true ? function () {
-  var c = styled.div``;
-  c.identifier = "Test2-ibltve";
-  c.displayName = "Test2";
-  return c;
-}() : function () {
-  var c = styled.div``;
-  c.identifier = "Test2-bjbly";
-  c.displayName = "Test2";
-  return c;
-}();
-const styles = { One: function () {
-    var c = styled.div``;
-    c.identifier = "One-1b6bjft";
-    c.displayName = "One";
-    return c;
-  }() };
+const Test = styled({
+  target: "div",
+  displayName: "Test",
+  identifier: "Test-18z24ew"
+})` width: 100%;`;
+const Test2 = true ? styled({
+  target: "div",
+  displayName: "Test2",
+  identifier: "Test2-1hn2b5r"
+})`` : styled({
+  target: "div",
+  displayName: "Test2",
+  identifier: "Test2-1hl3gfs"
+})``;
+const styles = { One: styled({
+    target: "div",
+    displayName: "One",
+    identifier: "One-1r8uh7y"
+  })`` };
 let Component;
-Component = function () {
-  var c = styled.div``;
-  c.identifier = "Component-1ww18aw";
-  c.displayName = "Component";
-  return c;
-}();
-const WrappedComponent = function () {
-  var c = styled(Inner)``;
-  c.identifier = "WrappedComponent-1o6g4hl";
-  c.displayName = "WrappedComponent";
-  return c;
-}();
+Component = styled({
+  target: "div",
+  displayName: "Component",
+  identifier: "Component-1e47qp"
+})``;
+const WrappedComponent = styled({
+  target: Inner,
+  displayName: "WrappedComponent",
+  identifier: "WrappedComponent-kam0ab"
+})``;
diff --git a/test/fixtures/add-identifier/after.js b/test/fixtures/add-identifier/after.js
index fa8dc34..22e01b1 100644
--- a/test/fixtures/add-identifier/after.js
+++ b/test/fixtures/add-identifier/after.js
@@ -1,30 +1,30 @@
-const Test = function () {
-  var c = styled.div` width: 100%;`;
-  c.identifier = "s-14xequy";
-  return c;
-}();
-const Test2 = true ? function () {
-  var c = styled.div``;
-  c.identifier = "s-18nhwt3";
-  return c;
-}() : function () {
-  var c = styled.div``;
-  c.identifier = "s-wi0iju";
-  return c;
-}();
-const styles = { One: function () {
-    var c = styled.div``;
-    c.identifier = "s-1d4mzhe";
-    return c;
-  }() };
+const Test = styled({
+  target: "div",
+  displayName: undefined,
+  identifier: "Test-137hlza"
+})` width: 100%;`;
+const Test2 = true ? styled({
+  target: "div",
+  displayName: undefined,
+  identifier: "Test2-bjbly"
+})`` : styled({
+  target: "div",
+  displayName: undefined,
+  identifier: "Test2-3rtkrg"
+})``;
+const styles = { One: styled({
+    target: "div",
+    displayName: undefined,
+    identifier: "One-1sf086v"
+  })`` };
 let Component;
-Component = function () {
-  var c = styled.div``;
-  c.identifier = "s-1qb5dq3";
-  return c;
-}();
-const WrappedComponent = function () {
-  var c = styled(Inner)``;
-  c.identifier = "s-15ghyhk";
-  return c;
-}();
+Component = styled({
+  target: "div",
+  displayName: undefined,
+  identifier: "Component-1uo0y5n"
+})``;
+const WrappedComponent = styled({
+  target: Inner,
+  displayName: undefined,
+  identifier: "WrappedComponent-1hk71jk"
+})``;