Repository: styled-components/babel-plugin-styled-components
Pull request #380 (2022-07-30 17:53:22)

Support pre-transpiled code (#379)
This adds support for identifying styled components imports using `_interopRequiredDefault` and calls to styled components that use sequence expressions (ex. `(0, _styled)('div')()`).

This should improve support for MUI + Next with SSR.

In `isStyled`, I added equivalent checks for sequence expressions wherever they could be used instead of member expressions. However, there's one case that I'm not sure will ever apply:

```js
(importLocalName('default', state) &&
  t.isCallExpression(tag) &&
  t.isMemberExpression(tag.callee) &&
  tag.object.property.name === 'default' &&
  tag.object.object.name === importLocalName('default', state))
```
  
Please let me know if this needs an equivalent sequence expression check.
  
I thought maybe this PR might add some new errors on re-transpilation, but I ran tests using outputs as inputs and didn't see any new problems.

eckmLJE: Re-posting this from the [issue](https://github.com/styled-components/babel-plugin-styled-components/issues/379). 
>We have run into this issue using module federation and our in-house component library. babel-plugin-styled-components successfully namespaces styled components written within each app, preventing class name collision when multiple apps are loaded on the same page with module federation. However, each app also imports from our component library which is already transpiled, and these component class names are not namespaced, leading to class name collision bugs. 

We've tested this PR in our dev environment and it fixes our issue.

Patch:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 34af3cf..86c249d 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -43,7 +43,7 @@ jobs:
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@v1
+      uses: github/codeql-action/init@v2
       with:
         languages: ${{ matrix.language }}
         # If you wish to specify custom queries, you can do so here or in a config file.
@@ -54,7 +54,7 @@ jobs:
     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
     # If this step fails, then you should remove it and run the build manually (see below)
     - name: Autobuild
-      uses: github/codeql-action/autobuild@v1
+      uses: github/codeql-action/autobuild@v2
 
     # ℹ️ Command-line programs to run using the OS shell.
     # 📚 https://git.io/JvXDl
@@ -68,4 +68,4 @@ jobs:
     #   make release
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v1
+      uses: github/codeql-action/analyze@v2
diff --git a/src/utils/detectors.js b/src/utils/detectors.js
index 834b77f..6fc5f37 100644
--- a/src/utils/detectors.js
+++ b/src/utils/detectors.js
@@ -1,3 +1,4 @@
+import getSequenceExpressionValue from './getSequenceExpressionValue'
 import { useTopLevelImportPathMatchers } from './options'
 
 const VALID_TOP_LEVEL_IMPORT_PATH_MATCHERS = [
@@ -81,6 +82,15 @@ export const isStyled = t => (tag, state) => {
   ) {
     // styled.something()
     return isStyled(t)(tag.callee.object, state)
+  } else if (
+    t.isCallExpression(tag) &&
+    t.isSequenceExpression(tag.callee) &&
+    t.isMemberExpression(getSequenceExpressionValue(tag.callee)) &&
+    getSequenceExpressionValue(tag.callee).property.name !==
+      'default' /** ignore default for #93 below */
+  ) {
+    // (..., styled).something()
+    return isStyled(t)(getSequenceExpressionValue(tag.callee), state)
   } else {
     return (
       (t.isMemberExpression(tag) &&
@@ -94,6 +104,12 @@ export const isStyled = t => (tag, state) => {
           importLocalName('default', state, {
             cacheIdentifier: tag.callee.name,
           })) ||
+      (t.isCallExpression(tag) &&
+        t.isSequenceExpression(tag.callee) &&
+        getSequenceExpressionValue(tag.callee).name ===
+          importLocalName('default', state, {
+            cacheIdentifier: getSequenceExpressionValue(tag.callee).name,
+          })) ||
       /**
        * #93 Support require()
        * styled-components might be imported using a require()
@@ -111,6 +127,13 @@ export const isStyled = t => (tag, state) => {
         t.isMemberExpression(tag.callee) &&
         tag.callee.property.name === 'default' &&
         tag.callee.object.name === state.styledRequired) ||
+      (state.styledRequired &&
+        t.isCallExpression(tag) &&
+        t.isSequenceExpression(tag.callee) &&
+        t.isMemberExpression(getSequenceExpressionValue(tag.callee)) &&
+        getSequenceExpressionValue(tag.callee).property.name === 'default' &&
+        getSequenceExpressionValue(tag.callee).object.name ===
+          state.styledRequired) ||
       (importLocalName('default', state) &&
         t.isMemberExpression(tag) &&
         t.isMemberExpression(tag.object) &&
diff --git a/src/utils/getSequenceExpressionValue.js b/src/utils/getSequenceExpressionValue.js
new file mode 100644
index 0000000..3a2f69c
--- /dev/null
+++ b/src/utils/getSequenceExpressionValue.js
@@ -0,0 +1,4 @@
+export default path =>
+  path.expressions && path.expressions.length
+    ? path.expressions[path.expressions.length - 1]
+    : undefined
diff --git a/src/visitors/assignStyledRequired.js b/src/visitors/assignStyledRequired.js
index 80c5413..72d5bb4 100644
--- a/src/visitors/assignStyledRequired.js
+++ b/src/visitors/assignStyledRequired.js
@@ -1,14 +1,27 @@
 import { isValidTopLevelImport } from '../utils/detectors'
 
 export default t => (path, state) => {
+  let init = path.node.init;
+
   if (
     t.isCallExpression(path.node.init) &&
     t.isIdentifier(path.node.init.callee) &&
-    path.node.init.callee.name === 'require' &&
-    path.node.init.arguments &&
-    path.node.init.arguments[0] &&
-    t.isLiteral(path.node.init.arguments[0]) &&
-    isValidTopLevelImport(path.node.init.arguments[0].value, state)
+    init.callee.name === '_interopRequireDefault' &&
+    init.arguments &&
+    init.arguments[0]
+  ) {
+    // _interopRequireDefault(require())
+    init = path.node.init.arguments[0];
+  }
+
+  if (
+    t.isCallExpression(init) &&
+    t.isIdentifier(init.callee) &&
+    init.callee.name === 'require' &&
+    init.arguments &&
+    init.arguments[0] &&
+    t.isLiteral(init.arguments[0]) &&
+    isValidTopLevelImport(init.arguments[0].value, state)
   ) {
     state.styledRequired = path.node.id.name
   }
diff --git a/src/visitors/transpileCssProp.js b/src/visitors/transpileCssProp.js
index 930fcf4..2c5b057 100644
--- a/src/visitors/transpileCssProp.js
+++ b/src/visitors/transpileCssProp.js
@@ -163,6 +163,7 @@ export default t => (path, state) => {
             ? property.key.name !== property.value.name
             : true) &&
           // and not a tricky expression
+          !t.isMemberExpression(property.value) &&
           !t.isLogicalExpression(property.value) &&
           !t.isConditionalExpression(property.value))
       ) {


Test patch:
diff --git a/test/fixtures/pre-transpiled/.babelrc b/test/fixtures/pre-transpiled/.babelrc
new file mode 100644
index 0000000..2c1e821
--- /dev/null
+++ b/test/fixtures/pre-transpiled/.babelrc
@@ -0,0 +1,13 @@
+{
+  "plugins": [
+    [
+      "../../../src",
+      {
+        "fileName": false,
+        "transpileTemplateLiterals": false,
+        "ssr": true
+      }
+    ],
+    ["@babel/plugin-proposal-class-properties", { "loose": true }]
+  ]
+}
diff --git a/test/fixtures/pre-transpiled/code.js b/test/fixtures/pre-transpiled/code.js
new file mode 100644
index 0000000..d662b17
--- /dev/null
+++ b/test/fixtures/pre-transpiled/code.js
@@ -0,0 +1,5 @@
+var _styled = _interopRequireDefault(require("styled-components"));
+
+const Test = (0, _styled.default)('div')({
+  width: '100%',
+});
diff --git a/test/fixtures/pre-transpiled/output.js b/test/fixtures/pre-transpiled/output.js
new file mode 100644
index 0000000..4f88f9f
--- /dev/null
+++ b/test/fixtures/pre-transpiled/output.js
@@ -0,0 +1,8 @@
+var _styled = _interopRequireDefault(require("styled-components"));
+
+const Test = (0, _styled.default)('div').withConfig({
+  displayName: "Test",
+  componentId: "sc-1m3e07f-0"
+})({
+  width: '100%'
+});
diff --git a/test/fixtures/transpile-css-prop/code.js b/test/fixtures/transpile-css-prop/code.js
index 755c436..1a4ed2c 100644
--- a/test/fixtures/transpile-css-prop/code.js
+++ b/test/fixtures/transpile-css-prop/code.js
@@ -233,6 +233,21 @@ const ObjectInterpolationLogical = ({ bg, content, height, width, ...p }) => {
   )
 }
 
+const ObjectInterpolationMember = p => {
+  const theme = useTheme()
+  const color = 'red'
+
+  return (
+    <p
+      css={{
+        color: theme.colors[color],
+      }}
+    >
+      H
+    </p>
+  )
+}
+
 const RenderPropComponentCSSProp = () => {
   return (
     <RenderPropComponent>
diff --git a/test/fixtures/transpile-css-prop/output.js b/test/fixtures/transpile-css-prop/output.js
index 339ef75..f3ad67c 100644
--- a/test/fixtures/transpile-css-prop/output.js
+++ b/test/fixtures/transpile-css-prop/output.js
@@ -206,6 +206,14 @@ var ObjectInterpolationLogical = function ObjectInterpolationLogical(_ref4) {
     </_StyledP14>;
 };
 
+var ObjectInterpolationMember = function ObjectInterpolationMember(p) {
+  var theme = useTheme();
+  var color = 'red';
+  return <_StyledP15 $_css19={theme.colors[color]}>
+      H
+    </_StyledP15>;
+};
+
 var RenderPropComponentCSSProp = function RenderPropComponentCSSProp() {
   return <RenderPropComponent>
       {function () {
@@ -309,6 +317,12 @@ var _StyledP14 = (0, _styledComponents["default"])("p")(function (p) {
   };
 });
 
+var _StyledP15 = (0, _styledComponents["default"])("p")(function (p) {
+  return {
+    color: p.$_css19
+  };
+});
+
 var _StyledDiv2 = (0, _styledComponents["default"])("div")(_templateObject18 || (_templateObject18 = _taggedTemplateLiteral(["\n            color: black;\n          "])));
 
 var _StyledDiv3 = (0, _styledComponents["default"])("div")(_templateObject19 || (_templateObject19 = _taggedTemplateLiteral(["\n            color: black;\n          "])));