웹팩(Webpack)으로 환경 설정하기

2.1. 웹팩(Webpack)으로 환경 설정하기

웹팩 무엇인지는 앞서 1.3장 에서 설명드렸습니다. 이제 npm을 통해 필요한 라이브러리들을 설치하고, 웹팩 빌드환경 구성 후 로컬에 개발용 서버를 띄우기까지의 과정을 차근차근 설명해 드리도록 하겠습니다.

2.1.2 NPM 설치하기

우선 개발에 필요한 라이브러리들을 설치하기 위해서는 npm이 필요합니다. npm이란 무엇일까요?

npm(Node Package Manager) 은 말 그대로 Node.js 기반의 패키지 관리자 입니다. 개발에 필요한 라이브리러가 있다면 간단한 명령어를 통해 해당 라이브러리를 쉽게 설치할 수 있게 해주며, 이와 종속적으로 연관된 라이브러리들도 함께 설치해줍니다. 또한, 특정파일(package.json)에 필요한 라이브러리들을 기술해 놓으면 npm이 이를 읽어 해당 라이브러리를 일괄 설치해주는 역할도 합니다.

npm은 Node.js를 설치하면 함께 설치가 되며, Node.js는 공식홈페이지(https://nodejs.org/ko/)에서 LTS버전을 다운받아 설치하면 됩니다. 정상적으로 설치되었는지 확인하려면 콘솔에서 다음과 같은 명령어를 통해 확인 가능합니다.

$node --version
6.9.2
$npm --version
4.0.5

2.1.2 프로젝트 생성하기

npm이 설치되었다면 이제 프로젝트를 생성하고 필요한 라이브러리들을 다운받을 준비가 되었습니다. 우선 프로젝트를 생성해볼까요? 명령 프롬프트를 실행하고 새로운 프로젝트 폴더를 만들어봅시다.

mkdir ng-pizza
cd    ng-pizza

그리고 아래 파일들을 프로젝트의 루트 디렉토리에 생성합니다.

angular-pizza
 ├ config
 │  ├ helper.js
 │  ├ webpack.common.js
 │  └ webpack.dev.js
 │  └ webpack.prod.js
 ├ package.json
 ├ tsconfig.json
 └ webpack.config.js

이 파일들은 각각 뭘 의미하는 걸까요? 파일에 내용을 채워가며 그 역할들을 알아보도록 하겠습니다.

2.1.2.1 타입스크립트 컴파일 설정

앵귤러는 타입스크립트로 개발하게 되는데, 타입스크립트는 브라우저에서 직접 실행할 수 없기 때문에 반드시 자바스크립트로 변환(Transpiled)되어야만 합니다. tsc(TypeScript Compiler)를 통해 변환하는데, tsc가 동작할 때 읽어들이는 설정 파일이 tsconfig.json 입니다. 기본적으로 설정해야하는 값은 아래와 값습니다.

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": ["es2015", "dom"],
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true,
    "typeRoots": [
      "node_modules/@types/"
    ]
  },
  "compileOnSave": true,
  "exclude": [
    "node_modules/*",
    "**/*-aot.ts"
  ]
}

