Web pack, es6, and angular on the front end.

Webpack

A module bundler

Web Development is complex

We need tools that make us better developers

  • Minify && Concat Javascript
  • Minify && Concat CSS
  • Vendor Prefix CSS
  • Compile SASS?
  • Transpile es6/Typescript
  • Run unit tests
  • Run e2e tests
  • Run a development server 
  • Lint our Javascript
  • Dependency Management
  • Tree shaking
  • Provide a developer and a build mode

Why Webpack

  • Purpose driven. 
    • Webpack is a module bundler for front end development. 
    • Grunt/Gulp are task runners.
  • A lot of mind share
    • Plenty of tutorials, SO's, and code laying around to help us accomplish our goals. 
  • It plays well with es6 and NPM. 
  • It's fast.

How Do I use it as a developer? 

{
  "name": "yops",
  "version": "1.0.0",
  "description": "Ye Old Programme Shoppe",
  "main": "gs-yops.js",
  "scripts": {
    "start": "node gs-yops",
    "build": "webpack --config webpack.build.js --bail",
    "dev": "webpack-dev-server --history-api-fallback --inline --progress",
    "test": "karma start",
    "test:live": "karma start --auto-watch --no-single-run"
  },
  "repository": {
    "type": "git",
    "url": "git+ssh://git@bitbucket.org/tssherpa/yops.git"
  },
  "author": "Kim Dang (kdang@globesherpa.com)",
  "license": "ISC",
  "homepage": "https://bitbucket.org/tssherpa/yops#readme",
  "dependencies": {
    "angular": "^1.5.0",
    "angular-route": "^1.5.0",
    "angular-ui-router": "^0.2.18",
    "babel-polyfill": "^6.5.0",
    "babel-runtime": "^6.5.0",
    "bootstrap": "^3.3.6",
    "config": "^1.19.0",
    "express": "^4.13.4",
    "gamma-logger": "git+ssh://git@bitbucket.org/tssherpa/gamma-logger.git",
    "newrelic": "^1.25.1"
  },
  "devDependencies": {
    "angular-mocks": "^1.5.0",
    "autoprefixer": "^6.3.2",
    "babel-core": "^6.5.1",
    "babel-loader": "^6.2.2",
    "babel-preset-es2015": "^6.5.0",
    "chai": "^3.5.0",
    "chai-as-promised": "^5.2.0",
    "clean-webpack-plugin": "^0.1.8",
    "compression-webpack-plugin": "0.2.0",
    "css-loader": "^0.23.1",
    "eslint": "^1.10.3",
    "eslint-config-angular": "^0.4.0",
    "eslint-loader": "^1.2.1",
    "eslint-plugin-angular": "^0.15.0",
    "extract-text-webpack-plugin": "^1.0.1",
    "file-loader": "^0.8.5",
    "html-webpack-plugin": "^2.8.1",
    "isparta-instrumenter-loader": "^1.0.0",
    "karma": "^0.13.19",
    "karma-chai": "^0.1.0",
    "karma-chai-as-promised": "^0.1.2",
    "karma-chai-sinon": "^0.1.5",
    "karma-coverage": "^0.5.3",
    "karma-junit-reporter": "^0.3.8",
    "karma-mocha": "^0.2.1",
    "karma-phantomjs2-launcher": "^0.5.0",
    "karma-sinon": "^1.0.4",
    "karma-sinon-chai": "^1.1.0",
    "karma-sourcemap-loader": "^0.3.7",
    "karma-spec-reporter": "0.0.24",
    "karma-webpack": "^1.7.0",
    "mocha": "^2.4.5",
    "node-libs-browser": "^1.0.0",
    "null-loader": "^0.1.1",
    "postcss-loader": "^0.8.0",
    "raw-loader": "^0.5.1",
    "rimraf": "^2.5.1",
    "sinon": "^1.17.3",
    "sinon-chai": "^2.8.0",
    "style-loader": "^0.13.0",
    "webpack": "^1.12.13",
    "webpack-dev-server": "^1.14.1"
  }
}

How Does it work with my app?

