Full Stack Web Developer.
Syaku (샤쿠)

Java, JS (ES6+), Spring, Spring security, jQuery, Reactjs, Bootstrap.

        

07-12 09:23


Spring Boot 2 보기 Front-end 보기 DevOps 보기 Spring 3 보기 Spring Security 3 보기

프론트엔드 개발을 위한 웹팩 기본 설정 가이드 #1 : frontend webpack configuration

프론트엔드 개발을 위한 웹팩 기본 설정 가이드 #1 : frontend webpack configuration

[경고] 이글은 즉흥적으로 작성된 내용이며 제가 개발하고 배우면서 직접 이해한 내용을 토대로 작성된 것이라 아주 개인적일 수 있습니다. 잘못된 설명이나 표현이 적절하지 못할 수 있습니다. 본 글은 웹팩을 이해하기 위한 최소한의 글이라는 것을 먼저 알려드리며 직접 꼭 웹팩 문서를 보고 공부하시기 바랍니다.

웹팩을 이용하여 프론트엔드 웹앱을 어떻게 빌드하고 구동하는 지 설명하였다.

웹팩은 번들러이다. 번들러라고 하면 이해가 쉽지 않은 용어이다. 풀어서 쉽게 설명한다면...

자바스크립트는 다양한 자원(image, css, sass, html)을 사용한다. 일반적으로는 html은 다양한 자원을 사용한다가 맞는 말이다. 하지만 웹팩의 기준점과 시작은 html이 아니라 자바스크립트이기 때문이다. (이것을 웹팩에서는 entry 라고 한다.)

일반적인 방법은 html 안에 모든것을 설계하였다. 어떤 스타일시트를 사용할지 어떤 자바스크립트를 사용할지 어떤 이미지를 사용할지를 말이다. 하지만 웹팩은 이것들을 자바스크립트를 이용하여 하나의 객체로 인식하게 하여 빌드하게 한다.

[참고] 나는 모듈러를 사용한 경험이 없고 jQuery 에서 바로 웹팩과 디자인패턴(리액트, 앵귤러js)의 기술를 배웠다 그리고 es6 기반으로 코딩하였기에 모듈러에 대한 설명이 부족하거나 자연스럽지 않을 수 있다.

그래서 웹팩은 자원들을 객체로 받아들이고 개발자가 설정한 구성대로 최적화(자원관리)를 진행한다. 이로서 보다 최적화된 자원을 구성할 수 있다. 똑같은 이미지를 2개이상 가지고 있을 필요가 없다. 웹팩이 알아서 처리한다... 똑같은 스타일시트를 가질 필요없다. 웹팩이 최적화한다... 똑같은 자바스크립트 코드를 사용하지 않는 다 이또한 웹팩이 최적화한다.

아주 완벽한 시스템이 아닐 수 없다. 과연 어떻게 이런게 가능한 것일까??

웹팩은 모듈러를 이용하여 자원을 인식하고 이것을 분석하여 최적화한다. 이말은 반대로 모듈러를 사용하지 않거나 올바르게 사용하지 않는 다면 문제가 될 수 있다와 같다.

모듈러란 자바스크립트 코드를 모듈화하여 사용할 수 있게 해준다. 나도 이부분은 대충 이해만 하지 자세한건 모르기 때문에 직접 찾아보도록한다.

흔히 사용되는 모듈러는 CommonJS와 AMD가 있다. AMD라하면 cpu 회사인데 헷갈린다. 너무 고유명사화된 명칭은 후발주자가 중복으로 사용하지 않았으면 좋겠다는 개인적인 생각이든다. 이질감이 들어서인듯?? 여튼 모듈러는 자바스크립트를 세분화하고 필요에 따라 호출(import? include?)할 수 있다.

이쯤되면 웹팩이 무엇때문에 연결고리를 찾는 지 감이 올것이다. 바로 import 키워드이다.

난 es6 코드를 사용하니 import 를 사용하겠다.

import React from 'react';

위와 같이 코드하면 node_modules 에서 react 폴더를 찾고 React 라는 객체를 만든다.

import React from './react';

위와 같이 코드하면 현재 소스코드에서 react/index.js 나 react.js 찾을 것이다.

index.js와 *.js 는 생략해도 된다.

css 나 image 도 위와 같은 방법으로 import 할 수 있다.

import style from './css/style.css';
import photo from './image/photo.png';

빌드되면 설정에 따라 파일이 생성된다. 일반적으로 file-loader 플러그인을 사용한다. 설명은 아래에서 하도록하겠다.

이제부터 웹팩을 어떻게 사용하지는 알아보자.

웹팩은 nodejs로 개발되고 동작한다. 당연히 패키지관리자는 npm 을 사용한다. 

우선 웹팩을 사용하기 위한 모든 라이브러리?를 설치한다.

$ npm install webpack webpack-dev-server

webpack-dev-server 는 개발시에 아주 유용한 테스트 서버이다.

이제 웹팩 설정파일을 만들자 기본 파일명은 webpack.config.js 이다. 프로젝트나 애플리케이션 폴더의 최상위(root) 경로에 만들면 된다.

< / > webpack.config.js

