From Angular CLI to Webpack 2 and other production solutions (with examples)

Alexander Konovalov
5 min readAug 4, 2017

--

I’m assuming that you have an Angular project (version 2 or 4) generated with Angular CLI 1.0 or higher.

See the full project you may here — https://github.com/sani-banani/angular4-starter

Reasons to migrate from Angular CLI to Webpack:

  1. More configurable (loaders, plugins,…).
  2. And more flexibility for various configurations (local, development, production).

0. Pre-steps:

0.1. Generating and serving an Angular project with Angular CLI (version 1.2.6).

ng new angular4-starter
cd angular4-starter
ng serve

More Angular CLI instructions is here.

0.2. Add some changes for CSS and add fonts and images.

ng build -prod

ng build -prod

1. Extract webpack file

Since Angular CLI v1.0, there’s the “eject” feature, that allows you to extract the webpack config file and manipulate it as you wish.

  1. Run ng eject so Angular CLI generates the webpack.config.js file.
  2. Run npm install so the new dependencies generated by CLI are satisfied

So, we have "ejected": truein .angular-cli.json, new file — webpack.config.js, and modified run scripts in package.json.

2. Transform result ugly webpack config

Refactor ugly config and make configs for local, development and production with WebpackMerge.

webpack — config config/webpack.local.js — env.dev
webpack — config config/webpack.prod.js — env.prod

If we add "ejected": false to .angular-cli.json we can also use the ng command.

ng build -prod

What we see? We have more smaller assets size with webpack than with command ng build -prod.

3. Other production solutions:

3.1. Node JS Server

If we use compression with node js, we have more smaller assets:

Without compression
With compression

server.js

const compression = require('compression');
const express = require('express');
const app = express();

app.set('port', (process.env.PORT || 8081));

// Gzip
app.use(compression());

// Run the app by serving the static files
// in the dist directory
app.use('/', express.static(__dirname + '/dist'));


// Start the app by listening on the default port
app.listen(app.get('port'), function() {
console.log('Angular4 Starter App listening on port ' + app.get('port'));
});

3.2. Lazy Module

Reasons: For best loading https://github.com/mgechev/angular-performance-checklist#lazy-loading-of-resources

File structure of administration module is below

administration-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AdministrationComponent } from './administration.component';
import { UserListComponent } from './user-list/user-list.component';
import { UserInfoComponent } from './user-info/user-info.component';
const routes: Routes = [
{ path: '', component: AdministrationComponent }, children: [
{ path: 'user-list/:page', component: UserListComponent },
{ path: 'user-list', redirectTo: 'user-list/0' },
{ path: 'user-info/:id', component: UserInfoComponent },
{ path: '', redirectTo: 'user-list/0' },
] }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AdministrationRoutingModule { }

app-routing.module.ts

{ path: 'administration', loadChildren: './+administration/administration.module#AdministrationModule' },

3.2. Shared Module for AppModule & LazyModule

Module with shared services and components:

import { NgModule } from '@angular/core';
import { CommonModule } from "@angular/common";
import { FormsModule } from '@angular/forms';
import { MyDatePickerModule } from 'mydatepicker';
import { SelectModule } from 'angular2-select';
import { Tab, Tabs } from '../tabs/tabs';
import { Countries } from './dictionary/countries';
@NgModule({
imports: [
CommonModule,
FormsModule,
MyDatePickerModule,
SelectModule,
],
exports: [
CommonModule,
FormsModule,
MyDatePickerModule,
SelectModule,
Tab, Tabs
],
declarations: [
Tab, Tabs
],
providers: [ Countries ],
})
export class SharedModule {}

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { HttpModule } from '@angular/http';
import { LightboxModule } from 'angular2-lightbox';
import { SharedModule } from './shared/shared.module';
import { BackendService } from './shared/service/backend.service';
import { ApiService } from './shared/service/api.service';
import { AppComponent } from './app.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { RegistrationComponent } from './registration/registration.component';
import { HomeComponent } from './home/home.component';
import { SuccessComponent } from './success/success.component';
import { FilesComponent } from './files/files.component';
import { BreadcrumbComponent } from './breadcrumb/breadcrumb.component';
import { ParticipantsComponent } from './participants/participants.component';
import { VisaComponent } from './visa/visa.component';
import { ProgramComponent } from './program/program.component';
import { FoodComponent } from './food/food.component';
import { PlaceComponent } from './place/place.component';
import { ContactsComponent } from './contacts/contacts.component';
import { DirectionsComponent } from './directions/directions.component';
import { CultureComponent } from './culture/culture.component';
import { UnderConstructionComponent } from './under-construction/under-construction.component';
@NgModule({
declarations: [
AppComponent,
PageNotFoundComponent,
RegistrationComponent,
HomeComponent,
SuccessComponent,
FilesComponent,
BreadcrumbComponent,
ParticipantsComponent,
VisaComponent,
ProgramComponent,
FoodComponent,
PlaceComponent,
ContactsComponent,
DirectionsComponent,
CultureComponent,
UnderConstructionComponent
],
imports: [
AppRoutingModule,
BrowserModule,
HttpModule,
LightboxModule,
SharedModule,
],
providers: [ ApiService, BackendService ],
bootstrap: [ AppComponent ]
})
export class AppModule { }

administration.module.ts

import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { AdministrationRoutingModule } from './administration-routing.module';
import { AdministrationComponent } from './administration.component';
import { UserListComponent } from './user-list/user-list.component';
import { UserInfoComponent } from './user-info/user-info.component';
@NgModule({
imports: [
AdministrationRoutingModule,
SharedModule,
],
exports: [],
declarations: [
AdministrationComponent,
UserInfoComponent,
UserListComponent,
],
providers: [],
})
export class AdministrationModule { }

3.3. Static and Node.js supporting

1) Use hash-urls (#) routing both for static and server mode

@NgModule({
imports: [ RouterModule.forRoot(routes, { useHash: true }) ],
exports: [ RouterModule ]
})

2) Use <base href=”./”>

3.4. Proxy

It is simple to avoid cors problems for local development. Let see examples below for angular-cli and webpack.

1) Angular CLI: Create proxy.conf.json for proxying requests

{
"/backend": {
"target": "http://amazon.com",
"secure": false
}
}

And start it:

ng serve --environment local --proxy-config proxy.conf.json --host 0.0.0.0 --port 8080

2) Webpack: Change webpack.local.js config for local development

const proxyConf = require('../proxy.conf.json');devServer: {
historyApiFallback: true,
stats: 'minimal',
proxy: proxyConf
}

--

--

Alexander Konovalov
Alexander Konovalov

Responses (3)

Write a response