Angular Webpack 설정

소개

Webpack은 응용 프로그램 소스 코드를 chunk로 묶어 브라우저에서 로드하도록 하는 도구이며 인기있는 모듈 번들러입니다.

이 문서는 다른 문서에서 설명한 SystemJS 접근법에 대한 훌륭한 대안입니다. 이 포스트는 Webpack을 소개하고 Angular 응용 프로그램에서 Webpack을 사용하는 방법을 설명합니다.

코드의 최종 결과를 다운로드 할 수도 있습니다.

Webpack이란?

Webpack은 강력한 모듈 번들러입니다. Bundle은 함께 포함된 Asset을 통합하는 JavaScript 파일입니다. 그리고 하나의 파일 요청으로 클라이언트에 제공되어야 합니다. Bundle에는 JavaScript, CSS style, HTML 및 거의 모든 종류의 파일이 포함될 수 있습니다.

Webpack은 응용 프로그램 소스 코드를 훑어서, import 문을 찾아 의존성 그래프를 작성하고, 하나 이상의 Bundle을 제공합니다. Webpack은 플러그인과 Rule을 통해 TypeScript, SASS, LESS 파일과 같은 JavaScript 파일이 아닌 파일을 사전 처리 및 축약이 가능합니다.

Webpack이 해야할 일과 그 일을 어떻게 해야하는지 JavaScript 구성 파일 인 webpack.config.js를 사용하여 정의합니다.

Entry와 output

하나 이상의 Entry 파일을 Webpack에 제공하고 발견되는 종속성을 찾아 통합시킵니다. 아래 예제에서 Entry 파일은 응용 프로그램의 루트 파일인 src/main.ts입니다.

webpack.config.js (single entry)

1
2
3
4
5
webpack.config.js (single entry)
content_copy
entry: {
'app': './src/main.ts'
},

Webpack은 파일을 검사하고 import 의존성을 재귀적으로 확인합니다.

src/main.ts

1
2
3
4
5
6
7
8
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent { }

위 예제에서 Webpack이 @angular/core를 import 하는 것을 발견했습니다. 그래서 번들에 포함될 잠재적인 의존성 리스트에 추가합니다. 그리고 @angular/core 파일을 열어 main.ts에서 완전한 종속성 그래프를 작성할 때까지 import 명령문을 계속 찾습니다.

그런 다음 이 파일을 설정에서 지정된 app.js 번들 파일로 통합합니다.

1
2
3
output: {
filename: 'app.js'
}

app.js 결과 Bundle은 애플리케이션 소스와 그 의존성을 포함하는 단일 JavaScript 파일입니다. 나중에 <script>태그를 사용하여index.html에 로드 할 것입니다.

Multiple bundles

여러분은 아마도 모든 파일을 하나의 거대한 Bundle로 만들고 싶지 않을 수도 있습니다. 휘발성 응용 프로그램 코드와 비교적 안정적인 vendor 코드 모듈을 분리하는 것이 좋습니다.

main.tsvendor.ts 두 Entry 포인트를 갖도록 설정을 변경할 수 있습니다.

1
2
3
4
5
6
7
8
entry: {
app: 'src/app.ts',
vendor: 'src/vendor.ts'
},
output: {
filename: '[name].js'
}

Webpack은 두개의 독립된 의존성 그래프를 만들고 두개의 Bundle 파일을 생성합니다. 하나는 애플리케이션 코드만 포함하고 있는 app.js이고, 다른 하나는 모든 vendor 의존성을 가진 vendor.js 파일입니다.

결과 이름의 [name]은 Webpack 플러그인이 항목 이름인 appvendor로 대체하는 placeholder입니다. 플러그인에 대해서는 나중에 다룹니다.

src/vendor.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
// Angular
import '@angular/platform-browser';
import '@angular/platform-browser-dynamic';
import '@angular/core';
import '@angular/common';
import '@angular/http';
import '@angular/router';
// RxJS
import 'rxjs';
// Other vendors for example jQuery, Lodash or Bootstrap
// You can import js, ts, css, sass, ...

Loaders

