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, ~~~
이 라인이다.
웹팩은 기본적으로 js 파일이나, json 파일을 제외한 다른 파일을 이해하지 못한다, 그래서 css 파일을 import 하면 오류가 나게 되는 데, 이를 중간에서 웹팩이 이해할 수 있는 형태로 변환 해 주는 것이 "로더"의 역할이다.
css 파일을 자바스크립트 또는 타입스크립트 파일 내부에 직접 임포트를 하려면 적절한 로더가 필요한데, 이때 사용되는 로더가 style-loader와 css로더이다.
Style-loader는 css를 <style></style>
태그 안에 넣어주는 역할을 한다. javascript 또는 typescript 파일에 import된 css 또는 sass 등의 스타일 시트 파일을 resolve 해 주지는 않는다.
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 가 잘 적용이 된 것을 볼 수 있다.
지금까지는 번들링된 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 파일을 보면, 번들링된 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 파일에서 코멘트와 공백이 삭제되며 미니마이즈가 된 것을 볼 수 있다.
번들링된 파일 이름이 동일하기 때문에 재 배포시에 캐싱 문제가 발생 할 수 있다. (이미 캐싱이 된 상황이라 새로 작성된 파일을 로딩을 안해줌) 이를 위해 번들링된 파일 이름에 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 옵션에 추가만 해 주면 끝난다.
생성되는 파일의 이름이 다르기 때문에 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, 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란?
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가 적용이 된 모습을 볼 수 있다.
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 주석을 붙여주면 된다