Create Angular Project from Scratch with Webpack

Create Angular project from scratch with Webpack

This post will explain how to create angular project from scratch with webpack and only dependencies, files, loaders and plugins which are really required.

Let’s start

Create folders and files

npm init -y to generate package.json.
This file contains project’s meta-data, dependency list and custom script commands

{
	"name": "tem",
	"version": "1.0.0",
	"description": "",
	"main": "index.js",
	"scripts": {
		"tsc": "tsc",
		"start": "webpack serve",
		"build": "webpack"
	},
	"keywords": [],
	"author": "",
	"license": "ISC"
}

Install dependencies

npm i -S @angular/common @angular/compiler @angular/core @angular/router @angular/platform-browser @angular/platform-browser-dynamic rxjs zone.js

Dependencies

Package Description
@angular/common Contains angular’s built in components, directives, etc (ngIf, ngFor)
@angular/compiler Compiles code to browser runnable
@angular/core Contains angular’s ngModule, Component, @ViewChild etc
@angular/router For routing (pages)
@angular/platform-browser Provides services that are essential to launch and run a browser app
@angular/platform-browser-dynamic To bootstrap app for browser
rxjs Contains Observable, Subject, Operators etc.
zone.js Angular uses internally to detect changes etc.

Install dev dependencies

npm i -D @angular/compiler-cli @ngtools/webpack css-loader html-loader html-webpack-plugin mini-css-extract-plugin postcss postcss-loader postcss-preset-env sass sass-loader to-string-loader typescript webpack webpack-cli webpack-dev-server
Package Description
@angular/compiler-cli Angular uses internally for AOT compiling
@ngtools/webpack Contains angular compiler plugin for webpack
css-loader Used to process css files
html-loader Used to process html files
html-webpack-plugin Help injecting bundles
mini-css-extract-plugin Extract css into its own file
postcss Used to add collection of css plugin/loaders
postcss-loader Used to add postcss plugin
postcss-preset-env Plugin for adding auto-prefixer and optimization
sass Used to convert sass to css
sass-loader Used to add sass plugin
to-string-loader convert css to string
typescript used to transpile typescript to javascript
webpack Used to bundle code
webpack-cli Webpack uses internally
webpack-dev-server Used for development for server

npm run tsc -- --init to generate tsconfig.json

  • Make sure to add tsc as script in package.json scripts object (See above)
  • Update tsconfig target, module and moduleResolution in order to work lazy load.
{
	...
	"target": "ES2015",
	"module": "ES2020",
	"moduleResolution": "node",
	...
}

All other files have to be created manually

project
│   package.json
│   tsconfig.json
│   angular.json
│   webpack.config.js    
└───src
│   │   main.ts
│   │   polyfills.ts
│   │   index.html
│   │   styles.scss
│   └───app
│       │   app.module.ts
│       │   app-routing.module.ts
│       │   app.component.ts
│       │   app.component.html
│       │   app.component.scss
│   └───environments
│       │   environment.ts
│       │   environment.prod.ts

Add content to the files

angular.json

	{
	"$schema": 	"./node_modules/@angular/cli/lib/config/schema.json",
	"version": 1,
	"newProjectRoot": "projects",
	"projects": {
		"sample": {
			"root": "",
			"sourceRoot": "src",
			"projectType": "application",
			"schematics": {
				"@schematics/angular:component": {
					"skipTests": true,
					"style": "scss"
				},
				"@schematics/angular:directive": {
					"skipTests": true
				},
				"@schematics/angular:guard": {
					"skipTests": true
				},
				"@schematics/angular:pipe": {
					"skipTests": true
				},
				"@schematics/angular:service": {
					"skipTests": true
				}
			},
		"prefix": "app",
		"architect": {}
		}
	},
	"defaultProject": "sample",
	"cli": {
		"packageManager": "npm"
	}
}

webpack.config.js

const  path = require('path');
const  sass = require('sass');
const  MiniCssExtractPlugin = require('mini-css-extract-plugin');
const  HtmlWebpackPlugin = require('html-webpack-plugin');
const  AngularCompilerPlugin = require('@ngtools/webpack').AngularCompilerPlugin;  

