LHJ

I'm a FE developer.

최적화 - 실습

04 Oct 2020 » node_webpack2

최적화 - 실습

// webpack.config.js
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  // TODO: 환경변수 NODE_ENV에 따라 development나 production 값을 설정하세요 
  mode: "development",
  entry: {
    main: "./src/app.js"
  },
  output: {
    filename: "[name].js",
    path: path.resolve("./dist")
  },
  devServer: {
    overlay: true,
    stats: "errors-only",
    proxy: {
      "/api": "http://localhost:8081"
    },
    hot: true
  },
  module: {
    rules: [
      {
        test: /\.(scss|css)$/,
        use: [
          process.env.NODE_ENV === "production"
            ? MiniCssExtractPlugin.loader // 프로덕션 환경
            : "style-loader", // 개발 환경
          "css-loader",
          "sass-loader"
        ]
      },
      {
        test: /\.(png|jpg|svg|gif)$/,
        loader: "url-loader",
        options: {
          name: "[name].[ext]?[hash]",
          limit: 10000 // 10Kb
        }
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader" // 바벨 로더를 추가한다
      }
    ]
  },
  plugins: [
    new webpack.BannerPlugin({
      banner: `빌드 날짜: ${new Date().toLocaleString()}`
    }),
    new webpack.DefinePlugin({}),
    new HtmlWebpackPlugin({
      template: "./src/index.html",
      templateParameters: {
        env: process.env.NODE_ENV === "development" ? "(개발용)" : ""
      },
      minify:
        process.env.NODE_ENV === "production"
          ? {
              collapseWhitespace: true, // 빈칸 제거
              removeComments: true // 주석 제거
            }
          : false,
      hash: process.env.NODE_ENV === "production"
    }),
    new CleanWebpackPlugin(),
    ...(process.env.NODE_ENV === "production"
      ? [new MiniCssExtractPlugin({ filename: `[name].css` })]
      : [])
  ]
  // TODO: 여기에 최적화 설정을 구성하세요 
};
<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>검색<%= env %></title>
  </head>
  <body>
    <div id="app">
      <header>
        <h2 class="container">검색</h2>
      </header>
  
      <div class="container">
        <form class="FormView">
          <input type="text" placeholder="검색어를 입력하세요" autofocus>
          <button type="reset" class="btn-reset"></button>
        </form>
  
        <div class="content">
          <div id="tabs"></div>
          <div id="search-keyword"></div>
          <div id="search-history"></div>
          <div id="search-result"></div>
        </div>
      </div>
    </div>

    <!-- TODO: 외부 라이브러리 axios를 로딩할수 있도록 웹팩에서 파일을 복사하세요 -->
    <script type="text/javascript" src="axios.min.js"></script>
  </body>
</html>

server/index.js

const express = require("express");
const morgan = require("morgan");
const path = require("path");

const app = express();

app.use(morgan("dev"));

app.use(express.static(path.join(__dirname, "../dist")));

const port = process.env.PORT || 8081;
const keywords = [
  { keyword: "이탈리아" },
  { keyword: "세프의요리" },
  { keyword: "제철" },
  { keyword: "홈파티" }
];
const search = [
  {
    id: 1,
    name: "[버거] 치즈버거처럼 생긴 새우버거",
    image:
      "https://images.unsplash.com/photo-1547584370-2cc98b8b8dc8?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60"
  },
  {
    id: 2,
    name: "[샐러드] 맛있는 색깔의 콥셀러드",
    image:
      "https://images.unsplash.com/photo-1512621776951-a57141f2eefd?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60"
  },
  {
    id: 3,
    name: "[피자] 썸네일 주소가 잘못된 상품",
    image: "http://foo.bar/image.jpg"
  }
];
let history = [
  { keyword: "검색기록2", date: "12.03" },
  { keyword: "검색기록1", date: "12.02" },
  { keyword: "검색기록0", date: "12.01" }
];

app.get("/api/keywords", (req, res) => {
  // res.header("Access-Control-Allow-Origin", "*");
  res.json(keywords);
});

app.get("/api/history", (req, res) => {
  res.json(history);
});

app.post("/api/history", (req, res) => {
  keyword = (req.query.keyword || "").trim();
  if (!keyword) return;

  history.filter(item => item.keyword !== keyword);

  history = [
    { keyword, date: "12.31" },
    ...history.filter(item => item.keyword !== keyword)
  ];

  res.json(history);
});

app.delete("/api/history", (req, res) => {
  const keyword = (req.query.keyword || "").trim();

  history = history.filter(item => item.keyword !== keyword);
  res.json(history);
});

app.get("/api/search", (req, res) => {
  res.json(search);
});

// app.get("*", (req, res) => {
//   res.sendFile(path.join(__dirname, "../dist/index.html"));
// });

app.listen(port, () => {
  console.log(`서버가 구동되었습니다. localhost:${port}`);
});
app.use(express.static(path.join(__dirname, "../dist")));

위 코드를 간단히 설명드리자면 ../dist 폴더, 즉 우리가 웹팩으로 빌드할 때 결과물이 담기는 폴더를 정적 파일로 제공하는 코드이다.
그래서 이전에는 API만 제공했던 서버였는데 이제는 빌드한 프론트엔드 코드를 정적 파일로 제공하는 웹 서버 역할도 한다.

npm run build

dist 폴더에 정적 결과물이 내보내진다.
해당 결과물을 server 폴더에서 서버로 돌려보자.

npm start

지금까지는 8080 포트에서 웹팩 개발서버로 접속해서 확인했는데, 이제는 프론트엔드 코드를 웹팩에다 빌드하고 그 결과물을 노드서버가 제공해준다.
그래서 8081로 접속해서 확인을 했고,

axios는 우리가 해결해야될 문제이다.
아직은 없기 때문에 404 에러로 응답된거고 나머지 main.js, main.css는 제대로 요청 후 응답되었다.
이미지도 제대로 로드되었다.


내 풀이

아래 코드들 추가했다.

// webpack.config.js
const mode = process.env.NODE_ENV || "development";
const TerserPlugin = require("terser-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");

module.exports = {
  mode,
  plugins: [
    new CopyPlugin({
      patterns: [
        {
          from: "./node_modules/axios/dist/axios.min.js",
          to: "./axios.min.js"
        }
      ]
    })
  ],
  optimization: {
    minimizer:
      mode === "production"
        ? [
            new OptimizeCSSAssetsPlugin(),
            new TerserPlugin({
              terserOptions: {
                compress: {
                  drop_console: true // 콘솔 로그를 제거한다.
                }
              }
            })
          ]
        : []
  },
  externals: {
    axios: "axios"
  }
};

externals 설정을 안해줬구나.
이거 빼먹지 말자~!
다시 수정~!!!

위와 같이 모듈 "axios"externals에 설정하고 전역 변수로 axios라고 설정해준다.
그럼 axios는 빌드할 때 빠질 것이다.
대신 이 때문에 CopyWebpackPlugin을 쓰는 것이다. CopyWebpackPlugin은 써놓고 externals는 생각을 못했다. 주의하자.

ls -lh dist

빌드 전에 dist 폴더의 용량을 보자.

그리고 dist/index.html 파일을 확인해보면 axios 모듈을 먼저 로드 후에 main.js 파일을 로드하고 있는 것을 확인할 수 있다.