컴파일 후 타겟 버전(es5, es6 등으로 설정 가능), 소스맵 사용 여부, Any 타입 허용 여부 등을 설정하였으며, 이외에도 사용자의 기호에 따라 설정할 수 있는 값이 많이 있으며, 자세한 내용은 Typescript Project Configuration(https://www.typescriptlang.org/docs/handbook/compiler-options.html) 페이지를 참고하시기 바랍니다.

2.1.2.2 웹팩(Webpack) 설정

config/helpers.js

이제 본격적으로 웹팩 설정을 해보도록 하겠습니다. 웹팩은 빌드 과정에서 개발자가 생성한 파일들의 위치에 접근하여 읽어들여야 합니다. 이를 위해 개발자가 작성한 파일 경로를 명시해야되는데, 윈도우나 유닉스 등의 사용자 빌드 환경에 상관없이 파일 경로를 조합해주기 위해 node.js에서 제공해주는 Path라는 라이브러리를 이용하여 config/helpers.js 를 작성해 보도록 하겠습니다.

var path = require('path');
var _root = path.resolve(__dirname, '..');
function root(args) {
  args = Array.prototype.slice.call(arguments, 0);
  return path.join.apply(path, [_root].concat(args));
}
exports.root = root;

위 소스를 통해 프로젝트의 루트 디렉토리를 명시하기가 무척 간단해 질 것이며, helper.root('폴더명') 과 같은 간단한 문법을 통해 하위 폴더 및 파일의 경로를 만들어내기가 용이해집니다.

config/webpack.common.js

다음은 웹팩의 공통환경 설정을 위해 config/webpack.common.js 를 작성해보도록 하겠습니다.

var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var helpers = require('./helpers');

module.exports = {
  entry: {
    'polyfills': './src/polyfills.ts',
    'vendor': './src/vendor.ts',
    'app': './src/main.ts'
  },

  resolve: {
    extensions: ['', '.js', '.ts']
  },

  module: {
    loaders: [
      {
        test: /\.ts$/,
        loaders: ['awesome-typescript-loader', 'angular2-template-loader']
      },
      {
        test: /\.html$/,
        loader: 'html'
      },
      {
        test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
        loader: 'file?name=assets/[name].[hash].[ext]'
      },
      {
        test: /\.css$/,
        exclude: helpers.root('src', 'app'),
        loader: ExtractTextPlugin.extract('style', 'css?sourceMap')
      },
      {
        test: /\.css$/,
        include: helpers.root('src', 'app'),
        loader: 'raw'
      }
    ]
  },

  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: ['app', 'vendor', 'polyfills']
    }),

    new HtmlWebpackPlugin({
      template: 'src/index.html'
    })
  ]
};

우선 entry에 대해서 알아볼까요? entry는 웹팩이 번들링할 할 항목들을 명시 할 수 있는데, 위 설정에서는 3가지로 나눠 묶습니다.

  • polyfills - 대부분의 모던 브라우저 및 앵귤러 구동에 필요한 표준 폴리필(polyfill)들, 즉 개발자가 특정 기능이 지원되지 않는 브라우저에서도 동일한 기능을 지원하기 위한 플러그인들 이라고 이해하면 좋을 것 같습니다.
  • vendor - 앵귤러, 부트스트랩 등 앵귤러 구동에 필수적인 파일들
  • app - 사용자가 작성하는 어플리케이션 소스 코드들

다음에 등장하는 module은 다양한 리소스를 자바스크립트에서 바로 사용할 수 있는 형태로 로딩해주는 기능을 합니다. 사용방법은 test에서 정규표현식 형태로 로딩할 파일의 패턴을 정하고, 적용할 로더를 설정합니다.

마지막으로 plugins 에서는 두개의 플러그인을 추가해줍니다.

  • CommonsChunkPlugin - 사용자가 작성하는 코드와 밴더 코드, 폴리필 코드를 나눠서 묶어주는 역할을 합니다. name에 명시한 대로 app, vendor, polyfills 로 묶습니다.
  • HtmlWebpackPlugin - 웹팩은 수많은 js 및 css를 생성하게 되는데, 이를 일일이 index.html에 넣어주기엔 너무 번거롭고, 각종 휴먼 애러가 발생할 수가 있습니다. HtmlWebpackPlugin은 스크립터와 링크를 index.html에 자동으로 삽입해주는 역할을 합니다.

이제 config/webpack.dev.js을 작성해보도록 하겠습니다. 그런데 왜 파일명 중간에 dev가 들어가는 걸까요? 이유는 개발환경(development)에서의 빌드구성과 운영환경(production)에서의 빌드구성에 대한 구분을 주기 위해서 입니다.

config/webpack.dev.js
var webpackMerge = require('webpack-merge');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');

module.exports = webpackMerge(commonConfig, {
  devtool: 'cheap-module-eval-source-map',

  output: {
    path: helpers.root('dist'),
    publicPath: 'http://localhost:8090/',
    filename: '[name].js',
    chunkFilename: '[id].chunk.js'
  },

  plugins: [
    new ExtractTextPlugin('[name].css')
  ],

  devServer: {
    historyApiFallback: true,
    stats: 'minimal'
  }
});

먼저 등장하는 webpack-merge에 대해서 알아보겠습니다. webpack-merge의 역할은 array나 object를 합치는 역할을 합니다. 즉, 앞서 작성한 webpack.common.js와 추가 정의할 webpack.dev.js의 내용을 합쳐줍니다. 새로 정의하는 object에 첫번째 등장하는 devtool부터 알아볼까요?

