Setting up twin.macro with Vite + React

franklivania

Chibuzo Franklin Odigbo

Posted on June 13, 2024

Setting up twin.macro with Vite + React

Introduction

Recently, I encountered a project which worked with twin macro. My first thought was that Tailwind is already perfect, so why want more? Then I started to use twin.macro, and I have gotten addicted to it. Not only does it have massive flexibility in styling, but you can also use it with emotion, or styled-components, and it is very clean too, when combined with it’s vs code IntelliSense extension.

So, my goal was to go from having my tailwind looking like this

const SpanStyled = ({ active, children }) => (
  <span className={`px-9 py-2 font-bienvenido text-base cursor-pointer rounded-full 
    ${active ? 'bg-brown-600 text-white' : 'bg-transparent text-black'} 
    hover:bg-brown-200`}>
    {children}
  </span>
);

Enter fullscreen mode Exit fullscreen mode

to this

const SpanStyled = styled.span(({ active }: { active: boolean }) => [
  tw`px-9 py-2 font-bienvenido text-base cursor-pointer rounded-full hocus:(bg-brown-20)`,
  active ? tw`bg-brown-600 text-white-900` : tw`bg-transparent text-black-900`
])

Enter fullscreen mode Exit fullscreen mode

I do not need to tell you which would be easier to maintain.

The Setup

Starting with tailwind and dependency installs

Aight, let's jump into it. This is a step-by-step process, which would help you use and be able to set up twin.macro for your vite + react or any other project you bootstrap with vite (I think).

So you have to do the first things first, bootstrap your project as so;

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

I used the typescript variant. So, you follow the prompt.
Then you install tailwind and its cohorts.

npm install @emotion/styled @emotion/css
Enter fullscreen mode Exit fullscreen mode
npm install -D tailwindcss autoprefixer postcss twin.macro babel-plugin-macros vite-plugin-babel-macros @emotion/babel-plugin @emotion/babel-plugin-jsx-pragmatic @types/babel-plugin-macros @babel/plugin-transform-react-jsx

Enter fullscreen mode Exit fullscreen mode

Aight, when you have installed all these, you would then need to initialise your tailwind with the postcss and autoprefixer, so you do;

npx tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode

Don't forget to add these

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

to your tailwindconfig.json, inside the content object,

 "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
Enter fullscreen mode Exit fullscreen mode

Folder and file configs for usage

Okay, we are all done with that. Now to the entire configurations.
Now, if you follow Ben Rogerson’s docs in his explanation, you will see the ability to add the babel field to your package.json, or to create a babel-plugin-macros.config.js file and add some code to it. I would suggest you rename it to .mjs, instead of .js
If you are adding to the package.json, you add;

"babelMacros": {
  "twin": {
    "preset": "emotion"
  }
},
Enter fullscreen mode Exit fullscreen mode

Then, if you are creating the babel config, you add this;

module.exports = {
  twin: {
    preset: 'emotion',
  },
}
Enter fullscreen mode Exit fullscreen mode

Alright, well done for coming this distance. All that remains for you to update is your vite.config, add your twin.d.ts, modify your tsconfig.json, and create and import your global styles. It is super easy.
For your vite.config.ts, or js for whichever, replace with this;

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

// https://vitejs.dev/config/
export default defineConfig({
  optimizeDeps: {
    esbuildOptions: {
      target: 'es2020',
    },
  },
  esbuild: {
    jsxFactory: 'jsx',
    jsxInject: 'import { jsx } from "@emotion/react"',
    logOverride: { 'this-is-undefined-in-esm': 'silent' },
  },
  plugins: [
    react({
      babel: {
        plugins: [
          'babel-plugin-macros',
          [
            '@emotion/babel-plugin-jsx-pragmatic',
            {
              export: 'jsx',
              import: '__cssprop',
              module: '@emotion/react',
            },
          ],
          [
            '@babel/plugin-transform-react-jsx',
            { pragma: '__cssprop' },
            'twin.macro',
          ],
        ],
      },
    }),
    macrosPlugin(),
  ],
  define: {
    'process.env': {},
  },
})

Enter fullscreen mode Exit fullscreen mode

When you are done with that, you then create a types folder at the root of your project and create a twin.d.ts or js, dependent, and add this

import 'twin.macro'
import { css as cssImport } from '@emotion/react'
import styledImport from '@emotion/styled'
import { CSSInterpolation } from '@emotion/serialize'

declare module 'twin.macro' {
  // The styled and css imports
  const styled: typeof styledImport
  const css: typeof cssImport
}

declare module 'react' {
  // The tw and css prop
  interface DOMAttributes<T> {
    tw?: string
    css?: CSSInterpolation
  }
}
Enter fullscreen mode Exit fullscreen mode

In my case, I also created a .babelrc.js, and then added this to it _this is not necessary!_

module.exports = {
  presets: [
    [
      "next/babel",
      {
        "preset-react": {
          runtime: "automatic",
          importSource: "@emotion/react",
        },
      },
    ],
  ],
  plugins: ["@emotion/babel-plugin", "babel-plugin-macros"],
};
Enter fullscreen mode Exit fullscreen mode

The only places we need to modify in our tsconfig.json, are;

"skipLibCheck": true,
"jsxImportSource": "@emotion/react",
Enter fullscreen mode Exit fullscreen mode

and


"include": ["src", "types"],
Enter fullscreen mode Exit fullscreen mode

Here, you would just add “types”, to the already existing src that is there, and you are good to go.
Then, you create a styles folder inside your src folder and create a GlobalStyles.tsx inside it. Then you would place this code inside it

import React from 'react'
import { Global } from '@emotion/react'
import tw, { css, theme, GlobalStyles as BaseStyles } from 'twin.macro'

const customStyles = css({
  body: {
    WebkitTapHighlightColor: theme`colors.purple.500`,
    ...tw`antialiased`,
  },
})

const GlobalStyles = () => (
  <>
    <BaseStyles />
    <Global styles={customStyles} />
  </>
)

export default GlobalStyles
Enter fullscreen mode Exit fullscreen mode

after you would then import it to your main.tsx, so it affects your application globally. Then, you are good to use twin.macro to start styling your components.

Conclusion.

So, when you are done setting it up, you should be able to edit your code optimally, and use the tw, instead of the className. Also note you have to import tw at the head of each document as you start to work on it.

So, from this

App.tsx before

main.tsx before

we have this...

App.tsx after

main.tsx after

If you want access to the file itself, and see how it was done, this is the repository here

https://github.com/Franklivania/vite-twin.gitv
Enter fullscreen mode Exit fullscreen mode

Cheers and happy coding 🍻&💖

💖 💪 🙅 🚩
franklivania
Chibuzo Franklin Odigbo

Posted on June 13, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related