bun 환경에서 node 로 migrate 후, 기본 webpack 설정

joepasss·2023년 9월 27일
0

webpack

목록 보기
5/7

bun 환경에서 image를 minimize 할 때 생긴 문제로 node 환경으로 migration 한 뒤에 이전에 포스팅 했던 기본적인 웹팩 설정들을 했다.

yarn init


yarn init

webpack init


typescript 패키지 설치
yarn add -D typescript ts-node @types/node

webpack 패키지 설치
yarn add -D webpack webpack-cli webpack-dev-server @types/webpack @types/webpack-dev-server

webpack.config.ts 만들기
touch webpack.config.ts

webpack 엔트리포인트 설정

// webpack.config.ts

import { Configuration } from "webpack";

const config: Configuration = {
  entry: "./src/index.ts",
};

export default config;

todo_app 에서 src와 images를 가져 왔는데, app의 엔트리포인트가 ./src/index.ts이기 때문에 작성해 준다

output 설정

// webpack.config.ts

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

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

export default config;

현재 webpack.config 파일이 루트 디렉터리에 있으므로, ./dist/main.js 가 만들어지게끔 설정했다.

typescript loader (ts-loader) 설정

패키지 설치
yarn add -D ts-loader

ts 파일과 tsx 파일을 resolvable 한 ext로 만들기 위해 resolve 옵션 추가

// webpack.config.ts

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

const config: Configuration = {
  entry: "./src/index.ts",
  output: {
    path: path.resolve(__dirname, "./dist"),
    filename: "main.js",
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js"],
  }
};

export default config;

ts-loader 설정

// webpack.config.ts

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

const config: Configuration = {
  entry: "./src/index.ts",
  output: {
    path: path.resolve(__dirname, "./dist"),
    filename: "main.js",
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js"],
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: "ts-loader",
      },
    ],
  },
};

export default config;

@issue/ failed to load 'webpack.config.ts' config

에러 전문

[webpack-cli] Failed to load 'webpack.config.ts' config
[webpack-cli] TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for webpack.config.ts
    at new NodeError (node:internal/errors:405:5)
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:99:9)
    at defaultGetFormat (node:internal/modules/esm/get_format:142:36)
    at defaultLoad (node:internal/modules/esm/load:120:20)
    at ModuleLoader.load (node:internal/modules/esm/loader:388:13)
    at ModuleLoader.moduleProvider (node:internal/modules/esm/loader:270:56)
    at new ModuleJob (node:internal/modules/esm/module_job:65:26)
    at ModuleLoader.#createModuleJob (node:internal/modules/esm/loader:282:17)
    at ModuleLoader.getJobFromResolveResult (node:internal/modules/esm/loader:240:34)
    at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:221:17) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

타입스크립트를 node 환경에서 실행 가능하게 해 주는 ts-node 패키지가 설치되어 있음에도 ts 파일 로드를 실패한다면 package.json에 'type: module' 이 설정되어 있는지 확인한 후, 이를 제거해 준다.

제거 후, import 문에서 에러가 날 것이다
에러 전문

[webpack-cli] Failed to load 'webpack.config.ts' config
[webpack-cli] webpack.config.ts:1
import path from "path";
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:73:18)
    at wrapSafe (node:internal/modules/cjs/loader:1153:20)
    at Module._compile (node:internal/modules/cjs/loader:1205:27)
    at Module.m._compile (/Users/joepasss/joepasss/webpack/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1295:10)
    at Object.require.extensions.<computed> [as .ts] (/Users/joepasss/joepasss/webpack/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:1091:32)
    at Function.Module._load (node:internal/modules/cjs/loader:938:12)
    at Module.require (node:internal/modules/cjs/loader:1115:19)
    at require (node:internal/modules/helpers:130:18)

import statement를 module 밖에서 사용할 수 없다는 에러가 뜨는데, tsconfig.json 파일에서 module이 "CommonJS"가 아닌 다른 종류로 되어 있는지 확인하고, 만일 다른 종류로 설정되어 있다면 CommonJS로 바꿔준다.

// tsconfig.json

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "CommonJS",
    ...
  },
}

수정이 완료된 후 yarn webpack 을 실행해 보면, 정상적으로 잘 되는 것을 볼 수 있다.

webpack config 파일 분리


production 환경에서 사용할 config과 dev 환경에서 config을 분리 해 주고, 공통적으로 필요한 config을 만들어준다.

폴더, 파일 생성
mkdir webpack; touch ./webpack/webpack.common.config.ts ./webpack/webpack.prod.config.ts ./webpack/webpack.dev.config.ts

common.config 작성

