Всем привет!
Современное front-end-приложение на Angular должно включать в себя следующие характеристики:
Существует много вариантов сборки, решающих эти задачи (angular cli, A2 seed и т. д.). Обычно они имеют сложную структуру, плохо настраиваются/расширяются и представляют собой монолит, который невозможно изменить.
В статье я расскажу, как совместить Angular 2+ с webpack и разобраться со всеми этапами сборки/разработки.
Вы удивитесь, как это просто.
Финальное приложение.
Постараюсь по максимуму осветить тонкие моменты. Итак, поехали.
Создаем папку с проектом, чтобы никто не догадался, назовем ее angular-project.
(Использую Webstorm, однако можете проделывать то же самое в вашем редакторе)

Устанавливаем node.js (npm в комплекте по умолчанию).
Создаем package.json, разумеется, количество подключаемых на проект модулей потенциально стремится к бесконечности, однако я оставлю только необходимые, на мой взгляд, для полноценной разработки. Модулей много, постараюсь обосновать, зачем они нужны.
Через терминал заходим в папку, где лежит package.json, и вводим команду npm i.
Так как мы используем команды rimraf, webpack и webpack-dev-server в терминале, то придется объяснить их вашему ПК с помощью команды npm i rimraf webpack webpack-dev-server -g
После этих манипуляций наш проект пополнился папкой node_modules.

Создаем README.md, куда кроме ссылки на эту статью можно добавить особенности разработки вашего проекта.
Создаем tslint.json, тут не буду останавливаться, так как нет серебряной пули.
Создадим postcss.config.js, чтобы не писать префиксы к стилям
Дальше пойдут немного более сложные манипуляции, пожалуйста, сфокусируйтесь.
Так как разработка A2+, на мой взгляд, невозможна без typescript, его надо настроить. Настройки обычные, однако если будут вопросы, спрашивайте в комментариях.
Самое сложное – дать понять webpack, что мы от него хотим. Для этого создаем webpack.conf.js, без паники, постараюсь все объяснить
Сейчас наш проект выглядит так, кроме папки src

Создаем структуру в папке src:

Пара комментариев: в папке app будет лежать наше angular приложение, в папке assets вспомогательные файлы, index.html просто кладем в src. В assets поддержим темизацию и разобьем папки для удобной работы со шрифтами, картинками, стилями.
В нашей компании мы используем БЭМ методологию, немного переработанную и более оптимальную, на наш взгляд. base.less – агрегирующий .less файл для base темы:
Заметим, что, на наш взгляд, следует разносить функциональную и стилевую части приложения: это решает ряд проблем как сборки, так и поддержки проекта. Если использовать БЭМ и парадигму один блок – один less файл, то проблем у подхода не обнаруживается. Однако есть куча альтернатив. Более подробно покопаться в assets можно в приложении, к этому посту. Вопросы задавайте в комментариях к статье.
index.html – стал безумно прост в A2+ приложениях
Вздохнем поглубже, все сложное мы уже сделали, теперь остался сам фреймворк)
Создадим структуру в папке app:

