Overview

SCSS란 css의 문법적 불편함을 보안하기 위한 CSS 전처리기(Preprocessor) 중에 하나로 CSS와 비슷한 문법을 제공해 퍼블리셔들에게 인기 있는 CSS Preprocessor 입니다. SCSS는 일반적인 CSS에서 제공하지 않는 변수, 믹스인, 중첩구조 등 여러 유용한 기능들은 제공하는데 VUE에서 이러한 기능을 사용하기 위해서는 일반적으로 VUE 프로젝트를 시작할 때 사용하는 VUE-CLI로 프로젝트를 생성 후 몇가지 추가 셋팅이 필요 합니다.

이 글에서는 이러한 VUE 프로젝트 내 추가설정에 대해서 이야기 해 보고자 합니다.

주요모듈 및 기본설정

해당 문서는 VUE-CLI 4.4.1을 기준으로 작성 되었으며 (20.10.19 기준 VUE-CLI v4.5.8) 해당 버전을 기준으로 설명하겠습니다.

아래와 같이 VUE-CLI의 webpack 탬플릿으로 프로젝트를 생성할 경우,

vue init webpack my-project

VUE 프로젝트의 주요모듈인 webpack과 vue, vue-loader 등은 아래와 같은 버전으로 설치 되지만 SCSS를 사용하기 위한 sass-loader(loader)와 node-sass(compiler) 모듈은 기본으로 설치 되어 있지 않기 때문에 별도로 해당 모듈을 설치해야 합니다.

"dependencies": {
    "vue": "^2.5.2",
    "vue-router": "^3.0.1"
  },
  "devDependencies": {
    ...
    "css-loader": "^0.28.0",
    "html-webpack-plugin": "^2.30.1",
    "postcss-loader": "^2.0.8",
    "url-loader": "^0.5.8",
    "vue-jest": "^1.0.2",
    "vue-loader": "^13.3.0",
    "vue-style-loader": "^3.0.1",
    "vue-template-compiler": "^2.5.2",
    "webpack": "^3.6.0",
    ...
  },
npm install —-save-dev sass-loader@7.3.1 node-sass

주의: sass-loader v8.0.0 이후로 webpack 최소 요구 버전이 v4.36.0이기 때문에 sass-loader의 버전을 v7.3.1로 설치했습니다.

"dependencies": {
    ...
  },
  "devDependencies": {
    ...
    "sass-loader": "^7.3.1",
    "node-sass": "^4.14.1",
    ...
  },

sass-loader와 node-sass 모듈을 추가로 설치 했다면 webpack 설정 중 vue-loader에 아래와 같이 scss-loader의 설정이 되어 있어야 합니다.

/build/webpack.base.conf.js

const vueLoaderConfig = require('./vue-loader.conf')

