예제
KO

Sass/SCSS

Hwaro는 빌드 시점에 순수 Crystal로 구현된 내장 컴파일러로 SCSS를 컴파일합니다. dart-sass 바이너리 설치도, npm 툴체인도, C 라이브러리도 필요 없습니다. 외부 의존성 없음이라는 Hwaro의 철학 그대로입니다.

빠른 시작

[sass]
enabled = true

SCSS 파일을 static/ 아래에 둡니다.

static/
├── css/
│   ├── _variables.scss   # partial — never published
│   ├── _mixins.scss      # partial — never published
│   └── style.scss        # entry — compiles to /css/style.css
// static/css/style.scss
@use "variables";
@use "mixins";

.card {
  color: variables.$primary;
  &:hover { color: variables.$accent; }

  @include mixins.respond(768px) {
    padding: 2rem;
  }
}

파셜이 아닌 모든 *.scss는 출력에서 같은 경로의 .css로 컴파일되므로(static/css/style.scss/css/style.css), 스타일시트 URL이 안정적으로 유지됩니다.

<link rel="stylesheet" href="{{ url_for(path="/css/style.css") }}">

규칙

설정

옵션 타입 기본값 설명
enabled bool false SCSS 컴파일 활성화
minify bool true 컴파일된 CSS 압축(에셋 파이프라인과 같은 압축기 사용)

지원 범위

Hwaro는 실용적인 SCSS 부분집합을 구현합니다. 직접 작성하는 사이트 스타일시트에서 실제로 쓰이는 기능들입니다.

기능 지원
$variables !default / !global, 렉시컬 스코프와 섀도잉 지원
중첩 규칙 ✅ 셀렉터 목록 포함(카테시안 조합)
& 부모 셀렉터 &:hover, &.mod, BEM &__elem / &--mod
#{...} 보간 ✅ 셀렉터, 속성 이름, 값, at-규칙 서두, 문자열, url(). 내부에서 전체 표현식 평가. 문자열은 리스트 안까지 모든 중첩 수준에서 따옴표를 벗겨 출력합니다(#{("a", "b")}a, b)
파셜 + @use ✅ 네임스페이스(colors.$primary), as x, as *, 1회 로드, with (...) 설정
@forward show / hide 필터, as prefix-*
@import(Sass 파일) ✅ 클래식 전역 병합 시맨틱. @forward만 하는 파셜(@import "components"components/_index.scss)도 포함하며, 순수 CSS 형태는 그대로 통과
@mixin / @include ✅ 기본값, 키워드 인자, 가변 인자 $args...(남는 키워드는 meta.keywords()로 전달), 스프레드(키워드는 $args...를 통해 그대로 전달), 인자를 받는 @content 블록(@content(1px) / @include m using ($a))
@function / @return ✅ 값 안에서 호출 가능한 사용자 함수, 기본값/키워드/가변 인자, 재귀
@extend + %placeholders ✅ 단순 셀렉터 타깃, 컴파운드 통합, !optional. 확장되지 않은 플레이스홀더는 출력되지 않음(차이점 참고)
SassScript의 & if(&, "&", "") 패턴. 부모 셀렉터를 값으로, 루트에서는 null
제어 흐름 @if / @else if / @else, @each(구조 분해 포함), @for(through/to, 내림차순), @while
SassScript 표현식 ✅ 산술(+ - * / %, 클래식 슬래시 나눗셈 규칙), 비교, and/or/not, 문자열, 리스트, 맵(/는 차이점 참고)
단위 변환 px/cm/mm/q/in/pt/pc, deg/grad/rad/turn, s/ms, Hz/kHz, dpi/dpcm/dppx가 산술·비교·==·math.*에서 변환됨
중첩 속성 font: 12px serif { family: sans; }font, font-family (재귀 적용)
내장 함수 sass:math(log / hypot / 삼각함수 포함), sass:string(insert / split 포함), sass:list(zip / set-nth / slash / is-bracketed 포함), sass:map(set / deep-merge / deep-remove 포함), sass:meta(keywords / variable-exists / function-exists / mixin-exists / content-exists / get-function / call 포함), sass:selector(문자열 수준의 parse / nest / append / unify / replace / is-superselector / simple-selectorsreplace@extend 부분집합과 같은 제약을 공유합니다: $original은 컴파운드만, 의사 클래스 재귀 없음, 첫 번째 접두 순서만), sass:color 부분집합(channel / hwb / ie-hex-str 포함) + 레거시 전역 이름(map-get, nth, darken, if() 등)
모듈 상수 math.$pi, math.$e, math.$epsilon, math.$max-safe-integer, math.$min-safe-integer, math.$max-number, math.$min-number
@debug / @warn / @error @error는 위치 정보가 담긴 메시지로 빌드를 실패시킴
@at-root ✅ 셀렉터 형태와 블록 형태, #{&} 접미, (with: ...) / (without: ...) 쿼리
규칙 안의 @media / @supports ✅ 중첩 밖으로 자동 버블링, 중첩된 @mediaand로 병합(쉼표 목록은 교차 조합), 피처 값에서 표현식 평가
@keyframes, @font-face, 커스텀 속성 ✅ 올바르게 통과
순수 CSS ✅ 유효한 .css는 그대로 컴파일(공백 정규화)

