webpack을 이용해서 Style sheet import 하기

joepasss·2023년 9월 19일
0

webpack

목록 보기
2/7
post-thumbnail

웹팩 기본 설정
웹팩 기본 설정 코드 보기

webpack을 이용해서 css 파일을 js에서 import 하는 방법


src 폴더에 styles 폴더를 만들고, index.css 만들기

# root dir에서
mkdir ./src/styles; touch ./src/styles/index.css

스타일이 적용이 되는지 확인하기 위해 background-color 속성을 추가 해 준다.

/* index.css */
body {
  background-color: #bada55;
}

이후 index.ts 파일에 직접 import를 해 준뒤에 bun run build를 실행 해 보자.

// index.ts
import "./styles/index.css"

console.log("hello!");

bun run build

$ webpack --config webpack/webpack.prod.config.ts
assets by status 703 bytes [cached] 1 asset
runtime modules 663 bytes 3 modules
cacheable modules 91 bytes
  ./src/index.ts 53 bytes [built] [code generated]
  ./src/styles/index.css 38 bytes [built] [code generated] [1 error]

ERROR in ./src/styles/index.css 1:5
Module parse failed: Unexpected token (1:5)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> body {
|   background-color: #bada55;
| }
 @ ./src/index.ts 1:0-28

webpack 5.88.2 compiled with 1 error in 102 ms
error: script "build" exited with code 1 (SIGHUP)

이런 에러가 뜨는데, 여기서 주의깊게 봐야 할 것은 You may need an appropriate loader to handle this file type, ~~~ 이 라인이다.

loader

웹팩은 기본적으로 js 파일이나, json 파일을 제외한 다른 파일을 이해하지 못한다, 그래서 css 파일을 import 하면 오류가 나게 되는 데, 이를 중간에서 웹팩이 이해할 수 있는 형태로 변환 해 주는 것이 "로더"의 역할이다.

css 파일을 자바스크립트 또는 타입스크립트 파일 내부에 직접 임포트를 하려면 적절한 로더가 필요한데, 이때 사용되는 로더가 style-loader와 css로더이다.

style-loader

Style-loader는 css를 <style></style> 태그 안에 넣어주는 역할을 한다. javascript 또는 typescript 파일에 import된 css 또는 sass 등의 스타일 시트 파일을 resolve 해 주지는 않는다.

css-loader

css-loader는 javascript 또는 typescript 파일에 import된 css 파일을 resolve 해 주는 역할을 해 주는 로더다.

그렇기 때문에 css-loader (resolve) -> style-loader (태그 안에 삽입) 순서로 적용을 시켜주면 된다.

적용

패키지 설치

bun add -D style-loader css-loader

dev 모드에서나, prod 모드에서나 css가 둘 다 필요하므로, webpack.common.config.ts 에 적용해 준다.

// webpack.common.config.ts
import path from "path";
import { Configuration } from "webpack";

const commonConfig: Configuration = {
  entry: "./src/index.ts",
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],  // 오른쪽 부터 실행하게 된다 (css-loader -> style-loader 순서)
      },
    ],
  },
  output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "main.js",
  },
};

export default commonConfig;

적용 한 이후 bun run build를 통해 번들링을 해 보면,

css 가 잘 적용이 된 것을 볼 수 있다.

전체 코드 보기

번들링된 CSS 파일 따로 추출하기


지금까지는 번들링된 main.js에 자바스크립트 문법으로 css를 적용하고 있는데 이를 mini-css-extract-plugin 을 이용해서 추출해 보자

패키지 설치

bun add -D mini-css-extract-plugin

prodcution 빌드에서 사용할 예정이므로, webpack.prod.config.ts 에 적용해 준다.

// webpack.prod.config.ts
import { Configuration } from "webpack";
import commonConfig from "./webpack.common.config";
import { merge } from "webpack-merge";
import MiniCssExtractPlugin from "mini-css-extract-plugin";

const prodConfig: Configuration = merge(commonConfig, {
  mode: "production",
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
});

export default prodConfig;