На первый взгляд – ребус.
Однако если вы прошли хотя бы Angular 2+ Tutorial, то все это вам уже знакомо. Для остальных же краткие комментарии: все приложение разбито на модули, фреймворк даже предоставляет такую сущность – module. Есть главный модуль – app.module.ts, есть дополнительные модули, расширяющие функционал приложения. Большая часть приложений будет иметь home, lazy и shared модули. Названия модулей, разумеется, опциональны, однако при соблюдении правил наименования у вас не возникнет проблем с расширяемостью приложения.
Про сам фреймворк говорить много не будем, есть отличная документация. Лучше сосредоточимся на тонких моментах:
С него все начинается
Для AoT (Ahead-of-Time Compilation) режима создаем другой главный файл ng-main-aot.ts, так нужно…
HMR, стили нашего приложения (на всякий случай оставил пример подключения картинок) и настройки hammerjs для мобильной разработки подключаем в app.module.ts таким образом:
Lazy loading модулей подключаем в ng-routing.module.ts
После подключения lazy модуля в роутере необходимо в модуле, который мы хотим загружать отложено, сделать (на примере lazy.module.ts) следующее:
Хм… ну вот в принципе и все. Покопаться в app папке можно в приложении к данному посту.
Для разработки с перезагрузкой странички на каждое изменение кода в редакторе, пишем в терминале, находясь папке с package.json: npm run serve
То же, но без перезагрузки странички: npm run hmr
Делаем prod сборку с AoT: npm run prod
Запускаем статический сервер, чтобы посмотреть prod: npm run prodServer
Почистить ./dist папку: npm run clean
Всего несколько шагов и у нас работают: webpack сборка с Angular 4, AoT, HMR, Lazy loading. Все, включая шаблоны и стили, аккуратно кладется в бандл и оптимизируется.
Разумеется, эту конфигурацию можно расширять, улучшать, менять, однако на мой взгляд, ее вполне достаточно, чтобы смело начать разрабатывать с Angular 2+.
P.S.
Небольшая реклама АoT: отличный boost к производительности вашего SPA приложения на Angular.

Спасибо за внимание.
Современное front-end-приложение на Angular должно включать в себя следующие характеристики:
- Возможность использования типизированного JS — Typescript
- Обеспечение удобства и производительности разработки с помощью HMR (hot module replacement);
- Модульность приложений и возможность отложенной загрузки модулей (Lazy Loading);
- AoT — режим (ahead-of-time), повышающий производительность приложения.
Существует много вариантов сборки, решающих эти задачи (angular cli, A2 seed и т. д.). Обычно они имеют сложную структуру, плохо настраиваются/расширяются и представляют собой монолит, который невозможно изменить.
В статье я расскажу, как совместить Angular 2+ с webpack и разобраться со всеми этапами сборки/разработки.
Вы удивитесь, как это просто.
Финальное приложение.
Постараюсь по максимуму осветить тонкие моменты. Итак, поехали.
1) Создаем проект
Создаем папку с проектом, чтобы никто не догадался, назовем ее angular-project.
(Использую Webstorm, однако можете проделывать то же самое в вашем редакторе)

