3월 04, 2017

Webpack2 에서 React HMR(Hot Module Replacement) 설정하기

Webpack2

얼마 전에 Webpack이 2버전을 올린지 꽤 되었죠?
오늘은 Webpack2 버전 부터 시작하는 저같은 분을 위해 간단한 세팅하는 포스팅을 올릴께요.

먼저 Webpack은, Javascript 모듈 번들러입니다. 프론트엔드 환경은 서버측과 달리 기본적으로 모듈 시스템이 제공되지 않기 때문에 Webpack이 모든 asset(.html, .js, .css, 이미지, … 등)을 하나 이상의 bundle.js 파일로 통합하고, 그 과정에서 import, require 등의 구문을 읽어들여서 의존성 관리를 Webpack이 알아서 해주는 것입니다.

Webpack 을 이해하는데 있어서 기본적인 4가지 Concept 은 여기에 잘 나와있지만, 간단하게 정리하고 넘어가겠습니다.

Entry

Entry는 웹팩이 Application 을 읽어들일 첫번째 시작점입니다. ‘여기서부터 시작해서, 여기에 추가된 의존성을 따라서 번들링해라’라고 지정해주는 것입니다.

Output

Output 은 번들링이 끝난 후 결과물을 어느 경로에 놓고, 무슨 파일명으로 저장할 지 등을 설정하는 옵션입니다.

Loaders

Webpack은 모든 파일(.css, .html, .scss, .jpg, …등)을 모듈로 취급합니다. Loaders는 이러한 파일들을 의존성 그래프에 추가할때 Webpack이 알아들을 수 있는 Javascript module 로 모조리 변형시킵니다.

Loaders 에서는 두 가지 개념이 중요합니다.
test: 어떤 특정 파일이 로더에 의해 변형되어야 하는지 설정
use: 여기에 명시한 로더를 사용해서 파일이 종속성 그래프에 추가될 수 있도록 변형합니다.
//e.g)
module: {
    rules: [
      {test: /\.(js|jsx)$/, use: 'babel-loader'}
    ]
  }
예를 들어 위 문장은 웹팩에게, “프로젝트 내에서 importrequire() 문 안의 ’js’나 ‘jsx’ 확장자 파일을 보면 번들에 추가하기 전에 babel-loader를 사용해서 변형시켜” 라고 말하는 것입니다.

Plugins

플러그인은 흔히 생각하는 확장기능이라고 생각하면 됩니다. 원하는 기능을 plugins array에 new 구문과 함께 추가하기만 하면 됩니다.

HMR(Hot Module Replacement)

React.js 프로젝트에서 Hot Module Replacement(HMR) 를 설정하려면, 먼저 webpack-dev-server에 대해 알아야 합니다.
webpack-dev-server는 코드 변경을 감지하고 자동으로 recompiling 하여 페이지를 refresh 시켜 줍니다.

더 나아가서, HMR은 앱이 실행중일 때 코드를 변경하였을 경우, 페이지 refresh 없이 해당 모듈을 교체, 추가, 삭제하는 편리한 기능입니다. 따라서 코드를 변경할 때마다 webpack을 실행해서 번들링하고, 앱을 구동하는 recompiling 과정을 생략할 수 있습니다.

방법에 대한 것은 위 링크에 너무 잘 나와있기 때문에, 저는 몇가지 주의사항만 적도록 하겠습니다.

  1. devServer의 contentBase를 output 의 path와 일치시키고, 해당 경로내에 번들(예를 들어, bundle.js)을 불러오는 index.html 파일하나를 꼭 두셔야 합니다.(그렇지 않으면 앱 페이지 대신 디렉토리 구조가 나오는 화면만 보시게 될거에요)
  2. 위 튜토리얼을 모두 따라했는데도 HMR이 아닌 전체 페이지 refresh가 계속 일어난다면, 코드에 에러가 있어서 그럴수도 있습니다. 예를 들어 정의되지 않은 함수를 사용했다거나…
  3. 위 튜토리얼을 그대로 따라하지 않고 프로젝트에 맞게 필요한 부분만 넣으셔도 됩니다.
  4. react-hot-loader는 HMR 기능에서 state 초기화를 막는 효과를 추가하는 것입니다.
  5. .babelrc 파일에서 {"modules": false} 를 설정해서 Webpack2의 내장 import 구문 해석과 충돌하지 않도록 해줘야 합니다.
마지막으로 제 프로젝트의 webpack.config.js.babelrc 코드를 올릴테니 참고하시기 바랍니다.

// webpack.config.js
const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: [
    './src/app/index.jsx',
    'webpack-dev-server/client?http://localhost:8080',
    'react-hot-loader/patch',
    'webpack/hot/only-dev-server',
    ],
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    publicPath: '/'
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: 'babel-loader',
      },
    ],
  },
  devtool: 'inline-source-map',
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    hot: true,
    publicPath: '/'
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin(),
  ]
};
//.babelrc
{
  "presets": [
    ["es2015", {"modules": false}],
    "stage-2",
    "react"
  ],
  "plugins": [
    "react-hot-loader/babel"
  ]
}