본문 바로가기
시작/TIL(Today I Learned)

StyleXjs : StyleX 세팅 및 기초 (feat. NEXT)

by 백씨네 2023. 12. 26.

목차

1. StyleX 세팅 및 기초

2. 설치

3. 오류

4. styleX의 핵심 기능

5. 핵심 조건

6. 기본적인 사용법

  • 6.1 stylex.create
  • 6.2 Pseudo-classes
  • 6.3 media queries (반응형)
  • 6.4 여러 스타일 지정하기
  • 6.5 stylex.props

7. 예시 코드

 

로고부터 Atomic...

 

StyleX 세팅 및 기초

공식문서 : https://stylexjs.com/

 

StyleX

 

stylexjs.com

 
현재 프론트엔드 개발에서 React는 중요한 역할을 하고 있다. React를 개발한 Metark StyleX 라는  새로운 CSS-in-JS 라이브러리를 만들었는데, 이는 React와 잘 호환될 것으로 추측된다. Next.js 또한 React 기반 프레임워크이므로 Next.js와 React를 사용하는 프로젝트에서는 StyleX를 쓰게 될 것이라 생각하고 StyleX의 세팅 및 기초에 대해서 알아봤다.
 

꽤 빠르게 올라갈 것 같은 느낌이다...

 
 
 
웹 앱 스타일링을 위한 간단하고 사용하기 쉬운 JS 구문 및 컴파일러
인라인스타일과 정적 CSS의 장점을 결합한 방식이다.
스타일을 정의하고 사용하기 위해서는 컴포넌트 내의 로컬 지식만 있으면 되고, 미디어 쿼리와 같은 기능을 유지하면서 특정성 문제를 피할 수 있다.
StyleX는 충돌이 없는 Atomic CSS를 사용하여 최적화된 스타일을 빌드한다. 
(요즘 Atomic이 트랜드인건가..?)

확장성

  • Atomic CSS로 CSS 파일 크기를 최소화한다.
  • 컴포넌트 수가 증가하더라도 CSS 크기가 균등하게 유지된다.
  • 커지는 코드베이스 내에서도 스타일은 읽기 쉽고 유지보수가 가능하다.

각 스타일 규칙을 가장 작은 단위로 분해하여 개별 클래스로 만드는 방식이다.
프로젝트의 컴포넌트 수가 증가하더라도 CSS 파일 크기가 균등하게 유지된다.
결과적으로 이를 통해서 중복을 줄이고 CSS 파일 크기를 최소화할 수 있다.

예측 가능성

  • 요소에 적용된 클래스 이름은 해당 요소에만 직접 스타일을 적용할 수 있다.
  • 특정성 문제가 없다.
  • 마지막으로 적용된 스타일이 항상 승리한다!

클래스 이름은 각 요소에만 적용되기 때문에 다른 요소에는 영향을 주지 않으며 이로 인해 CSS의 특정성 문제가 발생하지 않는다.
또한, 마지막에 작성된 스타일이 항상 우선적으로 적용된다.

재조합 (요소를 분해 조합하여 재사용 가능하다)

  • 조건부 스타일 적용
  • 컴포넌트와 파일 경계를 넘어 임의의 스타일을 병합하고 조합한다.
  • 컴포넌트 내의 로컬 상수와 표현식을 사용하여 스타일을 DRY하게 유지하거나 성능에 대해 걱정하지 않고 스타일을 반복한다.

조건부 스타일을 통해서 상황에 맞는 다양한 스타일링이 가능하고, 스타일을 자유롭게 병합하고 조합할 수 있다. 그리고 여러 컴포넌트에서 공통으로 사용되는 색상이나 글꼴과 같은 상수를 로컬 상수로 선언하여 재사용할 수 있다.

빠름

  • 런타임에 스타일 주입이 없다.
  • 모든 스타일은 컴파일 타임에 정적 CSS 파일에 번들링된다.
  • 클래스 이름을 병합하는 런타임을 최적화한다.

런타임에서 스타일을 주입하는 것이 아닌 컴파일 타임에 정적 CSS 파일에 번들링 되기 때문에 빠르다. 또한, 클래스 이름을 병합하는 런타임을 최적화하여 빠르게 렌더링 할 수 있다.

타입 안정성

  • 타입 안정성 API
  • 타입 안정성 스타일
  • 타입 안정성 테마

설치

$ npm install --save @stylexjs/stylex

