Skip to content

Syntactically Awesome Style Sheets

Sass(syntactically awesome stylesheets, 사스)는 햄튼 캐틀린이 설계하고 나탈리 바이첸바움이 개발한 종속형 시트 언어이다. 초기 버전들 이후에 바이첸바움과 크리스 엡스타인은 Sass 파일에 쓰이는 단순 스크립팅 언어인 SassScript로 Sass의 확장을 계속하였다.

How to install

npm

npm install -g sass

macOS

brew install sass/sass/sass

컴파일 방법

sass input.scss output.css
sass --watch input.scss output.css

node-sass

node 기반의 sass. 자세한 내용은 항목 참조.

dart-sass

dart 기반의 sass.

SCSS

Include file

To share variables between Sass files, you can use Sass's @use rule. For example, src/App.scss and other component style files could include with variable definitions.

This will allow you to do imports like

@use "./shared.scss"; // 상대경로 적용.
@use 'styles/_colors.scss'; // 'src/' 안의 경로 적용.
@use '~nprogress/nprogress'; // '~'  node_module 디렉토리 경로. , 패키지 폴더 적용.

SCSS vs SASS

Sass(Syntactically Awesome Style Sheets)의 3버전에서 새롭게 등장한 SCSS는 CSS 구문과 완전히 호환되도록 새로운 구문을 도입해 만든 Sass의 모든 기능을 지원하는 CSS의 상위집합(Superset) 입니다. 즉, SCSS는 CSS와 거의 같은 문법으로 Sass 기능을 지원한다는 말입니다. 더 쉽고 간단한 차이는 {}(중괄호)와 ;(세미콜론)의 유무입니다.

Sass:

.list
  width: 100px
  float: left
  li
    color: red
    background: url("./image.jpg")
    &:last-child
      margin-right: -10px

SCSS:

.list {
  width: 100px;
  float: left;
  li {
    color: red;
    background: url("./image.jpg");
    &:last-child {
      margin-right: -10px;
    }
  }
}

Sass는 선택자의 유효범위를 ‘들여쓰기’로 구분하고, SCSS는 {}로 범위를 구분합니다.

아래는 Mixins 예제입니다.

Sass는 단축 구문으로 사용합니다.

=border-radius($radius)
  -webkit-border-radius: $radius
  -moz-border-radius:    $radius
  -ms-border-radius:     $radius
  border-radius:         $radius

.box
  +border-radius(10px)

SCSS:

@mixin border-radius($radius) {
  -webkit-border-radius: $radius;
     -moz-border-radius: $radius;
      -ms-border-radius: $radius;
          border-radius: $radius;
}

.box { @include border-radius(10px); }
  • Sass는 =+ 기호로 Mixins 기능을 사용했고,
  • SCSS는 @mixin@include로 기능을 사용했습니다.

변수

sass에서 변수라는 기능을 제공하는 이유는, 재사용성 을 위함이다. 변수로 등록된 스타일은 쉽게 다른 코드에서 사용할 수 있기 때문이다.

$font-stack : Helvetical, sans-serif;
$primary-color : #333;

body{
    font : 100% &font-stack;
    color: $primary-color;
}

Selector Combinators

ul > {
  li {
    list-style-type: none;
  }
}

h2 {
  + p {
    border-top: 1px solid gray;
  }
}

p {
  ~ {
    span {
      opacity: 0.8;
    }
  }
}

Functions

Nesting (중첩)

HTML을 작성할 때의 중첩되고 시각적인 계층 구조가 있다. 하지만, css는 그렇지 않다. Sass를 통해서 이와 HTML의 시각적인 계층구조와 대응 할 수 있는 방식으로 코드를 작성할 수 있다.

nav{
    ul{
    margin: 0;
    padding: 0;
    list-style: none;
    }
    li{
        display: inline-block;
    }
    a{
        display: block;
        padding: 6px 12px;
        text-decoration: none;
    }
}

이 코드가 css로 컴파일 되면 다음과 같다.

nav ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
nav li {
  display: inline-block;
}
nav a {
  display: block;
  padding: 6px 12px;
  text-decoration: none;
}

nav -> ul(li,a)과 같은 html의 계층구조를 따르는 scss와 달리, css는 개별의 코드로 작성된 것을 통해서 이의 장점을 느낄 수 있다.

Partials

공식문서에 따르면, scss의 파일 구조를 모듈화 할 수 있다. 하지만, 아까 처음 명령어를 통해서 모듈화된 코드가 있다고 가정했을 때 이를 나누어 모듈화 시킬 scss를 제외하고 scss만 컴파일 하기에는 상당히 고생할 수도 있다. 따라서

