EnterJS 2015 - Continuous Integration for Frontend Code

61

Transcript of EnterJS 2015 - Continuous Integration for Frontend Code

Page 1: EnterJS 2015 - Continuous Integration for Frontend Code
Page 2: EnterJS 2015 - Continuous Integration for Frontend Code

Continuous Integration forFrontend CodePresentation

Marcel Birkner - Software ConsultantBastian Krol - Software Consultant

Page 4: EnterJS 2015 - Continuous Integration for Frontend Code

About codecentric

Big Data Nerds Agile Ninjas Continuous Delivery Gurus

Java Specialists Performance Geeks Hipster Developers

4/62

Page 5: EnterJS 2015 - Continuous Integration for Frontend Code
Page 6: EnterJS 2015 - Continuous Integration for Frontend Code

> 280 employeesAnd we are looking for more!

6/62

Page 7: EnterJS 2015 - Continuous Integration for Frontend Code

Continuous Integration for Frontend CodeWhy should you use it?

What is the benefit?

Page 8: EnterJS 2015 - Continuous Integration for Frontend Code

JavaScript & CSS have grown up

8/62

Page 9: EnterJS 2015 - Continuous Integration for Frontend Code

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

·

·

·

·

9/62

Page 10: EnterJS 2015 - Continuous Integration for Frontend Code

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

·

·

·

10/62

Page 11: EnterJS 2015 - Continuous Integration for Frontend Code

We can do better!

11/62

Page 12: EnterJS 2015 - Continuous Integration for Frontend Code

What we will cover

This is frontend only

Ideal: backend & frontend in one CD pipeline

Asset Optimization

Testing

The Delivery Pipeline

Local Development (Docker)

·

·

·

·

12/62

Page 13: EnterJS 2015 - Continuous Integration for Frontend Code

Asset Optimization

Page 14: EnterJS 2015 - Continuous Integration for Frontend Code

Common Problems

Bad Code Quality

Misconfigured Caching

Lots of assets (JS, CSS, images) ⇒ Lots of HTTP requests ⇒ Slow

·

·

·

14/62

Page 15: EnterJS 2015 - Continuous Integration for Frontend Code

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

·

·

·

·

·

·

·

15/62

Page 16: EnterJS 2015 - Continuous Integration for Frontend Code

Alternatives

Grunt: Gulp | Broccoli | npm | make | ...

ESLint: JSHint | JSLint

SASS: Less | Stylus

Module System + Bundler: Webpack | Browserify

·

·

·

·

16/62

Page 17: EnterJS 2015 - Continuous Integration for Frontend Code

# of HTTP Requests — Concatenation

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

GRUNT

17/62

Page 18: EnterJS 2015 - Continuous Integration for Frontend Code

Download Size — Minification

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

GRUNT

18/62

Page 19: EnterJS 2015 - Continuous Integration for Frontend Code

# of HTTP Requests — Embed Images in CSS

.img-foo { background-image: inline-image("foo.png");}

SCSS

sass: { app: { options: { compass: true, }, files: { '<%= cssTargetDir %>/master.css': '<%= cssSrcDir %>/master.scss', } }},

GRUNT

.img-foo { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAA...');}

CSS

19/62

Page 20: EnterJS 2015 - Continuous Integration for Frontend Code

Use the Browser Cache

Versioned File Namestime stamp: app.min.20150619-0913.js

hash: app.min.2412fbca2a07a.js

content changes ⇒ file name changes

file can be kept in browser cache forever

·

·

·

·

20/62

Page 21: EnterJS 2015 - Continuous Integration for Frontend Code

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', ] },},

GRUNT

21/62

Page 22: EnterJS 2015 - Continuous Integration for Frontend Code

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>

HTML

22/62

Page 23: EnterJS 2015 - Continuous Integration for Frontend Code

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>

HTML

23/62

Page 24: EnterJS 2015 - Continuous Integration for Frontend Code

Development: Turnaround Time Is Important

Production Mode vs. Development Mode

grunt watch

Live Reload

·

·

·

24/62

Page 25: EnterJS 2015 - Continuous Integration for Frontend Code

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

25/62

Page 26: EnterJS 2015 - Continuous Integration for Frontend Code

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>

HTML

26/62

Page 27: EnterJS 2015 - Continuous Integration for Frontend Code

Development Mode - Replace References (cont'd)

Alternative: grunt-usemin to concat, minify & replace in one step

processhtml: { dist: { files: { '<%= appTargetDir %>/index.html': ['<%= appSrcDir %>/index.html'] } }},

GRUNT

27/62

Page 28: EnterJS 2015 - Continuous Integration for Frontend Code

grunt watch

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

GRUNT

28/62

Page 29: EnterJS 2015 - Continuous Integration for Frontend Code

dev-build

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

GRUNT

29/62

Page 30: EnterJS 2015 - Continuous Integration for Frontend Code

Live Reload

See changes instantly

Never press F5 again

Let's see this in action!

·

·

·

30/62

Page 31: EnterJS 2015 - Continuous Integration for Frontend Code

Measure it

Google PageSpeed

Yslow

Fiddler

·

·

·

31/62

Page 32: EnterJS 2015 - Continuous Integration for Frontend Code

Comparison

Unoptimized Version

Optimized Version

32/62

Page 33: EnterJS 2015 - Continuous Integration for Frontend Code

Testing

Page 34: EnterJS 2015 - Continuous Integration for Frontend Code

Front end unit tests

34/62

Page 35: EnterJS 2015 - Continuous Integration for Frontend Code

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)

·

·

·

·

·

·

·

35/62

Page 36: EnterJS 2015 - Continuous Integration for Frontend Code

Karma and Mocha (JS Test Framework)

Running Unit Tests with Karma

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'); })); });

JAVASCRIPT

npm install -g karma-clikarma start karma.conf.js// orgrunt karma

BASH

36/62

Page 37: EnterJS 2015 - Continuous Integration for Frontend Code

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); }));});

