티스토리 뷰

개발../Webpack

개발 서버

링재호 2022. 9. 29. 18:42

웹팩 개발서버

개발환경과 운영환경을 맞춤으로써 배포시 잠재적 문제를 미리 확인할 수 있다.
프론트엔드 개발환경에서 이러한 개발용 서버를 제공해주는 것이 Webpack-dev-server 이다.

npm install Webpack-dev-server

package.json

{
  "scripts": {
    "start": "webpack-dev-server --progress"
  }
}

--progress 를 추가하면 빌드 진행률을 보여준다.

기본 설정

웹팩 설정 파일의 devServer 객체에 개발 서버 옵션을 설정할 수 있다.

webpack.config.js

module.exports = {
  devServer: {
    // 아웃풋
    contentBase: path.join(__dirname, "dist"),
    // 브라우저를 통해 접근하는 경로
    publicPath: "/",
    // 개발환경에서 도멘을 맞추어야 하는 상황에서 아용
    host: "dev.domain.com",
    // 빌드, 에러시 경고를 브라우저에 표시
    overlay: true,
    // 포트
    port: 8081,
    // 메세지 수준
    stats: "errors-only",
    // 히스토리 API 를 사용하는 SPA 개발시 설정한다. 404 발생시 index.html로 리다이렉트 한다.
    historyApiFallback: true,
  },
}

API 연동

프론트엔드에서는 서버와 데이터를 주고 받기 위해 ajax 를 사용하는데 보통 ajax 서버를 어딘가에 띄워 놓고 프론트 서버와 함께 개발한다.
devServer 에 before 라는 속성을 사용하여 처리할 수 있는데, 이 미들웨어는 익스프레스를 사용할 수 있다.

// webpack.config.js
module.exports = {
  devServer: {
    before: (app, server, compiler) => {
      app.get("/api/keywords", (req, res) => {
        res.json([
          { keyword: "이탈리아" },
          { keyword: "세프의요리" },
          { keyword: "제철" },
          { keyword: "홈파티" },
        ])
      })
    },
  },
}

connect-api-mocker 를 사용하여 목업 api 작업이 방대한 경우 사용할 수 있다.

npm install connect-api-mocker
// webpack.config.js:
const apiMocker = require("connect-api-mocker")

module.exports = {
  devServer: {
    before: (app, server, compiler) => {
      app.use(apiMocker("/api", "mocks/api"))
    },
  },
}

첫번째 인자는 설정할 라우팅 경로인데 /api로 들어온 요청에 대해 처리하겠다는 의미다. 두번째 인자는 응답으로 제공할 목업 파일 경로인데 방금 만든 mocks/api 경로를 전달했다.

목업 API 갯수가 많다면 직접 컨트롤러를 작성하는 것 보다 목업 파일로 관리하는 것을 추천한다.

프록시

다른 url 로 호출하면 CORS 정책 때문이라는 메시지가 나온다. 리소스에 Access-Control-Allow-Origin 이 없어서 나오는 에러인데,
해결하는 방법은 여러가지가 있다.

  1. 응답부분에서 res.header("Access-Control-Allow-Origin", "*") 헤더를 추가한다.
  2. cors 미들웨어를 설치하여 셋팅해준다.
  3. 웹팩 개발서버에 프록시 속성을 사용하여 서버를 설정해준다.
    // webpack.config.js
    module.exports = {
    devServer: {
     proxy: {
       "/api": "http://localhost:8081", // 프록시
     },
    },
    }

개발 서버에 들어온 모든 http 요청 중 /api 로 시작되는 것은 8081 서버로 요청하는 설정이다.

핫 모듈 리플레이스먼트

웹팩 개발서버는 코드의 변화를 감지해서 전체 화면을 갱신하기 때문에 개발속도를 높일 수 있다.
하지만, 어떤 상황에서는 전체 화면을 갱신하는 것이 좀 불편한 경우도 있다. SPA 는 브라우저에서 데이터를 들고 있기 때문에
리프레시 후에 모든 데이터가 초기화 되어버리기 때문이다.
전체 화면이 갱신되는 것이 아닌, 변경한 모듈만 바꿔치기할 수 있도록 하는 것이 핫 모듈 리플레이스먼트이다.

설정

// webpack.config.js:
module.exports = {
  devServer = {
    hot: true,
  },
}
import form from "./form";
import result from "./result";

if (module.hot) {
  console.log("핫 모듈 켜짐");

  module.hot.accept("./result", async () => {
    console.log("result 모듈 변경 됨");
    resultEl.innerHTML = await result.render();
  });

  module.hot.accept("./form", () => {
    formEl.innerHTML = form.render();
  });
}

되면, result, form 에 대한 모듈 변경이 일어나면 hot 아래 에 콜백함수가 전달되어 브라우저 갱신 없이 부분 화면이 갱신되는 것을 볼 수 있습니다.

최적화