$ npm install --save-dev @stylexjs/babel-plugin
# next.js
$ npm install --save-dev @stylexjs/nextjs-plugin

Next에서 사용하기 위해서 .babelrc.js 파일을 생성하고 아래와 같이 작성한다.

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,
                },
            },
        ],
    ],
}

그리고 next.config.js 파일에 아래와 같이 작성한다.

/** @type {import('next').NextConfig} */
const styleXPlugin = require("@stylexjs/nextjs-plugin")

const nextConfig = {}

module.exports = styleXPlugin({
    rootDir: __dirname,
})(nextConfig)

dev-runtime 설치

컴파일러 및 빌드 프로세서를 이용하지 않고 styleX를 사용하기 위해선 dev-runtime을 설치해야 한다.

$ npm install --save-dev @stylexjs/dev-runtime

이렇게 설치를 하면 프로덕션에 배포할 준비가 될 때까지 추가 설정 없이 @stylexjs/stylex를 가져와서 사용할 수 있다.

이 부분은 아직 설정하는 방법을 못 찾았다...
아시는 분 공유 좀...

ESLint 플러그인

StyleX 컴파일러는 스타일의 유효성을 검사하지 않으며 많은 유효하지 않은 스타일을 컴파일할 수 있다. 스타일을 작성할 때 스타일의 유효성을 검사하기 위해서는 ESLint 플러그인을 설치해야 한다.

$ npm install --save-dev @stylexjs/eslint-plugin

그리고 .eslintrc.js 파일에 아래와 같이 작성한다.

module.exports = {
    extends: "next/core-web-vitals",
    plugins: ["@stylexjs"],
    rules: {
        // The Eslint rule still needs work, but you can
        // enable it to test things out.
        "@stylexjs/valid-styles": "error",
        "ft-flow/space-after-type-colon": 0,
        "ft-flow/no-types-missing-file-annotation": 0,
        "ft-flow/generic-spacing": 0,
    },
}

오류

Syntax error: "next/font" requires SWC although Babel is being used due to a custom babel config being present.
styleX에서 직접 언급한 내용을 따르면
This is an issue that we cannot fix on our end. StyleX is dependent on a Babel plugin. Using it within NextJS means using Webpack+Babel and not SWC.
next/font only works if you use SWC. Sorry, there's nothing we can do about this.
라고 했다.
마지막줄에 "SWC를 사용하는 경우에만 작동합니다. 죄송하지만 이 문제에 대해서는 저희가 할 수 있는 일이 없습니다."
라고 하는 것을 보면 StyleX에서 할 수 있는 건 없는 것 같고 이 오류가 발생하지 않게 하기 위해서
next/font를 사용하지 않는 방법으로 가야 할 것 같다.

기존

import type { Metadata } from "next"
import { Inter } from "next/font/google"
import "./globals.css"

const inter = Inter({ subsets: ["latin"] })

export const metadata: Metadata = {
    title: "Create Next App",
    description: "Generated by create next app",
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html lang="en">
            <body className={inter.className}>{children}</body>
        </html>
    )
}

아래와 같이 수정한다.

수정

import type { Metadata } from "next"
import "./globals.css"

export const metadata: Metadata = {
    title: "Create Next App",
    description: "Generated by create next app",
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html lang="en">
            <body className={""}>{children}</body>
        </html>
    )
}

font를 사용하지 않고 className을 빈 문자열로 설정한다.

styleX의 핵심 기능

styleX의 핵심은 두 가지 기능으로 요약될 수 있다.

  • stylex.create
  • stylex.props

스타일을 생성하는 데 사용되는 create 함수와 스타일을 적용하는 데 사용되는 props 함수이다.

핵심 조건

StyleX는 사전 컴파일에 의존하므로 모든 스타일을 정적으로 분석할 수 있어야 한다.
즉, 모든 "원시 스타일 객체"는 오직 하나만 포함해야 한다.

가능한 것

  • 객체 리터럴
  • 문자열 리터럴
  • 숫자 리터럴
  • 배열 리터럴
  • 'null' or 'undefined'
  • 상수, 간단한 표현식, 내장 메서드 (예:.toString())
  • 동적 스타일을 위한 화살표 함수

불가능한 것

  • 함수호출 ( StyleX 함수 제외)
  • 다른 모듈에서 가져온 값 (.stylex.js 파일에서 stylex를 사용하여 만든 CSS 변수 제외)

