Back in the distant past, some very smart people wrote a paper about the past, present and future of user interface software tools. In it they discuss the idea of a tool having a low threshold to learning and a high ceiling of capability. Inevitably, they say, we build tools that start with low threshold and slowly add capability until the high ceiling of capability is reached. Unfortunately, this (almost?) always means that the low threshold to learn is lost amid all the added complexity.
I have seen this happen with FORTRAN, C/C++, Java, JavaScript, and HTML. It’s a pain, but I think it’s inevitable. Interestingly, I think that if you keep things hard, they paradoxically stay simple. The difference between GL, OpenGL and WebGL is really not all that different. There was a big change with the introduction of shaders, but that’s one major shift in something like 20 years.
Now it’s happening to tools. It’s easy to write a quick tool that handles some aspect of development. If it has low threshold for learning and good utility, then it gets picked up and suddenly we have a new way of doing the same old thing. Maybe it’s better, but often it’s just different. The unfortunate result is now we have stacks of frameworks, languages and tools that we don’t understand well. The normal scenario is:
- Have a confounding problem.
- Ask Google/StackOverflow about it.
- Try the responses that seem best until something works
- Move on to the next confounding problem
As a professional developer, I only have so much time to drill down into things to obtain deep understanding. Many times, you have to trust. It’s faith-based coding, and it really reminds me of building a tower from Jenga blocks. We add and subtract things all the time. It’s a miracle that the thing stays up as often as they do.
My adventures with ‘thrangularJS’ has settled down to the point where I’m building reasonably complex pieces that need to be assembled in a particular order. The watcher in IntelliJ will compile TypeScript to JavaScript, but just in the context of that one file. If a change has been made in a TypeScript file, chances are that it will have to ramify through the project. This is one of those things that has to happen using compilers that doesn’t happen with interpreters.
So, I start to look at what the web development community is doing with dependency management these days. The answer seemed to be Grunt with a typescript task. This worked, but it compiled all the files even if only one needed to be changed. What I really wanted was a makefile. But the makefile needed to be triggered by a watcher. It turns out that there has been some thought on this, and it works in a clean way.
Since I’m on a windows box, I’m using GnuMake. It’s extremely stable, last updated in 2006 (when iPhones were introduced, I think). I’m also using Grunt, installed by npm. Not as stable as make, but it’s never done me wrong. Following the install of make, and Grunt the components have to be set up in the project directory:
npm init npm install grunt --save-dev npm install grunt-exec --save-dev npm install grunt-contrib-watch --save-dev
Then we need a makefile. This is mine, and there are probably better ways to do it. But it’s clear (you can learn everything you ever wanted to know about make here):
CC = tsc CFLAGS= --declaration --noImplicitAny --target ES5 --sourcemap build: modules/AppMain.js modules/AppMain.js : directives/WGLA2_directives.js controllers/WGLA1_controller.js \ modules/AppMain.ts $(CC) $(CFLAGS) modules/AppMain.ts classes/WebGlInterfaces.js : classes/WebGlInterfaces.ts $(CC) $(CFLAGS) classes/WebGlInterfaces.ts classes/WebGlCanvasClasses.js : classes/WebGlInterfaces.js \ classes/WebGlCanvasClasses.ts $(CC) $(CFLAGS) classes/WebGlCanvasClasses.ts classes/WebGlComponentClasses.js : classes/WebGlInterfaces.js \ classes/WebGlComponentClasses.ts $(CC) $(CFLAGS) classes/WebGlComponentClasses.ts controllers/WGLA1_controller.js : classes/WebGlInterfaces.js classes/WebGlComponentClasses.js classes/WebGlCanvasClasses.js \ controllers/WGLA1_controller.ts $(CC) $(CFLAGS) controllers/WGLA1_controller.ts directives/WGLA2_directives.js : classes/WebGlInterfaces.js classes/WebGlComponentClasses.js classes/WebGlCanvasClasses.js \ directives/WGLA2_directives.ts $(CC) $(CFLAGS) directives/WGLA2_directives.ts
Last, we need a GruntFile.js to knit it all together:
module.exports = function (grunt) { //grunt.loadNpmTasks('grunt-ts'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-exec'); grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), exec: { make: { command: 'make build' } }, watch: { files: ['**/*.ts', '!**/*.d.ts'], tasks:['exec:make'] //tasks: ['ts'] } }); grunt.registerTask('default', ['watch']); }
And that’s it. Make is a little tricky to learn (high threshold), but has tremendous power and flexibility (high ceiling). Grunt is kept simple and obvious and can be swapped out easily if something better comes along. Other tasks can be added to make such as uglify, test, deploy, pretty much whatever you want. And it’s guaranteed to go in the right order.
It’s a good block to keep in your Jenga tower.