Webpack4 설정

React 프로젝트를 시작할때 Webpack을 설정하는게 쉽지 않다. 그래서 한번 기본적인것들을 정리해보려한다. 그래야 다음에 새로 시작할때 덜 헤맬것 같다.

목차

  1. webpack.config.js
  2. entry
  3. output
  4. module( + loader)
  5. plugin
  6. devServer
  7. devtool
  8. Babel 설정

webpack.config.js

Zero configuration

Webpack4에와서 webpack.config.js파일은 더이상 필수가 아니다. Create-React-App처럼 별다른 configuration없이 webpack을 사용할 수 있다. 하지만 나는 webpack을 내 입맛대로 사용하고 싶기 때문에 webpack.config.js를 사용한다.

용도

webpack.config.js 파일은 이름에서 알 수 있듯이, webpack이 여러파일들을 합쳐서 하나의 bundle.js파일로 만드는 과정에서 내가 여러가지 설정들을 알려주는 용도로 쓰인다. webpack은 일을 진행하면서 저 파일을 계속 참고한다.

예시

// webpack.config.js example

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const join = require('path').join;

module.exports = {
  name: 'user',
  entry: join(__dirname, '/index.js'),
  devtool: 'source-map',
  output: {
    filename: "main.js",
    path: join(__dirname, '../../../public/user/'),
    publicPath: '/user'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },
      {
        test: /\.(scss|css)$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              minimize: {
                safe: true
              }
            }
          },
          {
            loader: 'postcss-loader',
            options: {
                plugins: () => [require('autoprefixer')({
                    'browsers': ['> 10%', 'last 2 versions']
                })],
            }
          },
          {
            loader: 'sass-loader',
            options: {}
          }
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "style.css",
      path: join(__dirname, '../../../public/user/'),
      chunkFilename: "[id].css"
    })
  ],
  devServer: {
    proxy: {
      '/': {
        target: 'http://localhost:8080',
        changeOrigin: true
      },
      '/uploads': {
        target: 'http://localhost:8080',
        pathRewrite: {'^/uploads' : '/admin/uploads/'},
        changeOrigin: true
      },
      '/socket.io/': {
        target: 'http://localhost:8080'
      }
    },
    historyApiFallback: true,
  }
};

내가 만들기는 했지만, 구글링 안하고 만들라고 하면 못할것같다. 그만큼 복잡해 보인다. 하지만 하는일이 많으니 어쩔 수 없이 설정도 길어지는건 당연한것 같다.

Entry

webpack이 어디서부터 bundling을 시작해야 할지 명시한다. String / Array / Object를 쓸 수 있다.

string

entry포인트가 하나일때 사용된다. 주로 String을 많이 쓴다.

module.exports = {
  entry: join(__dirname, '/index.js')
}

array

아무 종속성이 없는 다른 js파일을 같이 bundling할때 사용된다.

module.exports = {
  entry : [join(__dirname, '/index.js'), '../libraris/easy-touch.js', '../libraris/google-addon.js']
}

object

Page가 여러개라서 bundle.js파일도 각 페이지에 맞게 여러개 만들때 필요하다. Entry를 제외한 다른 설정들(loader, plugin)은 공유할 수 있기 때문에 유용하다(각 페이지별로 webpack.config.js파일을 따로 만들지 않아도 된다).

from : https://www.youtube.com/watch?v=PcPzKMZzyqc

Object같은 경우는 Webpack Documentation관련 Youtube 영상 을 보면 더 좋다(나도 잘 모르기 때문 !).

Output

output: {
  filename: "main.js",
  path: join(__dirname, '../../../public/user/'),
  publicPath: '/user'
}

filename

build 후에 만들어지는 js파일의 이름을 정한다.

path

main.js파일이 저장될 directory를 설정한다. absolute path를 적어줘야한다. 보통 join함수를 써서 absolute path를 표현한다.

publicPath

(이부분은 내용이 많아서 따로 작성하였습니다. 이곳을 참고해 주시기 바랍니다.)

Module

module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: "babel-loader" // 알아서 babelrc.js 파일을 참고한다
      }
    },
    {
      test: /\.(scss|css)$/,
      use: [
        MiniCssExtractPlugin.loader,
        {
          loader: "css-loader",
          options: {
            minimize: {
              safe: true
            }
          }
        },
        {
          loader: 'postcss-loader',
          options: {
              plugins: () => [require('autoprefixer')({
                  'browsers': ['> 10%', 'last 2 versions']
              })],
          }
        },
        {
          loader: 'sass-loader',
          options: {}
        }
      ]
    }
  ]
}

Mudule 속성은 loader와 loader를 적용시키는 rule을 포함하고 있습니다.

rules