기본적인 사용법

stylex.create

import * as stylex from "@stylex/stylex"
const styles = stylex.create({
    base: {
        fontSize: 16,
        lineHeight: 1.5,
        color: "rgb(60, 60, 60)",
    },
    highlighted: {
        color: "rebeccapurple",
    },
})

Pseudo-classes

의사 클래스 (예 : :hover)는 스타일 객체의 키로 사용할 수 있다.

const styles = stylex.create({
    base: {
        fontSize: 16,
        lineHeight: 1.5,
        color:{
            default: "rgb(60, 60, 60)",
            ":hover": "rebeccapurple",
        },
        }
    },
})

원하는 속성에 default를 이용해서 기본값을 지정하고 hover, active, focus 등의 의사 클래스를 키로 스타일을 지정할 수 있다.

media queries (반응형)

styleX를 이용한 반응형 스타일링은 아래와 같이 작성한다.

const styles = stylex.create({
    wrapper: {
        display: "flex",
        width: {
            default: "200px",
            "@media (min-width: 600px)": "400px",
        },
    },
})

조금 더 편하게 쓰기 위해서 범위를 지정하고 사용할 수 있다.

const MEDIA_MOBILE = '@media (min-width: 600px)' as const;
const styles = stylex.create({
    wrapper: {
        display: "flex",
        width: {
            default: "200px",
            [MEDIA_MOBILE]: "400px",
        },
    },
})

이를 이용해서 활용하는 방법은 아래와 같다.

const styles = stylex.create({
    button: {
        backgroundColor: {
            default: "blue",
            [MEDIA_MOBILE]: "red",
            ":hover": {
                default: "skyblue",
                [MEDIA_MOBILE]: "black",
            },
        },
    },
})

여러 스타일 지정하기

const styles = stylex.create({
    // ...지정 스타일
})
const text = stylex.create({
    // ...지정 스타일
})

stylex.props

지정한 스탕을 적용하기 위해서는 stylex.props를 사용한다.

import * as stylex from "@stylex/stylex"
const styles = stylex.create({
    base: {
        fontSize: 16,
        lineHeight: 1.5,
        color: "rgb(60, 60, 60)",
    },
    highlighted: {
        color: "rebeccapurple",
    },
})
function MyComponent({ highlighted }) {
    return (
        <div {...stylex.props(styles.wrapper)}>
            <div {...stylex.props(styles.container)}>
                <p {...stylex.props(styles.button, text.base)}>Hello world</p>
            </div>
        </div>
    )
}

예시 코드

create-next-app을 이용해서 예시 코드를 작성해 보았다.

// layout.tsx
import type { Metadata } from "next"
import "./globals.css"

export const metadata: Metadata = {
    title: "Create Next App",
    description: "Generated by create next app",
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html lang="en">
            <body>{children}</body>
        </html>
    )
}
// page.tsx
import * as stylex from "@stylexjs/stylex"

export default function Home() {
    return (
        <div {...stylex.props(styles.wrapper)}>
            <div {...stylex.props(styles.container)}>
                <p {...stylex.props(styles.button, text.base)}>Hello world</p>
            </div>
        </div>
    )
}

const MEDIA_MOBILE = "@media (min-width: 600px)"
const styles = stylex.create({
    wrapper: {
        width: "100vw",
        height: "100vh",
    },
    container: {
        display: "flex",
        width: "100%",
        height: "100%",
        flexDirection: "column",
        alignItems: "center",
        justifyContent: "center",
        backgroundColor: "rgb(255, 171, 171)",
    },
    button: {
        backgroundColor: {
            default: "blue",
            [MEDIA_MOBILE]: "red",
            ":hover": {
                default: "skyblue",
                [MEDIA_MOBILE]: "black",
            },
        },
        width: {
            default: "200px",
            [MEDIA_MOBILE]: "400px",
        },
        height: "50px",
        justifyContent: "center",
        alignItems: "center",
        display: "flex",
        cursor: "pointer",
    },
})
const text = stylex.create({
    base: {
        fontSize: 16,
        lineHeight: 1.5,
        cursor: "pointer",
        color: {
            default: "rgb(255, 255, 255)",
            ":hover": "rgb(0, 0, 0)",
        },
    },
    highlighted: {
        color: "rebeccapurple",
    },
})

예시 코드의 결과물

 
 


 

댓글