시작하며
CSS를 조금 더 편리하게 사용할 수 있는 방법에 대해서 소개하고자 합니다. 어떻게 글을 작성하면 방문해 주시는 분들이 이해를 더 잘하실 수 있을까 고민해 보았습니다. CRA(create-react-app)을 통해서 만든 프로젝트의 기본 파일을 각각의 방식으로 바꿔서 함께 설명을 드리면 쉽게 프로젝트를 따라 만드시고 고치는 시간을 가질 수 있을 것이라 생각합니다. App.jsx를 SASS / CSS Module / CSS in JS로 작성하지만 같은 화면을 보여주도록 해보겠습니다.
각 프로젝트는 CSS를 달리 표현하는 것은 import를 하는 방식부터 차이를 보입니다.
1. import './App.scss'; - (SASS)
2. import styles from './App.module.scss'; - (CSS Module + SASS)
3. import styled, { keyframes, css } from 'styled-components'; - (CSS in JS)
하지만 결국은 CSS를 제공하기 위한 방법이니 프로젝트의 성격에 맞게 방식을 선택하시면 좋겠습니다.
CRA
툴체인 중 하나인 Create React App은 최신 자바스크립트 라이브러리 사용과 webpack기본 설정을 자동으로 진행해 줍니다.
// 프로젝트를 생성할 디렉토리(가칭 : my-directory)로 이동
cd my-directory
// 생성할 프로젝트 폴더(가칭 : my-app)
npx create-react-app my-app
해당 명령어 이후 my-app이라는 프로젝트 폴더가 생기며, 해당 폴더를 Visual Studio Code로 열 경우 프로젝트 구조를 한눈에 확인하실 수 있습니다. (ko.reactjs.org/docs/create-a-new-react-app.html#create-react-app)
SASS
SASS(Syntactically Awesome StyleSheets)는 CSS 전처리기의 종류 중 하나이며 다른 종류로는 Less(Leaner Style Sheets), Stylus가 있습니다. 각 종류들은 약간의 syntax차이만 존재하고 전반적인 사용 목적과 원리는 비슷하기에 서로 간의 장벽은 낮은 편입니다. 각 전처리기가 허용하는 문법에 따라 작성된 파일을 CSS로 컴파일하는 것을 통해 브라우저가 파싱 이후 CSSOM을 생성할 수 있습니다. SASS의 경우 Libsass, node-sass, 그리고 Sass가 있는데 Libsass와 node-sass는 deprecated 되었으니, sass(dart-sass)가 차후 업데이트를 생각한다면 좋은 선택일 듯합니다. (sass-lang.com/dart-sass, itnext.io/the-css-preprocessor-dilemma-node-sass-or-dart-sass-32a0a096572, stackshare.io/stackups/node-sass-vs-sass)
// for installation
npm install sass
// usage for single file
sass style.scss:style.css
// usage for source directory
sass src/scss:deploy/css
터미널에서 명령어를 통해서 트랜스 파일 할 수 있으며, webpack을 사용한다면 sass-loader를 사용해서 전 처리할 수 있습니다. CRA로 프로젝트를 생성하면 sass-loader도 같이 설치되며, webpack.config.js에 작성되어 있습니다. (sass-loader는 dart-sass나 node-sass를 필요로 합니다. 그래서 추가로 "npm install node-sass"나 "npm install sass"를 하는 것으로 sass-loader를 사용할 수 있습니다. webpack.js.org/loaders/sass-loader)
// 생성할 프로젝트 폴더(가칭 : my-app-scss)
npx create-react-app my-app-scss
CRA로 생성된 프로젝트 package.json에 있는 터미널 명령어, "script": "start": "node scripts/start.js"는 webpack.config의 설정값에 의해서 프로젝트를 컴파일합니다. (CRA로 만들어진 프로젝트는 webpack.config 등 webpack 설정에 관한 파일들이나 start.js / build.js / test.js 등 스크립트 실행에 필요한 파일들을 숨겨놓습니다. 스크립트 실행에 필요한 파일 작성 이후 같은 위치에 저장하거나 webpack 설정을 변경해야 한다면 "npm run eject"를 사용해서 숨겨진 파일들을 보이게 처리할 수 있습니다.)
// start.js
const configFactory = require('../config/webpack.config');
// ...
const config = configFactory('development');
// ...
// Create a webpack compiler that is configured with custom messages.
const compiler = createCompiler({
appName,
config,
devSocket,
urls,
useYarn,
useTypeScript,
tscCompileOnError,
webpack,
});
// ...
webpack.config에서는 SASS에 대해서 sass-loader를 사용합니다.
// webpack.config.js
// ...
// Opt-in support for SASS (using .scss or .sass extensions).
// By default we support SASS Modules with the
// extensions .module.scss or .module.sass
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
},
'sass-loader',
),
// Do not consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules, but using SASS
// using the extension .module.scss or .module.sass
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
},
'sass-loader',
),
},
// ...
CRA로 구성된 프로젝트가 SASS를 어떻게 사용(확장자 .scss는 하나도 없지만)하고 있는지에 대해서 말씀드렸습니다. 자연스럽게 "그런데 왜 쓰는 건데?"라는 생각이 떠오르실 것 같습니다. SASS를 비롯한 CSS Preprocessor의 목적은 개발 속도를 높이는 것에 있습니다.
SASS는 Vanilla CSS에서는 할 수 없는 다음과 같은 기능들을 지원합니다.
1. variable declaration / allocation / call
변수는 $로 시작하는 것을 통해서 정의와 할당을 할 수 있습니다. 변수는 4가지 자료형을 지원합니다.
1) number (0, 1px, 0.3em, 0.4 rem,...)
2) string
3) color (red, yellow, yellowgreen,...)
4) boolean (true, false)
// those are the same
// scss
$primary-color: #3bbfce;
$margin: 16px;
.content-navigation {
border-color: red;
color: darken($primary-color, 10%);
}
.border {
padding: $margin / 2;
margin: $margin / 2;
border-color: $primary-color;
}
// sass
$primary-color: #3bbfce
$margin: 16px
.content-navigation
border-color: red
color: darken($primary-color, 10%)
.border
padding: $margin/2
margin: $margin/2
border-color: $primary-color
// compile into css
.content-navigation {
border-color: #ff0000;
color: #2b9eab;
}
.border {
padding: 8px;
margin: 8px;
border-color: #3bbfce;
}
2. code block nesting
CSS는 논리적인 네스팅을 지원하지만 코드 블록 자체는 네스팅 할 수 없습니다. 네스팅 코드를 삽입하기 위해서 Indented syntax와 SCSS syntax의 방식이 있으며, 전자는 들여 쓰기(Indent)와 개행 문자(Line Break, LB)를 통해서 python처럼 구분하는 것을 의미합니다. 후자는 블록 안의 줄을 구분하기 위해서 curly bracket "{", "}"을 사용하는 것을 의미합니다. 또한 semicolon ";"을 인지합니다. 전통적으로 Indented syntax는 .sass확장자를, SCSS syntax는 .scss확장자를 가집니다.
// those are the same
// scss
table.hl {
margin: 2em 0;
td.ln {
text-align: right;
}
}
li {
font: {
family: serif;
weight: bold;
size: 1.3em;
}
}
// sass
table.hl
margin: 2em 0
td.ln
text-align: right
li
font:
family: serif
weight: bold
size: 1.3em
// css
table.hl {
margin: 2em 0;
}
table.hl td.ln {
text-align: right;
}
li {
font-family: serif;
font-weight: bold;
font-size: 1.3em;
}
Mixin annotation (@mixin)을 사용하는 것을 통해 부모 참조를 포함한 더 복잡한 네스팅이 가능합니다.
// scss
@mixin table-base {
th {
text-align: center;
font-weight: bold;
}
td, th {
padding: 2px;
}
}
#data {
@include table-base;
}
3. loop
For, Each, While annotation (@for, @each, @while)을 사용해 변수에 대한 루프를 사용할 수 있습니다. 비슷한 클래스를 가진 요소에 대해서 특정 위치에 따라 각기 다른 스타일을 적용할 수 있습니다.
// those are the same
// sass
$squareCount: 3
@for $i from 1 through $squareCount
#square-#{$i}
background-color: red
width: 50px * $i
height: 120px / $i
// css
#square-1 {
background-color: red;
width: 50px;
height: 120px;
}
#square-2 {
background-color: red;
width: 100px;
height: 60px;
}
#square-3 {
background-color: red;
width: 150px;
height: 40px;
}
4. selector inheritance
CSS는 DOM계층은 지원하지만 셀렉터 상속을 허용하지 않습니다. Extend annotation (@extend)를 통해서 다른 셀렉터를 참조하는 코드 블록을 작성할 수 있습니다.
// those are the same
// sass
.error
border: 1px #f00
background: #fdd
.error.intrusion
font-size: 1.3em
font-weight: bold
.badError
@extend .error
border-width: 3px
// css
.error, .badError {
border: 1px #f00;
background: #fdd;
}
.error.intrusion,
.badError.intrusion {
font-size: 1.3em;
font-weight: bold;
}
.badError {
border-width: 3px;
}
위 기능을 통해서, CSS에서 길게 작성을 해야만 했던 것을 더욱 짧고 가독성이 높은 코드로 작성할 수 있습니다. 다만 결국은 CSS로 컴파일하기 위한 과정이 필요하기 때문에 빌드 타임이 늘어나는 것이 아니냐는 이야기도 있습니다. 그러나 Vanilla CSS작성을 할 때 하나의 버튼이 공통된 속성을 토대로 5~6개의 다른 스타일을 가지고 있다면 해당 스타일에 대해서 각자 중복된 코드를 일일이 복사-붙여 넣기를 하는 과정을 수행해서 작성해야 합니다. 물론 SCSS를 통해서 작성된 파일이 컴파일되면 같아지겠지만, 유지 보수 측면에서 특정 속성을 변경하거나 추가하는 것을 통해서 스타일을 변경해야 한다면 코드가 서로 산재되게 되며 버그가 발생했을 때 이슈를 처리하기가 어렵습니다. SCSS를 작성할 때 중복된 Mixin선언과 사용을 하게 되면 CSS 코드의 양이 배수로 증가하기 때문에 중복을 배제하는 것이 중요합니다. (webactually.com/post_author/sam-richard)
CRA로 프로젝트를 생성하는 것과 SCSS의 기능에 대해서 말씀드렸습니다. 다음 글에서는 CRA로 생성된 프로젝트에 SCSS를 적용하고, 해당 부분을 CSS Module로 변경하는 것을 소개해 보도록 하겠습니다.
※ 잘못된 부분이나 궁금한 점 댓글로 작성해 주시면 최대한 답변하겠습니다. 읽어주셔서 감사합니다.
※ CSS #3 - SASS (2)에서 SCSS파일 변경과 CSS Module 적용을 소개해 볼 예정입니다.
'React > CSS' 카테고리의 다른 글
CSS #6 - CSS-in-JS (2) Styled-Component with Storybook (0) | 2021.06.27 |
---|---|
CSS #5 - CSS-in-JS (1) Pros and Cons (2) | 2021.06.20 |
CSS #4 - SASS (3) with CSS Module (0) | 2021.05.23 |
CSS #3 - SASS (2) with CSS Module (0) | 2021.05.01 |
CSS #1 - CSS priority (1) | 2021.04.18 |