MiniCssExtractPlugin.loader와 style-loader는 같이 사용이 불가능하다.

그리고, 이전에 common.config 에 적용한 css 관련 로더 옵션은 현재 dev.config 에서만 사용하므로, 이도 옮겨준다.

// webpack.dev.config.ts
import path from "path";
import { Configuration as WebpackConfiguration } from "webpack";
import { Configuration as WebpackDevServerConfiguration } from "webpack-dev-server";
import commonConfig from "./webpack.common.config";
import { merge } from "webpack-merge";

interface Configuration extends WebpackConfiguration {
  devServer?: WebpackDevServerConfiguration;
}

const prodConfig: Configuration = merge(commonConfig, {
  mode: "development",
  devtool: "eval-cheap-module-source-map",
  devServer: {
    port: 9000,
    static: {
      directory: path.resolve(__dirname, ".."),
    },
    devMiddleware: {
      index: "index.html",
      writeToDisk: true,
    },
    client: {
      overlay: true,
    },
    liveReload: true,
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
});

export default prodConfig;

이후, bun run build 커멘드를 사용해 빌드해 보면, dist 폴더에 css가 따로 번들링 된 것을 볼 수 있다.

지금 index.html에 css파일이 import 되지 않았으므로, link 태그로 따로 추가해 준다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Webpack!</title>
  </head>
  <body>
    <h1>HELLO!</h1>
  </body>

  <script src="./dist/main.js"></script>
  <link rel="stylesheet" href="./dist/main.css" />
</html>

CSS MINIMIZE

번들링된 css 파일을 보면, 번들링된 js 파일과 다르게 minimize 되지 않고 파일 그대로 출력이 되는데 css-minimizer-webpack-plugin 을 통해 해결이 가능하다.

패키지 설치

bun add -D css-minimizer-webpack-plugin

production 모드에서만 사용할 예정이기 때문에, webpack.prod.config.ts 에서 적용을 해 준다

// webpack.prod.config.ts

import { Configuration } from "webpack";
import commonConfig from "./webpack.common.config";
import { merge } from "webpack-merge";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
import CssMinimizerPlugin from "css-minimizer-webpack-plugin";

const prodConfig: Configuration = merge(commonConfig, {
  mode: "production",
  optimization: {
    minimize: true,
    minimizer: [
      `...`, // 기존 웹팩에서 제공하는 minimize 옵션을 사용하겠다는 뜻
      new CssMinimizerPlugin({
        minimizerOptions: {
          preset: [
            "default",
            {
              discardComments: { removeAll: true },
            },
          ],
        },
      }),
    ],
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
});

export default prodConfig;

CssMinimizerPlugin은 plugins: ~~ 옵션이 아닌 minimizer 옵션 내부에 들어간다.

index.css 내부에 comment 를 삽입한 후 build를 해 보면

/* src/styles/index.css */

/* COMMENT!! */
body {
  background-color: #bada55;
}
/* dist/main.css */

body{background-color:#bada55}

번들링된 css 파일에서 코멘트와 공백이 삭제되며 미니마이즈가 된 것을 볼 수 있다.

전체 코드 보기

js, css 캐싱 문제 해결 (번들링된 파일에 해쉬값 추가)


번들링된 파일 이름이 동일하기 때문에 재 배포시에 캐싱 문제가 발생 할 수 있다. (이미 캐싱이 된 상황이라 새로 작성된 파일을 로딩을 안해줌) 이를 위해 번들링된 파일 이름에 hash 값을 추가하는 작업이 필요한데 이를 적용해 보자

// webpack.prod.config.ts

import { Configuration } from "webpack";
import commonConfig from "./webpack.common.config";
import { merge } from "webpack-merge";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
import CssMinimizerPlugin from "css-minimizer-webpack-plugin";

const prodConfig: Configuration = merge(commonConfig, {
  mode: "production",
  output: {
    filename: "js/[name].[contenthash:12].js",
  },
  optimization: {
    minimize: true,
    minimizer: [
      `...`,
      new CssMinimizerPlugin({
        minimizerOptions: {
          preset: [
            "default",
            {
              discardComments: { removeAll: true },
            },
          ],
        },
      }),
    ],
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "cs/[name].[contenthash:12].css",
    }),
  ],
});

export default prodConfig;

js 파일은 output 옵션 내부에, css 파일은 MiniCssExtractPlugin 내부에 작성해 주면 된다.

prod 모드에서 output을 적용했기 때문에 common 모드에서 output의 filename을 삭제해 준다

// webpack.common.config.ts

import path from "path";
import { Configuration } from "webpack";

const commonConfig: Configuration = {
  entry: "./src/index.ts",
  output: {
    path: path.resolve(__dirname, "../dist"),
  },
};

export default commonConfig;

dev config에서도 css에 캐싱 문제가 있을 수 있으니, style-loader를 이용해서 적용해 보자

// webpack.dev.config.ts

import path from "path";
import { Configuration as WebpackConfiguration } from "webpack";
import { Configuration as WebpackDevServerConfiguration } from "webpack-dev-server";
import commonConfig from "./webpack.common.config";
import { merge } from "webpack-merge";

interface Configuration extends WebpackConfiguration {
  devServer?: WebpackDevServerConfiguration;
}

const devConfig: Configuration = merge(commonConfig, {
  mode: "development",
  devtool: "eval-cheap-module-source-map",
  devServer: {
    port: 9000,
    static: {
      directory: path.resolve(__dirname, ".."),
    },
    devMiddleware: {
      index: "index.html",
      writeToDisk: true,
    },
    client: {
      overlay: true,
    },
    liveReload: true,
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        loader: "style-loader",
      },
      {
        test: /\.css$/,
        loader: "css-loader",
        options: {
          modules: {
            localIdentName: "[local]--[md4:hash:7]",
          },
        },
      },
    ],
  },
});

