styled-components/babel-plugin-styled-components

How use with Vite.js, especially displayName for debugging.

valerii15298 opened this issue ยท 30 comments

When using with CRA, its possible to see name of component inside css classes, but I cannot figure out how to make a nice developing/debugging experience when using with Vite.js. Is there any advices how to show in classes names of components when using with Vite.js?

I suggest using https://www.npmjs.com/package/vite-plugin-babel-macros and importing from styled-components/macro. We have this working nicely in our vite setup (although that's with a custom plugin that should do the same as the package I pointed you too).

Hi @rockwotj, please could you share your code how you set it up?
Or could you please tell me what am I doing wrong?

I have this config

// vite.config.ts
import { defineConfig } from 'vite';
import reactRefresh from '@vitejs/plugin-react-refresh';
import macrosPlugin from "vite-plugin-babel-macros"

export default defineConfig({
  plugins: [
    reactRefresh(),
    macrosPlugin(),
  ],
});

And then I just import from styled-components/macros

// App.tsx

import styled from 'styled-components/macros';

But I get a Typescript error and error from Vite.
I have the latest version of Styled Components: "styled-components": "^5.3.1"

// Typescript
Cannot find module 'styled-components/macros' or its corresponding type declarations.

// Vite
error when starting dev server:
Error: The following dependencies are imported but could not be resolved:
  styled-components/macros (imported by /Users/ronaldruzicka/Projects/vite-project/src/App.tsx)

Thanks!

Hi @rockwotj, please could you share your code how you set it up?
Or could you please tell me what am I doing wrong?

Looks like it's not installed. Did you run npm install?

Oh my bad, I was using plural /macros instead of singular /macro ๐Ÿคฆโ€โ™‚๏ธ
Now it's working properly, thanks a lot!

@ronaldruzicka I added the macros plugin and started importing styled like this:
import styled from 'styled-components/macro

But I still don't get proper class names, they are still randomized/minified. Is there anything else I should do?

@IvanBernatovic you need to have a config file to get display names, see the docs for more: https://styled-components.com/docs/tooling#experimental-config

@IvanBernatovic I didn't use any other config. I just included that macrosPlugin in the vite.config.ts and then used import styled from 'styled-components/macro'; in the component and it worked.

I have these versions:

"styled-components": "^5.3.0"
"vite-plugin-babel-macros": "^1.0.5",

But maybe try that experimental config as well.

@rockwotj @ronaldruzicka Thank you for your responses! I tried to add config as per styled-components docs but it still didn't work. I tried many things but in the end, solution was even simpler than this approach. This is what worked for me in vite.config.js:

// vite.config.js

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [
          [
            'babel-plugin-styled-components',
            {
              displayName: true,
              fileName: false
            }
          ]
        ]
      }
    })
  ]
})

No need to install any other plugin for babel macros (nor vite nor babel macros plugin) and no need to change exports to import styled from 'styled-components/macro'.

velsa commented

@IvanBernatovic
I'm new to Vite. What additional config should I have to run the same plugin in a Typescript project?
If I just add it as in your example, I get lots of errors from vite. It seems that it tries to parse all TS typecasts as react components :)
E.g. it thinks that <ISomeInterface> is a component and complains.

@IvanBernatovic I'm new to Vite. What additional config should I have to run the same plugin in a Typescript project? If I just add it as in your example, I get lots of errors from vite. It seems that it tries to parse all TS typecasts as react components :) E.g. it thinks that <ISomeInterface> is a component and complains.

Unfortunately, I can't help you with that as I don't have this setup (vite+typescript+styled-components) so I can't tinker around with config. Maybe if you had a reproducible example (a repo or codesandbox project) I could take a look.

Did you try other approaches from previous comments?

E.g. it thinks that <ISomeInterface> is a component and complains.

@velsa That's a known peculiarity of tsx. For generics you can use <T extends unknown> (you don't need this if there are two or more params, only one). For casting you can use as. See microsoft/TypeScript#6897 and lots more if you google tsx generic cast

@IvanBernatovic

But I still don't get proper class names, they are still randomized/minified. Is there anything else I should do?

Just connecting some dots, this was almost certainly because of styled-components/styled-components#3635

velsa commented

Btw, for anyone interested, I eventually did the following:

  plugins: [
    // For all styled components:
    // create classnames from fileName and displayName in development
    react({
      babel: {
        presets: ['@babel/preset-typescript'],
        plugins: [
          '@babel/plugin-transform-typescript',
          [
            'babel-plugin-styled-components',
            {
              ssr: false,
              pure: true,
              displayName: true,
              fileName: false,
            },
          ],
        ],
      },
    }),
  ];

