docs(cb-webpack): add guide chapter about webpack

This commit is contained in:
Foxandxss 2016-04-13 19:20:51 +02:00 committed by Ward Bell
parent 8b1683f513
commit 96238bbfab
39 changed files with 972 additions and 78 deletions

View File

@ -13,48 +13,64 @@
"tsc": "tsc",
"tsc:w": "tsc -w",
"typings": "typings",
"webdriver:update": "webdriver-manager update"
"webdriver:update": "webdriver-manager update",
"start:webpack": "webpack-dev-server --inline --progress --port 8080",
"test:webpack": "karma start karma.webpack.conf.js",
"build:webpack": "rm -rf dist && webpack --config config/webpack.prod.js --progress --profile --bail"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@angular/common": "2.0.0-rc.1",
"@angular/compiler": "2.0.0-rc.1",
"@angular/core": "2.0.0-rc.1",
"@angular/http": "2.0.0-rc.1",
"@angular/platform-browser": "2.0.0-rc.1",
"@angular/platform-browser-dynamic": "2.0.0-rc.1",
"@angular/router": "2.0.0-rc.1",
"@angular/router-deprecated": "2.0.0-rc.1",
"@angular/upgrade": "2.0.0-rc.1",
"@angular/common": "2.0.0-rc.1",
"@angular/compiler": "2.0.0-rc.1",
"@angular/core": "2.0.0-rc.1",
"@angular/http": "2.0.0-rc.1",
"@angular/platform-browser": "2.0.0-rc.1",
"@angular/platform-browser-dynamic": "2.0.0-rc.1",
"@angular/router": "2.0.0-rc.1",
"@angular/router-deprecated": "2.0.0-rc.1",
"@angular/upgrade": "2.0.0-rc.1",
"systemjs": "0.19.27",
"es6-shim": "^0.35.0",
"reflect-metadata": "^0.1.3",
"rxjs": "5.0.0-beta.6",
"zone.js": "^0.6.12",
"angular2-in-memory-web-api": "0.0.7",
"bootstrap": "^3.3.6"
},
"devDependencies": {
"concurrently": "^2.0.0",
"lite-server": "^2.2.0",
"typescript": "^1.8.10",
"typings": "^0.8.1",
"canonical-path": "0.0.2",
"concurrently": "^2.0.0",
"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.16.1",
"http-server": "^0.9.0",
"lodash": "^4.11.1",
"jasmine-core": "~2.4.1",
"jasmine-core": "^2.4.1",
"karma": "^0.13.22",
"karma-chrome-launcher": "^0.2.3",
"karma-cli": "^0.1.2",
"karma-htmlfile-reporter": "^0.2.2",
"karma-jasmine": "^0.3.8",
"karma-phantomjs-launcher": "^1.0.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^1.7.0",
"lite-server": "^2.2.0",
"lodash": "^4.11.1",
"null-loader": "^0.1.1",
"phantomjs-prebuilt": "^2.1.7",
"protractor": "^3.3.0",
"rimraf": "^2.5.2"
"raw-loader": "^0.5.1",
"rimraf": "^2.5.2",
"style-loader": "^0.13.1",
"ts-loader": "^0.8.2",
"typescript": "^1.8.10",
"typings": "^0.8.1",
"webpack": "^1.13.0",
"webpack-dev-server": "^1.14.1",
"webpack-merge": "^0.12.0"
},
"repository": {}
}

View File

@ -1,6 +1,7 @@
{
"ambientDependencies": {
"es6-shim": "registry:dt/es6-shim#0.31.2+20160317120654",
"jasmine": "registry:dt/jasmine#2.2.0+20160412134438"
"jasmine": "registry:dt/jasmine#2.2.0+20160412134438",
"node": "registry:dt/node#4.0.0+20160509154515"
}
}

View File

@ -1,14 +0,0 @@
// #docregion
/**
* Declares the 'commonjs' format module object that identifies the "module id" for the current module.
* Set a component's `moduleId` metadata property to `module.id` for module-relative urls
* when the generated module format is 'commonjs'.
*/
declare var module: {id: string};
/**
* Declares the 'system' format string that identifies the "module id" for the current module.
* Set a component's `moduleId` metadata property to `__moduleName` for module-relative urls
* when the generated module format is 'system'.
*/
declare var __moduleName: string;

View File

