BAM

Getting Started with Angular and Webpack

Create a starter project using Angular and Webpack.

Objectives

In this tutorial, we’ll build a simple Angular application from scratch using webpack as a bundler

Feel free to follow along, or check out this repository for the end result.

Angular CLI

If you’ve spent much time with Angular, you’ve probably heard of the Angular CLI. It’s a handy tool for scaffolding new projects quickly.

You can set up a cli project like so:

npm install -g @angular/cli
ng new my-cli-app
cd my-cli-app
npm start

That’s all there is to it. The CLI provides many useful features, so it’s worth knowing about.

Why Not Use the CLI?

When it comes to my projects, I prefer not to use the CLI. There are a couple of reasons:

  • The Angular CLI obfuscates the webpack components (until you use ng eject). This is great when you’re new to Angular, but now I prefer to control my own builds.
  • It’s not trivial to reorganize the CLI project structure, and I’m not a fan of having so many configurations at the root.

Really, what it comes down to is customization options, particularly if you are going to use Angular for professional projects. You should learn how to use the Angular CLI, but you should also know how to create a project from scratch (which, conveniently, is the subject of this tutorial).

Project Setup

Create a new directory for your project. In that directory, add a package.json file.

{
    "name": "angular-seed",
    "version": "1.0.0",
    "description": "",
    "scripts": {
    },
    "dependencies": {
        "@angular/common": "4.3.6",
        "@angular/compiler": "4.3.6",
        "@angular/core": "4.3.6",
        "@angular/platform-browser": "4.3.6",
        "@angular/platform-browser-dynamic": "4.3.6",
        "core-js": "2.5.1",
        "rxjs": "5.4.3",
        "zone.js": "0.8.17"
    },
    "devDependencies": {
        "@types/node": "8.0.27",
        "angular2-template-loader": "0.6.2",
        "awesome-typescript-loader": "3.2.3",
        "css-loader": "0.28.7",
        "exports-loader": "0.6.4",
        "html-webpack-plugin": "2.30.1",
        "raw-loader": "0.5.1",
        "typescript": "2.5.2",
        "webpack": "3.5.6",
        "webpack-dev-server": "2.7.1"
    }
}

Install the dependencies listed in package.json using npm:

npm install

Now create two additional directories: config and src. Your project folder should look like this:

project/
|-- config/
|-- src/
|-- package.json

Add Angular and TypeScript

The src directory should contain our application’s entry points and the folder containing the application code.

Entry Points

To create our entry points, add two new files to src: main.ts and polyfill.ts.

main.ts is responsible for bootstrapping the Angular application.

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule).catch((err) => console.error(err));

polyfill.ts contains some shims that are required for the app to run:

// internet explorer ES6
//import 'core-js/es6/symbol';
//import 'core-js/es6/object';
//import 'core-js/es6/function';
//import 'core-js/es6/parse-int';
//import 'core-js/es6/parse-float';
//import 'core-js/es6/number';
//import 'core-js/es6/math';
//import 'core-js/es6/string';
//import 'core-js/es6/date';
//import 'core-js/es6/array';
//import 'core-js/es6/regexp';
//import 'core-js/es6/map';
//import 'core-js/es6/set';

import 'core-js/es6/reflect';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';

Note: if you are planning to run the application in IE, you will need to uncomment all of the import statements. The three at the bottom are required for all browsers.

Setup the Angular Application

Create an app directory in src. Every angular application needs a root component and a root module.

Root Component

Create three files to represent the root component: app.component.ts, app.component.html, and app.component.css.

app.component.ts defines the component and its supporting files.


import { Component } from '@angular/core';@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: [ './app.component.css' ]
})
export class AppComponent { }

app.component.html and app.component.css provide the template and styling for the component, respectively.

<p class="hello">Hello, World!</p>
.hello {
    color: darkblue;
}

Root Module