I added vite react plugin which accepts babel configuration and I am also doing TS transform ('@babel/plugin-transform-typescript) before using babel-plugin-styled-components.

This approach seems to play nicely with vite )

displayName worked fine with the babel macros described above but I had some issues with the css prop. In the end what worked for me is

import { defineConfig } from 'vite';
import reactPlugin from '@vitejs/plugin-react';
import macrosPlugin from "vite-plugin-babel-macros"

export default defineConfig({
  plugins: [
    macrosPlugin(),
    reactPlugin(),
  ],
});

and then import using import styled from "styled-components/macro";

Note that macros plugin is added before react plugin. babel-plugin-styled-components relies on JSX nodes in AST for the css prop to work. So this transform needs to happen before JSX gets transformed away. You also need to have the macro import in the same module where your css prop is used. Below the styled import is unused but without it the prop wouldn't work.

import React from "react";
import styled from "styled-components/macro";
import { someStyle } from "./styled";

export default function Component() {
    return <div css={someStyle} />;
}

Hope this helps someone.

@rockwotj @ronaldruzicka Thank you for your responses! I tried to add config as per styled-components docs but it still didn't work. I tried many things but in the end, solution was even simpler than this approach. This is what worked for me in vite.config.js:

// vite.config.js

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [
          [
            'babel-plugin-styled-components',
            {
              displayName: true,
              fileName: false
            }
          ]
        ]
      }
    })
  ]
})

No need to install any other plugin for babel macros (nor vite nor babel macros plugin) and no need to change exports to import styled from 'styled-components/macro'.

not working on production.
Did someone managed to make it work on production mode?

@rockwotj @ronaldruzicka Thank you for your responses! I tried to add config as per styled-components docs but it still didn't work. I tried many things but in the end, solution was even simpler than this approach. This is what worked for me in vite.config.js:

// vite.config.js

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [
          [
            'babel-plugin-styled-components',
            {
              displayName: true,
              fileName: false
            }
          ]
        ]
      }
    })
  ]
})

No need to install any other plugin for babel macros (nor vite nor babel macros plugin) and no need to change exports to import styled from 'styled-components/macro'.

This works for me too, but needed to also add ssr: false. โš ๏ธ Without this hot-reload of styles was not reliable at all - changing properties works, but removing would just keep last value always persisted (until next reload).

react({
  babel: {
    plugins: [
      ['babel-plugin-styled-components', { ssr: false, pure: true, displayName: true, fileName: true }]
]}})

On the other hand I am not confident if this is proper solution or some kind of ugly thing we all hate from CRA (Rewire/Craco). Would be nice if Vite would be able to support such basic styled-components functionality out of the box or at least have documented how to do it correctly.

Hey, I'm trying to use vite-plugin-babel-macros inside my project. It works, but I'd like not to have the fileName displayed inside the generated class. I tried like this, but I still have the fileName inside the class:

export default defineConfig({
    plugins: [
        react({
            babel: {
                plugins: [
                    [
                        "babel-plugin-styled-components",
                        {
                            fileName: false,
                            displayName: true,
                            meaninglessFileNames: ["index", "styles"],
                        },
                    ],
                ],
            },
        }),
        macrosPlugin(),
    ],
})

How can I fix it? Thanks!

Hi guys
I managed to have this config working in at least couple of ways (only displayName, namespace, fileName...) but the result is that elements has now classes with prefixes done accordingly to the configuration plus additional not prefixed class that is actually used in generate CSS. This is not what I expected, I hoped to have CSS styles assigned to prefixed classes. Is there a way to add some prefixes to classes that are actually used?

image

In the example above I need to have styles assigned to StyledDiv-gzSrWC and not hnYDyV as they are now (ideally class hnYDyV should not exist in the element at all)

This is the code that generated this example:

const StyledDiv = styled.div`
  color: red;
  padding: 10px;
  background-color: blue;
`;

const App = () => <StyledDiv>hello react</StyledDiv>;

and this is the config in vite.config.js:

    plugins: [
      react({
        babel: {
          plugins: [
            [
              "babel-plugin-styled-components",
              {
                ssr: false,
                pure: true,
                displayName: true,
                fileName: false,
              },
            ],
          ],
        },
      }),

@MeLlamoPablo works for me, thanks.
For anyone who needs to customize the config a bit more, here all the available params: https://styled-components.com/docs/tooling

@rockwotj @ronaldruzicka Thank you for your responses! I tried to add config as per styled-components docs but it still didn't work. I tried many things but in the end, solution was even simpler than this approach. This is what worked for me in vite.config.js:

// vite.config.js

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [
          [
            'babel-plugin-styled-components',
            {
              displayName: true,
              fileName: false
            }
          ]
        ]
      }
    })
  ]
})

