In an era of omnipresent frameworks, libraries and tooling, it may be hard to decide what tool to use and when.
I know from experience, that the first thing you do, once you decide to write a module or CLI tool, is set up an environment. Some people love it, some hate it. But no matter on which side you are, you'll most likely end up spending way too much time doing it, polishing every aspect of the setup.
Sure, you could use webpack, eslint, jasmine or even TypeScript to get great compile error messages. The truth is though, most of the time, as developers, we can get by with tools that require almost no configuration. These "out-of-the-box" tools are usually perfectly acceptable, and will help us jump straight to solving the problem, while providing an almost instant feedback loop.
When talking about the minimal setup, things that come to mind are testing, linting, watching changes and making sure that you won't break anything before committing changes.
Here's a step-by-step to help you go from having nothing, to being productive in five minutes or less (depending on NPM's mood).
Init Node.js project and GIT repository
# Create a directory and cd into it (#protip – $_ holds argument of your last command)
$ mkdir awesome-module && cd $_
# Initialize Node.js project with default settings
$ npm init --yes
# Create initial folders and files
$ mkdir lib test
$ touch index.js lib/meaningOfLife.js test/index.test.js test/meaningOfLife.test.js
# Initialize GIT repository and create initial commit
$ git init
$ git add -A; git commit -am "Awesome Module, day one"
Install tools
Here, we'll use four simple modules, each having a single purpose. Ava for testing, Standard for linting, Chokidar-cli for file watching and Precommit-hook for automatically running npm scripts.
Why those tools? Because they don't require any configuration and take the cognitive load from your brain. One less thing to think and worry about.
$ npm i --save-dev ava standard chokidar-cli precommit-hook
Remember to create a .gitignore
file and add node_modules
to it! We don't want it in our repository.
Set up tools
Open package.json
and add those scripts to your file.
"scripts": {
"test": "ava",
"lint": "standard",
"dev": "chokidar '**/*.js' -c 'standard && ava'"
},
"pre-commit": ["test", "lint"],
Aaaand you are done! Once you run npm run dev
you'll get all of your JS files linted by Standard.js and tests run by Ava. There's nothing more to it, and you can start working right away.
The same goes for creating GIT commits. You won't be able to do so, unless all of your tests are green and linter is happy.
Two things worth noting:
- You don't have to install
standard
orava
globally, as they are run from within thenode
context. - Because we use
&&
instead of;
here in thedev
script, tests won't be triggered unless you pass linter rules. It makes the feedback loop even faster.
Working with the setup
Because the environment assumes you'll work in TDD style (and you probably should!), once you run your dev
script, you can create tests and they will be added to the test suite, without any need for restarting a watcher or rebuilding anything.
Let's create the first test.
// test/meaningOfLife.test.js
const test = require('ava')
const meaningOfLife = require('../lib/meaningOfLife')
test('Real meaning of life', (t) => {
t.is(meaningOfLife(), 42)
})
Once you save the file, you'll get instantly notified that one of your tests is failing. Let's fix it.
// lib/meaningOfLife.js
module.exports = () => 42
And we are back to green! It's as simple as that. Let's create another module that'll multiply a numeric parameter, unit test this module and see whether it'll work nicely with our “meaning of life” (note that it's already an integration test, not unit!).
// lib/multiply.js
module.exports = (number) => {
if (typeof number !== 'number') throw new TypeError('Only numbers can be multiplied!')
return number * 2
}
// test/multiply.test.js
const test = require('ava')
const multiply = require('../lib/multiply')
test('Can multiply numbers', (t) => {
t.is(multiply(10), 20)
})
test('Throws when you try to multiply non-number value', (t) => {
t.throws(() => multiply('ohai!'), 'Only numbers can be multiplied!')
})
Now, to test it all together:
// test/index.test.js
const test = require('ava')
const awesomeModule = require('../index')
test('Works nicely together', (t) => {
t.is(awesomeModule(), 84)
})
// index.js
const meaningOfLife = require('./lib/meaningOfLife')
const multiply = require('./lib/multiply')
module.exports = () => multiply(meaningOfLife())
It works! In just a few minutes, we got everything up and running.
We, as developers, are often charmed by shiny new tools. We seem to forget that those things should make our work easier, faster and less error prone. The simplest solutions are more than enough, more often than we think. Instead of spending an enormous amount of time on the setup, we should spend it on writing the software itself. And following these steps will allow you to do just that.
Once your project starts to grow, you may find yourself in need of something more complex. In most cases, however, it just won't happen. And those tools will suit your needs quite well for a long, long time.
Top comments (23)
The only thing
precommit
package do, is running all the scripts listed in package.json, eg."pre-commit": ["test", "lint"]
before everygit commit
command.When you install it, it sets up everything for you, so that you don't need to do or remember anything.
Sounds good, does it cancel operation
commit
in case of not successful command execution, like 'test' etc?That's exactly how it works. There is
--no-verify
flag (which is a default git flag, rather than any other library thing), but I doesn't recommend using it if you wish to push your commits :)Great article, although ironically I've now wasted 30 minutes trying to get StandardJS to work. It appears to install fine but just cries "command not found" when I try and run it :/ I use plenty of other npm libraries on a daily basis so no idea whats wrong with this one ... Shame
It's because I don't use npm global installation in this article. If you want it to be accessible from anywhere, you have to write
npm install -g standard
.However, if you use it withing
package.json
scripts, then it can be installed locally, without-g
flag and it will still run when done throughnpm run lint
or any othernpm
script.Great post, thanks for advice. It would be really nice if there was an easy way to add ES6 imports/exports to the flow.
There is, but why would you need an ES6 modules in Node.js? It doesn't support it natively anyway. Or do you want to use ES6 modules and just be able to test them like I described in an article?
I know that they're not supported yet, but I hope we'll get it soon. I am a front-end dev, and so in general I have to spend a lot of time tuning webpack/babel before I start a project. And as you probably know, ES6 modules are commonly used in front-end nowadays.
Testing would also be nice.
I got you covered! ;)
gist.github.com/kamilogorek/0a2d7f...
And then you can use ES6 modules in your tests as well as in your code :)
Thanks a lot, Kamil,
you're a real MVP :)
Very helpful! I set it up with the support for ES6-Modules a few posts down! Just starting to write (serious) JS-code, and never used npm / node before. So this covers a lot for me! (TDD, git and more).
<3 <3 <3
Glad I could help! :)
Thanks! This is great post!!! 🙏
Dude, thank you so much! I've been attempting getting a modern JS-based development environment working smoothly for a little while now. This is exactly what I was looking for!
Except only minority of devs use mentioned tools. There are modern tools out there that have much greater support.
Glad I could help! :)
"Because the environment assumes you’ll work in TDD style"
What does TDD mean ?
Test Driven Development, a practice where you write tests first, and the implementation afterwards.
Can read more online, or Test Driven Development: By Example by Kent Beck
Test Driven Development – en.wikipedia.org/wiki/Test-driven_...
Thank you !
Looks nice, apart from Standard. XO achieves the same goal.
Thanks. It's just a matter of a preference, as they both achieve then same thing, as you just said.
Valuable post. Where's the damn like/heart/+1 button on this thing? Need2bookmark