export default devConfig;

devConfig에서는 css-loader의 option으로 구현했다.

생성되는 파일의 이름이 다르기 때문에 build 또는 serve 를 할 때 마다 새로운 파일이 생성되어 필요 없는 파일을 제거할 필요가 있는데, 이를 위해 clean-webpack-plugin을 이용해 제거해준다.

패키지 설치

bun add -D clean-webpack-plugin

dev 환경이든, prod 환경이든 삭제를 해 줘야하기 때문에, common.config 에 적용해 준다.

// webpack.common.config.ts

import path from "path";
import { Configuration } from "webpack";
import { CleanWebpackPlugin } from "clean-webpack-plugin";

const commonConfig: Configuration = {
  entry: "./src/index.ts",
  output: {
    path: path.resolve(__dirname, "../dist"),
  },
  plugins: [new CleanWebpackPlugin()],
};

export default commonConfig;

plugins 옵션에 추가만 해 주면 끝난다.

html 파일에 js, css 파일 자동 임포트하기


생성되는 파일의 이름이 다르기 때문에 build를 할 때 마다 html에 임포트된 js의 파일 이름과 css의 파일 이름을 변경해 줘야 하는데, 이를 자동으로 해 주는 플러그인(html-webpack-plugin)를 이용해서 자동 임포트를 할 수 있다.

패키지설치

bun add -D html-loader html-webpack-plugin

html 또한 웹팩이 이해를 못해주기 때문에 로더를 적용해야 한다.

template.html 작성

<!-- src/template.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Webpack!</title>
  </head>
  <body>
    <h1>HELLO!</h1>
  </body>
</html>

기존과 동일하나, 자동으로 css, js 파일이 임포트되기 때문에 script 태그와 link 태그만 삭제했다.

dev 환경, prod 환경 관계없이 템플릿에서 html 파일을 생성해야 하므로 common.config 에 적용한다.

// webpack.common.config.ts

import path from "path";
import { Configuration } from "webpack";
import { CleanWebpackPlugin } from "clean-webpack-plugin";
import HtmlWebpackPlugin from "html-webpack-plugin";