No need to install any other plugin for babel macros (nor vite nor babel macros plugin) and no need to change exports to import styled from 'styled-components/macro'.

I also got the same issue my vite-config doesn't change anything it remains the same when I install vite.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
})

I try a lot of things to solve this issue but till now I can't able to solve this issue in my system.

 "dependencies": {

    "eslint-plugin-styled": "^0.1.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "styled-components": "^6.0.3",
    "vite-plugin-babel-macros": "^1.0.6"
  },
  "devDependencies": {
    "@types/react": "^18.2.14",
    "@types/react-dom": "^18.2.6",
    "@vitejs/plugin-react": "^4.0.1",
    "babel-plugin-styled-components": "^2.1.4",
    "eslint": "^8.44.0",
    "eslint-plugin-react": "^7.32.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.1",
    "vite": "^4.4.0"
  }

I also installed the babel macros but the issue is not solved

code app.js :

import styled from 'styled-components/macros';
function App() {


  return (
   <Container>
      hello
   </Container>
  )
}

export default App

const Container=styled.button`
  color: red;
`

issue :

15 | window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
16 | }
17 | import styled from "styled-components/macros";
| ^
18 | function App() {
19 | return /* @PURE */ jsxDEV(Container, { children: "hello" }, void 0, false, {

zibra commented

Disabling ssr helped in my case (Nx, Vite, latest versions of SC and babel plugin)

Just leaving this for anyone stuck like I was. I needed an older version of styled-components in order to get the /macro folder working with my Vite/React/Styled-components project. The version I settled on was 5.3.0. I believe the folder was removed in v6.1 because of low usage. After getting the correct version, a compilation of the advice above worked for me.

So In 2024, what is the way to get this working? I have spent about two hours messing around with this and I still simply get an error every time I try to spin up my app. Debugging is terrible. Why is this a separate package and not a config for the base library? Who doesn't need readable class names when debugging?

So In 2024, what is the way to get this working? I have spent about two hours messing around with this and I still simply get an error every time I try to spin up my app. Debugging is terrible. Why is this a separate package and not a config for the base library? Who doesn't need readable class names when debugging?

I'm using the @vitejs/plugin-react-swc plugin, which itself accepts an array of plugins. This is the config I'm using

[
      '@swc/plugin-styled-components',
      {
        displayName: true,
        fileName: true,
        ssr: false,
      },
    ]

So basically no babel plugin, but if you can make the switch, this works perfectly fine.

aesy commented

@Sheraff Thanks for the pointer. For me, unfortunately, it gives me a deadlock when trying to build, no idea why. Would love to hear if it happens to anyone else and if they could resolve it.

@aesy You're not alone, @vitejs/plugin-react-swc doesn't seem to do the trick for me. During development, index.html loads, but after that, Vite just crashes silently.

I am getting this error [vite] Pre-transform error: failed to handle: assertion failed: !result.is_null() (x2)

This is the config i am using

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import { nodePolyfills } from 'vite-plugin-node-polyfills'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react({
    plugins: [[
      "@swc/plugin-styled-components",
      {
        displayName: true,
        ssr: true
      }
    ]]
  }), nodePolyfills()]
})

This error only comes up when i add @swc/plugin-styled-components plugin

unyo commented

For those having trouble with @swc/plugin-styled-components, I found luck with downgrading to @swc/plugin-styled-components@1.5.111: vitejs/vite-plugin-react-swc#190 (comment). With @swc/plugin-styled-components@1.5.115, vite was just crashing upon visiting http://localhost:5173/ in the browser, with this error:

zsh: bus error  