devtool은 웹 브라우저의 개발자 도구 연동과 관계가 있습니다. 앞서 계속 설명해온 바와 같이 웹팩에서는 여러 소스들을 묶어버리기 때문에 사용자가 작성한 소스 그대로 개발자 도구에서 디버깅하기가 어렵습니다. 하지만 devtool을 설정하면 개발자 도구에서 사용자가 작성한 코드 그대로 디버깅이 가능해 더욱 효율적인 개발이 가능하다는 장점이 있습니다.

그 다음에 정의하는 output은 웹팩에 의해 빌드된 소스의 저장과 관련된 정보를 정의할 수 있습니다. path에서는 빌드된 소스의 저정경로 프로젝트 root 경로 아래의 dist로 지정합니다. publicPathindex.html에서 불러올 script나 link의 경로를 지정합니다. 그 외 filenamechunkFilename은 번들 파일의 이름을 정의합니다.

그 아래 등장하는 plugins에 정의하고 있는 ExtractTextPlugin는 우리가 소스에 작성하는 css를 외부의 .css로 추출해주는 역할을 합니다. 그리고 이는 index.html에 link에 명시되게 됩니다.

마지막으로 등장하는 devServer는 웹팩 서버가 기동되기 위한 설정을 하게 됩니다. 웹팩 서버 기동하는 방법은 이 장에 마지막에 등장할 package.json에서 설명하도록 하겠습니다.

config/webpack.prod.js

앞서 우리는 개발환경에서의 웹팩 설정을 살펴보았습니다. 그럼 운영환경에서는 어떻게 빌드해야 할까요? webpack.prod.js 를 작성해 보고 차이점을 알아보도록 하겠습니다.

var webpack = require('webpack');
var webpackMerge = require('webpack-merge');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');

const ENV = process.env.NODE_ENV = process.env.ENV = 'production';

module.exports = webpackMerge(commonConfig, {
  devtool: 'source-map',

  output: {
    path: helpers.root('dist'),
    publicPath: '/',
    filename: '[name].[hash].js',
    chunkFilename: '[id].[hash].chunk.js'
  },

  htmlLoader: {
    minimize: false
  },

  plugins: [
    new webpack.NoErrorsPlugin(),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.UglifyJsPlugin({
      mangle: {
        keep_fnames: true
      }
    }),
    new ExtractTextPlugin('[name].[hash].css'),
    new webpack.DefinePlugin({
      'process.env': {
        'ENV': JSON.stringify(ENV)
      }
    })
  ]
});

우선 큰 차이점은 개발서버를 사용하지 않습니다. 왜냐하면 운영서버에서는 더 나은 성능의 운영용 웹서버를 사용하기 때문이죠. 따라서 이제 실제로 웹팩로 빌드되어 묶인 파일들은 dist 폴더에 위치하게 됩니다. 또한 output을 보면 [hash]가 추가 되었음을 알 수 있는데요, 이는 소스가 빌드 될때마다 새로운 해쉬값을 받아 파일명을 생성하기 때문에, 브라우저 캐싱으로 인해 새로 반영한 소스가 로딩되지 않는 현상을 막을 수 있습니다.

그 외 다양한 플러그인들이 설정되어 있는데, 각각의 역할은 아래와 같습니다.

  • NoErrorsPlugin - 소스에 애러가 있을 시 빌드를 하지 않도록 막아줍니다.
  • DedupePlugin - 번들소스에 들어있는 중복 코드를 제거해 줍니다.
  • UglifyJsPlugin - 번들소스 파일을 압축해 줍니다.
  • DefinePlugin - 어플리케이션에서 환경변수를 정의할 수 있게 해줍니다.
webpack.config.js

이제 웹팩 설정이 거의 끝나갑니다. 마지막으로 어떠한 설정파일을 로딩할지만 설정하면 정말로 끝입니다. 그리고 그 마지막은 정말 간단합니다. 웹팩은 프로젝트 루트에 위치한 webpack.config.js 를 읽어서 동작하므로, 해당 파일에 어떤 설정파일을 로딩할지만 정의해주면 됩니다.

module.exports = require('./config/webpack.dev.js');
package.json

