처음에는 간편한 인라인 스타일링을 제공하는 Tailwind가 마음에 들어 사용하게 되었습니다.
Tailwind는 많은 장점을 가지고 있지만, 동적 스타일링과 컴포넌트 내에서 가독성이 정말 떨어지는 className들이 불편했습니다. 이건 정말 아니다싶어 구글링중 twin.macro
라이브러리를 발견했고 기존에 사용하던 Nextjs와 twin.macro, styled-component를 함께 사용하기로 결정했습니다.
<div className='flex bg-black justify-center items-center md:absolute md:right-0......'>안녕하세요</div>
기존 tailwind만 사용할때 md:absolute md:right-0 md:left-0 이런것들을 twin.macro를 사용하면 md:(absolute right-0 left-0) 묶어서 사용할 수 있습니다.. 감격
twin.macro는 Tailwind를 사용 할 때 편리한 기능을 제공하는 매크로 패키지입니다.
이 패키지는 styled-components와 함께 사용하여 클래스를 동적으로 생성하고 적용 할 수 있도록 도와줍니다.
npm i twin.macro styled-components
package.json 파일에 babelMacro 설정
//package.json
"babelMacros": {
"twin": {
"config": "tailwind.config.js",
"preset": "styled-components"
}
}
npm i -D babel-plugin-macros
.babelrc 파일을 생성하여 아래와 같은 설정을 추가해줍니다.
//babelrc
{
"presets": ["next/babel"],
"plugins": ["babel-plugin-macros"]
}
기존의 tailwind global.css로 GlobalStyle 대체합니다.
//기존 tailwind 설정
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
* {
@apply m-0 p-0 box-border;
}
ol, ul, li {
@apply list-none;
}
}
// _app.js
import './styles/globals.css';
function MyApp({ Component, pageProps}) {...}
Next.js 12버전 이상에서 컴파일러를 babel에서 swc로 변경했습니다. Nextjs에서 .babelrc 파일이 있다면 컴파일러를 babel로 사용하게됩니다. twin.macro는 babel plugin을 사용하는데 둘 중 하나는 포기해야합니다. 그렇다고 방법이 없는건 아닙니다. styled-components와 같이 사용할 수 있는 방법을 알아보겠습니다.
swc와 babel plugin을 같이 사용하려 합니다. 우선 패키지를 설치해주세요
npm i -D babel-loader babel-plugin-macros babel-plugin-styled-components
최상위 폴더에 withTwin.js를 만들고 복사 해주세요.
const path = require('path');
// 사용할 폴더 경로
const includedDirs = [
path.resolve( __dirname, 'components' ),
path.resolve( __dirname, 'pages' ),
path.resolve( __dirname, 'styles' ),
];
module.exports = function withTwin(nextConfig) {
return {
...nextConfig,
webpack(config, options) {
const { dev, isServer } = options;
const patchedDefaultLoaders = options.defaultLoaders.babel;
patchedDefaultLoaders.options.hasServerComponents = false;
patchedDefaultLoaders.options.hasReactRefresh = false;
config.module = config.module || {};
config.module.rules = config.module.rules || [];
config.module.rules.push({
test: /\.(jsx|js)$/,
include: includedDirs,
use: [
patchedDefaultLoaders,
{
loader: 'babel-loader',
options: {
sourceMaps: dev,
plugins: [
require.resolve('babel-plugin-macros'), //설치 패키지
[
require.resolve('babel-plugin-styled-components'), // 설치 패키지
{ ssr: true }, //styled-components SSR을 위한 설정입니다.
],
],
},
},
],
});
if (!isServer) {
config.resolve.fallback = {
...(config.resolve.fallback || {}),
fs: false,
module: false,
path: false,
os: false,
crypto: false,
};
}
if (typeof nextConfig.webpack === 'function') {
return nextConfig.webpack(config, options);
} else {
return config;
}
},
};
};
원래 styled-components SSR 설정을 하기위해서는 별도의 설정이 필요했는데요. Next.js 공식 문서에서 babel-plugin-styled-components를 이식하기 위해 노력했다고 합니다. 생각보다 내용이 짧으니 한번 읽어보시는걸 추천 드립니다.
//next.config.js
const withTwin = require( './withTwin' );
const nextConfig = withTwin({ // withTwin 적용
reactStrictMode: true,
swcMinify: true,
//...그 외 설정
})
module.exports = nextConfig;
import tw from 'twin.macro';
const TestComponent = tw.div`
flex
w-full
h-full
`
import { styled } from 'twin.macro';
const TestComponent = styled.div`
${tw`flex justify-center items-center rounded-md text-white`}
${props => props.rounded && tw`rounded-full`}
${props =>
props.size === 'medium' && tw`h-8`
}
`
const Test = ({ size, rounded, children }) => {
return(
<TestComponent
size={size}
rounded={rounded}
>
{children}
</TestComponent>
)
}
공식문서 가이드에서 자주 사용하게 될 방법들입니다.
import tw from 'twin.macro'
const Wrapper = tw.section`flex w-full`
const Column = tw.div`w-1/2`
const Component = () => (
<Wrapper>
<Column></Column>
<Column></Column>
</Wrapper>
)
import tw, { styled } from 'twin.macro'
const Container = styled.div(({ hasBg }) => [
tw`flex w-full`,
hasBg && tw`bg-black`,
])
const Column = tw.div`w-1/2`
const Component = ({ hasBg }) => (
<Container {...{ hasBg }}>
<Column></Column>
<Column></Column>
</Container>
)
import tw, { styled } from 'twin.macro'
const containerVariants = {
light: tw`bg-white text-black`,
dark: tw`bg-black text-white`,
crazy: tw`bg-yellow-500 text-red-500`,
}
const Container = styled.section(() => [
tw`flex w-full`,
({ variant = 'dark' }) => containerVariants[variant],
])
const Column = tw.div`w-1/2`
const Component = () => (
<Container variant="light">
<Column></Column>
<Column></Column>
</Container>
)
import tw from 'twin.macro'
const Text = tw.div`text-white`
const Component = () => <Text tw="text-black">Has black text</Text>
import tw, { styled } from 'twin.macro'
const Container = tw.div`bg-black text-white`
const BlueContainer = tw(Container)`bg-blue-500`
const RedContainer = styled(Container)(({ hasBorder }) => [
tw`bg-red-500 text-black`,
hasBorder && tw`border`,
])
const BlueContainerBold = tw(BlueContainer)`font-bold`
const Component = () => (
<>
<Container />
<BlueContainer />
<RedContainer hasBorder />
</>
)
import tw from 'twin.macro'
const Button = tw.button`
bg-black
[> i]:block
[> span]:(text-blue-500 w-10)
`
const Component = () => (
<Button>
<i>Icon</i>
<span>Label</span>
</Button>
)