Webpack은 JavaScript, TypeScript, CSS, SASS, LESS, 이미지, HTML, 글꼴 등 어떤 종류의 파일도 Bundle로 묶을 수 있습니다. Webpack 자체는 JavaScript 파일만 인식합니다. JavaScript 파일이 아닌 파일은 loader를 사용하여 JavaScript로 변환할 수 있습니다. 다음과 같이 TypeScript 및 CSS용 loader를 구성할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
rules: [
{
test: /\.ts$/,
loader: 'awesome-typescript-loader'
},
{
test: /\.css$/,
loaders: 'style-loader!css-loader'
}
]

Webpack은 다음과 같은 import 문을 만나면 test RegEx 패턴을 적용합니다.

1
2
3
import { AppComponent } from './app.component.ts';
import 'uiframework/dist/uiframework.css';

패턴이 파일이름과 일치하면 Webpack은 연관된 loader로 파일을 처리합니다.

첫번째 import 파일은 .ts 패턴과 일치합니다. 그래서 Webpack은 awesome-typescript-loader로 그 파일을 처리합니다. 그리고 파일이 두번째 패턴과 일치하지 않으므로 두번째 loader는 무시됩니다.

두번째 import는 두번째로 .css 패턴과 일치합니다. 두개의 loader가 (!) 문자로 묶여 있습니다. Webpack은 체인 연결된 loader를 오른쪽에서 왼쪽으로 적용합니다. 그래서 css loader를 적용하여 CSS @importurl(...)문장을 flat하게 만듭니다. 그런 다음 CSS 스타일을 페이지에 <style< 요소 안에 추가하기 위해 style 로더를 적용합니다.

플러그인

Webpack에는 잘 정의 된 빌드 파이프 라인이 있습니다. uglify 축약 플러그인과 같은 플러그인으로 그 파이프 라인에 연결할 수 있습니다.

1
2
3
plugins: [
new webpack.optimize.UglifyJsPlugin()
]

Webpack 구성

간단한 설명이 끝났으니 Angular 응용 프로그램에 대한 Webpack 구성을 직접 작성해 보겠습니다.

개발 환경을 설정하는 것으로 시작하겠습니다.

새 프로젝트 폴더를 만듭니다.

1
2
mkdir angular-webpack
cd angular-webpack

아래 파일들을 생성합니다.

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{
"name": "angular2-webpack",
"version": "1.0.0",
"description": "A webpack starter for Angular",
"scripts": {
"start": "webpack-dev-server --inline --progress --port 8080",
"test": "karma start",
"build": "rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail"
},
"license": "MIT",
"dependencies": {
"@angular/common": "~4.2.0",
"@angular/compiler": "~4.2.0",
"@angular/core": "~4.2.0",
"@angular/forms": "~4.2.0",
"@angular/http": "~4.2.0",
"@angular/platform-browser": "~4.2.0",
"@angular/platform-browser-dynamic": "~4.2.0",
"@angular/router": "~4.2.0",
"core-js": "^2.4.1",
"rxjs": "5.0.1",
"zone.js": "^0.8.4"
},
"devDependencies": {
"@types/node": "^6.0.45",
"@types/jasmine": "2.5.36",
"angular2-template-loader": "^0.6.0",
"awesome-typescript-loader": "^3.0.4",
"css-loader": "^0.26.1",
"extract-text-webpack-plugin": "2.0.0-beta.5",
"file-loader": "^0.9.0",
"html-loader": "^0.4.3",
"html-webpack-plugin": "^2.16.1",
"jasmine-core": "^2.4.1",
"karma": "^1.2.0",
"karma-chrome-launcher": "^2.0.0",
"karma-jasmine": "^1.0.2",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.1",
"null-loader": "^0.1.1",
"raw-loader": "^0.5.1",
"rimraf": "^2.5.2",
"style-loader": "^0.13.1",
"typescript": "~2.0.10",
"webpack": "2.2.1",
"webpack-dev-server": "2.4.1",
"webpack-merge": "^3.0.0"
}
}

src/tsconfig.json

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es2015", "dom"],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}
}

webpack.config.js

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

karma.conf.js