loader와 그 loader를 어느 파일에 적용시킬지에 관한 규칙이 들어있습니다.

test: /\.js$/, // .js파일을 읽을때는
exclude: /node_modules/, // node_modules폴더 안에있는 파일들은 제외하고
use: {
  loader: "babel-loader" // babel-loader를 사용하라!
}

위를 한글말로 표현하면, node_modules폴더안에 있는 파일들을 제외한 .js 파일들을 읽을때는 babel-loader를 사용하라! 라는 말이 된다. 참고로 test부분에는 정규표현식을 사용해야한다. 아래는 쪼금 더 복잡한 구성의 예시이다.

test: /\.(scss|css)$/, // scss혹은 css파일을 읽을때
use: [
  MiniCssExtractPlugin.loader, // css파일 분리
  {
    loader: "css-loader", // css-loader를 사용하는데
    options: {
      minimize: true // css 파일을 읽고나서 minimize해라
    }
  },
  {
    loader: 'postcss-loader', // postcss-loader를 사용해라
    options: {
        plugins: () => [require('autoprefixer')], // autoprefixer 플러그인을 사용해서 css속성에 브라우저별 prefixer를 붙여라
    }
  },
  {
    loader: 'sass-loader' // sass-loader를 사용해서 sass파일을 읽고 css로 compile해라
  }
]

MiniCssExtractPlugin.loader

원래 style-loader & css-loader를 사용하면 css는 bundle.js파일 안에 들어가게 된다. 그런데 css파일이 방대해지면, css파일을 따로 분리하는게 좋다. 이 loader를 사용하면 Webpack이 읽은 css파일들을 한대 모아서 (MiniCssExtractPlugin를 사용해) 하나의 파일로 만들어준다.

css-loader

Webpack은 기본적으로 js파일만 읽을 줄 아는데, css-loader를 사용하면 css파일도 잘 읽는다. 읽어서 뭐하냐구? => bundle.js파일 안에 포함시킨다. css문법검사는 안하는걸로 알고있다.

postcss-loader

postcss는 소프트웨어이다. 이 소프트웨어에 사용할 수 있는 다양한 플러그인이 있다. 그중에 하나가 autoprefixer인데, 이 플러그인을 사용하면 -webkit- 혹은 -ms-같은 것들을 내가 일일이 css에 적어줄 필요가 없게된다.

.box {
  border-radius: 10px;
}

// postcss의 autoprefixer플러그인을 사용하면 아래와 같이 변환된다.

.box {
  -webkit-border-radius: 10px;
  -moz-border-radius: 10px;
}

postcss도 따로 config파일을 만들어 줘도 되기는 하지만, 꼴랑 플러그인 하나 쓰기 때문에 webpack.config.js에 직접 (아래와같이) 적어준것이다.

{
    loader: 'postcss-loader', // postcss-loader를 사용해라
    options: {
        plugins: () => [require('autoprefixer')], // autoprefixer 플러그인을 사용해서 css속성에 브라우저별 prefixer를 붙여라
    }
},

sass-loader

.scss파일을 만나면 sass-loader를 통해서 node-sass프로그램이 scss -> css로 번역해준다.

Loader의 우선순위

use: [
  {
    loader: 'sass-loader',
  },
  {
    loader: "css-loader",
    options: {
      minimize: {
        safe: true
      }
    }
  },
]

위처럼 쓰면 css-loader -> sass-loader순으로 실행되기 때문에 아래와 같은 에러가 생긴다.

ERROR in ./scss/style.scss (./node_modules/sass-loader/lib/loader.js??ref–5-1!./node_modules/css-loader??ref–5-2!./node_modules/postcss-loader/src??ref–5-3!./scss/style.scss)
Module build failed (from ./node_modules/sass-loader/lib/loader.js):
@charset “UTF-8”; ^ Invalid CSS after “e”: expected 1 selector or at-rule, was “exports = module.ex” in /Users/mac88/Desktop/Projects/react-discovery-v2/src/admin/admin-client/scss/style.scss (line 1, column 1)

다시말하자면, sass코드를 css로 변환하기 전에 읽어버리면 webpack은 “이게 무슨 문법이지??” 하는 에러가 생긴다. 그래서 sass -> css 변환하고 css-loader를 통해서 읽어야 정상적으로 작동이된다.

webpack은 아래에서 위로 loader를 실행시키기 때문에 아래와 같이 작성해줘야한다.

use: [
  {
    loader: "css-loader", // 두번째 실행
    options: {
      minimize: {
        safe: true
      }
    }
  },
  {
    loader: 'sass-loader', // 첫번째 실행
  },
]

Plugins

