SSR 서버 만들기 (3) - Linaria로 CSS-in-JS 적용하기

2023-10-29에 씀
직접 만들어 보는 SSR 서버 시리즈의 다른 글
  1. SSR 서버 만들기 (1) - React Tree를 렌더링해 응답하는 서버 만들기
  2. SSR 서버 만들기 (2) - 번들링과 트랜스파일
  3. SSR 서버 만들기 (3) - Linaria로 CSS-in-JS 적용하기

레이아웃에 스타일을 입혀주기 위해 다양한 방법을 사용할 수 있다.

현재까지 이 프로젝트의 클라이언트 측에서는 자바스크립트 코드를 전혀 실행하지 않고 있는데 스타일링을 위해 자바스크립트 코드를 추가하고 싶지 않았다.

그래서 Linaria를 이번 프로젝트에 사용해 보기로 했다.


Linaria는 CSS-in-JS 방식의 스타일링 라이브러리이다. 가장 큰 특징은 빌드된 후에 자바스크립트를 전혀 남기지 않고 모두 CSS로 변환되는, zero-runtime CSS in JS 라이브러리라는 점이다.

Linaria가 번들링되어 HTML에서 사용되기까지의 플로우를 간략히 살펴보면 아래와 같다.

  1. 웹팩에 설정된 loader 중 @linaria/webpack-loader 가 tsx 파일에서 linaria 코드를 만나면, 이 코드로 css를 만든다.
  2. 웹팩에 설정된 loader 중 css-loader가 linaria가 만든 css를 자바스크립트로 불러올 수 있게 해준다.
  3. 웹팩에 설정된 plugin 중 MiniCssExtractPlugin은 css 파일을 JS 청크에서 분리해 별도의 파일로 존재할 수 있게 해준다.
  4. 빌드 후 별도의 파일로 존재하는 css를 koa 서버에서 static 파일로 서빙해준다.
  5. 응답 HTML이 koa가 서빙하는 css를 불러올 수 있도록, link 태그를 추가한다.

Linaria는 styled-components에서 스타일을 적용하는 방식과 유사한 방식으로 스타일을 작성한다.

1export const Page = (props: AppProps) => {
2 return (
3 <DefaultContainer {...props}>
4 <div className={container}>
5 <WarningTemplate
6 iconName={"warning"}
7 title={"존재하지 않는 페이지에요"}
8 iconSize={53}
9 />
10 </div>
11 </DefaultContainer>
12 );
13};
14
15// 스타일 작성
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: 53
5 })));
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";
3
4export const App = ({ bandalartKey }: { bandalartKey: string }) => {
5 return <div className={containerStyle}>{bandalartKey}</div>;
6};
7
8const containerStyle = css`
9 background-color: red;
10`;

웹팩 설정에 loader와 plugin을 아래와 같이 추가한다.

1// webpack.config.js
2module.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.ts
2import koa from "koa";
3import serve from "koa-static";
4
5// ...
6
7app.use(serve(__dirname + "/public"));
8
9// ...

이렇게 하면 public 하위 경로의 파일을 정적으로 서빙할 수 있게 된다.

마지막으로, HTML의 head 부분에 위 css 파일을 가져오게 해주는 link 태그를 추가한다.

1const assetPath = process.env.ASSET_PATH ?? "";
2const router = new Router();
3
4const 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`;
직접 만들어 보는 SSR 서버 시리즈의 다른 글
  1. SSR 서버 만들기 (1) - React Tree를 렌더링해 응답하는 서버 만들기
  2. SSR 서버 만들기 (2) - 번들링과 트랜스파일
  3. SSR 서버 만들기 (3) - Linaria로 CSS-in-JS 적용하기
프로필 사진

조예진

이전 포스트
SSR 서버 만들기 (2) - 번들링과 트랜스파일
다음 포스트
인터랙티브 맵 구현기