@ -1,6 +1,7 @@
{
"ambientDependencies": {
"es6-shim": "registry:dt/es6-shim#0.31.2+20160317120654",
"jasmine": "registry:dt/jasmine#2.2.0+20160412134438"
"jasmine": "registry:dt/jasmine#2.2.0+20160412134438",
"node": "registry:dt/node#4.0.0+20160509154515"
}
}

View File

@ -1,13 +0,0 @@
/**
* Declares the 'commonjs' format module object that identifies the "module id" for the current module.
* Set a component's `moduleId` metadata property to `module.id` for module-relative urls
* when the generated module format is 'commonjs'.
*/
declare var module: {id: string};
/**
* Declares the 'system' format string that identifies the "module id" for the current module.
* Set a component's `moduleId` metadata property to `__moduleName` for module-relative urls
* when the generated module format is 'system'.
*/
declare var __moduleName: string;

View File

@ -0,0 +1,5 @@
dist
!karma.webpack.conf.js
!webpack.config.js
!config/*
!public/css/styles.css

View File

@ -0,0 +1,12 @@
// #docregion
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;
// #enddocregion

View File

@ -0,0 +1,22 @@
// #docregion
Error.stackTraceLimit = Infinity;
require('es6-shim');
require('reflect-metadata');
require('zone.js/dist/zone');
require('zone.js/dist/long-stack-trace-zone');
require('zone.js/dist/jasmine-patch');
require('zone.js/dist/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.setBaseTestProviders(
browser.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
browser.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS
);

View File

@ -0,0 +1,39 @@
// #docregion
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'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: false,
browsers: ['PhantomJS'],
singleRun: true
};
config.set(_config);
};
// #enddocregion

View File

@ -0,0 +1,64 @@
// #docregion
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var helpers = require('./helpers');
module.exports = {
// #docregion entries
entry: {
'polyfills': './src/polyfills.ts',
'vendor': './src/vendor.ts',
'app': './src/main.ts'
},
// #enddocregion
// #docregion resolve
resolve: {
extensions: ['', '.js', '.ts']
},
// #enddocregion resolve
// #docregion loaders
module: {
loaders: [
{
test: /\.ts$/,
loader: 'ts'
},
{
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'
}
]
},
// #enddocregion loaders
// #docregion plugins
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['app', 'vendor', 'polyfills']
}),
new HtmlWebpackPlugin({
template: 'src/index.html'
})
]
// #enddocregion plugins
};
// #enddocregion

View File

@ -0,0 +1,26 @@
// #docregion
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:8080/',
filename: '[name].js',
chunkFilename: '[id].chunk.js'
},
plugins: [
new ExtractTextPlugin('[name].css')
],
devServer: {
historyApiFallback: true,
stats: 'minimal'
}
});
// #enddocregion

View File

@ -0,0 +1,36 @@
// #docregion
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 // workaround for ng2
},
plugins: [
new webpack.NoErrorsPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin('[name].[hash].css'),
new webpack.DefinePlugin({
'process.env': {
'ENV': JSON.stringify(ENV)
}
})
]
});
// #enddocregion

View File

@ -0,0 +1,31 @@
// #docregion
module.exports = {
devtools: 'source-map',
resolve: {
extensions: ['', '.ts', '.js']
},
module: {
loaders: [
{
test: /\.ts$/,
loader: 'ts'
},
{
test: /\.html$/,
loader: 'html'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'null'
},
{
test: /\.css$/,
loader: 'null'
}
]
}
}
// #enddocregion

View File

@ -0,0 +1,2 @@
// #docregion
module.exports = require('./config/karma.conf.js');

View File

@ -0,0 +1,48 @@
{
"name": "angular2-webpack",
"version": "1.0.0",
"description": "A webpack starter for angular 2",
"scripts": {
"start": "webpack-dev-server --inline --progress --port 8080",
"test": "karma start",
"build": "rm -rf dist && webpack --config config/webpack.prod.js --progress --profile --bail",
"postinstall": "typings install"
},
"license": "MIT",
"dependencies": {
"@angular/common": "2.0.0-rc.1",
"@angular/compiler": "2.0.0-rc.1",
"@angular/core": "2.0.0-rc.1",
"@angular/http": "2.0.0-rc.1",
"@angular/platform-browser": "2.0.0-rc.1",
"@angular/platform-browser-dynamic": "2.0.0-rc.1",
"@angular/router-deprecated": "2.0.0-rc.1",
"es6-shim": "^0.35.0",
"reflect-metadata": "0.1.2",
"rxjs": "5.0.0-beta.6",
"zone.js": "0.6.12"
},
"devDependencies": {
"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",
"jasmine-core": "^2.4.1",
"karma": "^0.13.22",
"karma-jasmine": "^0.3.8",
"karma-phantomjs-launcher": "^1.0.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^1.7.0",
"null-loader": "^0.1.1",
"phantomjs-prebuilt": "^2.1.7",
"raw-loader": "^0.5.1",
"style-loader": "^0.13.1",
"ts-loader": "^0.8.1",
"typescript": "^1.8.9",
"typings": "^0.7.12",
"webpack": "^1.12.14",
"webpack-dev-server": "^1.14.1",
"webpack-merge": "^0.9.0"
}
}

View File

@ -0,0 +1,6 @@
/* #docregion */
body {
background: #0147A7;
color: #fff;
}
/* #enddocregion */

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -0,0 +1,9 @@
/* #docregion */
main {
padding: 1em;
font-family: Arial, Helvetica, sans-serif;
text-align: center;
margin-top: 50px;
display: block;
}
/* #enddocregion */