const commonConfig: Configuration = {
  entry: "./src/index.ts",
  output: {
    path: path.resolve(__dirname, "../dist"),
  },
  module: {
    rules: [
      {
        test: /\.html$/,
        use: ["html-loader"],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: "index.html",
      template: "src/template.html",
    }),
    new CleanWebpackPlugin(),
  ],
};

export default commonConfig;

전체 코드 보기

SASS 적용해 보기


sass, less 등의 전처리기를 이용하려면 단순히 loader만 추가해 주면 된다. 여기서는 sass-loader를 이용해 sass를 적용해 보자

index.css -> index.scss 로 이름을 변경하고, index.ts 에 import 되어 있는 index.css 를 index.scss로 변경한 후 config을 수정하겠다.

패키지 설치

bun add -D sass sass-loader

dev.config 수정

// webpack.dev.config.ts

import path from "path";
import { Configuration as WebpackConfiguration } from "webpack";
import { Configuration as WebpackDevServerConfiguration } from "webpack-dev-server";
import commonConfig from "./webpack.common.config";
import { merge } from "webpack-merge";

interface Configuration extends WebpackConfiguration {
  devServer?: WebpackDevServerConfiguration;
}

const devConfig: Configuration = merge(commonConfig, {
  mode: "development",
  devtool: "eval-cheap-module-source-map",
  devServer: {
    port: 9000,
    static: {
      directory: path.resolve(__dirname, ".."),
    },
    devMiddleware: {
      index: "index.html",
      writeToDisk: true,
    },
    client: {
      overlay: true,
    },
    liveReload: true,
  },
  module: {
    rules: [
      {
        test: /\.(css|scss)$/,
        loader: "style-loader",
      },
      {
        test: /\.(css|scss)$/,
        loader: "css-loader",
        options: {
          modules: {
            localIdentName: "[local]--[md4:hash:7]",
          },
        },
      },
      {
        test: /\.scss$/,
        loader: "sass-loader",
      },
    ],
  },
});

export default devConfig;

sass는 css의 전처리기이기 때문에 css로더가 필요하다.

// webpack.prod.config.ts

import { Configuration } from "webpack";
import commonConfig from "./webpack.common.config";
import { merge } from "webpack-merge";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
import CssMinimizerPlugin from "css-minimizer-webpack-plugin";

