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:
- Angular, for data-binding
- Gulp, for building
- SASS, for styling
- TypeScript, for scripting
- RequireJS, for moduling
- Karma, for test running
- Mocha, for testing
- Chai, for asserting
- 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.log
s
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!