2) Окружение
Устанавливаем node.js (npm в комплекте по умолчанию).
Создаем package.json, разумеется, количество подключаемых на проект модулей потенциально стремится к бесконечности, однако я оставлю только необходимые, на мой взгляд, для полноценной разработки. Модулей много, постараюсь обосновать, зачем они нужны.
package.json
{
"name": "angular-project",
"version": "1.0.0",
"description": "angular scaffolding",
"author": "maxim1006",
"license": "MIT",
"dependencies": {
//блок необходимых для Angular модулей
"@angular/animations": "^4.3.6",
"@angular/common": "^4.3.6",
"@angular/compiler": "^4.3.6",
"@angular/compiler-cli": "^4.3.6",
"@angular/core": "^4.3.6",
"@angular/forms": "^4.3.6",
"@angular/http": "^4.3.6",
"@angular/platform-browser": "^4.3.6",
"@angular/platform-browser-dynamic": "^4.3.6",
"@angular/router": "^4.3.6",
//модули для hmr
"@angularclass/hmr": "^2.1.1",
"@angularclass/hmr-loader": "^3.0.2",
//polyfills для es5
"core-js": "^2.5.0",
//модуль для работы декораторов в браузере
"reflect-metadata": "^0.1.8",
//модуль для работы с реактивным программированием
"rxjs": "^5.4.3",
//типизация и доп. возможности для js
"typescript": "2.3.4",
//зоны в js, очень интересно, обязательно почитайте
"zone.js": "^0.8.17"
},
"devDependencies": {
//для сборки AoT (Ahead-of-Time Compilation) angular
"@ngtools/webpack": "^1.6.2",
//поддержка типизации, чтобы не ругался typescript
"@types/es6-shim": "^0.31.35",
"@types/jasmine": "^2.5.54",
"@types/node": "^7.0.43",
//routing в приложении
"angular-router-loader": "^0.6.0",
//так как на выходе получится бандл со встроенными темплейтами
"angular2-template-loader": "^0.6.2",
//чтобы не писать префиксы в css
"autoprefixer": "^6.3.7",
//для оптимизации работы typescript в webpack
"awesome-typescript-loader": "^3.2.3",
//если вдруг надо скопировать папку/файл
"copy-webpack-plugin": "^4.0.1",
//для работы с css
"css-loader": "^0.28.5",
"css-to-string-loader": "^0.1.2",
//es6 polyfills
"es6-shim": "^0.35.1",
//для мобильной разработки
"hammerjs": "^2.0.8",
//чтобы webpack работал с html
"html-webpack-plugin": "^2.29.0",
//препроцессор для более удобной работы со стилями
"less": "^2.7.2",
"less-loader": "^4.0.3",
//по завершению сборки сможем вызвать коллбек
"on-build-webpack": "^0.1.0",
//вставляет результат работы webpack на страничку
"raw-loader": "^0.5.1",
//для работы со стилями
"postcss-loader": "^1.3.3",
"style-loader": "^0.17.0",
//линтер
"tslint": "^5.7.0",
//если надо что-нибудь удалить
"rimraf": "^2.6.1",
//чтобы вставить картинки в css в виде base64
"url-loader": "^0.5.8",
//webpack
"webpack": "^3.5.5",
//и его встроенный express сервер
"webpack-dev-server": "^2.7.1"
},
//когда введем в терминале эти команды с помощью npm run __command__ (например npm run serve)выполняться соответствующие команды)
"scripts": {
//Запускаем сервер. При каждом сохранении в вашем редакторе при работе с файлами проекта страничка будет перезагружаться, и вы будете видеть результат. Расскажем подробнее о команде. Для начала запускаем веб-сервер с данными настройками. Если мы хотим видеть в консоли, что с ним происходит (что бандлится и т. д.), используем (флаг --profile); если хотим, чтобы при сохранении в редакторе webpack автоматически обновлял результат, используем (--watch); ну а если хотим видеть проценты компиляции, можем опционально использовать (флаг –-progress).
"serve": "webpack-dev-server --config ./webpack.config.js --profile --watch --progress",
//то же, что и serve, но без перезагрузки страницы
"hmr": "webpack-dev-server --config ./webpack.config.js --profile --watch --progress",
//создаем prod папочку с нашим проектом
"prod": "npm run aot",
//посмотреть как наш проект выглядит в prod, мало ли что
"prodServer": "webpack-dev-server --config ./webpack.config.js --open",
//очищаем ./dist на всякий случай
"clean": "rimraf ./dist",
//нужно, чтобы в webpack.js понять, что мы делаем aot. Делать это необязательно, но для наглядности нужно.
"aot": "webpack",
//тесты для приложения
"test": "karma start"
}
}
3) Установка модулей
Через терминал заходим в папку, где лежит package.json, и вводим команду npm i.
4) Установка глобальных модулей
Так как мы используем команды rimraf, webpack и webpack-dev-server в терминале, то придется объяснить их вашему ПК с помощью команды npm i rimraf webpack webpack-dev-server -g
После этих манипуляций наш проект пополнился папкой node_modules.