Create a file called app.module.ts in app – this will load the root component and provide the root node of the application.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
@NgModule({
    imports: [
        BrowserModule
    ],
    declarations: [
        AppComponent
    ],
    providers: [ ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

At this point, your src directory should look like this:

src/
|-- app/
    |-- app.component.css
    |-- app.component.html
    |-- app.component.ts
    |-- app.module.ts 
|-- main.ts
|-- polyfill.ts

TypeScript Configuration

The loader we’ll use to handle TypeScript files with webpack will use the TypeScript compiler. Create a file called tsconfig.json at the project root.

{
    "compilerOptions": {
        "module": "commonjs",
        "moduleResolution": "node",
        "target": "es5",
        "lib": ["es6", "dom"],  
        "typeRoots": ["node_modules/@types"],
        "types": ["node"],  
        "sourceMap": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true
    },
    "exclude": [
        "node_modules"
    ]
}

Webpack Configuration

Now that we have an Angular application, it’s time to create the webpack build we’ll use to run it.

In the config directory, create a file called webpack.dev.js.

const webpack = require('webpack');
const path = require('path');

// constants
const APP_NAME = 'My App';
const OUTPUT_PATH = path.resolve(__dirname, './../dev');
const SOURCE_PATH = path.resolve(__dirname, './../src');

// plugins
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {

    // entry point(s) for the bundle
    entry: {
        'main': './src/main.ts',
        'polyfill': './src/polyfill.ts'
    },

    // configuration options for the files webpack generates
    // filename - naming pattern for the output bundle files
    // sourceMapFilename - naming pattern for the output source map files
    // path - the location where files will be created
    output: {
        filename: '[name].bundle.js',
        sourceMapFilename: '[name].map',
        path: OUTPUT_PATH,
        pathinfo: true // makes devtool: eval more effective
    },

    // specifies the developer tool to use with debugging
    // in a development build, we want the fastest tool, which is eval
    devtool: 'eval',

    // resolve.extensions tells webpack which extensions should be used to resolve modules
    // extensions allows you to use import statements without specifying certain extensions
    // modules specifies the location of node_modules relative to the config file
    resolve: {
        extensions: [ '.ts', '.js' ],
        modules: [ path.resolve(__dirname, './../node_modules') ]
    },

    // module.rules specifies which webpack loaders to use for which file types
    module: {
        rules: [
            { test: /\.ts$/, use: [ 'awesome-typescript-loader', 'angular2-template-loader' ]},
            { test: /\.html$/, loader: 'raw-loader' },
            { test: /\.css$/, use: [ 'exports-loader?module.exports.toString()', 'css-loader' ]}
        ]
    },

    // a list of plugins that are used to further customize the build
    plugins: [
        // this plugin is necessary for angular's routing to work correctly
        new webpack.ContextReplacementPlugin(
            /angular(\\|\/)core(\\|\/)@angular/,
            SOURCE_PATH,
            {}
        ),
        new HtmlWebpackPlugin({
            title: APP_NAME,
            template: './config/index.template.ejs',
            chunksSortMode: 'dependency'
        })
    ],

    // options for running with webpack-dev-server
    devServer: {
        contentBase: OUTPUT_PATH,
        port: 3000
    }

}

Notice that we’re using HtmlWebpackPlugin – this plugin automatically generates the index.html file on build. In order for it to work, we need to add the template file. Create a file called index.template.ejs in the config directory.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title><%= htmlWebpackPlugin.options.title %></title>
        <base href="/">
    </head>
    <body>

        <noscript>
            You must have JavaScript enabled to use this app.
        </noscript>

        <app-root></app-root>

    </body>
</html>

Note that app-root is the same tag we specified in app.component.ts.

Build Scripts

The final pieces are the build scripts, which will allow us to build and run the project.

In package.json, add two lines to the empty scripts object we created before.

    "scripts": {
        "build": "webpack --config ./config/webpack.dev.js",
        "start": "webpack-dev-server --open --config ./config/webpack.dev.js"
    },

The build script will build the code and produce a “dev” directory that contains all the outputted files. The start script uses webpack-dev-server to run a local instance of the application in a browser.

This is the simplest approach – our dev build config file is passed to webpack as a --config parameter. But there’s a good chance that you will eventually add multiple build modes. Rather than specify each config list individually in the scripts, let’s create an intermediate file that selects the configuration based on an environment parameter.

Add a file to the project root called webpack.config.js.

module.exports = function (env) {
    return require(`./config/webpack.${env}.js`)
}

Then, update your package.json scripts:

    "scripts": {
        "build": "webpack --env=dev",
        "start": "webpack-dev-server --open --env=dev"
    },

Now, if you add an alternate build mode with config file webpack.prod.js, you can create a script for it by passing in --env=prod.

Run the Project

Now that everything is set up, we’re ready to run the application. In the command line, run:

npm run start

A new browser window or tab should appear once the application loads, and you’ll see the Hello, World message from app.component.html. Congratulations, you’ve created your first Angular application!

Leave a Reply

Your email address will not be published. Required fields are marked *