Repository: styled-components/babel-plugin-styled-components
Pull request #4 (2016-11-23 09:14:10)

Generate unique identifier
Will generate a unique identifier from a incrementing number and (if it
exists) the displayName of the component. Since these identifiers are
directly used as classes inside the DOM, we have to make sure they're
reasonably unique and don't start with a number.

I use murmurhash becuase it is the quickest hashing functions that
avoids collisions as good as possible, see https://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed for an in-depth analysis.

/cc @geelen 


Patch:
diff --git a/package.json b/package.json
index 03be794..29fbabd 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
 {
-  "version": "0.0.2",
+  "version": "0.0.3-0",
   "name": "babel-plugin-styled-components",
   "description": "Improve the debugging experience and add server-side rendering support to styled-components",
   "repository": "styled-components/babel-plugin-styled-components",
diff --git a/src/index.js b/src/index.js
index e889bb0..4b2fa97 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,11 +1,14 @@
 import template from 'babel-template'
 
+import hash from './utils/hash'
+
 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 isStyled = (tag) => (tag.object && tag.object.name == 'styled') || (tag.callee && tag.callee.name == 'styled')
 
+
 let id = 0
 
 export default function({ types: t }) {
@@ -58,24 +61,27 @@ export default function({ types: t }) {
           }
 
           let newNode
-          if (addDisplayName && addIdentifier) {
+          if (addIdentifier) {
             id++
-            newNode = buildNodeWithDisplayNameAndIdentifier({
-              VALUE: path.node,
-              DISPLAYNAME: t.stringLiteral(displayName),
-              IDENTIFIER: t.numericLiteral(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),
             })
-          } else if (addIdentifier) {
-            id++
-            newNode = buildNodeWithIdentifier({
-              VALUE: path.node,
-              IDENTIFIER: t.numericLiteral(id),
-            })
           }
           path.node._styledComponentsSeen = true
 
diff --git a/src/utils/hash.js b/src/utils/hash.js
new file mode 100644
index 0000000..8e50a69
--- /dev/null
+++ b/src/utils/hash.js
@@ -0,0 +1,50 @@
+/**
+ * JS Implementation of MurmurHash2
+ *
+ * @author <a href="mailto:gary.court@gmail.com">Gary Court</a>
+ * @see http://github.com/garycourt/murmurhash-js
+ * @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a>
+ * @see http://sites.google.com/site/murmurhash/
+ *
+ * @param {string} str ASCII only
+ * @return {string} Base 36 encoded hash result
+ */
+function murmurhash2_32_gc(str) {
+    let l = str.length;
+    let h = l;
+    let i = 0;
+    let k;
+
+    while (l >= 4) {
+        k = ((str.charCodeAt(i) & 0xff)) |
+            ((str.charCodeAt(++i) & 0xff) << 8) |
+            ((str.charCodeAt(++i) & 0xff) << 16) |
+            ((str.charCodeAt(++i) & 0xff) << 24);
+
+        k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+        k ^= k >>> 24;
+        k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+
+        h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
+
+        l -= 4;
+        ++i;
+    }
+
+    /* eslint-disable no-fallthrough */ // forgive existing code
+    switch (l) {
+    case 3: h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
+    case 2: h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
+    case 1: h ^= (str.charCodeAt(i) & 0xff);
+        h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+    }
+    /* eslint-enable no-fallthrough */
+
+    h ^= h >>> 13;
+    h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+    h ^= h >>> 15;
+
+    return (h >>> 0).toString(36);
+}
+
+export default murmurhash2_32_gc


Test patch:
diff --git a/test/fixtures/add-identifier-and-display-name/after.js b/test/fixtures/add-identifier-and-display-name/after.js
index c2f5a25..ecfcef0 100644
--- a/test/fixtures/add-identifier-and-display-name/after.js
+++ b/test/fixtures/add-identifier-and-display-name/after.js
@@ -1,36 +1,36 @@
 const Test = function () {
   var c = styled.div` width: 100%;`;
-  c.identifier = 7;
+  c.identifier = "Test-1s574it";
   c.displayName = "Test";
   return c;
 }();
 const Test2 = true ? function () {
   var c = styled.div``;
-  c.identifier = 8;
+  c.identifier = "Test2-ibltve";
   c.displayName = "Test2";
   return c;
 }() : function () {
   var c = styled.div``;
-  c.identifier = 9;
+  c.identifier = "Test2-bjbly";
   c.displayName = "Test2";
   return c;
 }();
 const styles = { One: function () {
     var c = styled.div``;
-    c.identifier = 10;
+    c.identifier = "One-1b6bjft";
     c.displayName = "One";
     return c;
   }() };
 let Component;
 Component = function () {
   var c = styled.div``;
-  c.identifier = 11;
+  c.identifier = "Component-1ww18aw";
   c.displayName = "Component";
   return c;
 }();
 const WrappedComponent = function () {
   var c = styled(Inner)``;
-  c.identifier = 12;
+  c.identifier = "WrappedComponent-1o6g4hl";
   c.displayName = "WrappedComponent";
   return c;
 }();
diff --git a/test/fixtures/add-identifier/after.js b/test/fixtures/add-identifier/after.js
index c154bf5..fa8dc34 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 = 1;
+  c.identifier = "s-14xequy";
   return c;
 }();
 const Test2 = true ? function () {
   var c = styled.div``;
-  c.identifier = 2;
+  c.identifier = "s-18nhwt3";
   return c;
 }() : function () {
   var c = styled.div``;
-  c.identifier = 3;
+  c.identifier = "s-wi0iju";
   return c;
 }();
 const styles = { One: function () {
     var c = styled.div``;
-    c.identifier = 4;
+    c.identifier = "s-1d4mzhe";
     return c;
   }() };
 let Component;
 Component = function () {
   var c = styled.div``;
-  c.identifier = 5;
+  c.identifier = "s-1qb5dq3";
   return c;
 }();
 const WrappedComponent = function () {
   var c = styled(Inner)``;
-  c.identifier = 6;
+  c.identifier = "s-15ghyhk";
   return c;
 }();