1
module.exports = require('./config/karma.conf.js');

config/helpers.js

1
2
3
4
5
6
7
8
9
10
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;

이 파일들 중 상당수는 다른 Angular 문서 가이드, 특히 Typescript 구성npm 패키지 포스트와 비슷합니다.

Webpack, 플러그인 및 loader는 패키지로도 설치 가능합니다. 그것들은 업데이트 된 packages.json에 나열되어 있습니다.

터미널 창을 열고 npm 패키지를 설치하십시오.

1
npm install

Polyfills

대부분의 브라우저에서 Angular 응용 프로그램을 실행하려면 Polyfill이 필요합니다(브라우저 지원 설명서 참조).

Polyfill은 응용 프로그램 및 vendor Bundle과 별도의 Bundle로 제공되어야합니다. 아래의 코드 polyfills.tssrc/ 폴더에 추가하십시오.

src/polyfills.ts

1
2
3
4
5
6
7
8
9
10
11
import 'core-js/es6';
import 'core-js/es7/reflect';
require('zone.js/dist/zone');
if (process.env.ENV === 'production') {
// Production
} else {
// Development and test
Error['stackTraceLimit'] = Infinity;
require('zone.js/dist/long-stack-trace-zone');
}

LOADING POLYFILLS

다른 ES6 및 메타 데이터 shim 직후에 polyfills.ts에서 zone.js를 로드하십시오.

polyfills.ts Bundle 파일이 먼저 로드되기 때문에 제작 환경이나 개발 환경에 맞는 브라우저 환경을 구성하기에 좋은 곳입니다.

공통 구성

개발자는 일반적으로 개발, 프로덕션 및 테스트 환경에 대해 별도의 구성을 가지고 있습니다. 그리고 세가지 모두 공통된 구성이 있을수 있습니다.

webpack.common.js라는 파일에 공통 구성을 모아두겠습니다.

config/webpack.common.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
loaders: [
{
loader: 'awesome-typescript-loader',
options: { configFileName: helpers.root('src', 'tsconfig.json') }
} , 'angular2-template-loader'
]
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file-loader?name=assets/[name].[hash].[ext]'
},
{
test: /\.css$/,
exclude: helpers.root('src', 'app'),
loader: ExtractTextPlugin.extract({ fallbackLoader: 'style-loader', loader: 'css-loader?sourceMap' })
},
{
test: /\.css$/,
include: helpers.root('src', 'app'),
loader: 'raw-loader'
}
]
},
plugins: [
// Workaround for angular/angular#11580
new webpack.ContextReplacementPlugin(
// The (\\|\/) piece accounts for path separators in *nix and Windows
/angular(\\|\/)core(\\|\/)@angular/,
helpers.root('./src'), // location of your src
{} // a map of your routes
),
new webpack.optimize.CommonsChunkPlugin({
name: ['app', 'vendor', 'polyfills']
}),
new HtmlWebpackPlugin({
template: 'src/index.html'
})
]
};

webpack.common.js 살펴 보기

Webpack은 JavaScript commonjs 모듈 파일에서 설정을 읽는 NodeJS 기반 도구입니다.

이 설정은 의존성을 require 문으로 가져오고 여러 객체를 module.exports 객체의 프로퍼티로 내 보냅니다.

  • entry - 번들을 정의하는 진입 파일.
  • resolve - 확장자가 없을 때 파일 이름을 확인하는 방법.
  • module.rules - module은 파일들이 로딩되는 방법을 결정하기위한 rules를 가진 객체입니다.
  • plugins - 플러그인의 인스턴스를 만듭니다.

entry

첫번째는 entry 객체입니다.

config/webpack.common.js

1
2
3
4
5
entry: {
'polyfills': './src/polyfills.ts',
'vendor': './src/vendor.ts',
'app': './src/main.ts'
},

entry 객체는 세개의 Bundle을 정의합니다.

  • polyfills - 대부분의 최신 브라우저에서 Angular 응용 프로그램을 실행하는 데 필요.
  • vendor - Angular, lodash 및 bootstrap.css와 같은 타사 의존 라이브러리.
  • app - 응용 프로그램 코드.