// 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"),
    filename: "main.js",
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js"],
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: "ts-loader",
      },
    ],
  },
};

export default commonConfig;

기존에 작성한 webpack.config.ts 와 거의 동일하다. 위치가 root 디렉터리에서 /webpack 폴더로 바뀌었으므로, output의 path 속성을 ./dist 에서 ../dist 로 바꿔준다

기존 작성한 webpack.config.ts는 이제 사용하지 않으므로 삭제해 준다 rm -r ./webpack.config.ts

webpack config을 합치려면 webpack-merge 패키지가 필요하므로 설치해 준다. yarn add -D webpack-merge

prod.config 작성

// webpack.prod.config.ts

import { Configuration } from "webpack";
import commonConfig from "./webpack.common.config";
import { merge } from "webpack-merge";

const prodConfig: Configuration = merge(commonConfig, {
  mode: "production",
});

export default prodConfig;

dev.config 작성


import { Configuration } from "webpack";
import commonConfig from "./webpack.common.config";
import { merge } from "webpack-merge";

const devConfig: Configuration = merge(commonConfig, {
  mode: "development",
});

export default devConfig;

prod.config 과 dev.config을 실행할 script를 package.json에 작성해 준다

// package.json

{
	...
	
  "scripts": {
    "build": "webpack --config ./webpack/webpack.prod.config.ts",
    "dev": "webpack --config ./webpack/webpack.dev.config.ts"
  }
  
	...
}

이후 yarn run build yarn run dev 또는 yarn dev 명령어로 실행되는 모습을 볼 수 있다.

전체 코드 보기

템플릿에서 html 자동생성 & sass 적용


src 폴더에 있는 index.html을 템플릿 삼아, 번들링된 js 파일과 css 파일을 자동으로 import 해 보자

웹팩은 기본적으로 js파일과 json 파일을 제외하면 이해하지 못하기 때문에 html을 load 해 주는 loader가 필요하다

패키지 설치
yarn add -D html-loader

로더 적용

// 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"),
    filename: "main.js",
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js"],
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: "ts-loader",
      },
      {
        test: /\.html$/,
        loader: "html-loader",
      },
    ],
  },
};

export default commonConfig;

템플릿을 기반으로 html을 생성해 주는 플러그인인, html-webpack-plugin도 설치해 준다
yarn add -D html-webpack-plugin

플러그인 적용

// webpack.common.config.ts

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

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

export default commonConfig;

이후 index.html에 명시적으로 import된 js, css 를 삭제한다

// index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Simple Todo App</title>
  </head>
  <body>
    <div class="container">
      <img
        class="header-image"
        src="../images/header-image.jpg"
        alt="To Do List"
      />
      <h1>To Do List</h1>
      <div class="todolist-wrapper">
        <input class="new-todo" placeholder="Enter text here" autofocus />
        <ul class="todo-list"></ul>
      </div>
    </div>
  </body>
</html>

yarn run build를 실행한 뒤 생성된 index.html 파일을 보면, <script defer="defer" src="main.js"></script>가 생성된 것을 볼 수 있다.

sass 파일 임포트 & css minimize


index.ts 파일에 sass import

//src/index.ts

...
import "./style.scss";
...

이후, yarn run build 를 해 보면 Error: Can't resolve './styles.scss' 에러가 뜬다.

sass는 css의 전처리기(preprocessor)이므로 sass 처리 -> css 처리 -> Style 태그 안에 삽입 이 필요하다.

sass처리는 sass-loader가 담당하고, css 처리는 css 로더가, style 태그 안에 삽입은 style-loader가 해 준다.

패키지 설치
yarn add -D sass sass-loader css-loader style-loader

loader 적용

// webpack.common.config.ts

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

const commonConfig: Configuration = {
  entry: "./src/index.ts",
  output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "main.js",
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js"],
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: "ts-loader",
      },
      {
        test: /\.html$/,
        loader: "html-loader",
      },
      {
        test: /\.(scss|sass)$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html",
      filename: "index.html",
    }),
  ],
};

export default commonConfig;

css는 사용하지 않을 예정이므로, 따로 설정해주지 않는다.

이후 yarn run build를 실행해 보면, css 파일이 따로 나오는게 아닌, js 파일에 통합된 걸 볼 수 있는데, css 파일을 mini-css-extract-plugin 으로 추출 해 보자

mini-css-extract-plugin을 사용하려면, style-loader가 아닌 MiniCssExtractPlugin.loader를 사용해야 하는데, MiniCssExtractPlugin.loader는 style-loader와 혼용이 불가능하다. 그래서, 기존에 common.config 에 작성한 css 관련 loader 옵션을 dev.config으로 옮긴 뒤 prod.config을 작성해야 한다.

