About codecentric

  • Big Data nerds Big Data Nerds
  • Agile Ninjas Agile Ninjas
  • Continuous Delivery Guru Continuous Delivery Gurus
  • Java Enterprise Specialists Java Specialists
  • Performance Geek Performance Geeks
  • Hipster Developer Hipster Developers

> 280 employees

And we are looking for more!

Continuous Integration for Frontend Code

Why should you use it?

What is the benefit?

JavaScript & CSS have grown up

Frontend Code is Mission Critical!

  • Broken JavaScript → broken app
  • Broken CSS → broken layout and/or broken app
  • It impacts the perceived performance drastically
  • Even more so on mobile

And Still ...

  • We often build superior CI/CD pipelines for our backends
  • But frontend code is often neglected
  • For our users, it's always the whole package

We can do better!

What we will cover

  • Asset Optimization
  • Testing
  • The Delivery Pipeline
  • Local Development (Docker)


This is frontend only

Ideal: backend & frontend in one CD pipeline

Asset Optimization

Common Problems

  • Bad Code Quality
  • Misconfigured Caching
  • Lots of assets (JS, CSS, images) ⇒ Lots of HTTP requests ⇒ Slow

Tools

  • Grunt — Task Runner
  • ESLint — Static Code Analysis
  • grunt-contrib-concat — Concatenation
  • grunt-contrib-uglify/UglifyJS — Minification
  • SASS — CSS preprocessor
  • compass — images → base 64 data URIs
  • grunt-version-assets — Versioned File Names

Alternatives

  • Grunt: Gulp | Broccoli | npm | make | ...
  • ESLint: JSHint | JSLint
  • SASS: Less | Stylus
  • Module System + Bundler: Webpack | Browserify

# of HTTP Requests — Concatenation

concat: {
  options: {
    separator: '\n;'
  },
  app: {
    src: [
      '<%= jsSrcDir %>/js/**/*.js',
    ],
    dest: '<%= jsTargetDir %>/app.js'
  }
}     

Download Size — Minification

uglify: {
  app: {
    files: {
      '<%= jsTargetDir %>/app.min.js': [ '<%= jsTargetDir %>/app.js' ],
    }
  }
}     

# of HTTP Requests — Embed Images in CSS

.img-foo {
  background-image: inline-image("foo.png");
}     
sass: {
  app: {
    options: { compass: true, },
    files: { '<%= cssTargetDir %>/master.css': '<%= cssSrcDir %>/master.scss', }
  }
},    
.img-foo {
  background-image:
    url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAA...');
}     

Use the Browser Cache

Versioned File Names

  • time stamp: app.min.20150619-0913.js
  • hash: app.min.2412fbca2a07a.js
  • content changes ⇒ file name changes
  • file can be kept in browser cache forever

