Javascript is an essential tool for building great user interfaces these days, and has some really mature tools for getting a great development environment. One problem I’ve had is there are many different ways to hooks these tools together.
After a lot of trial and error, I found a set that seems to work pretty well for me across different javascript frameworks.
Goals
- write my app with multiple files, but deploy as one js file
- have a machine handle mechanical boilerplate like IIFEs, strict mode, etc
- automated tests (with code coverage), that run whenever I change a file
- static analysis of my code to catch problems
- local webserver to use with my app, that automatically reloads the page whenever I change a file
- use ecmascript 6 (ES6) or other “compiles down to js” systems (e.g. typescript, react)
- use other open source javascript libraries
- use a small number of tools so it’s easier to understand
- cross-platform development (sometimes I’m on windows, sometimes on linux)
This feels like a pretty high bar, but javascript has some crazy capabilities, and it’d be a shame not to use them.
Solution
npm for tasks and dependencies
I use npm as a task runner and for dependencies. It also seems like everything published to bower is also published to npm, so I’m happy to skip other package managers. Just don’t look too closely in the node_modules
folder, it’s madness in there.
The npm scripts are effective for defining tasks without needing to install things globally nor edit my $PATH
. Any node-based CLI tools installed to the local node_modules
folder via npm install
are automatically on the $PATH
when using npm scripts. I’d rather spend a little disk space than manage the unintended consequences of shared global state. It also helps reduce setup time for other developers. We can get away with just git clone && npm install && npm start
.
I generally make a few custom tasks:
npm start
– start a local webserver and spin up a bunch of filesystem watchers to run tests and refresh my browsernpm run build
– create a final bundle for distributionnpm run ci
– run tests and static analysis for continous integration bots
I also tend to make subtasks, and chain them together using the limited syntax that is shared between sh
and cmd.exe
.
exorcist + browserify + plugins for compilation
I use browserify for an awful lot of things. It’s my compiler, translator, minifier, and bundler. I tend to think of it all as compilation, but that’s a simplification.
The most basic feature is using nodejs modules to write my code in many files and specify the dependencies between them using require
statements at the top of each file. This nicely mirrors imports in every other programming environment (e.g. using
in C#, import
in python, require
, require_once
, include
in php, etc).
exorcist creates a separate source map file from the browserify output. This is really useful for testing, since most browsers will load up the source map automatically and show you you’re actual source code in the debugger instead of whatever unreadable garbage that actually gets executed.
Most of the browserify functionality comes through plugins:
- browserify-css – include css in my bundle like
require('./foo.css')
- stringify – include html in my bundle
- babelify – convert ES6 or react into plain old ES5 that the world can actually execute, using the babel compiler
- babel-preset-2015 – lets me use ES6
- babel-preset-react – lets me use react‘s weird syntax
- browserify-istanbul – calculate code coverage during tests, using istanbul.
- uglifyify – compact my code using uglifyJS to reduce load times
- envify – bake build-time environment variables into my code. Mostly for including version information (git tag, commit hash, build number, etc)
- browserify-ngannotate – automate some annoying boilerplate to make angularjs‘s dependency injection work with minified code
eslint + karma + jasmine + phantomjs + plugins for testing
This is probably the first bit of javascript build tooling I ever figured out, and I’ve been using it every since. karma has a “watch” option where it will keep re-running tests as code changes.
- jasmine – library for writing tests
- phantomjs – headless browser to run tests on the command line
- karma + plugins – test runner, executes tests and reports the results
- karma-browserify – pass tests through browserify so I can use ES6,
require
, and calcuate code coverage - karma-coverage – write code coverage reports
- karma-jasmine – run jasmine tests
- karma-phantomjs-launcher – run tests in phantomjs
- karma-junit-reporter – write tests results in junit-compatable xml for CI tools like Jenkins
- karma-browserify – pass tests through browserify so I can use ES6,
- eslint – static analyzer to let me know when I’m writing obviously bad code
watchify + live-server + npm-run-all for active development
The “reload-as-you-edit” features are part of what make javascript such a productive environment for user interfaces, especially when you have multiple monitors. I love having a browser / terminal on one monitor showing the UI and test output, and then my editor on another monitor. It’s really great to be able to simply save a file and glance over and see if my tests pass or the UI looks alright.
- watchify – sister project to browserify, updates a bundle efficiently as you change files
- live-server – HTTP and live-reload server. Watches the filesystem and tells browser windows to refresh themselves when things change
- npm-run-all – cross-platform helper to run several npm scripts in parallel; I have been using parallelshell, but that’s been deprecated
Conclusion
All in all that’s 29 direct dependencies, and probably hundreds or thousands of indirect dependencies. That still feels crazy to me, but I think that’s mostly because I have to hook it all up myself. Something like eclipse or visual studio has a ton of moving parts, but it’s one installer so I tend not to think about it. You can see some examples on some of my hobby projects: c4-lab, chore-cat, and kpi-me.
There’s room for improvement (e.g. debugging tests, speed), but this setup has worked out pretty well for me. In my last post I decided against it, but then found some more tools that really brought it all together.
Most of these kinds of setups include grunt or gulp, but I haven’t really needed them. Between npm scripts, browserify, endless plugins, and shell redirection I can accomplish the same results with one less dependency and config file. I feel like if I did adopt grunt or gulp, I’d basically have the same setup, but with their plugins instead of browserify plugins.