Andrew Fontaine

Mostly code

Angular, TypeScript, RequireJS, and More, Pt. 1

17 Dec 2015

As Angular 2 reaches closer and closer to completion, and as TypeScript releases newer and cooler features, one may want to be prepared for the coming TypeScript revolution. However, sometimes setting things up isn’t as clear as it should be. Fortunately for you, dear reader, I’ve done this a few times now, and am willing to help you out.

Over the course of a few posts, I’m not sure how many yet, we’ll go over setting up an Angular 1 project with the following technologies:

  1. Angular, for data-binding
  2. Gulp, for building
  3. SASS, for styling
  4. TypeScript, for scripting
  5. RequireJS, for moduling
  6. Karma, for test running
  7. Mocha, for testing
  8. Chai, for asserting
  9. and Sinon, for spying.

Wow, what a list. Let’s get cracking.

Basic Project Set-up

We’ll be going over the basic project for now. This post will cover the first 5 items, as I found these to be relatively straight-forward. Testing, on the other hand, can get… testy.

Structure

We want a good project structure to make it easy for developers to get up and running, and make it easy to make changes in the right places.

Having done a few Java things in my day, I’m a big fan of having way too many folders. As such, that’s what I’ll be doing. Granted, the over-all set up will be easily customizable, as our main project structure doesn’t have much of an overall impact.

Step 1, make all the folders. Okay, not too many (for now).

$ tree angular
angular
└── src
    ├── sass
    └── ts

Step 2, install some tools.

$ npm install -g tsd bower gulp

There’s our build tool! We’re also bringing in bower to manage our front-end dependencies, and tsd to manage community-sourced TypeScript definitions for existing JavaScript libraries (like angular).

Step 3, initialize everything, filling out values in the appropriate places.

$ npm init && bower init && tsd init

With the above folder layout, we need to ensure our bower_componenets ends up in an appropriate place. Add the following to a .bowerrc file in the root directory.

{
  "directory": "src/bower_components"
}

Now, let’s install some dependencies. First up, bower.

$ bower install --save angular requirejs

We can always add more as we go, so let’s start small. We need the TypeScript definitions for these libraries, too.

$ tsd install angular require --save

These files will installed into a typings directory, with a file that sources all the downloaded files, called tsd.d.ts. We’ll want to source this file in our own type definition file in the root of our ts directory, so that our IDEs and editors and compilers know what types exist.

Now, we should have something that looks like the following:

$ tree angular
.
├── bower.json
├── package.json
├── src
│   └── bower_components
│       ├── angular
│       │   ├── angular-csp.css
│       │   ├── angular.js
│       │   ├── angular.min.js
│       │   ├── angular.min.js.gzip
│       │   ├── angular.min.js.map
│       │   ├── bower.json
│       │   ├── index.js
│       │   ├── package.json
│       │   └── README.md
│       └── requirejs
│           ├── bower.json
│           ├── README.md
│           └── require.js
├── tsd.json
└── typings
    ├── angularjs
    │   └── angular.d.ts
    ├── jquery
    │   └── jquery.d.ts
    ├── requirejs
    │   └── require.d.ts
    └── tsd.d.ts

8 directories, 19 files

We are nearly there! Next up, configuring the TypeScript compiler. Make a tsconfig.json in the root of your project, and insert the following:

{
  "compilerOptions": {
    "module": "amd",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "sourceMap": true,
    "target": "ES5"
  },
  "compileOnSave": false,
  "buildOnSave": false,
  "exclude": "node_modules",
  "filesGlob": "src/ts/**/*.ts"
}

The filesGlob, while not technically a legitimate option, at the very least will get Atom to update it when new TypeScript files are added.

and drop a references.d.ts in src/ts/.

/// <reference path="../../typings/tsd.d.ts" />

When we eventually compile, our compiler will look at this and know where to find type definitions we downloaded with tsd!