module.exports = {

    ... code ...
};

이렇게 시작한다. code 에 설정을 작성한다.

const path = require('path'); // node.js path 사용할 수 있다.

module.exports = {

    ... code ...
};

외부 라이브러리나 자원을 사용할 수 있다.

const path = require('path');

module.exports = {
  entry: './app/index.js', // index.js 를 시작으로 빌드된다.
  
  output: {
    filename: 'bundle.js', // 빌드가 완료되면 filename 으로 파일을 생성한다.
    path: path.resolve(__dirname, 'dist'), // 경로에 filename 이 생성된다.
    publicPath: ''
  }
};

publicPath 는 서비스가 시작되는 루트 경로를 제외한 상대경로(서브경로)를 설정하는 데 간혹 무시되는 경우가 많다. 하지만 꽤 중요한 설정이다. 이 설정으로 인해 자원을 찾지 못하는 경우가 많다. 즉 이 설정은 자원이 웹팩에 의해 빌드될때 사용되는 경로이다. 설명이 좀 난해한데 소스를 보면 쉽게 이해할 수 있다. 기본값은 현재 위치 경로를 사용한다.

HtmlWebpackPlugin 은 html 파일안에 빌드된 자원들을 자동으로 삽입해주는 플러그인인데 자원이 삽입될때 아래와 같이 처리된다.

budler.js -> budler.js

publicPath: '' -> <srcipt src="budler.js">

publicPath: '/react/' -> <srcipt src="/reacr/budler.js">

publicPath: './react/' -> <srcipt src="./reacr/budler.js">

SPA (싱글 페이지 애플리케이션) 인 경우에는 엔트리가 한개일 수 있다. 하지만 여러개의 SPA 혹은 App가 있다면 엔트리를 여러개로 사용할 수 있다. 필요에 따란 자바스크립트 소스를 세분화하는 것도 좋은 방법일 수 있다.

module.exports = {
    entry: {
        'appA': 'appA',
        'appB': 'appB'      
    },
    
    output: {
       filename: '[name].bundle.js', // appA.bundle.js, appB.bundle.js 으로 생성된다.
    
    ... skip ...
};

윈도우에서 경로를 인식못할 경우가 있다. 그래서 path 를 이용하여 상대경로 구해주는 것이 좋다. path.resolve(__dirname, 'dist')

엔트리를 자동으로 구할 수 있다. 이건 직접 프로그램을 코드하면 된다. 나중을 위해 이부분을 다시 표현하기 위해 자동엔트리모음이라고 명명한다.

/src
    /appA
        index.js
    /appB
        index.js
    /appC
        index.js
    ...
    ...
    ...

위와 같을 경우 node.js 를 사용하여 모든 경로의 /src/*/index.js 패턴을 가진 경로를 엔트리로 만들 수 있다. glob 를 사용하기 때문에 설치해준다.

$ npm install -D glob
const glob = require('glob')
let entries = {}
let dirnames = {} // 나중에 설정에서 사용되는 변수이다.
const entryTarget = glob.sync('./src/*/index.js')

for (var i in entryTarget) {
  const entry = entryTarget[i]
  const dirname = path.dirname(entry)

  const name = dirname.replace(/\.\/src\/(.*)/, '$1')
  console.log(`entry -> ${name}`)
  entries[name] = entry
  dirnames[name] = dirname.replace(/\.\/src\/(.*)/, '$1')
}

module.exports = {
    entry: entries
    ... skip ...

이젠 image 와 css 를 자바스크립트에서 사용하기 위해 설정을 해준다. 그전에 필요한 로더를 설치해야한다. 특정 파일을 읽기 위해 웹팩은 로더를 설치하고 모듈에 설정해줘야 한다.

$ npm install -D style-loader css-loader file-loader extract-text-webpack-plugin

그리고 css 를 자바스크립트에 인라인 되지 않고 파일로 분리할 수 있게 extract-text-webpack-plugin함께 설치하였다.

file-loader 는 이미지 파일을 사용하기 위한 것이다. 이미지외 html도 사용할 수 있다.

https://github.com/webpack-contrib/file-loader

const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
  module: {
    rules: [
      // css 처리를 위한 설정.
      {
        test: /\.css$/, // import 에서 *.css 를 찾는 다.
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: "css-loader"
        })
      },
      
      // 이미지 처리를 위한 설정.
      {
        // import 에서 png, jpg, gif 확장자를 가진 파일을 찾는 다.
        test: /\.(png|jpg|gif)$/,
        // name 설정으로 파일명을 생성한다.
        // publicPath 경로를 소스에 사용한다. <img src="/images/[hash].[ext]" />
        // outputPath 경로에 파일 이미지를 복사 및 생성한다. 
        use: 'file-loader?name=[hash].[ext]&publicPath=/images/&outputPath=images/'
      },
    ]
  },
  plugins: [
    new ExtractTextPlugin({
      filename: './[name].css' // 혹은 [name].css 사용할 수 있다. name 엔트리명을 따른다.
    })
  ]
}

