As Sam introduced recently, B-Line Medical is now using TypeScript, which is a superset of JavaScript. TypeScript lets us have our JavaScript and type it too, but it comes at the cost of having to compile the TypeScript into JavaScript before we can execute anything. This post will show how we set up Grunt—a JavaScript task runner—to do this compilation for us.
Why Use Grunt?
We’re currently developing using WebStorm 8, an IDE by JetBrains. WebStorm actually comes with a TypeScript file watcher, which watches your TypeScript files and automatically compiles them into JavaScript whenever you change anything. This is pretty awesome in theory, but had a few problems in practice:
- The watcher would trigger a compilation for literally every change, so when you’re in the middle of writing a long statement it attempts to compile when you’ve only written half a line. This results in a lot of annoying error messages until you finish what you’re working on.
- The watcher would sometimes silently fail, so everything would seem to be broken because it just wasn’t getting compiled. It often took many iterations of adding and removing the watcher and restarting WebStorm to get the watcher to reactivate.
Running the TypeScript compilation process through Grunt took care of both of these issues, and since we were already using Grunt to handle other things like running tests, this process worked out perfectly.
Installing Grunt
Note: the following assumes a basic familiarity with the command prompt and that you already have node.js installed and have a node.js project to play with. Run all commands from the root directory of a node.js project—all you need to have is a package.json
file, but some uncompiled TypeScript files will be helpful, too! If you’d rather look at the code than try it yourself, feel free to clone this repository in GitHub: https://github.com/blinemedical/GruntTypeScriptWatchExample.
The first step is to install the Grunt Command Line Interface (grunt-cli
). You can do this with:
npm install -g grunt-cli
Make sure to include the -g
flag so the tool will be available from any directory. You should now be able to run Grunt from any directory, and you should see the following output:
grunt-cli: The grunt command line interface. (v0.1.13)
Fatal error: Unable to find local grunt.
If you’re seeing this message, either a Gruntfile wasn’t found or grunt hasn’t been installed locally to your project. For more information about installing and configuring grunt, please see the Getting Started guide:
http://gruntjs.com/getting-started
You’ll notice it said “Unable to find local grunt”. To remedy this, run:
npm install grunt --save-dev
The --save-dev
flag will update your package.json
file so anyone else who checks out your project and does an npm install
will get the Grunt module. Now when you run grunt
you should see:
A valid Gruntfile could not be found. Please see the getting started guide for more information on how to configure grunt: http://gruntjs.com/getting-started
Fatal error: Unable to find Gruntfile.
We’re still failing, but that’s fine—we’re learning how to work through these issues and why. For example, the Gruntfile that’s missing is the file that contains the tasks that Grunt will run for you. For now, create an empty file in the same directory as package.json
named Gruntfile.js
. Running grunt
will now show you:
Warning: Task “default” not found. Use –force to continue.
Aborted due to warnings.
Don’t worry about this warning for now—we will add the default task in the last section of this post.
You will need a few more local node modules to use Grunt to compile TypeScript. Run the following commands:
npm install grunt-typescript --save-dev
and then
npm install grunt-contrib-watch --save-dev
We’ll use the grunt-typescript
module to compile our TypeScript into JavaScript. The grunt-contrib-watch
module will watch our TypeScript files for changes so we can get continuous compilation. Fun fact: modules that start with grunt-contrib
come from the official Grunt development team.
That’s all the setup we need! Now let’s make Grunt do something.
Compiling TypeScript
To use Grunt to compile TypeScript, we’re going to make use of the grunt-typescript
module. Open up your Gruntfile and add the following lines:
module.exports = function (grunt) { grunt.loadNpmTasks('grunt-typescript'); };
This will load the node.js module called grunt-typescript
so Grunt can make use of it.
Now we want to configure the TypeScript task.
module.exports = function (grunt) { grunt.loadNpmTasks('grunt-typescript'); grunt.initConfig({ typescript: { options: { sourceMap: true }, examples: { src: ['examples/**/*.ts'] } } }); };
If you already have some TypeScript in your project, change the filepath
in the src
array to point to your TypeScript files. Otherwise, make a directory named examples
at the same level as package.json
and your Gruntfile. Put a TypeScript file that you want to compile in that directory.
Now run:
grunt typescript
You should see the following output:
Running “typescript:example_files” (typescript) task
2 files created. js: 1 file, map: 1 file, declaration: 0 files (706ms)Done, without errors.
And now there should be a compiled JavaScript file alongside your TypeScript file!
Tasks And Targets
Let’s take a closer look at what we did here. First, grunt.initConfig
sets ups tasks and targets. A task is something like “typescript”: it’s a task that you want Grunt to perform. You can use tasks by getting node.js modules for Grunt tasks, such as grunt-typescript
, or by writing your own tasks (unfortunately, those are beyond the scope of this post).
Targets are subdivisions of tasks, and are frequently used to run the same task with different parameters. For example, we could change our configuration to add another target:
grunt.initConfig({ typescript: { options: { sourceMap: true }, examples: { src: ['examples/**/*.ts'] }, moar_examples: { src: ['moar_examples/**/*.ts'] } } });
Now if we run grunt typescript
we see this output:
Running “typescript:example_files” (typescript) task
2 files created. js: 1 file, map: 1 file, declaration: 0 files (753ms)Running “typescript:moar_example_files” (typescript) task
0 files created. js: 0 files, map: 0 files, declaration: 0 files (4ms)Done, without errors.
Grunt ran the typescript
task, and since we didn’t specify which target we wanted, it ran both of them. In order to only run one target, you specify it with a colon like so:
grunt typescript:examples
TypeScript Options
You may notice that our options
target looks different from the others. The options
target lets us tell grunt-typescript
how we want it to compile our files. In this example, we only specify one option: sourceMap
. This option tells the compiler to also generate *.js.map
files, so our IDE and browser can map the generated JavaScript to the original TypeScript (this is really helpful for debugging).
Options can be specified as its own target, like we did here. That results in the options applying to all of the targets. You can also specify options on a per-target basis, such as:
examples: { options: { sourceMap: true }, src: ['examples/**/*.ts'] }
You can see all the available options at https://github.com/k-maru/grunt-typescript, which is the GitHub repository for the grunt-typescript
module.
Note that grunt-typescript
automatically places the compiled files in the same directory as the originals. If you want to change where the compiled files go, or put all the compiled files in the same file, you can specify a destination for the compilation in a target with the dest
property, for example:
examples: { src: ['examples/**/*.ts'], dest: 'examples/compiled.js' }
Continuous Compilation
So far we’ve made it so Grunt will compile our files whenever we run the grunt typescript
command. This can get inconvenient when you’re actively developing and testing frequently. We will now set up a watcher so Grunt will compile our TypeScript whenever it detects a change in a TypeScript file.
Add the following to the top of your Gruntfile:
grunt.loadNpmTasks('grunt-contrib-watch');
And add another task to your initial configuration:
grunt.initConfig({ typescript: { ... }, watch: { files: ['./**/*.ts'], tasks: ['typescript'] } });
Now you can run grunt watch
and you should see the following:
Running “watch” task
Waiting…
If you change one of your TypeScript files, you should see something like:
>> File “moar_examples\another_example_file.ts” changed.
Running “typescript:example_files” (typescript) task
File C:\Users\lily.seropian\Documents\GruntTypeScriptWatchExample\examples\compiled.js created.
js: 1 file, map: 1 file, declaration: 0 files (746ms)Running “typescript:moar_example_files” (typescript) task
2 files created. js: 1 file, map: 1 file, declaration: 0 files (683ms)Done, without errors.
Completed in 2.118s at Fri Jul 18 2014 11:23:11 GMT-0400 (Eastern Daylight Time) – Waiting…
Note: unless you’re working with an editor that saves your work automatically, like WebStorm, you will have to save your TypeScript file before Grunt will notice it changed.
In the example above, the files
target specifies which files Grunt should watch for changes. The tasks
target tells Grunt what to do when a file Grunt is watching changes. So, when Grunt sees a change on any file ending in .ts
, it runs the typescript
task and compiles the TypeScript into JavaScript.
To end the watch
task, hit Ctrl-C or exit the command prompt.
The Default Task
The default
task is great if you want Grunt to always run multiple things at once, or if you’re just lazy (editor’s note: “efficient” would also work here!). The default
task is what runs when you run the grunt
command without any arguments. To set up the default
task, add the following to the end of your Gruntfile:
grunt.registerTask('default', 'compiles typescript', [ 'typescript', 'watch' ]);
This is just a preview of how to make custom tasks. The registerTask
method registers a new task with Grunt. The first parameter is the name of the task; default
is a special value that instructs the task should be run when Grunt is invoked without any arguments. The next parameter is a simple description of what the task does. The last parameter is a list of Grunt tasks that Grunt should run, in that order.
In our example, Grunt will first compile all the existing TypeScript into JavaScript, then start watching for changes so it can recompile when necessary. Now when you run grunt
, you should see:
Running “typescript:example_files” (typescript) task
File C:\Users\lily.seropian\Documents\GruntTypeScriptWatchExample\examples\compiled.js created.
js: 1 file, map: 1 file, declaration: 0 files (757ms)Running “typescript:moar_example_files” (typescript) task
2 files created. js: 1 file, map: 1 file, declaration: 0 files (703ms)Running “watch” task
Waiting…
That’s everything! Now we’ve set up Grunt to do one-time compilation, continuous compilation, and both. If you’re curious about how to make custom tasks, poke around http://gruntjs.com/creating-tasks. Again, if you want to see this in action, look at or clone https://github.com/blinemedical/GruntTypeScriptWatchExample.