알 수 없는 함수(var(), clamp(), color-mix() 등)는 손대지 않고 그대로 통과합니다. 인자는 평가되고(translate($x * 2, -50%) 동작), 정적인 calc()는 하나의 숫자로 접히며(차이점 참고), 토큰 전체를 이루는 url($v) / url(ns.$img)는 변수가 치환됩니다($는 원시 URL에 쓸 수 없는 문자입니다). 더 큰 url 토큰 안에 $가 박혀 있는 형태(url(plain$x.png))는 유효한 순수 CSS이므로 바이트 단위로 그대로 통과합니다. dart-sass는 여기서 오류를 내고, 반대로 url($a + $b)처럼 표현식이 들어간 내용은 dart-sass가 평가하지만 Hwaro는 원문 그대로 둡니다.

@use "sass:math";
$breakpoints: (sm: 640px, md: 768px, lg: 1024px);

@function rem($px, $base: 16px) { @return math.div($px, $base) * 1rem; }

@mixin respond($name) {
  @if not map-has-key($breakpoints, $name) { @error "unknown breakpoint #{$name}"; }
  @media (min-width: map-get($breakpoints, $name)) { @content; }
}

@each $name, $bp in $breakpoints {
  .container-#{$name} { max-width: $bp - 24px; }
}
@for $i from 1 through 12 {
  .col-#{$i} { width: math.percentage(math.div($i, 12)); }
}
.hero {
  font-size: rem(28px);
  @include respond(md) { font-size: rem(40px); }
}

색상

색상 함수는 hex 리터럴(#rgb, #rgba, #rrggbb, #rrggbbaa), CSS 색상 키워드(red, rebeccapurple, transparent), 그리고 레거시 콤마 철자 rgb(…) / rgba(…) / hsl(…) / hsla(…)에 동작합니다. hsl()로 만든 색상은 선언된 색조/채도를 기억하므로 hue(hsl(221, 14%, 100%))221deg를 답합니다.

$brand: #336699;

.button {
  background: $brand;
  border-color: darken($brand, 10%);      // #264d73
  color: scale-color($brand, $lightness: 60%);
  box-shadow: 0 1px 2px rgba($brand, 0.4); // rgba(51, 102, 153, 0.4)
}
분류 함수
명도 darken, lighten
채도 saturate, desaturate, grayscale
색상(Hue) adjust-hue, complement
혼합 mix, invert
알파 rgba($color, $alpha), opacify / fade-in, transparentize / fade-out
복합 adjust-color, scale-color, change-color
성분 조회 red, green, blue, hue, saturation, lightness, alpha / opacity, color.channel
기타 ie-hex-str, color.hwb(color.hwb($h $w $b)color.hwb($h, $w, $b) 모두)

같은 함수들을 sass:color 모듈의 최신 이름으로도 쓸 수 있습니다. color.adjust, color.scale, color.change, color.mix, color.complement, color.grayscale, color.invert, 그리고 성분 조회 함수들입니다.

@use "sass:color";
.a { border-color: color.scale(#336699, $lightness: -20%); }

계산된 색상은 불투명하면 #rrggbb로, 그렇지 않으면 rgba(r, g, b, a)로 직렬화됩니다. 수정하지 않은 색상은 작성한 철자를 그대로 유지하므로 #FFF#FFF로 남습니다.

색상은 표기가 달라도 채널 값으로 비교됩니다. #ffffff == #FFF, red == #f00, rgb(255, 0, 0) == #ff0000은 모두 true입니다(dart-sass 시맨틱).

@extend

@extend는 단순 셀렉터 타깃(클래스, %placeholder, id, 요소, 의사 클래스)에 동작합니다. Bootstrap을 포함한 실제 스타일시트의 사용 방식을 전부 커버합니다. 타깃이 속한 컴파운드는 확장자의 마지막 컴파운드와 통합되고, 조상 컴파운드는 앞에 붙으며, 확장되지 않은 %placeholder 규칙은 출력에 등장하지 않습니다.

%visually-hidden { position: absolute; clip: rect(0 0 0 0); }
.sr-only { @extend %visually-hidden; }
// → .sr-only { position: absolute; clip: rect(0 0 0 0); }

타깃을 찾지 못하면 위치 정보가 담긴 오류로 빌드가 실패합니다. !optional을 붙이면 허용됩니다. dart-sass의 전체 extend 알고리즘과의 차이는 아래 차이점 목록을 참고하세요.

미지원 (아직)

복합 단위(px*em, px/s처럼 분자·분모 단위 목록이 필요한 곱셈·나눗셈), @forward ... with (...), math.random / unique-id()(빌드는 결정적이어야 합니다), 들여쓰기 방식의 .sass 문법, 소스맵은 지원하지 않습니다.

미지원 지시문은 위치 정보가 담긴 오류와 함께 빌드를 실패시킵니다 — Hwaro는 조용히 깨진 CSS를 내보내지 않습니다.

Error [HWARO_E_CONTENT]: Sass: static/css/style.scss:14:3: @forward ... with (...) is not supported

표현식 시맨틱

이 컴파일러의 첫 번째 의무는 순수 CSS 보장이므로, 표현식은 두 단계 정책을 따릅니다.

dart-sass와의 차이

오류

컴파일 실패는 콘텐츠 오류로 분류되며(종료 코드 5), path:line:column 위치 정보가 함께 표시됩니다.

Error [HWARO_E_CONTENT]: Sass: static/css/_mixins.scss:7:12: undefined variable: "$primry"

hwaro serve 중에는 오류가 브라우저 오버레이에 표시되고, 이전 출력은 디스크에 그대로 남습니다.

다른 기능과의 상호작용