common.config

// webpack.common.config.ts

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

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

export default commonConfig;

dev.config

// webpack.dev.config.ts

import { Configuration } from "webpack";
import commonConfig from "./webpack.common.config";
import { merge } from "webpack-merge";

const devConfig: Configuration = merge(commonConfig, {
  mode: "development",
  module: {
    rules: [
      {
        test: /\.(scss|sass)$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
    ],
  },
});

export default devConfig;

prod.config

// 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: /\.(scss|sass)$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
      },
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
});

export default prodConfig;

production 모드에서, 번들링되는 파일의 이름이 항상 같기 때문에, 캐싱 문제가 발생 할 수 있다. 브라우저에서 같은 이름이기 때문에 캐싱된 파일을 가져와서, 빌드된 최신 결과를 확인하지 못 할 수 있기 때문에, 생성되는 파일의 이름에 hash 값을 추가해 캐싱 문제를 해결해야 한다

common.config 에서 output 옵션에 들어간 filename을 dev.config으로 옮겨준다.

common.config

// webpack.common.config.ts

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

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

export default commonConfig;

dev.config

// webpack.dev.config.ts

import { Configuration } from "webpack";
import commonConfig from "./webpack.common.config";
import { merge } from "webpack-merge";

const devConfig: Configuration = merge(commonConfig, {
  mode: "development",
  output: {
    filename: "main.js",
  },
  module: {
    rules: [
      {
        test: /\.(scss|sass)$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
    ],
  },
});

export default devConfig;

이제 prod.config 에서 생성되는 js, css에 hash 값을 추가한다

prod.config

// 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",
  output: {
    filename: "js/[name].[contenthash:12].js",
  },
  module: {
    rules: [
      {
        test: /\.(scss|sass)$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash:12].css",
    }),
  ],
});

export default prodConfig;

js의 경우, output 프로퍼티로 해쉬를 추가했고, css는 MiniCssExtractPlugin을 이용해 추가했다. 컨텐츠가 바뀔 때 마다 해쉬를 새로 작성하라는 의미에서 contenthash를 사용했다.

이후, yarn run build를 해 보면, 생성이 되지만, 이전에 생성된 파일을 수동으로 삭제해줘야 하는 불편함이 있는데, clean-webpack-plugin을 이용해서 자동으로 이전 빌드된 파일들을 삭제해 줄 수 있다.

yarn add -D clean-webpack-plugin 으로 설치 후, plugins 에 추가만 해 주면 된다.

common.config

// webpack.common.config.ts

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

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

export default commonConfig;

이후, yarn run build 또는 yarn dev를 실행해 보면, 자동으로 이전에 빌드된 파일이 삭제되는 것을 볼 수 있다.

css minimizer plugin을 통해 번들링된 css 를 미니마이즈 해 보자,
yarn add -D css-minimizer-webpack-plugin 으로 패키지를 설치한 후, prod.config에 적용

prod.config

// 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: /\.(scss|sass)$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "css/[name].[contenthash:12].css",
    }),
  ],
});

export default prodConfig;

webpack-dev-server 적용


기존 Configuration 인터페이스에서는 devServer 프로퍼티가 없으므로, 타입을 새로 작성해 준다

import { Configuration as WebpackConfiguration } from "webpack";
import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server'

interface Configuration extends WebpackConfiguration {
  devServer?: WebpackDevServerConfiguration;
}

dev.config

// webpack.dev.config.ts

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";
import path from "path";

interface Configuration extends WebpackConfiguration {
  devServer?: WebpackDevServerConfiguration;
}

const devConfig: Configuration = merge(commonConfig, {
  mode: "development",
  output: {
    filename: "main.js",
  },
  devServer: {
    port: 9000,
    static: path.resolve(__dirname, "../dist"),
    devMiddleware: {
      index: "index.html",
      writeToDisk: true,
    },
    client: {
      overlay: true,
    },
    liveReload: true,
  },
  module: {
    rules: [
      {
        test: /\.(scss|sass)$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
    ],
  },
});

export default devConfig;

이후, package.json 에 써놓은 dev 스크립트를 수정해야 한다. dev server를 열기 위해서는 webpack 명령이 아닌 webpack serve 명령을 이용해야 하기 때문에 webpack --config ./webpack/webpack.dev.config.ts 에서 webpack serve --config ./webpack/webpack.dev.config.ts 로 바꿔준다

// package.json

{
	...
	  "scripts": {
	    "build": "webpack --config ./webpack/webpack.prod.config.ts",
	    "dev": "webpack serve --config ./webpack/webpack.dev.config.ts"
	}
}

전체 코드 보기

0개의 댓글