Omegapoint

2016-04-24

Effective development workflow for Angular 2.0 and Typescript


If you want to start learning Angular 2.0 and Typescript there are a lot of information available for learning the basics. But if you have some requirements on your development environment and want to use continuous integration then you need something more.

My requirements are:
  • A package manager to deal with frontend modules
  • Write code in a modular way and module resolution should be handled automaticly
  • Hot-reloading of typescript in my local environment
  • The code must transpile down to ES5
  • I don't want to write or maintain build scripts
  • Continuous integration: run unit-tests written in typescript and bundle the project in a single app.js file (ES5)
So is this possible? Yes it is! If we choose JSPM as our package manager and configure Systemjs to fetch all modules.

Prerequisites

Before starting make sure you have NodeJS and NPM installed, Node needs to be version 4.x or greater. I also assume some basic knowledge about Node and NPM in this blog post.

JSPM - another package manager?

Yes, JSPM is one more package manager. But it is first when combined with SystemJS that it becomes powerful. What is SystemJS then? The following description comes from its github repo: "Universal dynamic module loader - loads ES6 modules, AMD, CommonJS and global scripts in the browser and NodeJS."

So we can use several types of modules, and SystemJS loads them as requested into the browser.
In order to use JSPM and SystemJS you need to install jspm and initialize an empty project, this can be done with the following commands:
npm install jspm -g
jspm init -y
Now you can enable SystemJS by a simple script in your index.html:
<!doctype html>
<html>
<head>
  <title>My First Angular2 App</title>
  <script src="node_modules/angular2/bundles/angular2-polyfills.min.js"></script>
  <script src="jspm_packages/system.js"></script>
  <script src="config.js"></script>
</head>
<body>
  <app></app>
  <script>
    System.import("app").then(function(){ console.log("running") });
  </script>
</body>
</html>
JSPM manage packages which needs to be downloaded, but it also updates a configuration file which SystemJS use to map module imports to physical files. This means that the workflow for a developer becomes very easy. For example if I'm using twitter bootstrap for styling I probably want some native Angular2 directives as well. The ng2-bootstrap project is available for this and I only need to perform two simple steps to add it to my application:
1. jspm install ng2-bootstrap
2. import {Alert} from 'ng2-bootstrap/ng2-bootstrap'; //import a component in Typescript

Basic setup

By following this blog by Mario Brendel a basic project can be configured. I've created a seed project here which I will extend in this blog post.
Clone the repo and follow the readme instructions to setup the project in your local enviroment.
The basic setup provides a jspm-enbabled project with hot reloading and typescript support, and we can use the bundle program which jspm includes to create a ES5 version of our application. 

Loading other assets via JSPM

When writing enterprise applications it is common to write stylesheets and html templates as external resources and these must also be bundled before we can deploy our application to a web server.
We don't want to use templateUrl to link in our templates, since that is internal to Angular and nothing that systemjs understand. Instead we should import our templates as we do with .ts files and assign the import to a string variable, this way the template will be included when we create our bundle.
First start by installing the systemjs text plugin:
jspm install text
Now we can import templates and add to our component like this:

import {Component} from 'angular2/core';
import template from './app.component.html!text';
import stylesheet from './app.component.css!text';

@Component({
  selector: 'app',
  template: template,
  styles: [stylesheet]
})
export class AppComponent {
constructor() { }
}
Notice this exclamation mark in the syntax, now we can keep our external resources and they are included when running or bundling our application.

Configure a test runner

We are going to add support for writing unit tests of our angular code by using karma test runner and PhantomJS. We will use the plugin karma-jspm for this, so we can manage dependencies in the same way as we do when running our application.
Add the following npm dependencies:
npm install jasmine-core karma karma-chrome-launcher karma-coverage karma-jasmine karma-jspm karma-phantomjs2-launcher phantomjs-prebuilt traceur --save-dev
Now create a karma.conf.js in the root folder:

// Karma configuration