JAVASCRIPT

37/62

Page 38: EnterJS 2015 - Continuous Integration for Frontend Code

Chai Assertion Library (BDD/TDD framework)

Should

Expect

Assert

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

JAVASCRIPT

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

JAVASCRIPT

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

JAVASCRIPT

38/62

Page 39: EnterJS 2015 - Continuous Integration for Frontend Code

End2End tests

39/62

Page 40: EnterJS 2015 - Continuous Integration for Frontend Code

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

·

·

·

·

·

40/62

Page 41: EnterJS 2015 - Continuous Integration for Frontend Code

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/); });

});

JAVASCRIPT

41/62

Page 42: EnterJS 2015 - Continuous Integration for Frontend Code

Protractor and Jasmine

Running End2End Tests with Protractor

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);});

JAVASCRIPT

npm install -g protractorwebdriver-manager startprotractor test/client/e2e/conf.js

BASH

42/62

Page 43: EnterJS 2015 - Continuous Integration for Frontend Code

Demo :: Project Staffing App

Page 44: EnterJS 2015 - Continuous Integration for Frontend Code

44/62

Page 45: EnterJS 2015 - Continuous Integration for Frontend Code

45/62

Page 46: EnterJS 2015 - Continuous Integration for Frontend Code

46/62

Page 47: EnterJS 2015 - Continuous Integration for Frontend Code

Project Staffing App - TEST Environmenthttp://54.170.140.7:9000/

Delivery Pipelinehttp://54.75.209.193/jenkins/view/EnterJS-Pipeline/

Info: Server will be shutdown after talk

47/62

Page 48: EnterJS 2015 - Continuous Integration for Frontend Code

Delivery Pipeline

48/62

Page 49: EnterJS 2015 - Continuous Integration for Frontend Code

Delivery Pipeline Steps

49/62

Page 50: EnterJS 2015 - Continuous Integration for Frontend Code

Collect the reports

Mocha reporter ⇒ unit test

Jasmine reporter ⇒ end2end tests

ESLint ⇒ static code analysis

·

·

·

50/62

Page 51: EnterJS 2015 - Continuous Integration for Frontend Code

Mocha Report (unit tests)

51/62

Page 52: EnterJS 2015 - Continuous Integration for Frontend Code

Protractor Report (end2end tests)

52/62

Page 53: EnterJS 2015 - Continuous Integration for Frontend Code

ESLint Report (static code analysis)

53/62

Page 54: EnterJS 2015 - Continuous Integration for Frontend Code

Using Container during Development

Docker / boot2docker

docker-compose aka fig

Docker Hub / Registry

·

·

·

Page 55: EnterJS 2015 - Continuous Integration for Frontend Code

Docker

Container Technology, Lightweight, Portable

55/62

Page 56: EnterJS 2015 - Continuous Integration for Frontend Code

boot2docker

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

56/62

Page 57: EnterJS 2015 - Continuous Integration for Frontend Code

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

BASH

57/62

Page 58: EnterJS 2015 - Continuous Integration for Frontend Code

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 ...

BASH

58/62

Page 59: EnterJS 2015 - Continuous Integration for Frontend Code

Docker Hub

https://registry.hub.docker.com/

Official Repositories: redis, ubuntu, WordPress, MySQL, mongoDB, nodeJS, ...

Share your own Containers

·

·

·

59/62

Page 60: EnterJS 2015 - Continuous Integration for Frontend Code

Do you have comments or questions?

Marcel Birkner - Software ConsultantBastian Krol - Software Consultant

Page 61: EnterJS 2015 - Continuous Integration for Frontend Code