5) README.md
Создаем README.md, куда кроме ссылки на эту статью можно добавить особенности разработки вашего проекта.
6) Линтер
Создаем tslint.json, тут не буду останавливаться, так как нет серебряной пули.
tslint.json
{
"rules": {
"no-unused-variable": true,
"curly": true,
"no-console": [
true,
"log",
"error",
"debug",
"info"
],
"no-debugger": true,
"no-duplicate-variable": true,
"no-eval": true,
"no-invalid-this": true,
"no-shadowed-variable": true,
"no-unsafe-finally": true,
"no-var-keyword": true,
"triple-equals": [
true,
"allow-null-check",
"allow-undefined-check"
],
"semicolon": [
true,
"always",
"ignore-interfaces"
],
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
]
}
}
7) PostCss
Создадим postcss.config.js, чтобы не писать префиксы к стилям
postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')({
browsers: [
'last 2 versions'
],
cascade: true
})
]
};
Дальше пойдут немного более сложные манипуляции, пожалуйста, сфокусируйтесь.
8) Настройка Typescript tsconfig.json
Так как разработка A2+, на мой взгляд, невозможна без typescript, его надо настроить. Настройки обычные, однако если будут вопросы, спрашивайте в комментариях.
tsconfig.json
{
//Настраиваем компилятор typescript
"compilerOptions": {
"target": "es5",
"module": "es2015",
"declaration": false,
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false,
"suppressImplicitAnyIndexErrors": true,
"skipLibCheck": true,
"lib": ["es6", "dom"],
"outDir": "./dist/",
"typeRoots": [
"./node_modules/@types/"
]
},
"compileOnSave": false,
"buildOnSave": false,
//наше приложение будет лежать в папке ./src
"include": [
"./src/**/*"
],
//запрещаем typescript обращать внимание на:
"exclude": [
"node_modules/*",
"dist/*",
"dist-serve/*",
"node/*",
"**/*.spec.ts"
],
//настраиваем loader для webpack
"awesomeTypescriptLoaderOptions": {
"forkChecker": true,
"useWebpackText": true,
"useCache": true
},
//нужно для AoT
"angularCompilerOptions": {
"genDir": ".",
"skipMetadataEmit" : true
}
}
9) Настройка Webpack
Самое сложное – дать понять webpack, что мы от него хотим. Для этого создаем webpack.conf.js, без паники, постараюсь все объяснить
webpack.conf.js
"use strict";
//это node модули и webpack плагины, которые понадобятся нам в разработке
const path = require('path');
const fs = require('fs');
const webpack = require('webpack');
const WebpackOnBuildPlugin = require('on-build-webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const AotPlugin = require('@ngtools/webpack').AotPlugin;
//помните, в package.json были команды serve, hmr, prod и т. д.? так вот, текущую команду (например, если вы введете npm run serve, то команда будет называться ‘serve’) можно получить и обработать вот так:
const ENV = process.env.npm_lifecycle_event ? process.env.npm_lifecycle_event : '';
const isStatic = ENV === 'serve';
const isHmr = ENV === 'hmr';
const isProd = ENV === 'prod';
const isTest = ENV === 'test';
const isAot = ENV.includes('aot');
const isProdServer = ENV.includes('prodServer');
//в зависимости от команды, мы будем объяснять webpack что делать
//обычно из webpack.conf.js экспортируется функция, возвращающая объект с настройками
module.exports = function makeWebpackConfig() {
console.log(`You are in ${ENV} mode`); //напомнить что мы запустили
let config = {}; //главный объект с настройками
//если вдруг кто-то выполнит команду npm run prodServer, не выполнив предварительно npm run prod, кидаем напоминалку
if (isProdServer) {
if (!fs.existsSync('./dist')) {
throw "Can't find ./dist, please use 'npm run prod' to get it.";
}
}
//подключаем sourcemaps
if (isHmr || isStatic) {
config.devtool = 'inline-source-map';
} else {
config.devtool = 'source-map';
}
//обозначаем главный файл, который будет создавать webpack. Этот файл доступен в index.html по пути “./ng-app.js”
config.entry = {
'ng-app': './src/app/ng-main.ts'
};
//специально для AoT режима нужно создать другой файл с другим наполнением, так надо…
if (isAot) {
config.entry['ng-app'] = './src/app/ng-main-aot.ts';
}
// Имя файла, который создаст webpack будет 'ng-app’, так как задали filename: '[name].js', также когда запустим prod сборку, результирующий бандл попадет в папку './dist', это указали с помощью path: root('./dist')
config.output = isTest ? {} : {
path: root('./dist'), //root – всего лишь функция, для создания правильных путей относительно папки, в которой находится webpack.config.js
filename: '[name].js'
};
//в свойстве entry при настройке webpack обязательно нужно задать какой-нибудь файл, иначе возникнет ошибка, но в режиме prodServer нам нужно лишь посмотреть на нашу prod сборку. По этой причине и создаем поддельный файл, чтобы сервер ни на что, кроме статики, не отвлекался. Можно в корень проекта, рядом с webpack.conf.js, положить пустой файл webpack-prod-server.js, чтобы в логи сервера не попадала ошибка, что этого файла нет, хотя и без него сервер будет работать.
if (isProdServer) {
config.entry = {
'server': './webpack-prod-server.js'
};
config.output = {};
}
//указываем расширения файлов, с которыми webpack будет работать
config.resolve = {
extensions: ['.ts', '.js', '.json', '.html', '.less', '.svg']
};
//определяем так называемые loaders: если будут вопросы по ним, отвечу в комментариях. Если коротко, тут готовый пример для превращения ts в js, html вставляем в js бандл, less компилируем в css и вставляем в js бандл, картинки до 10 кб в base64 и вставляем в js бандл.
config.module = {
rules: [
{
test: /\.ts$/,
use: isAot ? [{loader: '@ngtools/webpack'}] : [
{
loader: 'awesome-typescript-loader?'
},
{
loader: 'angular2-template-loader'
},
{
loader: 'angular-router-loader'
}
].concat(isHmr ? '@angularclass/hmr-loader?pretty=' + !isProd + '&prod=' + isProd : []),
exclude: [/\.(spec|e2e|d)\.ts$/]
},
{
test: /\.html$/, loader: 'raw-loader',
exclude: [/node_modules\/(?!(ng2-.+))/, root('src/index.html')]
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: "url-loader?name=[name].[ext]&limit=10000&useRelativePath=true"
},
{
test: /\.less$/,
use: [
{loader: "css-to-string-loader"},
{loader: "css-loader"},
{loader: "postcss-loader"},
{loader: "less-loader"}
]
}
]
};
//если работаем не в режиме тестирования, то подключаем webpack плагины
if (!isTest) {
config.plugins = [
//не останавливать webpack warcher при ошибках
new webpack.NoEmitOnErrorsPlugin(),
//передать текущий режим в наши .ts файлы, как их получить в .ts файлах увидите чуть позже
new webpack.DefinePlugin({
'process.env': {
'STATIC': isStatic,
'HMR': isHmr,
'PROD': isProd,
'AOT': isAot
}
}),
//сделать что-то по окончании сборки
new WebpackOnBuildPlugin((stats) => {
console.log('build is done');
})
]
//если работаем в режиме hmr, то подключить плагин для hmr
.concat(isHmr ? new webpack.HotModuleReplacementPlugin() : []);
}
//если вы вызовете команду ‘npm run prod’, то запустите процесс создания prod сборки с AoT
if (isAot) {
config.plugins = [
//нужно для AoT режима
new AotPlugin({
tsConfigPath: './tsconfig.json',
entryModule: root('src/app/app.module.ts#AppModule')
}),
//Оптимизируем полученный бандл
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
screw_ie8: true,
conditionals: true,
unused: true,
comparisons: true,
sequences: true,
dead_code: true,
evaluate: true,
if_return: true,
join_vars: true
},
output: {
comments: false
},
sourceMap: true
}),
//Копируем нужные нам файлы в ./dist папку (js бандл туда положит сам webpack, а мы перенесем то, что нам понадобится дополнительно)
new CopyWebpackPlugin([
{from: 'index.html', context: './src'},
{from: 'assets/themes/base/fonts/**/*', context: './src'},
{from: 'assets/themes/base/images/other-images/**/*', context: './src'},
]),
new WebpackOnBuildPlugin((stats) => {
console.log('build in aot is done');
})
];
}
//Ну и наконец настроим наш webpack-dev-server
config.devServer = {
contentBase: isProdServer ? "./dist" : "./src",//корневая папка сервера, в prod режиме в ./dist, в режиме разработки в ./src
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization"
}, //стандартные заголовки для rest запросов
historyApiFallback: true, //включаем HTML5 history api, очень удобно 1ой строкой
compress: true,//включаем gzip
quiet: false, //ничего лишнего нам выводить в логи не нужно
inline: isHmr || isStatic || isProdServer, //inline mode
hot: isHmr, //включаем hmr, если в hmr режиме
stats: "minimal",
port: 9000,
//модное окно смерти при ошибке от Webpack
overlay: {
errors: true
},
//Опции для webpack warcher
watchOptions: {
aggregateTimeout: 50,
ignored: /node_modules/
}
};
return config;
};
//делаем правильный путь от текущей директории
function root(__path = '.') {
return path.join(__dirname, __path);
}
10) Структура src
Сейчас наш проект выглядит так, кроме папки src