View File

@ -0,0 +1,7 @@
<!-- #docregion -->
<main>
<h1>Hello from Angular 2 App with Webpack</h1>
<img src="../../public/images/angular.png">
</main>
<!-- #enddocregion -->

View File

@ -0,0 +1,22 @@
// #docregion
import {
it,
inject,
describe,
beforeEachProviders,
expect
} from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('App', () => {
beforeEachProviders(() => [
AppComponent
]);
it ('should work', inject([AppComponent], (app: AppComponent) => {
// Add real test here
expect(2).toBe(2);
}));
});
// #enddocregion

View File

@ -0,0 +1,12 @@
// #docregion
import { Component } from '@angular/core';
import '../../public/css/styles.css';
@Component({
selector: 'my-app',
template: require('./app.component.html'),
styles: [require('./app.component.css')]
})
export class AppComponent { }
// #enddocregion

View File

@ -0,0 +1,2 @@
// #docregion
export * from './app.component.ts';

View File

@ -0,0 +1,14 @@
<!-- #docregion -->
<!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>
<!-- #enddocregion -->

View File

@ -0,0 +1,14 @@
// #docregion
import { bootstrap } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { AppComponent } from './app';
// #docregion enable-prod
if (process.env.ENV === 'production') {
enableProdMode();
}
// #enddocregion enable-prod
bootstrap(AppComponent, []);
// #enddocregion

View File

@ -0,0 +1,15 @@
// #docregion
import 'es6-shim';
import 'reflect-metadata';
require('zone.js/dist/zone');
if (process.env.ENV === 'production') {
// Production
} else {
// Development
Error['stackTraceLimit'] = Infinity;
require('zone.js/dist/long-stack-trace-zone');
}

View File

@ -0,0 +1,15 @@
// #docregion
// Angular 2
import '@angular/platform-browser';
import '@angular/platform-browser-dynamic';
import '@angular/core';
import '@angular/common';
import '@angular/http';
import '@angular/router-deprecated';
// RxJS
import 'rxjs';
// Other vendors for example jQuery, Lodash or Bootstrap
// You can import js, ts, css, sass, ...
// #enddocregion

View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
},
"exclude": [
"node_modules",
"typings/main",
"typings/main.d.ts"
]
}

View File

@ -0,0 +1,7 @@
{
"ambientDependencies": {
"es6-shim": "registry:dt/es6-shim#0.31.2+20160317120654",
"jasmine": "registry:dt/jasmine#2.2.0+20160412134438",
"node": "registry:dt/node#4.0.0+20160509154515"
}
}

View File

@ -0,0 +1,3 @@
// #docregion
module.exports = require('./config/webpack.dev.js');
// #enddocregion

View File