(Edit: I think maybe I was getting an error because I forgot to install @swc/core perhaps? latest @swc/core isn't working with @swc/plugin-styled-components@1.5.111, it errors out on build with:)

[vite:react-swc] failed to handle: failed to invoke plugin: failed to invoke plugin on 'Some...

Background

I've been trying to find a good replacement for create-react-app, all the new ones (next.js, remix, expo) can't be hosted at a /unknown-at-build-time/subpath/ - BrowserRouter doesn't work with relative paths (<base href="." />), and there isn't a good way to swap out BrowserRouter with a HashRouter. And remix build apparently just builds server JS files instead of static files like next.js and gatsby and expo do. It has an unstable SPA mode that uses vite - so I'm checking it out since I can't use any browser history routers. I've used gatsby before, but I don't feel like graphql today, so I'm not sure if it would work for this case yet.

So since I have to use HashRouter, this limits me to vite, parcel, or create-react-app. And it seems parcel does not work with @swc/plugin-styled-components at the moment (it doesn't seem to parse .swrc), so vite is my best hope for a CRA replacement at the moment (as create-react-app is being depreciated)

Parcel works too, but that would require babel, which would probably have the same relative performance as CRA. I'm concerned about Vite hitting the 10 download concurrent limit for lots of files in a large project, but I'm willing to check out SWC's performance for a bit since I already know the capabilities of babel and parcel (though not babel + parcel yet)

I'm trying to retain usage of styled-components since it has the vendor prefixing that I'd like to have automatically - some of these other built-in CSS solutions for CRA alternatives don't seem to have that, and I have to support older hardware. I've used PostCSS in the past, but styled-component vendor prefixing automatically and the automatically generated displayName (vs emotion's manual labeling) make styled-components kind of unreplaceable at the moment.

(Note: For SSR frameworks, only next.js with page routing seems to do SSR styled-components well. Next.js w/ app routing requires 'use client'. Remix generate SSR CSS too, but requires some Error Boundary files (ie _boundary._index.js) that I would rather not have in the codebase for someone to break.)

Solutions

The solutions below all show displayName: true behavior.

SWC Config ๐Ÿ‘

install

npm install --save styled-components
npm install --save-dev @vitejs/plugin-react-swc @swc/plugin-styled-components @swc/core @vitejs/plugin-legacy terser

vite.config.js:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import legacy from '@vitejs/plugin-legacy'

export default defineConfig(({ mode }) => ({
  plugins: [
    react({ plugins: [["@swc/plugin-styled-components", {}]] }),
    legacy(),
  ],
  define: {
    'process.env.NODE_ENV': `'${mode || 'production'}'`, // browser throws an error about process not defined on determineTheme.ts
    'SC_DISABLE_SPEEDY': "true", // needed to enable vendor prefixing using 'vite build'
    // using process.env.SC_DISABLE_SPEEDY doesnt work due to the way styled-components checks for process.env 
    // https://github.com/styled-components/styled-components/blob/main/packages/styled-components/src/constants.ts#L17
    // I'm guessing process.env ternary worked in webpack but doesn't work with vite
    // you also need to wrap the app with <StyleSheetManager enableVendorPrefixes={true}>
  }
}))

package.json

  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "styled-components": "^6.1.8"
  },
  "devDependencies": {
    "@swc/core": "^1.3.106",
    "@swc/plugin-styled-components": "^1.5.115",
    "@vitejs/plugin-legacy": "^5.2.0",
    "@vitejs/plugin-react-swc": "^3.5.0",
    "terser": "^5.27.0",
    "vite": "^5.0.8"
  },

ref
ref

Babel Config ๐Ÿ‘

install

npm install --save styled-components
npm install --save-dev @vitejs/plugin-react babel-plugin-styled-components

vite.config.js

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react({
    babel: {
      plugins: [['styled-components', { displayName: true }]]
    },
  })],
  define: {
    'SC_DISABLE_SPEEDY': "true", // needed to enable vendor prefixing using 'vite build'
  }
})

package.json

  "dependencies": {
    "babel-plugin-styled-components": "^2.1.4",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^4.2.1",
    "vite": "^5.0.8"
  }

ref

Babel Macros Config (depreciated, macros removed in styled-components 6) ๐Ÿ›‘

install

npm install --save styled-components@5.3.0
npm install --save-dev vite-plugin-babel-macros @vitejs/plugin-react-swc

vite.config.js

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import macrosPlugin from 'vite-plugin-babel-macros'

export default defineConfig({
  plugins: [
    macrosPlugin(),
    react()
  ],
  define: {
    'SC_DISABLE_SPEEDY': "true", // needed to enable vendor prefixing using 'vite build'
  }
})

package.json

  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "styled-components": "^5.3.0",
    "vite-plugin-babel-macros": "^1.0.6"
  },
  "devDependencies": {
    "@vitejs/plugin-react-swc": "^3.5.0",
    "vite": "^5.0.8"
  }

usage

import styled from 'styled-components/macro'

const Container = styled('div')`
  border: 4px solid green;
`

ref
ref

More options?

Thank you all for your responses. I am hesitant to bind myself to old versions, and as I am beginning a new project, I think I will simply opt for a different way to manage styling inside of React rather than using styled components.

unyo commented

@bambery after some investigation I was able to get it working with the latest @swc/plugin-styled-components

https://github.com/unyo/vite-swc-styled-components-legacy