const prodConfig: Configuration = merge(commonConfig, {
  mode: "production",
  output: {
    filename: "js/[name].[contenthash:12].js",
  },
  optimization: {
    minimize: true,
    minimizer: [
      `...`,
      new CssMinimizerPlugin({
        minimizerOptions: {
          preset: [
            "default",
            {
              discardComments: { removeAll: true },
            },
          ],
        },
      }),
    ],
  },
  module: {
    rules: [
      {
        test: /\.(css|scss)$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
      {
        test: /\.scss$/,
        use: ["sass-loader"],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "cs/[name].[contenthash:12].css",
    }),
  ],
});

export default prodConfig;

전처리기를 사용하려면, 로더 하나만 추가해 주면 된다.

전체 코드 보기

PostCSS 추가하기


PostCSS란?
autoprefixer, stylelint, cssdb 등과 같은 유용한 CSS 관련 플러그인을 사용가능하게 해 주는 환경을 제공해 주는 도구

패키지 설치

bun add -D postcss postcss-loader

postcss.config.ts 파일 만들기

# in root dir
touch postcss.config.ts
// postcss.config.ts

module.exports = {
  plugins: [],
};

webpack config 파일 수정

// webpack.dev.config.ts

import path from "path";
import { Configuration as WebpackConfiguration } from "webpack";
import { Configuration as WebpackDevServerConfiguration } from "webpack-dev-server";
import commonConfig from "./webpack.common.config";
import { merge } from "webpack-merge";

interface Configuration extends WebpackConfiguration {
  devServer?: WebpackDevServerConfiguration;
}

const devConfig: Configuration = merge(commonConfig, {
  mode: "development",
  devtool: "eval-cheap-module-source-map",
  devServer: {
    port: 9000,
    static: {
      directory: path.resolve(__dirname, ".."),
    },
    devMiddleware: {
      index: "index.html",
      writeToDisk: true,
    },
    client: {
      overlay: true,
    },
    liveReload: true,
  },
  module: {
    rules: [
      {
        test: /\.(css|scss)$/,
        loader: "style-loader",
      },
      {
        test: /\.(css|scss)$/,
        loader: "css-loader",
        options: {
          modules: {
            localIdentName: "[local]--[md4:hash:7]",
          },
        },
      },
      {
        test: /\.(css|scss)$/,
        loader: "postcss-loader",
      },
      {
        test: /\.scss$/,
        loader: "sass-loader",
      },
    ],
  },
});

export default devConfig;
// webpack.prod.config.ts

import { Configuration } from "webpack";
import commonConfig from "./webpack.common.config";
import { merge } from "webpack-merge";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
import CssMinimizerPlugin from "css-minimizer-webpack-plugin";

const prodConfig: Configuration = merge(commonConfig, {
  mode: "production",
  output: {
    filename: "js/[name].[contenthash:12].js",
  },
  optimization: {
    minimize: true,
    minimizer: [
      `...`,
      new CssMinimizerPlugin({
        minimizerOptions: {
          preset: [
            "default",
            {
              discardComments: { removeAll: true },
            },
          ],
        },
      }),
    ],
  },
  module: {
    rules: [
      {
        test: /\.(css|scss)$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
      },
      {
        test: /\.scss$/,
        use: ["sass-loader"],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "cs/[name].[contenthash:12].css",
    }),
  ],
});

export default prodConfig;

주의해야 할 점은 css 전처리기 로더 전에 postcss-loader를 넣어 줘야 한다는 것이다. (css 전처리기 -> postcss -> css-loader -> style-loader 순서)

적용이 되는지 확인하기 위해 vender prefixer를 자동으로 적용해 주는 autoprefixer 플러그인을 적용해 보자

패키지 설치

bun add -D autoprefixer

적용 브라우저 범위 설정 (package.json 에서)

"browserslist": [
    "> 1%",
    "last 2 versions"
  ],

점유율 1퍼센트 이상, 이전 2개 버전을 지원하겠다는 뜻

postcss에 적용하기

// postcss.config.ts

module.exports = {
  plugins: [require("autoprefixer")],
};

autoprefixer 적용 여부를 알 수 있게 scss 파일 수정

/* COMMENT!! */
body {
  background-color: #bada55;
}

.auto-pre {
  display: inline-flex;
  transition: all 0.7s ease-in-out;
}

이후 bun run build를 실행 후 결과를 보면

/* dist/cs/main.css */

body {
	background-color:#bada55
}

.auto-pre {
	display:-webkit-inline-box;
	display:-ms-inline-flexbox;
    display:inline-flex;
    -webkit-transition:all .7s ease-in-out;
    transition:all .7s ease-in-out
}

autoprefixer가 적용이 된 모습을 볼 수 있다.

또 다른 PostCSS 플러그인, PurgeCSS


PurgeCSS 는 사용하지 않는 css를 제거하여 번들 사이즈를 줄여주는 역할을 해 주는 플러그인이다. webpack.config에서도 적용이 가능하지만, 여기서는 postcss.config 에서 적용 해 보자

패키지 설치

bun add -D @fullhuman/postcss-purgecss

postcss.config 수정

// postcss.config.ts

import purgecss from "@fullhuman/postcss-purgecss";

module.exports = {
  plugins: [
    require("autoprefixer"),
    purgecss({ content: ["./src/**/*.html"] }),
  ],
};

이렇게 수정한 뒤 생성된 css 파일을 보면, 사용되지 않는 .auto-pre 가 삭제된 모습을 볼 수 있다

/* dist/css/main.css */

body{background-color:#bada55}

purgecss가 특정 클래스를 삭제하지 못하도록 하려면

/* purgecss ignore */
.some_class {
	...
}

와 같이 purgecss ignore 주석을 붙여주면 된다

전체 코드 보기

0개의 댓글