Stylex: 새로운 CSS-in-JS
2023.12.26
이번에 새로 Meta에서 발표한 새로운 CSS-in-JS 라이브러리 Stylex를 사용해보았다.
현재 Meta에서 개발한 페이스북이나, 인스타그램과 같은 서비스에서 사용하고 있다고 한다.
-
해당 라이브러리에 관심을 가진 이유
기존에 CSS-in-JS 라이브러리로는 Emotion, Styled-components를 사용했었다.
하지만 Next13부터 React19의 Client Component 기능을 도입하면서 CSS-in-JS 라이브러리의 경우에는 Client Component만 사용할 수 있었기 때문에, SSR이나 SSG를 사용할때에는 사용할 수 없었기 때문에 tailwind를 사용했다.
tailwind는 다 좋지만 className에 길게 늘어진 코드에 가독성이 떨어지게 되는 문제가 있엇다.
stylex는 CSS-in-JS 라이브러리리지만, build time에 CSS를 생성하여 사용하기 때문에, 기존에 문제점들을 해결하면서 사용할 수 있을 것 같아서 관심을 가지게 되었다.
위 사진과 같이 ClassName에 랜덤한 클래스명으로 추가하여 사용하는 것을 볼 수 있다.
사용해 보기
1. 설치 및 세팅
pnpm install @stylexjs/stylex @stylexjs/open-props
pnpm install -D @stylexjs/babel-plugin @stylexjs/eslint-plugin @stylexjs/nextjs-plugin
/** @type {import('next').NextConfig} */
/** @type {import('next').NextConfig} */
const stylexPlugin = require('@stylexjs/nextjs-plugin');
const babelrc = require('./.babelrc.js');
const plugins = babelrc.plugins;
const [_name, options] = plugins.find(
plugin => Array.isArray(plugin) && plugin[0] === '@stylexjs/babel-plugin',
);
const rootDir = options.unstable_moduleResolution.rootDir ?? __dirname;
module.exports = stylexPlugin({
rootDir,
useCSSLayers: true,
})({
transpilePackages: ['@stylexjs/open-props'],
});
module.exports = {
presets: ["next/babel"],
plugins: [
[
"@stylexjs/babel-plugin",
{
dev: process.env.NODE_ENV === "development",
runtimeInjection: false,
genConditionalClasses: true,
treeshakeCompensation: true,
unstable_moduleResolution: {
type: "commonJS",
rootDir: __dirname,
},
},
],
],
};
현재 Nextjs에서 세팅을 할때에는 공식문서 나와 있는데로 하면 오류가 있고 github issue를 참고하여 수정을 했다.
2. 스타일링 하기
export interface ButtonProps
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'ref'> {
color?: ColorsType;
size?: SizesType;
textColor?: string;
}
const styles = stylex.create({
base: {
width: '100%',
border: 'none',
borderRadius: '0.25rem',
cursor: 'pointer',
':hover': {
opacity: 0.9,
},
},
backgroundColor: bg => ({
backgroundColor: bg,
':hover': {
opacity: 0.9,
},
}),
textColor: textColor => ({
color: textColor || '#ffffff',
}),
});
const buttonSize = stylex.create({
sm: {
fontSize: 14,
padding: '0.25rem 0.5rem',
},
md: {
fontSize: 16,
padding: '0.5rem 1rem',
},
lg: {
fontSize: 18,
padding: '0.75rem 1.5rem',
},
xl: {
fontSize: 20,
padding: '1rem 2rem',
},
});
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ children, color = 'primary', size = 'md', textColor, ...restProps }, ref) => {
return (
<button
ref={ref}
{...stylex.props(
styles.base,
styles.backgroundColor(colors[color]),
styles.textColor(textColor),
buttonSize[size],
)}
{...restProps}
>
{children}
</button>
);
},
);
Button.displayName = 'Button';
느낀점
-
좋았던 점
익숙한 CSS-in-JS방식의 CSS를 key:value 형식으로 작성을 하니 가독성이 높아 수정사항을 찾기 쉬웠다.
컴포넌트를 variants값을 만들때에는 tailwind와 달리 별다른 라이브러리나 세팅 없이 쉽게 할 수 있었다.
-
나빳던 점
이제 막 발표를 해서 그런지 자료가 부족했고, 세팅을 하는데 번거로움이 많았다.
기존에 CSS-in-JS와 다르게
{...stylex.props(styles.button)
과 같이 길게 작성을 해야되서 CSS부분은 좋았지만 컴포넌트 부분에 있어서는 코드가 길어져서 가독성이 떨어지는 단점이 있었다.
최종적으로는 저는 우선적으로 Client Component를 사용가능하고 가독성이 좋은 라이브러리를 사용을 하고 싶은데 tailwind나 stylex 둘다 가독성면에서는 그저 그랬다. 앞으로 stylex 업데이트를 기다려 봐야 할 것 같다.