Related: Tightening Nodejs Project, Tightening Jshint.
I use grunt to build my JavaScript projects. There are plugins to do everything, and if something is missing, I can easily write one myself.
As the project matures, there is often a switch from fast feature prototyping and finding what a client would pay for, to increasing the quality (tightening) the project once the contract is signed. There are several grunt plugins that can help tighten the project.
Before showing code examples, note that we have moved all source wild cards into a single config object
1 | var appFiles = { |
Strict dependency versioning
We enforce strict dependency versions using
grunt-nice-package. It removes
wild card symbols from dependencies listed in package.json
file. This ensures
that every build from clean slate is repeatable. You can configure the task
in several ways, but most projects can get by with defaults. Just load the task
and add it to the default pipeline
1 | grunt.loadNpmTasks('grunt-nice-package'); |
Filenames
We enforce consistent file naming scheme using grunt-filenames, for example all lowercase without any other characters.
1 | filenames: { |
There are two built-in schemes you can use instead of specifying regular expression,
dashes
and camelCase
.
Source code linting
We use four linting modules: grunt-contrib-jshint, grunt-eslint, grunt-jscs and grunt-jsonlint.
Jshint was our first linting tool, but we found eslint to be highly configurable, because we could write our own rules easily. For example, we use camel_case to glue client to backend naming scheme. Finally, we use JavaScript style checker jscs to complement jshint and find the remaining stylistic issues.
1 | jshint: { |
As the project matured, we turned on more rules, and we measure how many jshint
rules we specified using grunt-jshint-solid
plugin. It looks at our .jshintrc
file and tells us what percentage of settings we have
specified. Starting project can get by with only basic rules checked, while the production
code needs to follow specific rules, thus we shoot for 100% of rules set. This is not an
argument for tabs or spaces, just picking and checking the consistent white space either way.
Code complexity
Simple code is easy to read, debug and modify. We measure different static complexity metrics using grunt-complexity plugin. For now, we only print the results, but do not break the build if any file is above the threshold (too complex). Later we plan to switch high complexity from being just a warning to an error.
1 | complexity: { |
Hard-coded values
Our source follows strict style, thus we can find some bad smells using regular expressions.
There is a plugin grunt-regex-check that can
check source files against a regular expression. Unfortunately the plugin always breaks on a positive
match. I created a fork that can either warn or break.
For example, let us warn the user when there are URLs in the source code in the form
/endpoint/v*
(the URLs should be injected via config objects instead).
1 | 'regex-check': { |
We allow hard-coded URLs in the test (spec) files.
We can easily ban more regular expressions by adding targets to the task. For example,
let us forbid naming our AngularJs modules starting with ng-
1 | 'regex-check': { |
The regular expression works well because we enforce the same code style (no space between module
and
opening parenthesis, single quotes) using linters.
Technical debt
As we write code, we leave lots of TODOs
sprinkled around. This technical debt stays there
for long time because it is hidden. We keep reminding ourselves there are things to refactor
by finding and showing the todos using grunt-todos
1 | todos: { |
We find todos in the source and test code, writing filename and line number for each.
Conclusion
Once we have implemented certain features and signed a contract, we must make sure it continues to work. This means we need to turn on the screws tighter. Luckily, our build tools has multiple plugins that allows us to measure, warn and even stop the build if something suspicious finds its way into the source code. At present, we are looking into ways to adjust the limits using aged module, see Aged to perfection blog post.