Всем привет!

Современное 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.



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

Комментарии (23)


  1. Zakyann
    09.09.2017 17:18
    -4

    Все носятся с ангуляром, как с писаной торбой. Интересно было бы результат увидеть. Киньте ссыль, кто знает, на что-либо реализованное не нём.


    1. vasIvas
      09.09.2017 21:00
      +5

      а с чем носитесь Вы?


      1. Zakyann
        10.09.2017 10:14
        -2

        Мы как-то больше с jQuery и UniGui работаем. jQuery хватает для своих применений. UniGui же сильно ускорил разработку. Какого-то космоса на Ангуляре пока не увидел, но интересно.


      1. stardust_kid
        10.09.2017 14:20
        +1

        Нет бога кроме React'a и Дэн Абрамов пророк Его.


    1. uonick
      09.09.2017 21:12
      +4

      1. Zakyann
        09.09.2017 23:40
        +1

        Спасибо


  1. Varim
    09.09.2017 21:31

    "serve": "webpack-dev-server --config ./webpack.config.js --profile --watch --progress",
    "hmr": "webpack-dev-server --config ./webpack.config.js --profile --watch --progress",
    serve и hmr имеют одинаковый код, это нормально?


    1. maxim1006 Автор
      09.09.2017 21:48

      Привет, по сути эти команды выполняют одинаковый функционал, хотел продемонстрировать 2 разных варианта с hmr и без.


  1. sentyaev
    09.09.2017 22:10
    +2

    Существует много вариантов сборки, решающих эти задачи (angular cli, A2 seed и т. д.). Обычно они имеют сложную структуру, плохо настраиваются/расширяются и представляют собой монолит, который невозможно изменить.

    Не совсем понятно почему вам angular-cli не подходит.
    С ним довольно просто начать, а когда понадобится гибкость в настройке, можно сделать eject и вы получите webpack.json, и настраивайте как хотите.
    Я это к тому что angular-cli под капотом использует тотже webpack.
    В итоге вы получаете
    1. Быстрый старт
    2. Сохраняется возможность гибкой настройки точно также как и при использовании webpack
    3. Рост сложности конфигурации проекта растет вместе с развитием этого самого проекта, а не с первого дня разработки.


    1. maxim1006 Автор
      09.09.2017 22:32

      Добрый вечер. В нашей компании очень много различных проектов: продуктовых, портальных, b2c и т.д. => много специфики, поэтому нужен полный контроль над приложением.


      1. sentyaev
        09.09.2017 23:02
        +2

        Так этож самое интересное что вам такого нужно настраивать и почему нужно тратить кучу времени на старт проекта если есть готовое решение.
        Т.к. все что описано в статье решается следующими несколькими строками:
        npm install -g @angular/cli
        ng new PROJECT-NAME
        cd PROJECT-NAME
        ng serve

        И все, у вас все настроено, сгенерирована структура проекта, сгенерированы конфиги для prod и dev окружений (добавляются новые влет). Дополнительно вы получаете генераторы компонентов и всякого остального.
        Можно через 5 мин уже писать бизнес логику, а потом просто ng build --prod и у вас готовы артефакты для деплоя.

        И главное, как я уже писал, если вам нужна гибкая настройка есть «eject» команда, которая достает webpack конфигурацию.

        Вот мы полтора года пилим проект на A2+ (начинали когда еще angular-cli небыло, но как только она появилась, сразуже перешли, ибо проще) и только месяц назад я сделал этот самый «eject», т.к. решили наше приложение запустать еще и в electron.


  1. devlev
    09.09.2017 23:53

    Очень хорошая статья! Я хоть и не давно перешел на ноду но столкнулся с проблемой начальной организации проекта, ибо от нее слишком много чего потом будет зависеть. У меня тут возникли очень большие сложности, так как на начальном этапе лучше брать что-то готовое, а готовое и удобного как оказалось довольно таки мало.
    Я только не понял как здесь работать, с сервером данных? Можно ли его здесь подтянуть например как middleware?


    1. maxim1006 Автор
      10.09.2017 01:45

      Доброй ночи. Вы можете расширить данный подход Koa или Express сервером. Для лучшего усвоения материала представлена конфигурация для фе разработки.


  1. yarosroman
    10.09.2017 05:14

    Думал, что то интересное есть, проще было написать, возьмите эти файлы и запихайте в проект. Вжух и готово. Зачем ещё для hmr тащить пакеты в проект, webpack и без них прекрасно работает.


  1. justboris
    10.09.2017 12:42
    +1

    Установка глобальных модулей

    А вы знали, что npm подкладывает локальную папку ./node_modules/.bin в $PATH, так что глобальные модули ставить не обязательно?


    Достаточно добавить webpack в package.json, и его можно будет использовать в секции scripts.


    Вот статья с более подробным объяснением: https://www.joezimjs.com/javascript/no-more-global-npm-packages/


  1. justboris
    10.09.2017 13:01
    +2

    Конфиг вебпака у вас получился какой-то чрезмерно усложненный. Почитают люди статьи вроде вашей, и у них складывается впечатление, что проще делать невозможно. А на самом деле можно и нужно.


    1. Зачем вам такая куча профилей и команд для вебпака? По сути, обычно нужны только две
      • build — собирает для продакшена, со AOT и другими оптимизациями
      • dev — запускает dev-server с дебаг-сборкой и hot-reload
        Остальные команды лишь сбивают с толку, чтобы новый разработчик на проекте запутался в их многообразии
    2. Я правильно понимаю, что команда npm run prodServer запускает вебпак просто чтобы открыть статические файлы? Не проще будет использовать однострочник python -mSimpleHTTPServer, который есть в любой системе, или npm-модуль serve, если хочется все на JS? Создание пустого файла ./webpack-prod-server.js чтобы надурить вебпак, это дичайший костыль, не надо так

    После выкидывания лишних команд и другого оверхеда в коде останутся лишь простые бинарные условия, типа


    if(isProd) {
      config.push(new AotPlugin({...}));
      config.push(new webpack.optimize.UglifyJsPlugin());
    }

    Такой конфиг и при разработке понятнее, и статья с ним выглядит аккуратнее


    1. maxim1006 Автор
      10.09.2017 23:28

      Добрый вечер.

      1) представленные команды: dev, hmr — показывают разницу между обычной разработкой и hmr. Prod — собираем прод. Clean наврятли кого-то запутал.

      2) специально показано как не используя доп. Модулей запустить статический сервер.


      1. justboris
        10.09.2017 23:33

        1. От изобилия команд для запуска вебпака наглядность только падает. Определитесь сами, нужен вам hmr или нет, и оставьте только одну из двух. Читатели и пользователи от этого только выиграют.
        2. Не используя дополнительных иструментов можно и гвоздь микроскопом забить, но зачем? Использовать вебпак в качестве статического сервера — тот еще изврат.


  1. justboris
    10.09.2017 13:04
    +1

    А еще советую посмотреть на webpack-blocks, если еще не видели.


    Это набор типовых конфигураций для webpack, которые подключаются в пару строк.


  1. 0x1000000
    10.09.2017 14:29
    +1

    Начали использовать Ангуляр 2 когда он еще был в бете, потом перевели на одну из первых релизных версий и с тех пор не трогали, ибо и так работает :), да и каких-то особых новых возможностей в версии 4 и 5 я не вижу. В то время все примеры, включая Quick Start, были сделаны с использованием System JS загрузчика, который мы до сих пор используем в режиме разработки. И только для подакшена собираем бандлы с помощью Web Pack (которые, однако, тоже загружаются через SystemJS в runtime).
    Недавно посмотрел на последний Quick start и слегка удивился. SystemJS убрали совсем и все делают через Web Pack, что слегка, обескураживает, поскольку при малейшем изменении в коде приходится пересобрать проект, что бы увидеть его на экране. У нас же достаточно сохранить .ts файл и нажать F5 в браузере. Typescript автоматом пересоздаст .js (compile on save = true), System JS его автоматом подхватит. Очень удобно и nodejs постоянно в разработке не нужен поскольку Typescript у нас компилируется через Visual Studio.
    Может я не понял нового подхода, предлагаемого как в Angular Quick start, так и в этой статье? На первый взгляд это кажется очень неудобно.


    1. Starche
      10.09.2017 14:44
      +2

      Запускаете webpack --watch и в принципе это всё, имеете то же самое обновление по F5.
      Только компиляция .ts должна быть настроена не отдельно, а тоже через вебпак.


    1. justboris
      10.09.2017 14:59
      +2

      Если запустить webpack --watch, то он будет отслеживать изменения файлов и пересобирать только часть. Проект целиком пересобирать не нужно. Еще можно использовать webpack-dev-server, c ним еще и страница в браузере сама перезагрузится.


      Польза от вебпака в дев-моде в том, что вы используете один и тот же тул для разработки и продакшена. Так будет меньше шансов словить какой-то баг, который не воспроизвести локально, а только в проде.


      1. 0x1000000
        10.09.2017 15:09
        +1

        Спасибо за совет! Попробую поиграться с --watch