module.exports = {
  module : {
    rules : [
      {
        test : /\.vue$/,
        loader : 'vue-loader',
        options: vueLoaderConfig
      },
      ...

/build/vue-loader.conf.js

'use strict'
const utils = require('./utils')
const config = require('../config')
const isProduction = process.env.NODE_ENV === 'production'
const sourceMapEnabled = isProduction
  ? config.build.productionSourceMap
  : config.dev.cssSourceMap

module.exports = {
  loaders: utils.cssLoaders({
    sourceMap: sourceMapEnabled,
    extract: isProduction
  }),
  cssSourceMap: sourceMapEnabled,
  cacheBusting: config.dev.cacheBusting,
  ...
}

/build/utils.js

exports.cssLoaders = function (options) {
  ...

  function generateLoaders (loader, loaderOptions) {
    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]

    if (loader) {
      loaders.push({
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }

    if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader'
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
  }

  return {
    ...
    sass: generateLoaders('sass', { indentedSyntax: true }),
    scss: generateLoaders('sass'),
    ...
  }
}



SCSS구조와 @import

위와 같이 기본적인 설정을 마쳤다면 vue 템플릿에서 sass구문을 사용하는 것은 문제가 없습니다. 그러나 사이트가 커지면서 늘어나는 style구문들을 효율적으로 관리하기 위해서는 브라우저의 기본 스타일을 초기화 하는 reset.css 또는 normalize.css, 사이트의 모든 페이지에서 사용하는 layout 스타일 구문 같은 공통 스타일과 변수, 믹스인 등과 같은 설정 및 헬퍼 구문 등을 components와 pages 스타일들과는 별도로 분리하고 관리 할 필요가 있으며 이렇게 분리한 scss파일들 중 전역으로 사용하는 scss구문들은 모든 VUE탬플릿에서 적용 가능하도록 추가로 등록 해줄 필요가 있습니다.

설명을 위한 SCSS구조는 아래와 같은 Hugo Giradel의 Sass Boilerplate 구조를 기준으로 설명 하겠습니다.

@charset "UTF-8";

// 1. Configuration and helpers
@import
  '~@/assets/scss/abstracts/variables',
  '~@/assets/scss/abstracts/functions',
  '~@/assets/scss/abstracts/mixins';

// 2. Vendors
@import
  '~@/assets/scss/vendors/normalize';

// 3. Base stuff
@import
  '~@/assets/scss/base/base',
  '~@/assets/scss/base/fonts',
  '~@/assets/scss/base/typography',
  '~@/assets/scss/base/helpers';

// 4. Layout-related sections
@import
  '~@/assets/scss/layout/header',
  '~@/assets/scss/layout/footer';

// 5. Components
@import
  '~@/assets/scss/components/button';

// 6. Page-specific styles
@import
  '~@/assets/scss/pages/home';

참고: 위 구문에서 at(@)표시는 프로젝트 바로 및 src폴더를 가르키는 webpack설정 내 alias이고 tilde(~)표시는 scss구문에서 alias를 사용하기 위한 prefix입니다.

위 Sass Boilerplate 구조에서 설정 및 헬퍼의 구문들과 공통스타일 구문들을 별도의 scss파일로 분리 합니다. 이와 같이 분리하는 이유는 공통스타일의 경우 최상위 컴포넌트에서 @import 하면 모든 콤포넌트에서 해당 스타일을 적용 시킬 수 있으나 설정 값을 모아놓은 _abstracts.scss의 경우는 해당 설정을 사용하는 콤포넌트마다 매번 @import 시켜야만 스타일을 컴파일 하는 과정에서 해당 설정 값을 찾을 수 있기에 이런 번거로운 과정을 웹팩 설정에 포함시키기 위해서 입니다.

/src/assets/scss/_abstracts.scss

@charset "UTF-8";

// 1. Configuration and helpers
@import
  '~@/assets/scss/abstracts/variables',
  '~@/assets/scss/abstracts/functions',
  '~@/assets/scss/abstracts/mixins';

/src/assets/scss/_common.scss

@charset "UTF-8";

// 2. Vendors
@import
  '~@/assets/scss/vendors/normalize';

// 3. Base stuff
@import
  '~@/assets/scss/base/base',
  '~@/assets/scss/base/fonts',
  '~@/assets/scss/base/typography',
  '~@/assets/scss/base/helpers';

// 4. Layout-related sections
@import
  '~@/assets/scss/layout/header',
  '~@/assets/scss/layout/footer';

공통스타일 구문(_common.scss)들은 최상위 콤포넌트인 App.vue에 @import하며 설정 구문(_abstracts.scss)들의 경우 utils.js에 웹팩 구문으로 모든 콤포넌트 vue탬플릿에 @import 될 수 있도록 설정을 추가합니다.

/src/App.vue

<template>
  <div id="app">
    ...
  </div>
</template>

<script>
export default {
  name: 'App',
  ...
}
</script>

<style lang="scss">
@import '~@/assets/scss/common';
</style>

/build/utils.js

exports.cssLoaders = function (options) {
  ...
  return {
    ...
    sass: generateLoaders('sass', { indentedSyntax: true }),
//  scss: generateLoaders('sass'),
    scss : generateLoaders('sass', { data: '@import \'~@/assets/scss/abstracts\';' }),
    ...
  }
}

참고: 이 문서에서는 webpack v3 환경에서 예를 들었기에 utils.js에서 data 구문을 사용하여 _abstracts.scss를 모든 콤포넌트에 @import 시켰지만 webpack의 버전이 다를 경우 운영하는 프로젝트의 webpack 버전에 맞게 수정해 줄 필요가 있습니다. 예로 webpack v5.2.0의 sass-loader에서는 additionalData를 사용 합니다.

Conclusion

위와 같은 일련의 설정을 마쳤다면 아래와 같은 구조에서 scss를 @import하여 사용할 수 있을 것이라 판단합니다. 해당 구조로 scss를 구성하고 @import하여 사용하는 것이 절대적인 정답은 아니겠지만, 최소한 점점 커져가는 프로젝트에서 좀 더 확장성 있게 scss파일들을 관리하고 사용하는 방법이라고 생각합니다.

root
├─ index.html
├─ package.json
├─ ...
├─ build
│  ├─ utils.js < @import '~@/assets/scss/abstracts';
│  └─ ...
└─ src
   ├─ main.js
   ├─ App.vue < @import '~@/assets/scss/common';
   ├─ components
   │  ├─ component0.vue < @import '~@/assets/scss/componts/component0';
   │  └─ ... < @import '~@/assets/scss/componts/...';
   ├─ routes
   │  └─ index.js
   ├─ pages
   │  ├─ Home.vue < @import '~@/assets/scss/pages/home';
   │  └─ ... < @import '~@/assets/scss/pages/...';
   └─ assets
      └─ scss
         ├─ abstracts
         ├─ vendors
         ├─ base
         ├─ layout
         ├─ componts
         │  ├─ _component0.scss
         │  └─ ...
         ├─ pages
         │  ├─ _home.scss
         │  └─ ...
         ├─ _abstracts.scss
         └─ _common.scss

방대종 팀장 | 퍼블리싱팀
bangdj@brandi.co.kr
브랜디, 오직 예쁜 옷만