resolve - 확장자가 없는 import 파일 처리

애플리케이션은 많은 수의 JavaScript 파일 또는 TypeScript 파일을 import할 것입니다. 다음 예와 같이 명시적 확장자를 가진 import 문을 쓸 수도 있습니다.

1
import { AppComponent } from './app.component.ts';

그러나 대부분의 import 문은 확장자를 전혀 언급하지 않습니다. Webpack에게 .ts 확장자 또는 .js 확장자 (정규 자바 스크립트 파일과 미리 컴파일 된 TypeScript 파일용)로 일치하는 파일을 찾게 함으로써 확장자가 없는 파일 요청을 해석 하도록합니다.

config/webpack.common.js

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

Webpack이 스타일과 HTML에 대한 확장자가없는 파일을 읽어야 한다면 .css.html을 목록에 추가하십시오.

module.rules

module.rules는 Webpack에게 각 파일이나 모듈에 사용할 로더를 알려줍니다

config/webpack.common.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
module: {
rules: [
{
test: /\.ts$/,
loaders: [
{
loader: 'awesome-typescript-loader',
options: { configFileName: helpers.root('src', 'tsconfig.json') }
} , 'angular2-template-loader'
]
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file-loader?name=assets/[name].[hash].[ext]'
},
{
test: /\.css$/,
exclude: helpers.root('src', 'app'),
loader: ExtractTextPlugin.extract({ fallbackLoader: 'style-loader', loader: 'css-loader?sourceMap' })
},
{
test: /\.css$/,
include: helpers.root('src', 'app'),
loader: 'raw-loader'
}
]
},
  • awesome-typescript-loader - tsconfig.json 파일에 따라 Typescript 코드를 ES5로 변환하는 로더입니다.
  • angular2-template-loader - 각 구성 요소의 템플릿과 스타일을 로드합니다.
  • html-loader - html 템플릿 컴포넌트
  • 이미지/글꼴 - 이미지와 글꼴도 번들로 제공됩니다.
  • CSS - 첫 번째 패턴은 application-wide 스타일과 일치합니다. 두 번째 요소는 component-scoped 스타일(구성 요소의 styleUrls 메타 데이터 프로퍼티에 지정된 스타일)을 처리합니다.

첫번째 패턴은 application-wide 스타일을 위한 것입이다. 이것은 src/app 디렉토리에서 component-scoped 스타일이 있는 .css 파일을 제외시킵니다. ExtractTextPlugin(아래에 설명 됨)은 stylecss loader를 이 파일들에 적용합니다.

두번째 패턴은 component-scoped 스타일을 필터링하고 raw-loader를 통해 문자열로 로드합니다. 이는 Angular가 styleUrls 메타 데이터 프로퍼티에 지정된 스타일을 사용하여 수행하는 작업입니다.

배열 표기법을 사용하여 여러 로더를 연결할 수 있습니다.

plugins

마지막으로 세개의 플러그인 인스턴스를 만듭니다.

config/webpack.common.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
plugins: [
// Workaround for angular/angular#11580
new webpack.ContextReplacementPlugin(
// The (\\|\/) piece accounts for path separators in *nix and Windows
/angular(\\|\/)core(\\|\/)@angular/,
helpers.root('./src'), // location of your src
{} // a map of your routes
),
new webpack.optimize.CommonsChunkPlugin({
name: ['app', 'vendor', 'polyfills']
}),
new HtmlWebpackPlugin({
template: 'src/index.html'
})
]

CommonsChunkPlugin

app.js Bundle은 애플리케이션 코드 만 포함해야합니다. 모든 vendor 코드는 vendor.js Bundle에 들어 있습니다.

물론 응용 프로그램 코드에서 vendor 코드를 가져옵니다. 자체적으로, Webpack은 app.js 번들에서 vendor 코드를 유지할 만큼 똑똑하지 않습니다. 그래서 CommonsChunkPlugin이 그러한 작업을 합니다.