module.exports = (env) => {
	
	const  isDevelopment = env.WEBPACK_SERVE || false;
	const  sassOptions = [
		{
			loader:  'css-loader',
			options: { esModule:  false }
		},
		{
			loader:  "postcss-loader",
			options: {
				postcssOptions: {
					plugins: [
						[
							"postcss-preset-env",
							{ autoprefixer: { grid:  true } },
						]
					],
				},
			},
		},
		{
			loader:  'sass-loader',
			options: { implementation:  sass }
		}
	];

	const  config = {
	mode:  isDevelopment ? 'development' : 'production',
	resolve: {
		extensions: ['.ts', '.js']
	},
	entry: {
		main:  './src/main',
		styles:  './src/styles.scss'
	},
	output: {
		path:  path.resolve(__dirname, './dist'),
		filename:  '[name].[fullhash].bundle.js',
		chunkFilename:  '[id].[fullhash].chunk.js'
	},
	module: {
		rules: [
			{
				test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
				loader:  '@ngtools/webpack'
			},
			{
				test: /\.html$/,
				loader:  'html-loader',
				exclude:  path.resolve('./src/index.html')
			},
			{
				test: /\.scss$/,
				use: [
					'to-string-loader',
					...sassOptions
				],
				exclude: /styles\.scss/
			},
			{
				test: /styles\.scss/,
				use: [
					MiniCssExtractPlugin.loader,
					...sassOptions
				]
			}
		]
	},
	optimization: {
		minimizer: [`...`],
	},
	plugins: [
		new  MiniCssExtractPlugin(),
		new  HtmlWebpackPlugin({
			template:  './src/index.html',
			chunks: ['main', 'styles'],
			chunksSortMode:  'manual',
			inject:  'body',
			base:  '/'
		}),
		new  AngularCompilerPlugin({
			tsConfigPath:  path.resolve('tsconfig.json'),
			mainPath:  './src/main.ts',
			hostReplacementPaths: {
				'./src/environments/environment.ts':
				isDevelopment ?
					'./src/environments/environment.ts' :
					'./src/environments/environment.prod.ts'
			}
		})
	]
	}
	  

	if (isDevelopment) {
		config.output.filename = '[name].bundle.js';
		config.output.chunkFilename = '[id].bundle.js';

		config.target = 'web';
		config.devtool = 'eval-source-map';
		config.stats = {
		preset:  'minimal'
		};

		config.devServer = {
			open:  true,
			overlay:  true,
			hot:  true,
			liveReload:  false,
			writeToDisk:  env.writeToDisk,
		};
		
		config.optimization = { runtimeChunk:  'single' };
		
		/**Config for styles.scss */
		config.module.rules[3].use.splice(0, 1, 'style-loader');
	}
	return  config;
}

main.ts

import  './polyfills';
import { enableProdMode } from  '@angular/core';
import { platformBrowserDynamic } from  '@angular/platform-browser-dynamic';
import { AppModule } from  './app/app.module';
import { environment } from  './environments/environment';
declare const  module: { hot: { accept: () =>  void; }; };

if (environment.production) {
	enableProdMode();
}  

if (module.hot) {
	module.hot.accept();
}  

platformBrowserDynamic().bootstrapModule(AppModule);

polyfills.ts

import  'zone.js/dist/zone';

index.html

<!DOCTYPE  html>
<html  lang="en">

	<head>
		<meta  charset="UTF-8">
		<meta  http-equiv="X-UA-Compatible"  content="IE=edge">
		<meta  name="viewport"  content="width=device-width, initial-scale=1.0">
		<title>Angular from scratch with webpack</title>
	</head>

	<body>
		<app-root></app-root>
	</body>

</html>

styles.scss

html, body { height: 100%; }
body { margin: 0 }

app.module.ts

import { NgModule } from  '@angular/core';
import { BrowserModule } from  '@angular/platform-browser';
import { BrowserAnimationsModule } from  '@angular/platform-browser/animations';
import { AppComponent } from  './app.component';  
import { AppComponent } from  './app.component';  

@NgModule({
	declarations: [
		AppComponent
	],
	imports: [
		BrowserModule,
		BrowserAnimationsModule,
		AppRoutingModule
	],
	bootstrap: [AppComponent]
})

export  class  AppModule { }

app-routing.module.ts

import { NgModule } from  '@angular/core';
import { RouterModule, Routes } from  '@angular/router';  

const  routes: Routes = [];  

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

app.component.ts

import { Component } from  '@angular/core';

@Component({
	selector:  'app-root',
	templateUrl:  './app.component.html',
	styleUrls: ['./app.component.scss']
})

export  class  AppComponent { }

app.component.html

<h1>App component works</h1>

environment.ts

export  const  environment = {
	production:  false
};

environment.prod.ts

export  const  environment = {
	production:  true
};

Let’s test development, Run

npm start

Development working as expected!

Let’s test production, Run

npm run build

Production working as expected!

Some Details

main.ts

if (module.hot) {
	module.hot.accept();
}

Above code will listen for hot update from webpack and accept the new changes.


environment.ts will be replaced with environment.prod.ts
As you can see production value will be updated in production compilation so that angular will minimize and optimize code.

You can also add other properties which you want to keep different in production


Comments