_partial.scss 처럼 파일 명 앞에 언더 스코어 바를 통해서 빌드에서 제외 시킬 수 있다. 모듈화 하는 코드는 다음에서 바로 보도록 하자.

Modules

#Partial에서 다루었던 내용은, 빌드 목록에서 제외시킬 수 있는 파일 명을 작성하는 법을 배웠다. 그렇다면 이렇게 빌드에서 제외되어 모듈화 되는 scss의 코드는 어떻게 작성하고, 어떻게 import 해야 할까?

@use 라는 코드를 통해서 원하는 대로 scss 파일을 로드할 수 있다. ('단 _파일은 무조건 모듈로써 존재해야 한다.')

  • _base.scss: 빌드에서 제외된 partial 파일이다.
$font-stack: Helvetica, sans-serif
$primary-color: #333

body {
  font: 100% $font-stack;
  color: $primary-color;
}
  • styles.sass
@use 'base'; // _파일명의 파일명만 use 하면, 이의 속성을 사용할  있다.

.inverse {
  background-color: base.$primary-color;
  color: white;
}

Mixins

믹스 인을 통해서, 사이트 전체에서 재사용할 CSS declaraations를 만들 수 있다. 또한, 값들을 믹스인을 통해서 집어 넣을 수 있으며(파라미터), 이를 통해서 좀더 유연하고 동적인 CSS 구조를 만들 수 있다. 예시를 통해 보자.

@mixin transform($property) {
    -webkit-transform: $property;
    -ms-transform: $property;
    transform: $property;
}
.box { @include transform(rotate(30deg)); }

믹스인을 만들기 위해 해야 되는 절차는 다음과 같다.

  • @mixin 지시문을 앞에 사용,
  • 함수의 파라미터처럼 ($변수) 이름 정의
  • mixin 지시문을 사용하고 이름을 지정

이를 사용할때에는 @include를 통해서 사용할 수 있다.

( 반응형을 위한 미디어 쿼리 작성 또한 이를 통해서 진행 할 수 있다. )

Extend(확장) / Inheritance(상속)

Sass의 유용한 기능중에 하나는, @extend 를 통해서 한 선택기에서 다른 선택기로 CSS 속성 세트를 공유할 수 있다는 점이다.

%message-shared {
    border: 1px solid #ccc;
    padding: 10px;
    color: #333;
}

%equal-heights {
    display: flex;
    flex-wrap: wrap;
}

.message {
    @extend %message-shared;
}

.success {
    @extend %message-shared;
    border-color: green;
}

.error {
    @extend %message-shared;
    border-color: red;
}

.warning {
  @extend %message-shared;
  border-color: yellow;
}

다음의 코드를 보고, 알 수 있는점은 error, warning, message, success가 공통적으로 message-shared라는 place holder를 갖고 있다. 여기에 부가적인 기능들을 표현함으로써, 다른 기능을 표현 하고 있음을 알 수 있다.

연산자

CSS 에서의 연산자는 매우 유용하다. 표준 수학 연산자 +,-,*,/ 와 % 와 같은 연산자를 통해서 스타일을 처리할 수 있다.

.container {
  width: 100%;
}

article[role="main"] {
  float: left;
  width: 600px / 960px * 100%;
}

aside[role="complementary"] {
  float: right;
  width: 300px / 960px * 100%;
}

@import

Sass도 CSS와 동일한 문법으로 파일을 import 할 수 있습니다. 차이점이 있다면 여러개의 파일을 쉼표로 구분하여 import 할 수 있다는 것입니다.

// style.scss
@import 'foundation/code', 'foundation/lists';

@content

@mixin media($types...) {
  @each $type in $types {
    @media #{$type} {
      @content($type);
    }
  }
}

@include media(screen, print) using ($type) {
  h1 {
    font-size: 40px;
    @if $type == print {
      font-family: Calluna;
    }
  }
}

css 결과는 다음과 같다:

@media screen {
  h1 {
    font-size: 40px;
  }
}
@media print {
  h1 {
    font-size: 40px;
    font-family: Calluna;
  }
}

상위요소 참조 (Ampersand; &)

&을 사용하면 현재 블럭이 적용되는 셀렉터를 참조한다. 정확하게는 참조가 아닌 치환이다. 특히 현재 속성을 설정중인 셀렉터에 의사셀렉터를 적용할 때 유용하다.

a {
  text-decoration: none
  &:hover { text-decroation: underline; }
}

위 선언은 아래와 같이 컴파일 된다.