import angular from 'angular';
import uirouter from 'angular-ui-router';

import 'bootstrap/dist/css/bootstrap.css';

import navigationModule from './navigation/navigation-module';
import apiModule from './api/api-module';
import {baseUrl} from './base-url';
var locationConfig = function($locationProvider){
  $locationProvider.html5Mode({
    enabled: true,
    requireBase: false
  });
};

locationConfig.$inject = ['$locationProvider'];
angular.module('gammaPhrase', [
  uirouter,
  navigationModule,
  apiModule
])
.config(locationConfig)
.constant('baseUrl', baseUrl);

ecma2015

Javascript finally got good.

const moduleName = 'gammaPhrase.navigation.navigation-ctrl';

class NavigationCtrl {
  constructor(languageList, tagList){
    var navigation = this;
    navigation.languageList = languageList;
    navigation.tagList = tagList;
  }
}

NavigationCtrl.$inject = ['languageList', 'tagList'];
angular.module(moduleName, []).controller('NavigationCtrl', NavigationCtrl);
export default moduleName;

Demo Controller

Demo Service

const moduleName = 'gammaPhrase.api.language';
/**
 * LanguageService provides class methods for interacting with the Language API Resource
 */
export class LanguageService {
  constructor($http, baseUrl){
    this.$http = $http;
    this.baseUrl = baseUrl;
  }

  /**
   * Returns a collection representing available languages
   * @return [Collection] Collection of objects with a id, description, and slug properties.
   */
  getLanguage () {
    return this.$http.get(`${this.baseUrl}language`).then(({data}) => data);
  }
}

LanguageService.$inject = ['$http', 'baseUrl'];
angular.module(moduleName, []).service('LanguageService', LanguageService);

export default moduleName;

Why write our code this way?

  • Easy to reason about
  • Does not rely on anything framework specific except for rendering
    • Highly Portable
    • Requires rebuilding views and minimal logic 
      • Relatively cheap
  • Highly Testable
  • Not Es6 Reliant
    • Es6 is a tool to assist in code design
    • Can be replicated in ecma5

Makes Testing Easier

import {LanguageService} from '../language-service';
import {baseUrl} from '../../base-url';

describe('language api service', () => {
  var $http, languageService, $httpBackend, languageData, languageList;

  beforeEach(() => {
    inject((_$http_, _$httpBackend_) => {
      $http = _$http_;
      $httpBackend = _$httpBackend_;
    });

    languageService = new LanguageService($http, baseUrl);
  });

  describe('getLanguage method', () => {
    beforeEach(() => {
      languageData = [{'id':1,'description':'english','slug':'en'},{'id':2,'description':'spanish','slug':'es'}];
      $httpBackend.whenGET(`${baseUrl}language`).respond(200, {data: languageData});
      languageList = languageService.getLanguage();
      $httpBackend.flush();
    });

    it('should return a promise', () => {
      languageList.should.be.fullfilled;
    });

    it('should return language data', () => {
      languageList.should.become(languageData);
    });
  });
});

Old School

var mockWindow, mockModalSvc, sampleSvcObj;
beforeEach(function(){
  module(function($provide){
    $provide.service('$window', function(){
      this.alert= jasmine.createSpy('alert');
    });
    $provide.service('modalSvc', function(){
      this.showModalDialog = jasmine.createSpy('showModalDialog');
    });
  });
  module('services');
});

beforeEach(inject(function($window, modalSvc, sampleSvc){
  mockWindow=$window;
  mockModalSvc=modalSvc;
  sampleSvcObj=sampleSvc;
}));

Makes the Future attainable

import {Component} from 'angular2/angular2'

@Component({
  selector: 'my-component',
  template: `
    <div>
        Hello my name is {{name}}. 
        <button (click)="sayMyName()">
            Say my name
        </button>
    </div>
  `
})
export class MyComponent {
  constructor() {
    this.name = 'Max'
  }
  sayMyName() {
    console.log('My name is', this.name)
  }
}

What the Future Looks Like, and why.

By bobbiebarker

What the Future Looks Like, and why.

  • 892