Создаем структуру в папке src:

Пара комментариев: в папке app будет лежать наше angular приложение, в папке assets вспомогательные файлы, index.html просто кладем в src. В assets поддержим темизацию и разобьем папки для удобной работы со шрифтами, картинками, стилями.
В нашей компании мы используем БЭМ методологию, немного переработанную и более оптимальную, на наш взгляд. base.less – агрегирующий .less файл для base темы:
base.less
// Common
@import "themes/base/styles/common/normalize";
@import "themes/base/styles/common/colors";
@import "themes/base/styles/common/common";
@import "themes/base/styles/common/fonts";
@import "themes/base/styles/common/vars";
// Blocks
// (please, add new blocks in alphabetical order)
@import "themes/base/styles/blocks/app-component";
Заметим, что, на наш взгляд, следует разносить функциональную и стилевую части приложения: это решает ряд проблем как сборки, так и поддержки проекта. Если использовать БЭМ и парадигму один блок – один less файл, то проблем у подхода не обнаруживается. Однако есть куча альтернатив. Более подробно покопаться в assets можно в приложении, к этому посту. Вопросы задавайте в комментариях к статье.
11) index.hml
index.html – стал безумно прост в A2+ приложениях
index.html
<!DOCTYPE html>
<html>
<head>
<base href="/"> //нужно для A2+ routing
<meta charset="utf-8">
<title>Landing</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="/img/favicon.ico">
</head>
<body>
<app-component>Loading...</app-component>
<script type="text/javascript" src="./ng-app.js"></script>
</body>
</html>
12) Angular app
Вздохнем поглубже, все сложное мы уже сделали, теперь остался сам фреймворк)
Создадим структуру в папке app:

На первый взгляд – ребус.
Однако если вы прошли хотя бы Angular 2+ Tutorial, то все это вам уже знакомо. Для остальных же краткие комментарии: все приложение разбито на модули, фреймворк даже предоставляет такую сущность – module. Есть главный модуль – app.module.ts, есть дополнительные модули, расширяющие функционал приложения. Большая часть приложений будет иметь home, lazy и shared модули. Названия модулей, разумеется, опциональны, однако при соблюдении правил наименования у вас не возникнет проблем с расширяемостью приложения.
Про сам фреймворк говорить много не будем, есть отличная документация. Лучше сосредоточимся на тонких моментах:
ng-main.ts
С него все начинается
ng-main.ts
import './ng-polyfills'; //чтобы работало в ie 9+
import …
//в настройках webpack мы прокидывали переменные, тут их ловим
if (process.env.STATIC) {
//console.log("******************You are in Dev mode******************");
platformBrowserDynamic().bootstrapModule(AppModule).then(():any => {});
} else if (process.env.HMR) {
//нужно для hmr в Angular
//console.log("******************You are in HMR mode******************");
bootloader(main);
}
export function main() {
return platformBrowserDynamic()
.bootstrapModule(AppModule)
}
ng-main-aot.ts для AoT
Для AoT (Ahead-of-Time Compilation) режима создаем другой главный файл ng-main-aot.ts, так нужно…
ng-main-aot.ts
import …
console.log("******************You are in prod mode******************");
enableProdMode();
platformBrowser()
.bootstrapModuleFactory(<any>AppModuleNgFactory)
.catch(error=>console.log(error));
HMR, стили, hammerjs
HMR, стили нашего приложения (на всякий случай оставил пример подключения картинок) и настройки hammerjs для мобильной разработки подключаем в app.module.ts таким образом:
app.module.ts
require("style-loader!../assets/base.less"); //так подключаем стили через webpack
import …
//настраиваем hammer.js
export class MyHammerConfig extends HammerGestureConfig {
overrides = <any>{
'swipe': {velocity: 0.4, threshold: 20}
}
}
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
HomeModule,
NgRoutingModule
],
providers: [
],
bootstrap: [
AppComponent
]
})
export class AppModule {
constructor(public appRef: ApplicationRef) {}
hmrOnInit(store) {
if (!store || !store.state) return;
if ('restoreInputValues' in store) {
store.restoreInputValues();
}
this.appRef.tick();
delete store.state;
delete store.restoreInputValues;
}
hmrOnDestroy(store) {
let cmpLocation = this.appRef.components.map(cmp => cmp.location.nativeElement);
store.disposeOldHosts = createNewHosts(cmpLocation);
store.state = {data: 'yolo'};
store.restoreInputValues = createInputTransfer();
removeNgStyles();
}
hmrAfterDestroy(store) {
store.disposeOldHosts();
delete store.disposeOldHosts;
}
}
Lazy loading
Lazy loading модулей подключаем в ng-routing.module.ts
ng-routing.module.ts
import …
const routes: Routes = [
{path: '', redirectTo: '/home', pathMatch: 'full'},
{path: 'home', component: HomeComponent},
//так подключаем lazy модули, отдельные .js файлы webpack для них создаст сам
{path: 'lazy', loadChildren: './modules/lazy/lazy.module#LazyModule'},
{path: '**', component: PageNotFoundComponent},
];
@NgModule({
imports: [
RouterModule.forRoot(routes)
],
exports: [
RouterModule
]
})
export class NgRoutingModule { }
После подключения lazy модуля в роутере необходимо в модуле, который мы хотим загружать отложено, сделать (на примере lazy.module.ts) следующее:
lazy.module.ts
import …
const routes: Routes = [
{path: '', component: LazyComponent},
];
@NgModule({
imports: [SharedModule, RouterModule.forChild(routes)],
exports: [LazyComponent],
declarations: [LazyComponent]
})
export class LazyModule {}
Хм… ну вот в принципе и все. Покопаться в app папке можно в приложении к данному посту.
Для разработки с перезагрузкой странички на каждое изменение кода в редакторе, пишем в терминале, находясь папке с package.json: npm run serve
То же, но без перезагрузки странички: npm run hmr
Делаем prod сборку с AoT: npm run prod
Запускаем статический сервер, чтобы посмотреть prod: npm run prodServer
Почистить ./dist папку: npm run clean
Всего несколько шагов и у нас работают: webpack сборка с Angular 4, AoT, HMR, Lazy loading. Все, включая шаблоны и стили, аккуратно кладется в бандл и оптимизируется.
Разумеется, эту конфигурацию можно расширять, улучшать, менять, однако на мой взгляд, ее вполне достаточно, чтобы смело начать разрабатывать с Angular 2+.
P.S.
Небольшая реклама АoT: отличный boost к производительности вашего SPA приложения на Angular.

Спасибо за внимание.