레이아웃에 스타일을 입혀주기 위해 다양한 방법을 사용할 수 있다.
- 인라인 스타일: 레이아웃과 스타일이 혼합되기 때문에 선호하지 않는다. 렌더링이 여러 번 발생할 경우, 인라인 스타일로 전달되는 객체가 불필요하게 여러 번 생성될 가능성이 있다.
- Tailwind: 마찬가지로 레이아웃과 스타일이 혼합되어 선호하지 않는다. 스타일링을 위해 Tailwind만의 키워드를 사용해야 하는데, 이 키워드를 확인하기 위해 매번 공식 문서를 찾아봐야 하는 게 귀찮다.
- CSS / SCSS: 자바스크립트와 별개로 스타일시트 파일을 관리해야 하는 게 귀찮다.
- styled-components / emotion: 레이아웃과 스타일이 분리되지만 하나의 JS 안에서 함께 관리 가능하다는 점에서 CSS-in-JS 방식을 선호한다. 하지만 런타임에서 자바스크립트 코드로 존재하면서 그때그때 CSS를 추가해 주는 방식이고, 전달된 prop이 바뀔 때마다 새로운 CSS를 생성한다는 점이 별로다.
현재까지 이 프로젝트의 클라이언트 측에서는 자바스크립트 코드를 전혀 실행하지 않고 있는데 스타일링을 위해 자바스크립트 코드를 추가하고 싶지 않았다.
그래서 Linaria를 이번 프로젝트에 사용해 보기로 했다.
Linaria는 CSS-in-JS 방식의 스타일링 라이브러리이다. 가장 큰 특징은 빌드된 후에 자바스크립트를 전혀 남기지 않고 모두 CSS로 변환되는, zero-runtime CSS in JS 라이브러리라는 점이다.
Linaria가 번들링되어 HTML에서 사용되기까지의 플로우를 간략히 살펴보면 아래와 같다.
- 웹팩에 설정된 loader 중
@linaria/webpack-loader
가 tsx 파일에서 linaria 코드를 만나면, 이 코드로 css를 만든다. - 웹팩에 설정된 loader 중
css-loader
가 linaria가 만든 css를 자바스크립트로 불러올 수 있게 해준다. - 웹팩에 설정된 plugin 중
MiniCssExtractPlugin
은 css 파일을 JS 청크에서 분리해 별도의 파일로 존재할 수 있게 해준다. - 빌드 후 별도의 파일로 존재하는 css를 koa 서버에서 static 파일로 서빙해준다.
- 응답 HTML이 koa가 서빙하는 css를 불러올 수 있도록, link 태그를 추가한다.
Linaria는 styled-components에서 스타일을 적용하는 방식과 유사한 방식으로 스타일을 작성한다.
1export const Page = (props: AppProps) => {2 return (3 <DefaultContainer {...props}>4 <div className={container}>5 <WarningTemplate6 iconName={"warning"}7 title={"존재하지 않는 페이지에요"}8 iconSize={53}9 />10 </div>11 </DefaultContainer>12 );13};1415// 스타일 작성16const container = css`17 height: 100dvh;18 display: flex;19 flex-direction: column;20 justify-content: center;21 align-items: center;22`;
빌드 과정에서 위에서 작성한 스타일 코드는 아래와 같이 css 파일에 작성된다. 이 때, 클래스 이름도 자동으로 생성된다.
1.c1cfv1dx {2 height: 100dvh;3 display: -webkit-box;4 display: -webkit-flex;5 display: -ms-flexbox;6 display: flex;7 -webkit-flex-direction: column;8 -ms-flex-direction: column;9 flex-direction: column;10 -webkit-box-pack: center;11 -webkit-justify-content: center;12 -ms-flex-pack: center;13 justify-content: center;14 -webkit-align-items: center;15 -webkit-box-align: center;16 -ms-flex-align: center;17 align-items: center;18}
자바스크립트 번들에는 스타일 관련 코드는 전혀 남지 않고, 자동 생성된 CSS 클래스 이름이 컴포넌트의 className으로 들어간다.
1/*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_components_template_WarningTemplate__WEBPACK_IMPORTED_MODULE_1__.WarningTemplate, {2 iconName: 'warning',3 title: '존재하지 않는 페이지에요',4 iconSize: 535 })));6};7var container = "c1cfv1dx";
이처럼 Linaria는 빌드 타임에 단 한 번만 스타일을 생성하고, 그 이후에는 스타일 관련 코드를 전혀 실행하지 않게 된다.
Webpack 설정에 loader와 plugin을 추가해서 Linaria를 사용할 수 있게 설정해 보려고 한다.
linaria의 core 패키지와, linaria가 생성한 css를 처리해줄 css loader, plugin이 필요하다.
1npm i -D @linaria/core @linaria/webpack-loader css-loader mini-css-extract-plugin
linaria는 아래와 같이 사용할 수 있다.
1import React from "react";2import { css } from "@linaria/core";34export const App = ({ bandalartKey }: { bandalartKey: string }) => {5 return <div className={containerStyle}>{bandalartKey}</div>;6};78const containerStyle = css`9 background-color: red;10`;
웹팩 설정에 loader와 plugin을 아래와 같이 추가한다.
1// webpack.config.js2module.exports = {3 mode: isDev ? "development" : "production",4 target: "node",5 entry: "./src/index.ts",6 // linaria가 만든 css 파일을 추출하기 위해 MiniCssExtractPlugin 플러그인이 필요하다7 plugins: [8 new MiniCssExtractPlugin({9 filename: "public/styles.css",10 }),11 ],12 module: {13 rules: [14 {15 test: /\.tsx?$/,16 exclude: /node_modules/,17 use: [18 {19 loader: "babel-loader",20 },21 // tsx 파일을 만나면 linaria의 webpack-loader를 실행시킨다22 {23 loader: "@linaria/webpack-loader",24 },25 ],26 },27 {28 test: /\.css$/,29 use: [30 {31 loader: MiniCssExtractPlugin.loader,32 },33 {34 loader: "css-loader",35 options: {36 sourceMap: isDev,37 },38 },39 ],40 },41 ],42 },43 resolve: {44 extensions: [".js", ".jsx", ".json", ".ts", ".tsx"],45 },46 output: {47 filename: "bundle.js",48 path: path.resolve(__dirname, "dist"),49 },50};
빌드에 성공하면 위와 같이 public/styles.css
경로로 css 파일이 생성된다.
이 css 파일을 서버에서 서빙해주기 위해 koa-static
이 필요하다.
1// server.ts2import koa from "koa";3import serve from "koa-static";45// ...67app.use(serve(__dirname + "/public"));89// ...
이렇게 하면 public 하위 경로의 파일을 정적으로 서빙할 수 있게 된다.
마지막으로, HTML의 head 부분에 위 css 파일을 가져오게 해주는 link 태그를 추가한다.
1const assetPath = process.env.ASSET_PATH ?? "";2const router = new Router();34const createHtml = (content: string) => `5 <!DOCTYPE html>6 <html lang="ko">7 <head>8 <link rel="stylesheet" href="${assetPath}/styles.css" />9 <title>반다라트</title>10 </head>11 <body>12 <div id="root">${content}</div>13 </body>14 </html>15`;