Repository: styled-components/babel-plugin-styled-components
Pull request #323 (2021-04-30 03:11:24)

merge existing withConfig arguments to allow SSR when using shouldForwardProp
Resolves issue #322 where if you have an existing withConfig on your component in order to leverage shouldForwardProp babel was not adding a displayName or componentId

this takes the existing expression adds the additional args and replaces the existing withConfig

ithinkdancan: > I think "optional chaining" improves the readability of the code. How about applying this?

@1Seok2 I did not see optional chaining used anywhere else in the code so i instead matched convention with what was there. I assume that to support versions of node that do not support chaining.
ithinkdancan: @probablyup can I get a review on this?
probablyup: @ithinkdancan sorry for the delay in reviewing, can you also add a test case for `withConfig` and `attrs` being used together?
ithinkdancan: @probablyup good call out, I have updated the code to be a little smarted about merging the withConfig arguments
probablyup: @ithinkdancan could you merge from `main` to get the tests running? Just enabled actions for forks. TY!

Patch:
diff --git a/.prettierrc b/.prettierrc
index 36301bc..3d1f5e5 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,5 +1,6 @@
 {
   "semi": false,
   "singleQuote": true,
-  "trailingComma": "es5"
+  "trailingComma": "es5",
+  "arrowParens": "avoid"
 }
diff --git a/src/visitors/displayNameAndId.js b/src/visitors/displayNameAndId.js
index 42c1c1f..5f60a9e 100644
--- a/src/visitors/displayNameAndId.js
+++ b/src/visitors/displayNameAndId.js
@@ -17,6 +17,7 @@ const addConfig = t => (path, displayName, componentId) => {
   }
 
   const withConfigProps = []
+
   if (displayName) {
     withConfigProps.push(
       t.objectProperty(
@@ -34,6 +35,34 @@ const addConfig = t => (path, displayName, componentId) => {
     )
   }
 
+  const existingConfig = getExistingConfig(t)(path)
+
+  if (
+    existingConfig &&
+    existingConfig.arguments.length &&
+    !existingConfig.arguments[0].properties.some(prop =>
+      ['displayName', 'componentId'].includes(prop.key.name)
+    )
+  ) {
+    existingConfig.arguments[0].properties.push(...withConfigProps)
+    return
+  }
+
+  if (
+    path.node.callee &&
+    t.isMemberExpression(path.node.callee.callee) &&
+    path.node.callee.callee.property &&
+    path.node.callee.callee.property.name &&
+    path.node.callee.callee.property.name == 'withConfig' &&
+    path.node.callee.arguments.length &&
+    !path.node.callee.arguments[0].properties.some(prop =>
+      ['displayName', 'componentId'].includes(prop.key.name)
+    )
+  ) {
+    path.node.callee.arguments[0].properties.push(...withConfigProps)
+    return
+  }
+
   if (path.node.tag) {
     // Replace x`...` with x.withConfig({ })`...`
     path.node.tag = t.callExpression(
@@ -53,6 +82,29 @@ const addConfig = t => (path, displayName, componentId) => {
   }
 }
 
+const getExistingConfig = t => path => {
+  if (
+    path.node.callee &&
+    t.isMemberExpression(path.node.callee.callee) &&
+    path.node.callee.callee.property &&
+    path.node.callee.callee.property.name &&
+    path.node.callee.callee.property.name == 'withConfig'
+  ) {
+    return path.node.callee
+  }
+
+  if (
+    path.node.callee &&
+    t.isMemberExpression(path.node.callee.callee) &&
+    path.node.callee.callee.object &&
+    path.node.callee.callee.object.callee &&
+    path.node.callee.callee.object.callee.property &&
+    path.node.callee.callee.object.callee.property.name === 'withConfig'
+  ) {
+    return path.node.callee.callee.object
+  }
+}
+
 const getBlockName = file => {
   const name = path.basename(
     file.opts.filename,
@@ -155,7 +207,16 @@ export default t => (path, state) => {
           t.isMemberExpression(path.node.callee.callee) &&
           path.node.callee.callee.property &&
           path.node.callee.callee.property.name &&
-          path.node.callee.callee.property.name !== 'withConfig')
+          path.node.callee.callee.property.name !== 'withConfig') ||
+        // styled(x).withConfig({})
+        (isStyled(t)(path.node.callee, state) &&
+          t.isMemberExpression(path.node.callee.callee) &&
+          path.node.callee.callee.property &&
+          path.node.callee.callee.property.name &&
+          path.node.callee.callee.property.name == 'withConfig' &&
+          !path.node.callee.arguments[0].properties.some((prop) =>
+            ['displayName', 'componentId'].includes(prop.key.name)
+          ))
   ) {
     const displayName =
       useDisplayName(state) &&


Test patch:
diff --git a/test/fixtures/add-identifier-and-display-name/code.js b/test/fixtures/add-identifier-and-display-name/code.js
index a451c72..2d51438 100644
--- a/test/fixtures/add-identifier-and-display-name/code.js
+++ b/test/fixtures/add-identifier-and-display-name/code.js
@@ -13,3 +13,18 @@ const WrappedComponent3 = styled(Inner)({})
 const WrappedComponent4 = styled(Inner).attrs(() => ({ something: 'else' }))({})
 const WrappedComponent5 = styled.div.attrs(() => ({ something: 'else' }))({})
 const WrappedComponent6 = styled.div.attrs(() => ({ something: 'else' }))``
+const WrappedComponent7 = styled.div.withConfig({
+  shouldForwardProp: () => {},
+})({})
+
+const WrappedComponent8 = styled.div
+  .withConfig({
+    shouldForwardProp: () => {},
+  })
+  .attrs(() => ({ something: 'else' }))({})
+
+const WrappedComponent9 = styled.div
+  .attrs(() => ({ something: 'else' }))
+  .withConfig({
+    shouldForwardProp: () => {},
+  })({})
diff --git a/test/fixtures/add-identifier-and-display-name/output.js b/test/fixtures/add-identifier-and-display-name/output.js
index 1b3376b..2d81105 100644
--- a/test/fixtures/add-identifier-and-display-name/output.js
+++ b/test/fixtures/add-identifier-and-display-name/output.js
@@ -51,3 +51,22 @@ const WrappedComponent6 = styled.div.attrs(() => ({
   displayName: "WrappedComponent6",
   componentId: "sc-1cza72q-10"
 })``;
+const WrappedComponent7 = styled.div.withConfig({
+  shouldForwardProp: () => {},
+  displayName: "WrappedComponent7",
+  componentId: "sc-1cza72q-11"
+})({});
+const WrappedComponent8 = styled.div.withConfig({
+  shouldForwardProp: () => {},
+  displayName: "WrappedComponent8",
+  componentId: "sc-1cza72q-12"
+}).attrs(() => ({
+  something: 'else'
+}))({});
+const WrappedComponent9 = styled.div.attrs(() => ({
+  something: 'else'
+})).withConfig({
+  shouldForwardProp: () => {},
+  displayName: "WrappedComponent9",
+  componentId: "sc-1cza72q-13"
+})({});