Configuring RequireJS

Crack open an editor, and create a config.ts or main.ts or whatever you feel is fitting, and add the following.

/// <reference path="references.d.ts"/>

require.config({
  paths: {
    "angular": "../bower_components/angular/angular",
  },
  shim: {
    "angular": {
      "exports": "angular"
    },
  }
});

require(["angular", "./app.module"], (angular: angular.IAngularStatic) => {
  angular.element(document).ready(() => {
    angular.bootstrap(document, ["app"]);
  });
});

and in app.module.ts,

import {module} from "angular";

export var app: angular.IModule = module("app", []);

Note the use of the fancier import syntax, that lets us import the things we need without having to type angular. over and over again. If you prefer angular., you can either

import angular = require("angular");

or

import * as angular from "angular";

Let’s go over our config.ts, as being able to edit it later will come in handy.

Our call to require.config() passes in a JSON object (interface in TypeScript) with 2 keys, paths and shim. Both of these keys point to objects. paths maps string keys to, well, paths. Paths relative to the script location, that is. Defining these paths are great for RequireJS’ module resolution. Normally, if we wanted to require Angular, we would have to

//...
import * as angular from "../bower_components/angular/angular"
//...

instead of the easier

import * as angular from "angular"

A path will be added for every front-end library we will need to require over the course of our life. We can also define paths for our own modules, in the event that we have multiple angular modules that need access to things from other modules.

The shim key allows us to define what a library exports and what dependencies it has. This can be necessary if the library hasn’t been written in an AMD module compatible way. Hopefully, we shouldn’t have to do this much.

There, we have an Angular app set up. Good job! Now, we need to load the app somehow. First, we have to compile our TypeScript.

Compiling TypeScript

We need some gulp plugins to lint and compile our TypeScript into JavaScript for browser consumption, so we’ll install our dependencies first.

$ npm install --save-dev gulp gulp-typescript gulp-tslint gulp-sourcemaps

Once that’s done, we can set up a simple, naive gulpfile.js

var gulp = require('gulp');
var tsc = require('gulp-typescript');
var tslint = require('gulp-tslint');
var sourcemaps = require('gulp-sourcemaps');

var tsProject = tsc.createProject('tsconfig.json');

gulp.task('ts:compile', function() {
  return gulp.src('src/ts/**/*.ts')
    .pipe(tslint())
    .pipe(tslint.report('prose'))
    .pipe(sourcemaps.init())
    .pipe(tsc(tsProject)).js
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest('src/js'))
});

Let’s run our gulp task, and watch the magic happen.

$ gulp ts:compile
# lots of output

Fix any errors that are reported until it runs successfully. If we look in src/js, we should be able to examine all the freshly-compiled JavaScript! If you know your JavaScript, you can actually read the output, and, to be honest, it’s not too tough to understand. Next up, loading our JavaScript!

index.html

FINALLY! Finally, we can make our index.html file. Finally, we will be able to load our fancy code into the fancy browser and make some fancy things happen. The best part about RequireJS is that we don’t need 87 billion script tags to load all the fancy scripts that we’ve written. We only need one.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Fancy App</title>
  </head>
  <body>
    <script
      type="text/javascript"
      src="/bower_components/requirejs/require.js"
      data-main="/js/config">
    </script>
  </body>
</html>

Let’s add another gulp plugin so we can serve this bad boy.

$ npm install --save-dev gulp-webserver
...
var webserver = require('gulp-webserver');

gulp.task('serve', ['ts:compile'], function() {
  gulp.src('src')
    .pipe(webserver());
});

Load up http://localhost:8000 and see… a blank page! This is the part where I promise you it is working, and that I get you to add some console.logs just to be safe, but that’s all for this post. Part 2 will involve building actual angular components like controllers, directives, services, factories, and routes.

Cheers!

I ❤ feedback. Let me know what you think of this article on Twitter @afontaine_ca