코드가 많아지면 번들링된 결과물도 커지기 마련이다. 메가바이트 단위까지 커질 수 있는데 이러면 브라우저 성능에 영향을 줄 수 있다.

productuin

웹팩에 내장되어 있는 방법 중 mode 값을 설정하는 방식이 가장 기본이다.
process.env.NODE_ENV 값이 "development" 로 설정되어 어플리케이션에 전역변수로 주입된다.
반면 mode 를 "production" 으로 설정하면 자바스크립트 결과물을 최소화해준다.
그렇기에 환경변수에 따라 mode 를 설정해줄 수 있다.

const mode = process.env.NODE_ENV || "development"

module.exports = {
  mode
}
{
  "scripts": {
    "start": "webpack-dev-server --progress",
    "build": "NODE_ENV=production webpack --progress"
  }
}

optimazation 속성을 이용하여 css 파일도 합축해줄 수 있다.

npm install optimize-css-assets-webpack-plugin

terser-webpack-plugin 은 자바스크립트 코드를 난독화하고 debugger 구문을 제거한다.

npm install terser-webpack-plugin

externals 속성을 사용하여 빌드 과정에서 번들에 포함하지 않고 빌드 할 수 있도록 추가한다.
그렇게 하기 위해서는 externals: { axios: "axios" } 를 사용하고 node_modules 에서 index.html 로 로딩해주어야 한다.
이는 CopyWebpackPlugin 을 설치한다.

npm install copy-webpack-plugin
const CopyPlugin = require("copy-webpack-plugin")

module.exports = {
  plugins: [
    new CopyPlugin([
      {
        from: "./node_modules/axios/dist/axios.min.js",
        to: "./axios.min.js", // 목적지 파일에 들어간다
      },
    ]),
  ],
}

최종

const path = require("path");
const webpack = require("webpack");
const childProcess = require("child_process");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtreactPlugin = require("mini-css-extract-plugin");
const apiMocker = require("connect-api-mocker");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");

const mode = process.env.NODE_ENV || "development";

module.exports = {
  mode,
  // 시작점을 기준으로 모든 모듈을 찾아 번들링 해줌.
  entry: {
    main: "./src/app.js",
  },
  // 번들링한 결과를 아웃풋에 전달합니다.
  output: {
    // 절대경로
    path: path.resolve("./dist"),
    filename: "[name].js",
  },
  devServer: {
    overlay: true,
    stats: "errors-only",
    before: (app) => {
      app.use(apiMocker("/api", "mocks/api"));
    },
    hot: true,
  },
  optimization: {
    minimizer:
      mode === "production"
        ? [
            new OptimizeCSSAssetsPlugin(),
            new TerserPlugin({
              terserOptions: { compress: { drop_console: true } },
            }),
          ]
        : [],
    // splitChunks: {
    //   chunks: "all"
    // }
  },
  externals: {
    axios: "axios",
  },
  module: {
    rules: [
      {
        // 각 js 파일당 로더가 한 번 씩 호출됨.
        test: /\.css$/,
        // loader use
        use: [
          process.env.NODE_ENV === "production"
            ? MiniCssExtreactPlugin.loader
            : "style-loader",
          "css-loader",
        ],
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        loader: "url-loader",
        options: {
          // publicPath: './dist/',
          name: "[name].[ext]?[hash]",
          limit: 20000, // 20kb (파일 용량 제한), 이상이면 file-loader가 자동으로 실행됨.
        },
      },
      {
        test: /\.js$/,
        loader: "babel-loader",
        exclude: /node_modules/,
      },
    ],
  },
  plugins: [
    // new MyWebpackPlugin()
    new webpack.BannerPlugin({
      banner: `
        Build Date: ${new Date().toLocaleString()}
        Commit Version: ${childProcess.execSync("git rev-parse --short HEAD")}
        Author: ${childProcess.execSync("git config user.name")}
      `,
    }),
    new webpack.DefinePlugin({
      // 코드 형식
      TWO: "1+1",
      // 문자열 형식
      TWO2: JSON.stringify("1+1"),
      // 객체 형식
      "api.domain": JSON.stringify("http://dev.api.domain.com"),
    }),
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      templateParameters: {
        env: process.env.NODE_ENV === "development" ? "(개발용)" : "",
      },
      minify:
        process.env.NODE_ENV === "production"
          ? {
              collapseWhitespace: true,
              removeComments: true,
            }
          : false,
    }),
    new CleanWebpackPlugin(),
    ...(process.env.NODE_ENV === "production"
      ? [new MiniCssExtreactPlugin({ filename: "[name].css" })]
      : []),
    new CopyPlugin([
      {
        from: "./node_modules/axios/dist/axios.min.js",
        to: "./axios.min.js",
      },
    ]),
  ],
};

'개발.. > Webpack' 카테고리의 다른 글

린트  (0) 2022.09.29
바벨  (0) 2022.09.29
플러그인  (0) 2022.09.27
로더  (0) 2022.09.26
웹팩  (0) 2022.09.25
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함