@ -134,6 +134,12 @@
"hide": true
},
"webpack": {
"title": "Introduction to Webpack",
"intro": "Create your Angular 2 applications with a Webpack based tooling",
"hide": true
},
"glossary": {
"title": "Glossary",
"intro": "Brief definitions of the most important words in the Angular 2 vocabulary",

View File

@ -0,0 +1 @@
!= partial("../../../_includes/_ts-temp")

View File

@ -132,6 +132,12 @@
"intro": "Angular 1 applications can be incrementally upgraded to Angular 2."
},
"webpack": {
"title": "Introduction to Webpack",
"intro": "Create your Angular 2 applications with a Webpack based tooling",
"hide": true
},
"glossary": {
"title": "Glossary",
"intro": "Brief definitions of the most important words in the Angular 2 vocabulary",

View File

@ -0,0 +1 @@
!= partial("../../../_includes/_ts-temp")

View File

@ -131,6 +131,11 @@
"intro": "Angular 1 applications can be incrementally upgraded to Angular 2."
},
"webpack": {
"title": "Introduction to Webpack",
"intro": "Create your Angular 2 applications with a Webpack based tooling"
},
"glossary": {
"title": "Glossary",
"intro": "Brief definitions of the most important words in the Angular 2 vocabulary",

View File

@ -356,7 +356,7 @@ block module-id
[TypeScript compiler documentation](http://www.typescriptlang.org/docs/handbook/compiler-options.html)).
The variables are `__moduleName` and `module.id` respectively.
Here's an example in which we set the metadata `moduleId` with one of these variables.
Here's an example in which we set the metadata `moduleId` to `module.id`.
+makeExample('component-styles/ts/app/quest-summary.component.ts','', 'app/quest-summary.component.ts')

View File

@ -95,14 +95,19 @@ a(id="typings")
We created a `typings.json` file in the [QuickStart](../quickstart.html):
+makeJson('quickstart/ts/typings.1.json', null, 'typings.json')(format=".")
:marked
We identified two *typings* file in the QuickStart, the *d.ts* file for
[es6-shim](https://github.com/paulmillr/es6-shim/blob/master/README.md)
that brings ES2015/ES6 capabilities to our ES5 browsers and for the
[jasmine](http://jasmine.github.io/) test framework.
We identified three *typings* file in the QuickStart, the *d.ts* files for
QuickStart itself doesn't require these shims but many of the documentation samples do
and most of us would be disappointed if typical ES2015 features didn't work out-of-the-box
or we didn't support testing.
* [es6-shim](https://github.com/paulmillr/es6-shim/blob/master/README.md)
that brings ES2015/ES6 capabilities to our ES5 browsers
* [jasmine](http://jasmine.github.io/) typings for the Jasmine test framework
* [node](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/node/node.d.ts) for code that references objects in the nodejs environment;
see the [webpack](./webpack.html) chapter for an example.
QuickStart itself doesn't require these typings but many of the documentation samples do.
Most of us would be disappointed if we couldn't code against
typical ES2015 features or support testing right out-of-the-box.
We can also run the *typings* tool ourselves.
The following command (re)installs the typings files, as is sometimes necessary when the `postInstall` hook fails to do so.
@ -123,18 +128,8 @@ code-example(format="").
it tells npm to pass all arguments to the right of `--` to the *typings* command.
Learn about the features of the *typings* tool at its [site on github](https://github.com/typings/typings/blob/master/README.md).
:marked
### *typings.d.ts*
In the [QuickStart](../quickstart.html) we added a custom `typings.d.ts` file to the *typings* folder.
+makeExample('quickstart/ts/typings.d.1.ts', null, 'typings/typings.d.ts')(format=".")
:marked
The `typings.d.ts` file is the place to declare so-called *ambient* objects that will be globally available at runtime.
We only need it when there is *no typings file* to declare it for us.
This example declares objects that we need in order to tell Angular how to load templates and styles with [module-relative URLs](component-styles.html#!#relative-urls).
A module loader creates one or the other object dynamically as it loads a module.
These semi-globals are not described in any other `d.ts` file at this time so we have to declare them ourselves.
:marked
### Typing file collisions
The TypeScript compiler does not tolerate redefinition of a type. For example, it throws an error if it's given two definitions for
@ -152,7 +147,9 @@ code-example(format="").
.children
.file es6-shim
.children
.file es6-shim.d.ts
.file index.d.ts
.file ...
.children
.file main
.children
@ -160,11 +157,11 @@ code-example(format="").
.children
.file es6-shim
.children
.file es6-shim.d.ts
.file index.d.ts
.file ...
.children
.file browser.d.ts
.file main.d.ts
.file typings.d.ts
:marked
The `es6-shim` typings are duplicated and the `browser.d.ts` and `main.d.ts` have overlapping content.

View File

@ -0,0 +1,463 @@
include ../_util-fns
:marked
[**Webpack**](https://webpack.github.io/) is a popular module bundler,
a tool for bundling application source code in convenient _chunks_
and for loading that code from a server into a browser.
It's an excellent alternative to the *SystemJS* approach we use throughout the documentation.
In this guide we get a taste of Webpack and how to use it with Angular 2 applications.
<a id="top"></a>
## Table of contents
[What is Webpack?](#what-is-webpack)
* [Entries and outputs](#entries-outputs)
* [Loaders](#loaders)
* [Plugins](#plugins)
[Configuring Webpack](#configure-webpack)
* [Common configuration](#common-configuration)
* [Development configuration](#development-configuration)
* [Production configuration](#production-configuration)
* [Test configuration](#test-configuration)
[Trying it out](#try)
[Conclusions](#conclusions)
.l-main-section
<a id="what-is-webpack"></a>
:marked
## What is Webpack?
Webpack is a powerful module bundler.
A _bundle_ is a JavaScript file that incorporate _assets_ that *belong* together and
should be served to the client in a response to a single file request.
A bundle can include JavaScript, CSS styles, HTML, and almost any other kind of file.
Webpack roams over your application source code,
looking for `import` statements, building a dependency graph, and emitting one (or more) _bundles_.
With plugin "loaders" Webpack can preprocess and minify different non-JavaScript files such as TypeScript, SASS, and LESS files.
We determine what Webpack does and how it does it with a JavaScript configuration file, `webpack.config.js`.
a(id="entries-outputs")
.l-main-section
:marked
### Entries and outputs
We feed Webpack with one or more *entry* files and let it find and incorporate the dependencies that radiate from those entries.
In this example, we start from the application's root file, `src/app.ts`:
+makeExample('webpack/ts-snippets/webpack.config.snippets.js', 'one-entry', 'webpack.config.js (single entry)')(format=".")
:marked
Webpack inspects that file and traverses its `import` dependencies recursively.
+makeExample('webpack/ts-snippets/webpack.config.snippets.js', 'app-example', 'src/app.ts')(format=".")
:marked
Here it sees that we're importing *@angular/core* so it adds that to its dependency list for (potential) inclusion in the bundle.
It opens *@angular/core* and follows _its_ network of `import` statements until it has build the complete dependency graph from `app.ts` down.
Then it **outputs** these files to the `app.js` _bundle file_ designated in configuration:
+makeExample('webpack/ts-snippets/webpack.config.snippets.js', 'one-output', 'webpack.config.js (single output)')(format=".")
:marked
This `app.js` output bundle is a single JavaScript file that contains our application source and its dependencies.
We'll load it later with a &lt;script&gt; tag in our index.html.
#### Multiple bundles
We probably do not want one giant bundle of everything.
We'll likely prefer to separate our volatile application app code from comparatively stable vendor code modules.
We change the configuration so that we have two entry points, `app.ts` and `vendor.ts`:
+makeExample('webpack/ts-snippets/webpack.config.snippets.js', 'two-entries','webpack.config.js (two entries)')(format=".")
:marked
Webpack constructs two separate dependency graphs
and emits *two* bundle files, one called `app.js` containing only our application code and
another called `vendor.js` with all the vendor dependencies.
.l-sub-section
:marked
The `[name]` in the output name is a Webpack *placeholder* that is replaced with the entry names.
`app` and `vendor` respectively.
We need a plugin to make this work; we'll [cover that later](#commons-chunk-plugin) in the chapter.
:marked
We met `app.ts` earlier. We wrote `vendor.ts` such that it imports the vendor modules we need:
+makeExample('webpack/ts/src/vendor.ts', null,'src/vendor.ts')(format=".")
a(id="loaders")
.l-main-section
:marked
### Loaders
Webpack can bundle any kind of file: JavaScript, TypeScript, CSS, SASS, LESS, images, html, fonts, whatever.
Webpack itself doesn't know what to do with a non-JavaScript file.
We teach it to process such files into JavaScript with *loaders*.
Here we configure loaders for TypeScript and CSS:
+makeExample('webpack/ts-snippets/webpack.config.snippets.js', 'loaders', 'webpack.config.js (two entries)')(format=".")
:marked
As Webpack encounters `import` statements like these ...
+makeExample('webpack/ts-snippets/webpack.config.snippets.js', 'imports')(format=".")
:marked
... it applies the `test` RegEx patterns. When a pattern matches the filename, Webpack processes the file with the associated loader.
The first `import` file matches the `.ts` pattern so Webpack processes it with the `ts` (TypeScript) loader.
The imported file doesn't match the second pattern so its loader is ignored.
The second `import` matches the second `.css` pattern for which we have *two* loaders chained by the (!) character.
Webpack applies chained loaders *right to left* so it applies
the `css` loader first (to flatten CSS `@import` and `url(...)` statements) and
then the `style` loader (to append the css inside *&lt;style&gt;* elements on the page).
a(id="plugins")
.l-main-section
:marked
### Plugins
Webpack has a build pipeline with well-defined phases.
We tap into that pipeline with plugins such as the `uglify` minification plugin:
+makeExample('webpack/ts-snippets/webpack.config.snippets.js', 'plugins')(format=".")
a(id="configure-webpack")
.l-main-section
:marked
## Configure Webpack
After that brief orientation, we are ready to build our own Webpack configuration for Angular 2 apps.
Begin by setting up the development environment.
Create a **new project folder**
code-example(format="").
mkdir angular2-webpack
cd angular2-webpack
:marked
Add these files to the root directory:
+makeTabs(
`webpack/ts/package.webpack.json,
webpack/ts/typings.1.json,
webpack/ts/tsconfig.1.json,
webpack/ts/webpack.config.js,
webpack/ts/karma.webpack.conf.js,
webpack/ts/config/helpers.js`,
null,
`package.json,
typings.json,
tsconfig.json,
webpack.config.js,
karma.conf.js,
config/helpers.js`
)
.l-sub-section
:marked
Many of these files and much of their content should be familiar from other Angular 2 documentation chapters.
Learn about the `package.json` in the [npm packages](../guide/npm-packages.html) chapter.
We require packages for Webpack use in addition to the ones listed in that chapter.
Learn about `tsconfig.json` and `typings.json` in the [Typescript configuration](../guide/typescript-configuration.html) chapter.
:marked
Open a terminal/console window and install the *npm* packages with `npm install`.
a(id="common-configuration")
.l-main-section
:marked
### Common Configuration
We will define separate configurations for development, production, and test environments.
All three have some configuration in common.
We'll gather that common configuration in a separate file called `webpack.common.js`.
Let's see the entire file and then walk through it a section at a time:
+makeExample('webpack/ts/config/webpack.common.js', null, 'config/webpack.common.js')(format=".")
:marked
Webpack is a NodeJS-based tool so its configuration is a JavaScript _commonjs_ module file
that begins with `require` statements as such files do.
The configuration exports several objects, beginning with the *entries* described earlier:
+makeExample('webpack/ts/config/webpack.common.js', 'entries', 'config/webpack.common.js')(format=".")
:marked
We are splitting our application into three bundles:
* polyfills - the standard polyfills we require to run Angular 2 applications in most modern browsers.
* vendor - the vendor files we need: Angular 2, lodash, bootstrap.css...
* app - our application code.
.callout.is-critical
header Loading polyfills
:marked
Load Zone.js early, immediately after the other ES6 and metadata shims.
:marked
Our app will `import` dozens if not hundreds of JavaScript and TypeScript files.
We _might_ write `import` statements with explicit extensions as in this example:
+makeExample('webpack/ts-snippets/webpack.config.snippets.js', 'single-import')(format=".")
:marked
But most of our `import` statements won't mention the extension at all.
So we tell Webpack to _resolve_ module file requests by looking for matching files with
* an explicit extention (signified by the empty extension string, `''`) or
* `.js` extension (for regular JavaScript files and pre-compiled TypeScript files) or
* `.ts` extension.
+makeExample('webpack/ts/config/webpack.common.js', 'resolve', 'config/webpack.common.js')(format=".")
.l-sub-section
:marked
We could add `.css` and `.html` later if we want Webpack to resolve extension-less files with _those_ extension too.
:marked
Next we specify the loaders:
+makeExample('webpack/ts/config/webpack.common.js', 'loaders', 'config/webpack.common.js')(format=".")
:marked
* ts - a loader to transpile our Typescript code to ES5, guided by the `tsconfig.json` file
* html - for component templates
* images/fonts - Images and fonts are bundled as well.
* css - The pattern matches application-wide styles; the second handles component-scoped styles (the ones specified in a component's `styleUrls` metadata property).
.l-sub-section
:marked
The first pattern excludes `.css` files within the `/src/app` directories where our component-scoped styles sit.
It includes only `.css` files located at or above `/src`; these are the application-wide styles.
The `ExtractTextPlugin` (described below) applies the `style` and `css` loaders to these files.
The second pattern filters for component-scoped styles and loads them as strings via the `raw` loader &mdash;
which is what Angular expects to do with styles specified in a `styleUrls` metadata property.
:marked
Finally we add two plugins:
+makeExample('webpack/ts/config/webpack.common.js', 'plugins', 'config/webpack.common.js')(format=".")
a(id="commons-chunk-plugin")
:marked
#### *CommonsChunkPlugin*
We want the `app.js` bundle to contain only app code and the `vendor.js` bundle to contain only the vendor code.
Our application code `imports` vendor code. Webpack is not smart to keep the vendor code out of the `app.js` bundle.
We rely on the `CommonsChunkPlugin` to do that job.
.l-sub-section
:marked
It identifies the hierarchy among three _chunks_: `app` -> `vendor` -> `polyfills`.
Where Webpack finds that `app` has shared dependencies with `vendor`, it removes them from `app`.
It would do the same if `vendor` and `polyfills` had shared dependencies (which they don't).
a(id="html-webpack-plugin")
:marked
#### *HtmlWebpackPlugin*
Webpack generates a number of js and css files.
We _could_ insert them into our `index.html` _manually_. That would be tedious and error-prone.
Webpack can inject those scripts and links for us with the `HtmlWebpackPlugin`.
a(id="environment-configuration")
.l-main-section
:marked
### Environment-specific configuration
The `webpack.common.js` configuration file does most of the heavy lifting.
We create separate, environment-specific configuration files that build on `webpack.common`
by merging into it the peculiarities particular to their target environments.
These files tend to be short and simple.
a(id="development-configuration")
.l-main-section
:marked
### Development Configuration
Here is the development configuration file, `webpack.dev.js`
+makeExample('webpack/ts/config/webpack.dev.js', null, 'config/webpack.dev.js')(format=".")
:marked
The development build relies on the Webpack development server which we configure near the bottom of the file.
Although we tell Webpack to put output bundles in the `dist` folder,
the dev server keeps all bundles in memory; it doesn't write them to disk.
So we won't find any files in the `dist` folder (at least not any generated from `this development build`).
The `HtmlWebpackPlugin` (added in `webpack.common.js`) use the *publicPath* and the *filename* settings to generate
appropriate &lt;script&gt; and &lt;link&gt; tags into the `index.html`.
Our CSS are buried inside our Javascript bundles by deault. The `ExtractTextPlugin` extracts them into
external `.css` files that the `HtmlWebpackPlugin` inscribes as &lt;link&gt; tags into the `index.html`.
Refer to the Webpack documentation for details on these and other configuation options in this file
Grab the app code at the end of this guide and try:
code-example(format="").
npm start
a(id="production-configuration")
.l-main-section
:marked
### Production Configuration
Configuration of a *production* build resembles *development* configuration ... with a few key changes.
+makeExample('webpack/ts/config/webpack.prod.js', null, 'config/webpack.prod.js')(format=".")
:marked
We don't use a development server. We're expected to deploy the application and its dependencies to a real production server.
This time the output bundle files are physically placed in the `dist` folder.
Webpack generates file names with cache-busting hash.
Thanks to the `HtmlWebpackPlugin` we don't have to update the `index.html` file when the hashes changes.
There are additional plugins:
* **NoErrorsPlugin** - stops the build if there is any error.
* **DedupePlugin** - detects identical (and nearly identical) files and removes them from the output.
* **UglifyJsPlugin** - minifies the bundles.
* **ExtractTextPlugin** - extracts embedded css as external files, adding cache-busting hash to the filename.
* **DefinePlugin** - use to define environment variables that we can reference within our application.
Thanks to the *DefinePlugin* and the `ENV` variable defined at top, we can enable Angular 2 production mode like this:
+makeExample('webpack/ts/src/main.ts', 'enable-prod')(format=".")
:marked
Grab the app code at the end of this guide and try:
code-example(format="").
npm run build
a(id="test-configuration")
.l-main-section
:marked
### Test Configuration
We don't need much configuration to run unit tests.
We don't need the loaders and plugins that we declared for our development and production builds.
We probably don't need to load and process `css` files for unit tests and doing so would slow us down;
we'll use the `null` loader for all CSS.
We could merge our test configuration into the `webpack.common` configuration and override the parts we don't want or need.
But it might be simpler to start over with a completely fresh configuration.
+makeExample('webpack/ts/config/webpack.test.js', null, 'config/webpack.test.js')(format=".")
:marked
Here's our karma configuration:
+makeExample('webpack/ts/config/karma.conf.js', null, 'config/karma.conf.js')(format=".")
:marked
We're telling Karma to use webpack to run the tests.
We don't precompile our TypeScript; Webpack transpiles our Typescript files on the fly, in memory, and feeds the emitted JS directly to Karma.
There are no temporary files on disk.
The `karma-test-shim` tells Karma what files to pre-load and
primes the Angular test framework with test versions of the providers that every app expects to be pre-loaded.
+makeExample('webpack/ts/config/karma-test-shim.js', null, 'config/karma-test-shim.js')(format=".")
:marked
Notice that we do _not_ load our application code explicitly.
We tell Webpack to find and load our test files (the files ending in `.spec.ts`).
Each spec file imports all &mdash; and only &mdash; the application source code that it tests.
Webpack loads just _those_ specific application files and ignores the other files that we aren't testing.
:marked
Grab the app code at the end of this guide and try:
code-example(format="").
npm test
<a id="try"></a>
:marked
## Trying it out
Here is the source code for a small application that we can bundle with the
Webpack techniques we learned in this chapter.
+makeTabs(
`webpack/ts/src/index.html,
webpack/ts/src/main.ts,
webpack/ts/public/css/styles.css`,
null,
`src/index.html,
src/main.ts,
public/css/styles.css`
)
+makeTabs(
`webpack/ts/src/app/app.component.ts,
webpack/ts/src/app/app.component.html,
webpack/ts/src/app/app.component.css,
webpack/ts/src/app/app.component.spec.ts,
webpack/ts/src/app/index.ts`,
null,
`src/app/app.component.ts,
src/app/app.component.html,
src/app/app.component.css,
src/app/app.component.spec.ts,
src/app/index.ts`
)
+makeTabs(
`webpack/ts/src/vendor.ts,
webpack/ts/src/polyfills.ts`,
null,
`src/vendor.ts,
src/polyfills.ts`
)
:marked
The `app.component.html` displays the Angular logo image which we can download
from [here](https://raw.githubusercontent.com/angular/angular.io/master/public/resources/images/logos/angular2/angular.png).
### Highlights:
* There are no &lt;script&gt; or &lt;link&gt; tags in the `index.html`.
The `HtmlWebpackPlugin` inserts them dynamically at runtime.
* The `AppComponent` in `app.component.ts` imports the application-wide css with a simple `import` statement.
* The `AppComponent` itself has its own html template and css files which we load with the `require()` method
supplied by Webpack. Webpack stashes those component-scoped files in the `app.ts` bundle too.
* The `vendor.ts` consists of vendor dependency `import` statements that drive the `vendor.js` bundle.
The application imports these modules too; they'd be duplicated in the `app.js` bundle
if the `CommonsChunkPlugin` hadn't detected the overlap and removed them from `app.js`.
// WHAT GOOD IS THIS?
* We coded our app feature as a [barrel](../glossary.html#barrel).
<a id="conclusions"></a>
:marked
## Conclusions
We've learned just enough Webpack to configurate development, test and production builds
for a small Angular application.
_We could always do more_. Search the web for expert advice and expand your Webpack knowledge.
[Back to top](#top)

View File

@ -85,9 +85,7 @@ a(id="typings")
:marked
Add a **typings.json** file to the project folder and copy/paste the following:
+makeJson('quickstart/ts/typings.1.json', null, 'typings.json')(format=".")
:marked
Add a **typings** folder to the project folder and then add the following file to that *typings* folder:
+makeExample('quickstart/ts/typings.d.1.ts', null, 'typings/typings.d.ts')(format=".")
.l-verbose-section
:marked
Many JavaScript libraries extend the JavaScript environment with features and syntax
@ -491,10 +489,7 @@ figure.image-display
.file app.component.ts
.file main.ts
.file node_modules ...
.file typings
.children
.file typings.d.ts
.file ...
.file typings ...
.file index.html
.file package.json
.file styles.css