Versioned File Names (cont'd)

versioning: {
  options: {
    grepFiles: [ '<%= appTargetDir %>/**/*.html', ]
  },
  css: {
    src: [
      '<%= cssTargetDir %>/app.min.css',
    ]
  },
  js: {
    src: [
      '<%= jsTargetDir %>/app.min.js',
      '<%= jsTargetDir %>/vendor.min.js',
    ]
  },
},

Versioned File Names — Before

        <!DOCTYPE html>
        <html lang="en">
          <head>
            ...
            <link href="css/app.min.css" rel="stylesheet">
            ...
          </head>
          <body>
            ...
            <script src="js/vendor.min.js" type="text/javascript"></script>
            <script src="js/app.min.js" type="text/javascript"></script>
          </body>
        </html>
      

Versioned File Names — After

        <!DOCTYPE html>
        <html lang="en">
          <head>
            ...
            <link href="css/app.min.b678e30139fc04.css" rel="stylesheet">
            ...
          </head>
          <body>
            ...
            <script src="js/vendor.min.dda09628f6e1da.js" type="text/javascript"></script>
            <script src="js/app.min.8e46534a4f66158.js" type="text/javascript"></script>
          </body>
        </html>
      

Development: Turnaround Time Is Important

  • Production Mode vs. Development Mode
  • grunt watch
  • Live Reload

Production Mode versus Development Mode

Production Development
JS concatenated, minified source files, not minified
CSS compiled (SASS), concatenated, minified only compiled (SASS)
Images Embedded into CSS Embedded into CSS (by Sass/Compass)
HTML references optimized assets references source assets

Development Mode - Replace References

        <!DOCTYPE html>
        <html lang="en">
          <head>
            ...
            <!-- build:css css/app.min.css -->
            <link rel="stylesheet" href="css/master.css">
            <link rel="stylesheet" href="css/dashboard.css">
            ...
            <!-- /build -->
          </head>
          <body>
            <!-- build:js js/app.min.js -->
            <script src="js/app.js" type="text/javascript"></script>
            <script src="js/routes.js" type="text/javascript"></script>
            ...
            <!-- /build -->
         </body>
        </html>
      

Development Mode - Replace References (cont'd)

processhtml: {
  dist: {
    files: {
      '<%= appTargetDir %>/index.html': ['<%= appSrcDir %>/index.html']
    }
  }
},
      
Alternative: grunt-usemin to concat, minify & replace in one step

grunt watch

watch: {
  files: [
    '<%= jsSrcDir %>/**/*.js',
    '<%= cssSrcDir %>/**/*.scss',
    '<%= htmlSrcDir %>/**/*.html',
  ],
  tasks: [
    'dev-build',
  ],
  options: {
    livereload: true,
  }
}

dev-build

grunt.registerTask('dev-build', [
  'copy:cssThirdParty',
  'sass',
]);

Live Reload

  • See changes instantly
  • Never press F5 again
  • Let's see this in action!

Measure it

  • Google PageSpeed
  • Yslow
  • Fiddler

Comparison

Unoptimized Version

Not Optimized

Optimized Version

Optimized

Testing

Front end unit tests

Karma

  • Open Source Test Runner
  • Created by the AngularJS team
  • Write tests in Jasmine, Mocha, QUnit
  • CI support (Jenkins, Travis)
  • Based on Node.js and Socket.io
  • Run in Headless Modus with PhantomJS
  • Supported Browsers: Firefox, Chrome, Safari, IE (Desktop and Mobile)

Karma and Mocha (JS Test Framework)

  var expect = chai.expect;
  beforeEach(module('project-staffing'));
  describe('UpperCase Test', function() {
    it('should convert first charactor to UpperCase', inject(function(uppercaseFilter) {
      expect(uppercaseFilter('a')).to.equal('A');
      expect(uppercaseFilter('hello world')).to.equal('Hello World');
    }));
  }); 

Running Unit Tests with Karma

npm install -g karma-cli
karma start karma.conf.js
// or
grunt karma

Sinon (Mocking Framework)

var ActivityService;
var $http;

beforeEach(inject(function(_ActivityService_, _$http_) {
  ActivityService = _ActivityService_;
  $http = _$http_;
  sinon.stub($http, 'post', function(){});
}));

describe('Activity Service', function() {
  it('should have send http POST to backend after saving one activity',
      inject(function(ActivityService) {
    ActivityService.saveActivity('user', 'action', 'object');
    expect($http.post.callCount).to.equal(1);
  }));
});

Chai Assertion Library (BDD/TDD framework)

Should

chai.should();
foo.should.be.a('string');
foo.should.equal('bar');

Expect

var expect = chai.expect;
expect(foo).to.be.a('string');
expect(foo).to.equal('bar');

Assert

var assert = chai.assert;
assert.typeOf(foo, 'string');
assert.equal(foo, 'bar');

End2End tests

Protractor

  • Open Source E2E Testframework for AngularJS Apps
  • Tests run in a real browser
  • Tests can be written with Jasmine (default), Mocha, Cucumber
  • No more waits and sleeps
  • Build with Node.js on top of WebdriverJS

Protractor and Jasmine (BDD framework)

describe('Manage customer', function() {
  var ptor;

  beforeEach(function() {
    browser.get('/');
    ptor = protractor.getInstance();
    element(by.id('navEmployees')).click();
    element(by.id('navListEmployees')).click();
  });

  it('should navigate to list employees page', function() {
    expect(ptor.getCurrentUrl()).toMatch(/#\/list-employees/);
  });

});

Protractor and Jasmine

it('should find employee Maria on list search page', function() {
  createMultipleEmployees(); // Creates employees: Max, Maria, Daniel, John
  ...
  element(by.id('searchText')).sendKeys('Ma');
  expect(element.all(by.id('employee')).count()).toBe(2);
  element(by.id('searchText')).sendKeys('ria');
  expect(element.all(by.id('employee')).count()).toBe(1);
});

Running End2End Tests with Protractor

npm install -g protractor
webdriver-manager start
protractor test/client/e2e/conf.js

Demo :: Project Staffing App

Delivery Pipeline

Delivery Pipeline Steps

Collect the reports

  • Mocha reporter ⇒ unit test
  • Jasmine reporter ⇒ end2end tests
  • ESLint ⇒ static code analysis

Mocha Report (unit tests)

Protractor Report (end2end tests)

ESLint Report (static code analysis)

Using Container during Development



  • Docker / boot2docker
  • docker-compose aka fig
  • Docker Hub / Registry

Docker

Container Technology, Lightweight, Portable


boot2docker

Based on Tiny Core Linux (required for MacOS and Windows)


docker-compose

        project-staffing git:(master) ✗ docker-compose

        Commands:
          build     Build or rebuild services
          help      Get help on a command
          kill      Kill containers
          logs      View output from containers
          port      Print the public port for a port binding
          ps        List containers
          pull      Pulls service images
          rm        Remove stopped containers
          run       Run a one-off command
          scale     Set number of containers for a service
          start     Start services
          stop      Stop services
          restart   Restart services
          up        Create and start containers

docker-compose up

  ➜  project-staffing git:(master) ✗ docker-compose up
  Recreating projectstaffing_mongodb_1...
  Creating projectstaffing_nodejsserver_1...
  Building nodejsserver...
  Step 0 : FROM tcnksm/centos-ruby
   ---> 255207061af8
  Step 1 : RUN yum install -y npm
   ---> Using cache
   ---> c8ca0ad1bec0
  Step 2 : COPY . /opt/project-staffing/
   ---> dc70b159f357
  ...
  Step 5 : CMD node /opt/project-staffing/server.js
   ---> Running in 78d831b9f0f0
   ---> 88b07ba248a0
  Successfully built 88b07ba248a0
    ...

Docker Hub

  • https://registry.hub.docker.com/
  • Official Repositories: redis, ubuntu, WordPress, MySQL, mongoDB, nodeJS, ...
  • Share your own Containers

Links

Thank You!

Do you have comments or questions?