CommonsChunkPluginapp->vendor->polyfills의 3 개의 청크들 사이의 계층을 식별합니다. Webpack은 appvendor와 의존성을 공유했다는 것을 발견하면 app에서 공유 의존성을 제거합니다. 그리고 의존하지 않는 의존성을 공유했다면 polyfillsvendor에서제거 할 수 있습니다.

HtmlWebpackPlugin

Webpack은 많은 js 및 CSS 파일을 생성합니다. 그 파일들을 수동으로 index.html에 삽입 할 수 있습니다. 하지만 지루하고 오류가 발생하기 쉽습니다. 그래서 Webpack은 HtmlWebpackPlugin을 사용하여 스크립트와 링크를 삽입 할 수 있습니다.

특정 환경 구성

webpack.common.js 설정 파일에서 대부분의 어려운 작업을 수행합니다. 대상 환경과 관련된 특성을 병합하여 webpack.common에 빌드된 환경별 구성 파일을 별도로 작성할 수 있습니다.

이 파일들은 짧고 단순한 경향이 있습니다.

개발 환경 구성

다음은 webpack.dev.js 개발 설정 파일입니다.

config/webpack.dev.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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: '/',
filename: '[name].js',
chunkFilename: '[id].chunk.js'
},
plugins: [
new ExtractTextPlugin('[name].css')
],
devServer: {
historyApiFallback: true,
stats: 'minimal'
}
});

개발 빌드는 파일 맨 아래에 구성된 Webpack 개발 서버에 의존합니다.

Webpack이 output Bundledist 폴더에 넣도록 지시하더라도, dev 서버는 모든 Bundle을 메모리에 유지합니다. 디스크에 기록하지 않습니다. 적어도 이 개발 빌드에서 생성된 파일은 dist 폴더에 없습니다.

webpack.common.js에 추가 된 HtmlWebpackPluginpublicPath와 파일 이름 설정을 사용하여 적절한 <script><link>태그를 index.html에 생성합니다.

CSS 스타일은 기본적으로 Javascript Bundle 안에 있습니다. ExtractTextPlugin은 그것들을 HtmlWebpackPlugin<link>태그로 index.html에 넣을수 있는 외부 .css 파일들로 추출합니다.

이 파일의 이러한 구성 옵션 및 기타 구성 옵션에 대한 자세한 내용은 Webpack 문서를 참조하십시오.

이 가이드의 끝 부분에 있는 코드를 가지고 시도해 보세요.

1
npm start

프로덕션 환경 구성

프로덕션 빌드의 구성은 몇 가지 주요 변경 사항을 포함하며 개발 환경 구성과 유사합니다.

config/webpack.prod.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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'
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
new webpack.optimize.UglifyJsPlugin({ // https://github.com/angular/angular/issues/10618
mangle: {
keep_fnames: true
}
}),
new ExtractTextPlugin('[name].[hash].css'),
new webpack.DefinePlugin({
'process.env': {
'ENV': JSON.stringify(ENV)
}
}),
new webpack.LoaderOptionsPlugin({
htmlLoader: {
minimize: false // workaround for ng2
}
})
]
});

응용 프로그램과 그 의존 라이브러리를 실제 프로덕션 서버에 배포합니다. 하지만 개발에만 필요한 라이브러리는 배포하지 않습니다.

결과 Bundle 파일은 dist 폴더에 생성합니다.

Webpack은 cache-busting 해시로 파일 이름을 생성합니다. HtmlWebpackPlugin 덕분에 해시가 변경 될 때 index.html 파일을 업데이트 할 필요가 없습니다.

추가 플러그인이 있습니다.

  • NoEmitOnErrorsPlugin - 오류가 있으면 빌드를 중지합니다.
  • UglifyJsPlugin - 번들을 축약합니다.
  • ExtractTextPlugin - 내장 된 CSS를 외부 파일로 추출하여 파일 이름에 캐시 해싱 해시를 추가합니다.
  • DefinePlugin - 응용 프로그램에서 참조할 수 있는 환경 변수를 정의하는 데 사용됩니다.
  • LoaderOptionsPlugins - 특정 loader의 옵션을 대체합니다.