이 장의 맨 처음에 npm 이란 무엇이고 어떻게 설치하는지 설명드렸습니다. 그럼 이제 본격적으로 npm을 활용하여 앵귤러를 이용한 어플리케이션 개발과 빌드에 필요한 라이브러리들을 설치해보도록 하겠습니다. 우선 무엇을 설치해야할지를 정의해줘야하는데, 이는 프로젝트 루트 경로에 package.json 에 정의해주면 됩니다. 이후 콘솔창에 아래의 명령어를 입력합니다.

$npm install

이 후 package.json 에 정의한 라이브러리들의 설치가 진행되며, 라이브러리들이 프로젝트 루트 아래에 node_modules 에 설치가 됩니다.

{
  "name": "ng-pizza",
  "version": "1.0.0",
  "description": "Let's start developing your ng-pizza by angular",
  "scripts": {
    "start": "webpack-dev-server --inline --progress --port 8090",
    "build": "rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail"
  },
  "license": "MIT",
  "dependencies": {
    "@angular/common": "~2.4.0",
    "@angular/compiler": "~2.4.0",
    "@angular/core": "~2.4.0",
    "@angular/forms": "~2.4.0",
    "@angular/http": "~2.4.0",
    "@angular/platform-browser": "~2.4.0",
    "@angular/platform-browser-dynamic": "~2.4.0",
    "@angular/router": "~3.4.0",
    "core-js": "^2.4.1",
    "rxjs": "5.0.1",
    "zone.js": "^0.7.4"
  },
  "devDependencies": {
    "@types/node": "^6.0.45",
    "angular2-template-loader": "^0.4.0",
    "awesome-typescript-loader": "^3.0.0-beta.17",
    "css-loader": "^0.23.1",
    "extract-text-webpack-plugin": "^1.0.1",
    "file-loader": "^0.8.5",
    "html-loader": "^0.4.3",
    "html-webpack-plugin": "^2.15.0",
    "null-loader": "^0.1.1",
    "raw-loader": "^0.5.1",
    "rimraf": "^2.5.2",
    "style-loader": "^0.13.1",
    "typescript": "~2.0.10",
    "typings": "^1.3.2",
    "webpack": "^1.13.0",
    "webpack-dev-server": "^1.14.1",
    "webpack-merge": "^0.14.0"
  }
}

dependenciesdevDependencies 의 차이부터 알아보겠습니다. 우선 dependencies는 어플리케이션이 동작하는데 필수적인 라이브러리들을 정의합니다. 대표적으로 앵귤러 관련 라이브러리들이죠. 반면에 devDependencies는 개발과정에 필요한 라이브러리들을 정의합니다. 그래서 만약, 운영서버에는 devDependencies에 정의한 라이브러리들은 제외하고 설치하려면 아래와 같은 명령어를 입력합니다.

$npm install --production
개발서버 기동 및 빌드

자 이제 개발을 위한 서버 기동 및 빌드를 할 준비가 모두 끝났습니다. 웹팩을 통한 번들 소스를 만들어 내는 명령어는 어떻게 될까요? 아래와 같은 명령어를 입력합니다.

$npm run build

위 명령어를 입력하면 package.json에 정의해 놓은 scriptsbuild 설정을 읽어들여 실행해주는데, 이때 정의해 놓은 webpack.prod.js 의 설정대로 빌드하게 됩니다. 빌드된 소스는 설정파일에 정의해 놓은 대로 프로젝트 루트 경로 아래의 dist에 저장되게 됩니다.

하지만 우리는 웹팩 개발서버 기동을 통해 이러한 과정을 패스할 수 있습니다. 서버 기동 명령어도 빌드 명령어와 같이 같단합니다. 아래 명령어를 입력해줍니다.

$npm start

간단하죠? 사용자 소스 및 라이브러리에 이상이 없다면 빌드과정 중 애러 메세지 없이 마지막 줄에 아래와 같은 메세지가 나오면 빌드 및 서버 기동이 완료되었음을 의미합니다.

webpack: bundle is now VALID.

이렇게 개발환경을 위한 설정이 모두 끝났습니다. 하지만 지금 이 상태로는 브라우저에 띄워서 볼 수 있는 내용이 없습니다. 다음 장에서부터 본격적으로 사용자 웹 개발을 위한 프로젝트 구조를 정의해 보도록 하겠습니다.

results matching ""

    No results matching ""