a { text-decoration: none; }
a:hover { text-decoratino: underline; }

문자열의 치환 및 보간 (interpolation; #{...})

#{...}을 사용하면 문자열 내에 표현식의 결과를 내삽하거나, 다른 변수의 내용으로 치환하는 것이 가능하다. 이는 속성값의 일부 혹은 전체 뿐만 아니라 속성명이나 셀렉터에 대해서도 적용 가능하다.

$foo: bar;
$fontsize: 12px;
$lineheight: 30p;
p {
  font: #{$fontsize}/#{$lineheight};
  &.#{$foo} { color: red; }
}

이 예제는 다음과 같이 컴파일 된다.

p { font: 12px/30px; }
p.bar { color: red; }

Tailwind에서 @apply ... 에 사용할 때 좋다:

$temp-width: 40;

...

@apply w-#{$temp-width};

map

여러 weight의 @font-face를 만드는 map 과 @each 코드 샘플

$font-weights: (
        'Thin': 100,
        'ExtraLight': 200,
        'Light': 300,
        'Regular': 400,
        'Medium': 500,
        'SemiBold': 600,
        'Bold': 700,
        'ExtraBold': 800,
        'Black': 900,
);

@each $name, $weight in $font-weights {
  @font-face {
    font-family: 'Hahmlet';
    font-style: normal;
    font-weight: $weight;
    font-display: swap;
    src: local('Hahmlet #{$name}'),
    url('/fonts/Hahmlet/Hahmlet-#{$name}.woff2') format('woff2'),
    url('/fonts/Hahmlet/Hahmlet-#{$name}.woff') format('woff'),
    url('/fonts/Hahmlet/Hahmlet-#{$name}.ttf') format('ttf');
  }
}

TypeScript 통합

declaration.d.ts파일을 만들고 다음과 같이 작성:

declare module '*.scss' {
    const content: Record<string, string>;
    export default content;
}

Examples

no-select.scss

Sass (SCSS) mixin to disable user-select on an element

@mixin no-select {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

Troubleshooting

gyp ERR

패키지 설치시 에러가 발생될 수 있다:

  g++ -o Release/obj.target/binding/src/binding.o ../src/binding.cpp '-DNODE_GYP_MODULE_NAME=binding' '-DUSING_UV_SHARED=1' '-DUSING_V8_SHARED=1' '-DV8_DEPRECATION_WARNINGS=1' '-DV8_DEPRECATION_WARNINGS' '-DV8_IMMINENT_DEPRECATION_WARNINGS' '-D_GLIBCXX_USE_CXX11_ABI=1' '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-D__STDC_FORMAT_MACROS' '-DOPENSSL_NO_PINSHARED' '-DOPENSSL_THREADS' '-DBUILDING_NODE_EXTENSION' -I/home/yourname/.cache/node-gyp/16.14.2/include/node -I/home/yourname/.cache/node-gyp/16.14.2/src -I/home/yourname/.cache/node-gyp/16.14.2/deps/openssl/config -I/home/yourname/.cache/node-gyp/16.14.2/deps/openssl/openssl/include -I/home/yourname/.cache/node-gyp/16.14.2/deps/uv/include -I/home/yourname/.cache/node-gyp/16.14.2/deps/zlib -I/home/yourname/.cache/node-gyp/16.14.2/deps/v8/include -I../../nan -I../src/libsass/include  -fPIC -pthread -Wall -Wextra -Wno-unused-parameter -m64 -O3 -fno-omit-frame-pointer -fno-rtti -fno-exceptions -std=gnu++14 -std=c++0x -MMD -MF ./Release/.deps/Release/obj.target/binding/src/binding.o.d.raw   -c
In file included from /home/yourname/.cache/node-gyp/16.14.2/include/node/v8.h:30,
                 from /home/yourname/.cache/node-gyp/16.14.2/include/node/node.h:63,
                 from ../../nan/nan.h:56,
                 from ../src/binding.cpp:1:
/home/yourname/.cache/node-gyp/16.14.2/include/node/v8-internal.h: In function ‘void v8::internal::PerformCastCheck(T*)’:
/home/yourname/.cache/node-gyp/16.14.2/include/node/v8-internal.h:492:38: error: ‘remove_cv_t’ is not a member of ‘std’; did you mean ‘remove_cv’?
  492 |             !std::is_same<Data, std::remove_cv_t<T>>::value>::Perform(data);
      |                                      ^~~~~~~~~~~
      |                                      remove_cv
/home/yourname/.cache/node-gyp/16.14.2/include/node/v8-internal.h:492:38: error: ‘remove_cv_t’ is not a member of ‘std’; did you mean ‘remove_cv’?
  492 |             !std::is_same<Data, std::remove_cv_t<T>>::value>::Perform(data);
      |                                      ^~~~~~~~~~~
      |                                      remove_cv
/home/yourname/.cache/node-gyp/16.14.2/include/node/v8-internal.h:492:50: error: template argument 2 is invalid
  492 |             !std::is_same<Data, std::remove_cv_t<T>>::value>::Perform(data);
      |                                                  ^
/home/yourname/.cache/node-gyp/16.14.2/include/node/v8-internal.h:492:63: error: ‘::Perform’ has not been declared
  492 |             !std::is_same<Data, std::remove_cv_t<T>>::value>::Perform(data);
      |                                                               ^~~~~~~
../src/binding.cpp: In function ‘Nan::NAN_METHOD_RETURN_TYPE render(Nan::NAN_METHOD_ARGS_TYPE)’:
../src/binding.cpp:284:98: warning: cast between incompatible function types from ‘void (*)(uv_work_t*)’ {aka ‘void (*)(uv_work_s*)’} to ‘uv_after_work_cb’ {aka ‘void (*)(uv_work_s*, int)’} [-Wcast-function-type]
  284 |     int status = uv_queue_work(uv_default_loop(), &ctx_w->request, compile_it, (uv_after_work_cb)MakeCallback);
      |                                                                                                  ^~~~~~~~~~~~
../src/binding.cpp: In function ‘Nan::NAN_METHOD_RETURN_TYPE render_file(Nan::NAN_METHOD_ARGS_TYPE)’:
../src/binding.cpp:320:98: warning: cast between incompatible function types from ‘void (*)(uv_work_t*)’ {aka ‘void (*)(uv_work_s*)’} to ‘uv_after_work_cb’ {aka ‘void (*)(uv_work_s*, int)’} [-Wcast-function-type]
  320 |     int status = uv_queue_work(uv_default_loop(), &ctx_w->request, compile_it, (uv_after_work_cb)MakeCallback);
      |                                                                                                  ^~~~~~~~~~~~
In file included from ../../nan/nan.h:56,
                 from ../src/binding.cpp:1:
../src/binding.cpp: At global scope:
/home/yourname/.cache/node-gyp/16.14.2/include/node/node.h:842:43: warning: cast between incompatible function types from ‘void (*)(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE)’ {aka ‘void (*)(v8::Local<v8::Object>)’} to ‘node::addon_register_func’ {aka ‘void (*)(v8::Local<v8::Object>, v8::Local<v8::Value>, void*)’} [-Wcast-function-type]
  842 |       (node::addon_register_func) (regfunc),                          \
      |                                           ^
/home/yourname/.cache/node-gyp/16.14.2/include/node/node.h:876:3: note: in expansion of macro ‘NODE_MODULE_X’
  876 |   NODE_MODULE_X(modname, regfunc, NULL, 0)  // NOLINT (readability/null_usage)
      |   ^~~~~~~~~~~~~
../src/binding.cpp:358:1: note: in expansion of macro ‘NODE_MODULE’
  358 | NODE_MODULE(binding, RegisterModule);
      | ^~~~~~~~~~~
make: *** [binding.target.mk:133: Release/obj.target/binding/src/binding.o] Error 1
make: Leaving directory '/home/yourname/Project/answer-dev/fe/node_modules/node-sass/build'
gyp ERR! build error
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/home/yourname/Project/answer-dev/fe/node_modules/node-gyp/lib/build.js:194:23)
gyp ERR! stack     at ChildProcess.emit (node:events:526:28)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (node:internal/child_process:291:12)
gyp ERR! System Linux 5.13.0-39-generic
gyp ERR! command "/usr/local/bin/node" "/home/yourname/Project/answer-dev/fe/node_modules/node-gyp/bin/node-gyp.js" "rebuild" "--verbose" "--libsass_ext=" "--libsass_cflags=" "--libsass_ldflags=" "--libsass_library="
gyp ERR! cwd /home/yourname/Project/answer-dev/fe/node_modules/node-sass
gyp ERR! node -v v16.14.2

적어도 노드의 버전이 v16.14.2일 경우 node-sass의 버전이 5 라면 에러가 났다. ^7.0.1 으로 업그레이드하면 된다.

SassError: Undefined function

Error: Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
SassError: Undefined function.
4 │   min-height: math.div(100, 2);
  │                 ^^^^^^^^^^^^^^^^
  src\app\app.component.scss 4:17  root stylesheet

여러 내용을 확인해 보자.

See also

Favorite site