DefinePlugin과 맨 위에 정의 된 ENV 변수 덕분에 Angular 프로덕션 모드를 다음과 같이 설정할 수 있습니다.

src/main.ts

1
2
3
if (process.env.ENV === 'production') {
enableProdMode();
}

이 가이드의 끝 부분에 있는 코드를 가지고 시도해 보세요.

1
npm run build

테스트 환경 구성

단위 테스트를 실행하는 데 많은 구성이 필요하지 않습니다. 개발 및 프로덕션 빌드를 위해 선언한 loader 및 플러그인은 필요하지 않습니다. 단위 테스트를 위해 application-wide 스타일 파일을 로드하고 처리 할 필요가 없으므로 속도가 느려지지 않습니다. 그래서 CSS 파일에 null 로더를 사용할 것이다.

테스트 설정을 webpack.common 설정으로 합치고 싶지 않거나 필요하지 않은 부분을 무시할 수 있습니다. 완전히 새로운 구성으로 다시 시작하는 것이 더 간단 할 수 있습니다.

config/webpack.test.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
var webpack = require('webpack');
var helpers = require('./helpers');
module.exports = {
devtool: 'inline-source-map',
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
loaders: [
{
loader: 'awesome-typescript-loader',
options: { configFileName: helpers.root('src', 'tsconfig.json') }
} , 'angular2-template-loader'
]
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'null-loader'
},
{
test: /\.css$/,
exclude: helpers.root('src', 'app'),
loader: 'null-loader'
},
{
test: /\.css$/,
include: helpers.root('src', 'app'),
loader: 'raw-loader'
}
]
},
plugins: [
new webpack.ContextReplacementPlugin(
// The (\\|\/) piece accounts for path separators in *nix and Windows
/angular(\\|\/)core(\\|\/)@angular/,
helpers.root('./src'), // location of your src
{} // a map of your routes
)
]
}

Karma를 재구성하여 Webpack을 사용하여 테스트를 실행할 수 있습니다.

config/karma.conf.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var webpackConfig = require('./webpack.test');
module.exports = function (config) {
var _config = {
basePath: '',
frameworks: ['jasmine'],
files: [
{pattern: './config/karma-test-shim.js', watched: false}
],
preprocessors: {
'./config/karma-test-shim.js': ['webpack', 'sourcemap']
},
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-only'
},
webpackServer: {
noInfo: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: false,
browsers: ['Chrome'],
singleRun: true
};
config.set(_config);
};

TypeScript를 프리컴파일 파일하지 않습니다. Webpack은 Typescript 파일을 메모리에 저장하고 생성된 JS를 카르마에 직접 공급합니다. 디스크에 임시 파일이 없습니다.

karma-test-shim은 Karma에게 미리 로드 될 것으로 예상되는 provider의 테스트 버전으로 Angular 테스트 프레임 워크를 미리 로드하고 준비 할 파일을 알려줍니다.

config/karma-test-shim.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Error.stackTraceLimit = Infinity;
require('core-js/es6');
require('core-js/es7/reflect');
require('zone.js/dist/zone');
require('zone.js/dist/long-stack-trace-zone');
require('zone.js/dist/proxy');
require('zone.js/dist/sync-test');
require('zone.js/dist/jasmine-patch');
require('zone.js/dist/async-test');
require('zone.js/dist/fake-async-test');
var appContext = require.context('../src', true, /\.spec\.ts/);
appContext.keys().forEach(appContext);
var testing = require('@angular/core/testing');
var browser = require('@angular/platform-browser-dynamic/testing');
testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule, browser.platformBrowserDynamicTesting());

응용 프로그램 코드를 명시 적으로 로드하지 않습니다. Webpack에게 테스트 파일 (.spec.ts로 끝나는 파일)을 찾아서 로드하라고 지시합니다. 각 spec 파일은 테스트하는 모든 응용 프로그램 소스 코드만 가져옵니다. Webpack은 특정 응용 프로그램 파일만 로드하고, 테스트하지 않는 다른 파일은 무시합니다.

이 가이드의 끝 부분에 있는 코드를 가지고 시도해 보세요.

1
npm test

따라해보기