loader가 못하는 일들을 해준다. loader처럼 특정 파일을 만나서 실행되는건 아니고, plugin을 만든 제작자가 원하는 시점에, 어떤 기능이 실행된다. 자주 사용되는 plugin list는 여기서 확인할 수 있다.

devServer

웹팩을 더~아주~ 편하게 사용하도록 도와준다. 예를들어서 우리가 js파일 하나를 수정하고 저장하면 알아서 새롭게 bundling을 하고 브라우저를 자동으로 새로고침 해준다. webpack이 개발서버(dev server)를 만들어주기 때문에 가능한것이다.

설치방법

npm install webpack-dev-server --save-dev

옵션

devServer: {
  contentBase: path.join(__dirname, 'dist'),
  publicPath: path.join(__dirname, 'bundles'),
  compress: true,
  port: 9000
}

contentBase

어느 폴더가 개발서버(dev server)의 하드디스크가 될지를 정한다. 예를들어서, path.join(__dirname, 'dist')라고 적어주면 아래와 같은 일이 생긴다.

  1. webpack-dev-server를 킨다
  2. 브라우저에서 static 파일(js, css, image)을 서버에게 달라고 요청한다
  3. webpack-dev-server는 dist폴더안에서 찾는다

여기서 잠깐! static 파일이라는것은 webpack이 생성한 bundle.js가 아닌! bundle.css가 아닌! 파일을 의미한다. 말 그대로 언제나 그대로인 파일! static 파일이다.

publicPath

webpack이 bundling한 파일들이 있는 위치를 정한다. 근데 이거는 실제 내 폴더를 말하는게 아니라, 그냥 위치를 지정해주는것이다. 이렇게 지정하면 아래와 같은 일이 생긴다.

  1. webpack-dev-server를 켠다
  2. js파일을 약간 수정한다
  3. webpack이 자동으로 bundling을 하고, 이 결과물을 RAM에 저장시키며 이 위치를 publicPath로 정한다. 마치 메모리 주소값으로 publicPath를 사용하는것과 같다.
  4. 브라우저는 그 bundling된 파일을 서버에게 요청하고, 서버는 RAM에 있는 방금 막 bundling한 파일을 돌려준다.

이 publicPath는 output의 publicPath와 동일하게 맞춰주는게 좋다! 고한다.

compress

개발서버가 브라우저한테 어떤 파일을 돌려줄때 그거를 압축해서 돌려줄지 말지를 결정한다. 자세한 내용은 여기를 참고.

port

개발서버거 요청을 받을 port를 정해준다. 보통 8888을 많이 사용하는것 같다.

proxy

개발서버에는 DB가 없다. 그래서 보통은 proxy를 사용해서 개발서버에 요청을 보내면 개발서버가 다른 서버로 요청을 전달하는 방식을 사용한다.

devServer: {
  proxy: {
    '/': {
      target: 'http://localhost:8080',
      changeOrigin: true
    },
    '/uploads': {
      target: 'http://localhost:8080',
      pathRewrite: {'^/uploads' : '/admin/uploads/'},
      changeOrigin: true
    },
  },
  historyApiFallback: true,
}

/로 요청이 오면 그거를 8080포트로 휙 넘겨버린다.

/uploads로 요청이 오면, /uploads라는 string을 /admin/uploads/로 교체한뒤에 8080으로 넘겨버린다.

devtool

source-map을 어떤 스타일로 뿌려줄지 결정한다.

source-map?

브라우저는 최종적으로 우리가 webpack으로 bundling한 js파일을 본다. 그리고 내 코드에 어떤 문제가 생겼을때 크롬의 콘솔창에는 시뻘건 js코드 에러메세지와 함께 어떤 파일의 몇번째줄에서 해당 에러가 발생한것인지를 알려준다.

문제는 bundling한 js파일의 몇번째줄에서 해당 에러가 발생했는지 우리가 알아봤자 별 소용이 없다는것이다. 우리가 작업한 파일은 box.js(예를들어)이지 bundle.js가 아니기 때문이다.

이때! source-map을 설정해주면, 브라우저는 bundle.js파일을 읽었음에도 불구하고, 실제 우리가 작업한 box.js의 몇번째줄에서 에러가 발생했는지 알려준다. webpack이 bundle.js와 box.js 소스코드를 mapping해놨기 때문이다. 그래서 이름이 source-map이다.

source-map의 스타일이 다양한데, 아래를 참고해서 적절한거를 찾아 쓰면된다.

참고로 나는 다른건 어려워보여서 가장 기본적인 source-map을 쓴다.

entry: ... ,
devtool: 'source-map', // 위치는 상관없다
output: ...

Babel설정

작성중…

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다

Up Next:

react-native run-android가 안먹힐때

react-native run-android가 안먹힐때