> Hello World !!!

     

@syaku

webpack 모듈 번들러를 이용한 SPA 의 CSS 를 효율적으로 관리하기.

webpack 모듈 번들러를 이용한 SPA 의 CSS 를 효율적으로 관리하기.

SPA 는 단일 페이지 응용프로그램의 약자이다. 즉 여러개의 웹페이지를 하나의 웹페이지에서 구동되게 하는 기술이다.

그래서 생기는 문제점이 css 클래스명이 중복되어 스타일 디자인에 문제가 발생한다.

이를 해결하기 위해 다양한 방법이 있지만 동적보다 정적인 디자인에 한해 가능하다.

내가 개발하는 프로그램은 동적인 경우가 많다. 사용자가 쉽게 디자인을 실시간으로 변경할 수 있는 기능을 제공해야 한다.

내가 겪은 문제점들을 정리하고 효율적인 해결방법을 작성하려고 한다. (내용은 계속 추가될 것이다...)

중복되는 클래스

아래의 두파일은 webpack css-loader 를 이용하여 컴파일되면 하나의 파일로 합쳐진다. 이때 아래의 소스가 합처지면서 문제가 발생한다.

a.css

.title {
    color: red;
}

b.css

.title {
    color: blue;
}

build.css

.title {
    color: red;
}

.title {
    color: blue;
}

build.css 처럼 최종 파일이 생성되어 사용된다. 당연히 title  color  blue 가 될 것이다. 이문제를 해결하기 위해 title 클래스 명을 유니크하게 재설정할 수 있고, 부모 클래스명으로 감싸도 된다. 하지만 css-loader 를 이용하는 방법이 있다.

webpack.config.js

{
  test: /\.css$/,
  loader: ExtractTextPlugin.extract({
    fallback: 'style-loader',
    use: [
      {
        loader: 'css-loader',
        options: {
          camelCase: true,
          modules: true,
          localIdentName: '[path][name]__[local]--[hash:base64:5]'
        },
      },
    ],
  }),
},

웹팩 설정을 위와 같이한다면, .title 클래스명은 유니크한 클래스명으로 자동 변경된다. 그리고 아래와 같이 사용할 수 있다.

import a from 'a.css';
import b from 'b.css';

... skip ...

<div clasName={a.title} />
<div clasName={b.title} />

// 만약 여러개의 클래스명을 사용한다면
import classname from 'classnames';

<div clasName={classname(b.title, 'className', ...)} />

웹팩은 위 소스를 분석하여 className 에 들어간 클래스명을 유니크하게 생성해준다.

근데 이렇게 하기에는 너무 불편하며 bootstrap 이나 fontawsome 과 같은 오픈소스들의 클래스도 위와 같이해야한다. 배보다 배꼽이 더 커지는 작업이다.

그래서 충돌이 생기는 소스만 따로 css 모듈처리하고 아닌 것들은 기존처럼 사용할 수 있게 설정한다.

webpack.config.jsjs { test: /\.css$/, exclude: /\.module\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: [ { loader: 'css-loader', options: { importLoaders: 1, }, }, ], }), }, { test: /\.module\.css$/, include: path.join(__dirname, src), loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: [ { loader: 'css-loader', options: { camelCase: true, modules: true, localIdentName: '[path][name]__[local]--[hash:base64:5]' }, }, ], }), },

중복이 되는 클래스는 *.module.css 에서 관리하게 한다. 그럼 아래와 같이 사용할 수 있다.

import b from 'b.css';

... skip ...

<div clasName="container" />
<div clasName={b.title} />

// 만약 여러개의 클래스명을 사용한다면
import classname from 'classnames';

<button><i clasName={classname(b.title, 'fa', 'fa-close', ...)} />

css module 을 사용하면 클래스명이 유니크하게 생성되므로 상위 부모 클래스명을 가질필요가 없다. .layout .titie {} -> .title {} 해도 된다는 말이다.

만약 여러 클래스를 중첩으로 사용하고 싶다면.

:local(.className) {
  background: red;
  color: yellow;
}

:local(.subClass) {
  composes: className; // .className 의 스타일을 그대로 상속한다.
  background: blue;
}
// className="className subClass"
<div className={s.subClass} />

https://github.com/webpack-contrib/css-loader#composing 링크를 참조하고 import 도 가능하다.

동적인 스타일시트

Router 기술을 사용하면서 그 페이지에 맞는 css 로드하고 싶을때가 있다. SPA 는 한번 로드된 css는 새로고침 되기전까지 그대로 유지되므로 이것도 분명 클래스명 충돌이 생긴다. 이럴경우에는 sass 를 사용하도록 하자.

기존에 개발된 css 코드를 그대로 두고 최상위 클래스명을 하나 만들어 감싸주어라.

$ npm install node-sass sass-loader -D

//webpack.config.js

{
  test: /\.scss$/,
  include: path.join(__dirname, src),
  loader: ExtractTextPlugin.extract({
    fallback: 'style-loader',
    use: [
      {
        loader: 'css-loader',
        options: {
          sourceMap: true,
          importLoaders: 1,
        },
      },
      {
        loader: 'sass-loader',
        options: {
          sourceMap: true,
        }
      }
    ],
  }),
},

style.scss

.a {...}
.b {...}
.c {...}

style2.scss

.root {
    @import "./style"
}

build.css

.root .a { ... }
.root .b { ... }
.root .c { ... }

Router 가 변경될때마다 body 에 class 를 최상위 클래스명으로 변경해준다.