이 포스트에서 다루는 Webpack으로 Bundle을 묶을수 있는 간단한 응용 프로그램의 소스 코드입니다.

src/index.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<base href="/">
<title>Angular With Webpack</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>

src/main.ts

1
2
3
4
5
6
7
8
9
10
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { AppModule } from './app/app.module';
if (process.env.ENV === 'production') {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule);

src/assets/css/styles.css

1
2
3
4
body {
background: #0147A7;
color: #fff;
}

src/app/app.component.ts

1
2
3
4
5
6
7
8
9
10
import { Component } from '@angular/core';
import '../assets/css/styles.css';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent { }

src/app/app.component.html

1
2
3
4
5
<main>
<h1>Hello from Angular App with Webpack</h1>
<img src="../assets/images/angular.png">
</main>

src/app/app.component.css

1
2
3
4
5
6
7
main {
padding: 1em;
font-family: Arial, Helvetica, sans-serif;
text-align: center;
margin-top: 50px;
display: block;
}

src/app/app.component.spec.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('App', () => {
beforeEach(() => {
TestBed.configureTestingModule({ declarations: [AppComponent]});
});
it ('should work', () => {
let fixture = TestBed.createComponent(AppComponent);
expect(fixture.componentInstance instanceof AppComponent).toBe(true, 'should create AppComponent');
});
});

src/app/app.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
imports: [
BrowserModule
],
declarations: [
AppComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }

app.component.html은 다운로드 할 수있는 Angular 로고를 표시합니다. 프로젝트의 assets 폴더 아래에 images라는 폴더를 생성한 다음 이미지를 마우스 오른쪽 버튼으로 클릭하고 (Mac에서는 Cmd + 클릭) 다운로드 하십시오.

아래에 polyfillsvendor Bundle을 정의하는 TypeScript Entry 파일이 있습니다.

src/polyfills.ts

1
2
3
4
5
6
7
8
9
10
11
import 'core-js/es6';
import 'core-js/es7/reflect';
require('zone.js/dist/zone');
if (process.env.ENV === 'production') {
// Production
} else {
// Development and test
Error['stackTraceLimit'] = Infinity;
require('zone.js/dist/long-stack-trace-zone');
}

src/vendor.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
// Angular
import '@angular/platform-browser';
import '@angular/platform-browser-dynamic';
import '@angular/core';
import '@angular/common';
import '@angular/http';
import '@angular/router';
// RxJS
import 'rxjs';
// Other vendors for example jQuery, Lodash or Bootstrap
// You can import js, ts, css, sass, ...

하이라이트

  • <script>또는 <link>태그는 index.html에 없습니다. HtmlWebpackPlugin은 그것들을 런타임에 동적으로 삽입합니다.

  • appComponent.ts에있는 AppComponent는 간단한 import 문으로 application-wide css를 가져옵니다.

  • AppComponent 자체는 자체 html 템플릿과 css 파일을 가지고 있습니다. WebPack은 require()를 호출하여 이를 로드합니다. Webpack은 app.js Bundle에 있는 component-scoped 파일도 숨깁니다. 소스 코드에서 이러한 호출을 볼 수 없습니다. 그들은 angular2-template-loader 플러그인으로 뒤에 추가됩니다.

  • vendor.tsvendor.js Bundle을 구동시키는 vendor import 문으로 구성됩니다. 응용 프로그램은 이러한 모듈도 가져올 수 있습니다. CommonsChunkPlugin이 겹침을 감지하지 못하고 app.js에서 제거한 경우 app.js 번들에 중복될 수 있습니다.

결론

간단한 Angular 애플리케이션을 위한 개발, 테스트 및 프로덕션 빌드를 구성하기에 충분한 Webpack을 배웠습니다.

여러분은 더 많은 것을 할 수 있습니다. 웹에서 전문가 조언을 검색하고 Webpack 지식을 확장하십시오.


이 내용은 나중에 참고하기 위해 제가 공부하며 정리한 내용입니다.
의역, 오역, 직역이 있을 수 있음을 알려드립니다.
This post is a translation of this original article [https://angular.io/guide/webpack]

공유하기