파일로더는 임포트된 파일을 원하는 설정에 따라 파일을 생성해준다. 다른 경로의 파일이더라도 똑같은 파일명인 경우 중복으로 생성되기 때문에 꼭 hash를 사용해야 한다. 가독성을 위해 파일이름도 노출하고 싶다면 [name]-[hash].[ext] 하면 된다.

또 hash 를 사용하는 이유는 기존에 캐싱된 자원이 있는 경우 새로운 데이터라고 해도 파일명이 같으면 다시 로드되지 않는 다. 그래서 해쉬를 이용하면 변경된 파일만 hash 코드를 새로 갱신되기 때문에 캐싱되지 않고 새로 로드될 수 있게 된다. 이 캐시 문제는 모든 자원이 동일하다. 그래서 자바스크립트도 hash를 이용하여 이름을 갱신해야 한다.


이제 테스트 서버를 구동하기 위해 webpack-dev-server 를 설정한다.

docs : https://webpack.js.org/configuration/dev-server/#devserver

module.exports = {
  
  ... skip ...
  
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ],
  
  devServer: {
    inline: true, // 핫리로드를 위한 스크립트를 삽입한다.
    hot: true, // 소스가 변경되면 자동으로 빌드되어 반영된다.
    port: 8008, // 서버 포트
    host: '0.0.0.0', // localhost 를 사용하면 외부에서 접근할 수 없다.
    contentBase: './dist' // 서비스 웹 루트 경로
  }

웹팩이 빌드되면 엔트리에 맞는 자원들을 html에 자동으로 삽입되게 하고 소스가 변경되면 자동으로 페이지를 새로고침할 수 있는 설정이 있다. 이 기능을 개발시에 아주 유용하므로 필수로 사용해야 한다. 그렇기때문에 이를 지원하는 플러그인을 사용한다. 이 플러그인은 이뿐만아니라 템플릿을 이용하여 html을 만들수있는 기능도 있다.

https://github.com/jantimon/html-webpack-plugin

$ npm install -D html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  
  ... skip ...
  
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      filename: './index.html', // 빌드후 만들어지는 파일이명
      template: './src/index.html' // 빌드시 사용되는 템플릿
    })

  ],
  
  devServer: {
    ... skip ...
  }

엔트리가 여러개인 경우 html 페이지도 동일하게 생성되어야 한다. 그래서 HtmlWebpackPlugin 여러개를 설정해줘야 하는 데 이때도 직접 코드를 작성하면 된다.

아래 코드는 일부 변수가 자동엔트리모음 에서 생성된 것을 사용하고 있다.

let htmlWebpackPlugins = []

// 자동엔트리모음 의 entries 변수를 사용한다.
for (let key in entries) {
  htmlWebpackPlugins.push(htmlWebpackPlugins,
    new HtmlWebpackPlugin({
      // chunks 옵션으로 원하는 자바스크립트만 삽입한다.
      chunks: [ key ],
      filename: './' + key + '.html',  // 엔트리명으로 html 파일명을 만든다.
      
      // 엔트리마다 html 템플릿을 가지고 있게 할수도 있다. 자동엔트리모음 의 dirnames 변수를 사용한다.
      template: './src/' + dirnames[key] + '/index.html'
      
      // 그렇지 않고 한개의 템플릿을 사용할 경우는 다음과 같다.
      template: './src/index.html'
    })
  )
}


module.exports = {
  
  ... skip ...
  
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    htmlWebpackPlugins // 여기에 설정 배열을 넣는 다.
  ],
  
  ... skip ...

기본적인 설정은 이게 끝이다. 이정도면 웹팩을 이용하여 프론트엔드를 애플리케이션을 개발할 수 있다.

빌드를 하려면 $ webpack 테스트 서버를 구동하려면 $ webpack-dev-server 명령어를 command(terminal) 에서 입력하면 된다.

만약 명령어가 없다고 한다면 글로벌로 설치해야 한다.

$ npm install -g webpack webpack-dev-server

글로벌 설치가 싫다면 npm(package.json) 에 scripts 에 cli를 추가하면 된다.

< / > package.json


"scripts": {
    "dev": "webpack --colors --progress -d",
    "prod": "webpack --colors --progress -p",
    "serv": "webpack-dev-server",
    "serv2": "webpack-dev-server -p"
},

$ npm run [scripts]

webpack 개발자용 빌드와 운영용 빌드를 할 수 있다. 옵션 키워드는 각각 -d 와 -p 이다.

각 모드는 아래와 같은 옵션이 자동으로 사용된다.

-d => --debug --devtool source-map --output-pathinfo

-p => --optimize-minimize --optimize-occurrence-order


최종결과물 스크린샷

src -> dist 빌드됨.


다음 포스팅에는 자원을 최적화하고 react를 개발하기 위한 환경 설정에 대해 설명하겠다. react 는 기본적으로 babel 컴파일러를 사용한다.

위에서 사용된 소스는 Github 에서 완전한 소스를 확인할 수 있다.

https://github.com/syakuis/webpack-react/tree/session-1



posted syaku blog

Syaku Blog by Seok Kyun. Choi. 최석균.

http://syaku.tistory.com


댓글 남기기
◀ PREV 1234567 NEXT ▶