module.exports = function(config) {
  config.set({
  // base path that will be used to resolve all patterns (eg. files, exclude)
  basePath: '.',
  files: [
    'node_modules/traceur/bin/traceur-runtime.js',
    'node_modules/angular2/bundles/angular2-polyfills.min.js'
  ],
  // frameworks to use
  // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
  frameworks: ['jspm', 'jasmine],
    
  //list of files / patterns to load in the browser
  jspm: {
     loadFiles: [
     'app/**/*.spec.ts'
    ],
     serveFiles: [
     'app/**/*!(*.spec).ts',
     'typings/**/*.*',
     'tsconfig.json'
    ]
  },
  preprocessors: {},
  proxies: {  // avoid Karma's ./base virtual directory
    '/app/': '/base/app',
    '/jspm_packages/': '/base/jspm_packages/',
    '/typings/': '/base/typings/',
    '/tsconfig.json': '/base/tsconfig.json'
  }
  // test results reporter to use
  // possible values: 'dots', 'progress'
  // available reporters: https://npmjs.org/browse/keyword/karma-reporter
  reporters: ['progress'],
  // web server port
  port: 9876,

  colors: true,

  // level of logging
  // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG,
  logLevel: config.LOG_INFO,

  // enable / disable watching file and executing tests whenever any file changes
  autoWatch: false,
    
  // start these browsers
  // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher,
  browsers: ['PhantomJS2'],
    
  // Continuous Integration mode
  // if true, Karma captures browsers, runs the tests and exits,
  singleRun: true,
    
  phantomjsLauncher: {
    // Have phantomjs exit if a ResourceError is encountered (useful if karma exits without killing phantom)
    exitOnResourceError: true
  }
 });
};


The files array is important here, since phantom requires traceur for transpiling. We also need the angular polyfils. Karma is now configured to run all tests in the app folder, with the file extension spec.ts.

Run all tests by executing:
karma start karma.conf.js
When starting to write tests we will use matchers, for example:
expect(value).toBeNull() 
but typescript cannot find the type definitions for the matchers, so we need to add them. This can be done by using the npm module typings:
npm install -g typings
typings install jasmine
 
When the typings have been installed, we are ready to write jasmine tests, and we can run them using karma. 

Run scripts via npm

In order to keep the scripts we need as clean as possible I will use npm and the scripts section from package.json, update the scripts section so it looks like this;

{
   "jspm": {
      "dependencies": {
         "angular2": "npm:angular2@^2.0.0-beta.13",
         "systemjs-hot-reloader": "github:capaj/systemjs-hot-reloader@^0.5.6",
         "ts": "github:frankwallis/plugin-typescript@^4.0.5"
      },
      "devDependencies": {
         "babel": "npm:babel-core@^5.8.24",
         "babel-runtime": "npm:babel-runtime@^5.8.24",
         "core-js": "npm:core-js@^1.1.4"
      }
   },
   "devDependencies": {
      "angular2": "^2.0.0-beta.13",
      "chokidar-socket-emitter": "^0.5.1",
      "es6-shim": "^0.35.0",
      "http-server": "^0.9.0",
      "jspm": "^0.16.32",
      "open": "0.0.5",
      "reflect-metadata": "0.1.2",
      "rxjs": "5.0.0-beta.2",
      "zone.js": "^0.6.9"
   },
   "scripts": {
      "start": "node server.js",
      "test": "karma start karma.conf.js",
      "build": "jspm bundle-sfx app build/app.js",
      "presetup": "npm install -g jspm typings karma-cli",
      "setup": "npm install && jspm install && typings install",
      "setup-ci": "npm install && jspm install && typings install"
   }
}
Now a build job could be a bash script which runs:
npm run setup-ci
npm run test
npm run build
This assuming your ci server have node installed with jspm, typings and karma-cli installed globally.

Setting up the project for the first time and starting the application is also easy using npm:
npm run setup
npm start
Maybe you notice the presetup in the scripts section, npm scripts have pre and post hooks. This means if you name a script setup you can also add scripts named presetup/postsetup which will run before and after the actual script.

Summary

With this project setup we can avoid many problems with maintaining build scripts and we don't have to think much about how scripts are loaded into the browser, SystemJS take care of that for us. Also notice that this project setup is not tightly coupled to angular. If we want another setup with react for example, we just have to install the dependencies for react via jspm. We simplify the build process by using npm scripts only, no need for gulp, bower or grunt. But what if we have more complex scripts which needs to be executed during a build process? We can still use npm for that, you can add a bash script to your project and refer to that script in the scripts section of package.json. With npm, jspm and systemjs we have all the tools we need for a modern web development workflow.

Inga kommentarer:

Skicka en kommentar

Om Omegapoint

Omegapoint AB är ledande rådgivare och experter inom Systemarkitektur, Säkerhet och IT-ledning.

Twitter uppdateringar

Omegapoints kvitterström:

    Andra Omegapointbloggar