A Simple Node Checklist

Gleb Bahmutov, PhD

March 7, 2018 @ 14:00
Mont-Royal

@bahmutov   @confooca   #confoo

C / C++ / C# / Java / CoffeeScript / JavaScript / Node / Angular / Vue / Cycle.js / functional

JavaScript ninja, image processing expert, software quality fanatic, Microsoft MVP

@bahmutov   @confooca   #confoo

Cypress.io open source E2E test runner

JavaScript ninja, image processing expert, software quality fanatic, Microsoft MVP

@bahmutov   @confooca   #confoo

70 blog posts about Node

160 blog posts about JavaScript

  • Roman Empire

  • JavaScript

  • NPM

  • package.json and .npmrc

  • Checklists

  • How to automate

@bahmutov   @confooca   #confoo

tip: reload page to get new funny definition of "NPM"

I ❤️ publishing NPM modules

2000 years ago ....

@bahmutov   @confooca   #confoo

"Divide and Rule (Conquer)" 

@bahmutov   @confooca   #confoo

@bahmutov   @confooca   #confoo

@bahmutov   @confooca   #confoo

Factoring out a piece of code

  • Creates clear boundary (API)

  • Forces documenting it

  • Allows iteration behind versions

@bahmutov   @confooca   #confoo

README.md

Name and description

CI and other badges

Requirements and install

Example

Links and license

@bahmutov   @confooca   #confoo

About 8 years ago

Happily coding in C++ / C#

I need a library to do X...

@bahmutov   @confooca   #confoo

Finding a cross-platform C++ library is ... hard

Qt, GTK, .... autoconf, Makefiles

@bahmutov   @confooca   #confoo

Incredibly fragile

@bahmutov   @confooca   #confoo

About 5 years ago

Back to Java and ... JS?

Java Server

Swing UI

Client Browser

JS UI

@bahmutov   @confooca   #confoo

undefined is not a function

@bahmutov   @confooca   #confoo

JavaScript has a bad reputation

ES5 got wide adoption

= browsers are mostly the same!

@bahmutov   @confooca   #confoo

+ Node.js for the server

About 5 years ago

More info on how an idea becomes an ES feature

@bahmutov   @confooca   #confoo

ES standard is improving every year

@bahmutov   @confooca   #confoo

@bahmutov   @confooca   #confoo

JavaScript around us

  • Browser

  • Server

  • Desktop

  • Mobile

  • Gray areas:

    • IoT, robots, code snippets, build scripts, sw

@bahmutov   @confooca   #confoo

NPM Registry

aka OSS heaven

@bahmutov   @confooca   #confoo

@bahmutov   @confooca   #confoo

@bahmutov   @confooca   #confoo

source: https://npms.io

Let's write and share some JS code!

@bahmutov   @confooca   #confoo

mkdir foo-bar && cd foo-bar
npm init
name: (foo-bar) 
version: (1.0.0) 
description: Example module
...

@bahmutov   @confooca   #confoo

$ cat package.json 
{
  "name": "foo-bar",
  "version": "1.0.0",
  "description": "Example module",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "example",
    "node",
    "npm"
  ],
  "author": "Gleb Bahmutov <gleb.bahmutov@gmail.com>",
  "license": "MIT"
}
$ npm init -y
$ cat package.json
{
  "name": "foo-bar",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

@bahmutov   @confooca   #confoo

Something is missing ...

$ npm home debug

Something is missing ...

$ npm issues debug

package.json

{
  "name": "foo-bar",
  "version": "1.0.0",
  "description": "Example module",
  "author": "Gleb Bahmutov <gleb.bahmutov@gmail.com>",
  "bugs": "https://github.com/bahmutov/foo-bar/issues",
  "homepage": "https://github.com/bahmutov/foo-bar#readme",
  "repository": {
    "type": "git",
    "url": "https://github.com/bahmutov/foo-bar.git"
  },
  "license": "MIT"
}

and many, many more!

@bahmutov   @confooca   #confoo

{
  "name": "stop-build",
  "version": "0.0.0-development",
  "description": "Exits with non-zero code if there are modified Git files",
  "author": "Gleb Bahmutov <gleb.bahmutov@gmail.com>",
  "bugs": "https://github.com/bahmutov/stop-build/issues",
  "config": {
    "next-update": {
      "commands": {
        "pre-git": "./.git/hooks/pre-commit"
      }
    },
    "pre-git": {
      "commit-msg": "simple",
      "pre-commit": [
        "npm run pretest",
        "npm run deps",
        "npm run ban",
        "git add ."
      ],
      "pre-push": [
        "npm test",
        "npm run secure",
        "npm run license",
        "npm run ban -- --all",
        "npm run size"
      ],
      "post-commit": [],
      "post-merge": []
    }
  },
  "engines": {
    "node": ">=6"
  },
  "files": [
    "src/*.js",
    "!src/*-spec.js"
  ],
  "bin": {
    "stop-build": "src/index.js"
  },
  "homepage": "https://github.com/bahmutov/stop-build#readme",
  "keywords": [
    "build",
    "ci",
    "exit",
    "git",
    "utility"
  ],
  "license": "MIT",
  "main": "src/",
  "publishConfig": {
    "registry": "http://registry.npmjs.org/"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/bahmutov/stop-build.git"
  },
  "scripts": {
    "ban": "ban",
    "deps": "deps-ok",
    "issues": "git-issues",
    "license": "license-checker --production --onlyunknown --csv",
    "pretty": "prettier-standard 'src/*.js'",
    "prelint": "npm run pretty",
    "lint": "standard --verbose --fix src/*.js",
    "pretest": "npm run lint",
    "secure": "nsp check",
    "size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";",
    "test": "npm run unit",
    "unit": "mocha src/*-spec.js",
    "semantic-release": "semantic-release pre && npm publish && semantic-release post"
  },
  "devDependencies": {
    "ban-sensitive-files": "1.9.0",
    "deps-ok": "1.2.0",
    "git-issues": "1.3.1",
    "license-checker": "11.0.0",
    "mocha": "3.4.2",
    "nsp": "2.6.3",
    "pre-git": "3.15.0",
    "prettier-standard": "5.1.0",
    "semantic-release": "^6.3.6",
    "standard": "10.0.2"
  },
  "dependencies": {
    "ggit": "1.15.1",
    "pluralize": "5.0.0"
  }
}

@bahmutov   @confooca   #confoo

What are these?!

{
  "bin": {
    "foo": "src/index.js"
  }
}
$ ./node_modules/.bin/foo
$ $(npm bin)/foo

@bahmutov   @confooca   #confoo

Creating executable scripts

{
  "scripts": {
    "start": "foo"
  }
}

@bahmutov   @confooca   #confoo

{
  "files": [
    "src/*.ts",
    "!src/spec*.js",
    "dist"
  ],
  "scripts": {
    "size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";",
  }
}
$ ls src
banner.ts index.ts register.ts spec1.js	spec2.js
$ npm run size

> mocha-banner@0.0.0-development size /Users/gleb/git/mocha-banner
> t="$(npm pack .)"; wc -c "${t}"; tar tvf "${t}"; rm "${t}";

    3088 mocha-banner-0.0.0-development.tgz
-rw-r--r--  0 0      0        2518 Jan 17 11:00 package/package.json
-rw-r--r--  0 0      0        3164 Jan 17 11:03 package/README.md
-rw-r--r--  0 0      0         181 Jan 17 11:03 package/dist/banner.js
-rw-r--r--  0 0      0         242 Jan 17 11:03 package/dist/index.js
-rw-r--r--  0 0      0         370 Jan 17 11:03 package/dist/register.js
-rw-r--r--  0 0      0          80 Jan 17 11:02 package/src/banner.ts
-rw-r--r--  0 0      0         129 Jan 17 10:43 package/src/index.ts
-rw-r--r--  0 0      0         286 Jan 17 10:43 package/src/register.ts

Node is a moving target ...

@bahmutov   @confooca   #confoo

{
  "engines": {
    "node": ">=6",
    "npm": ">=3"
  }
}

Generates warning

$ node -v
v4.2.0
$ npm i
WARN: engine foo@1.0.0: wanted ...

@bahmutov   @confooca   #confoo

{
  "engines": {
    "node": ">=6",
    "npm": ">=3"
  }
}
engine-strict=true

.npmrc

Stops npm install

@bahmutov   @confooca   #confoo

package.json vs .npmrc

For the users of the package

For development

local only

global + profile + local

@bahmutov   @confooca   #confoo

$ cat .npmrc
registry=http://registry.npmjs.org/
save-exact=true
progress=false
package-lock=false
$ npm config ls -l

shows all defaults

@bahmutov   @confooca   #confoo

$ npm config ls -l
; cli configs
long = true
metrics-registry = "http://registry.npmjs.org/"
scope = ""
user-agent = "npm/5.6.0 node/v8.9.4 darwin x64"

; project config /Users/gleb/git/mocha-banner/.npmrc
package-lock = false
progress = false
registry = "http://registry.npmjs.org/"
save-exact = true

; userconfig /Users/gleb/.npmrc
@cypress:registry = "https://registry.npmjs.org/"
package-lock = false
progress = false
save-exact = true

; default values
access = null
allow-same-version = false
also = null
always-auth = false
auth-type = "legacy"
bin-links = true
browser = null
ca = null
cache = "/Users/gleb/.npm"
cache-lock-retries = 10
cache-lock-stale = 60000
cache-lock-wait = 10000
cache-max = null
cache-min = 10
cafile = undefined
cert = null
cidr = null
color = true
commit-hooks = true
depth = null
description = true
dev = false
dry-run = false
editor = "vi"
engine-strict = false
fetch-retries = 2
fetch-retry-factor = 10
fetch-retry-maxtimeout = 60000
fetch-retry-mintimeout = 10000
force = false
git = "git"
git-tag-version = true
global = false
global-style = false
globalconfig = "/Users/gleb/.nvm/versions/node/v8.9.4/etc/npmrc"
globalignorefile = "/Users/gleb/.nvm/versions/node/v8.9.4/etc/npmignore"
group = 20
ham-it-up = false
heading = "npm"
https-proxy = null
if-present = false
ignore-prepublish = false
ignore-scripts = false
init-author-email = ""
init-author-name = ""
init-author-url = ""
init-license = "ISC"
init-module = "/Users/gleb/.npm-init.js"
init-version = "1.0.0"
json = false
key = null
legacy-bundling = false
link = false
local-address = undefined
loglevel = "notice"
logs-max = 10
; long = false (overridden)
maxsockets = 50
message = "%s"
; metrics-registry = null (overridden)
node-options = null
node-version = "8.9.4"
offline = false
onload-script = null
only = null
optional = true
otp = 0
; package-lock = true (overridden)
package-lock-only = false
parseable = false
prefer-offline = false
prefer-online = false
prefix = "/Users/gleb/.nvm/versions/node/v8.9.4"
production = false
; progress = true (overridden)
proxy = null
read-only = false
rebuild-bundle = true
; registry = "https://registry.npmjs.org/" (overridden)
rollback = true
save = true
save-bundle = false
save-dev = false
; save-exact = false (overridden)
save-optional = false
save-prefix = "^"
save-prod = false
scope = ""
script-shell = null
scripts-prepend-node-path = "warn-only"
searchexclude = null
searchlimit = 20
searchopts = ""
searchstaleness = 900
send-metrics = false
shell = "/bin/bash"
shrinkwrap = true
sign-git-tag = false
sso-poll-frequency = 500
sso-type = "oauth"
strict-ssl = true
tag = "latest"
tag-version-prefix = "v"
timing = false
tmp = "/var/folders/bf/cgb7wvb905q3n6hgtjc1lqrm0000gn/T"
umask = 18
unicode = true
unsafe-perm = true
usage = false
user = 501
; user-agent = "npm/{npm-version} node/{node-version} {platform} {arch}" (overridden)
userconfig = "/Users/gleb/.npmrc"
version = false
versions = false
viewer = "man"

Solid project

  • good README

  • complete package.json

    • urls, files, scripts

  • opinionated .npmrc

I'm forgetting something ...

@bahmutov   @confooca   #confoo

@bahmutov   @confooca   #confoo

Checklists

Human brain "glazes" over routine details

Going through a list helps

@bahmutov   @confooca   #confoo

✅ fill all package.json fields

@bahmutov   @confooca   #confoo

  • description
  • keywords
  • author
  • bugs
  • homepage
  • license (!)
  • ...

✅ catch simple errors using a linter

ESLint, Standard, Xo

@bahmutov   @confooca   #confoo

✅ stop arguing about style

Good auto-formatting

Prettier

Prettier is an opinionated code formatter

Static source linter
Standard

One JavaScript Style to Rule Them All

@bahmutov   @confooca   #confoo

$ npm home prettier
$ npm home standard

@bahmutov   @confooca   #confoo

{
  "scripts": {
    "prelint": "prettier-standard '*.js'",
    "lint": "standard --fix *.js",
    "pretest": "npm run lint",
    "test": "exit 1"
  }
}

package.json

$ npm run test
--- runs prelint
--- runs lint
--- runs test

@bahmutov   @confooca   #confoo

// index.js
const add = (a, b) => a + b
const sub = (a, b) => {
  return a + b
}
module.exports.add = ad
module.exports.sub = sub

@bahmutov   @confooca   #confoo

> standard --fix *.js

  my-app/index.js:1:7: 
   'add' is assigned a value but never used.
  my-app/index.js:5:22: 
   'ad' is not defined.

@bahmutov   @confooca   #confoo

✅ start unit testing right away

Tape, Mocha, Ava, Jest, ...

Delightful JavaScript Testing

@bahmutov   @confooca   #confoo

$ npm install --save-dev jest
$ node_modules/.bin/jest
/* eslint-env jest */
describe('add', () => {
  const add = require('.').add
  it('adds numbers', () => {
    expect(add(1, 2)).toBe(3)
  })
})

@bahmutov   @confooca   #confoo

$ jest
 PASS  ./test.js
  add
    ✓ adds numbers (4ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.763s, estimated 1s
Ran all test suites.

@bahmutov   @confooca   #confoo

Never "npm publish" yourself

@bahmutov   @confooca   #confoo

✅ CI + semantic release

✅ git hooks

✅ commit message convention

✅ use logging library

✅ crash reporting

✅ secure dependencies

✅ check missing dependencies

@bahmutov   @confooca   #confoo

Automate

@bahmutov   @confooca   #confoo

$ npm install -g yo generator-node-bahmutov
git init
git remote add origin <remote git>
yo node-bahmutov

Answer 3 questions: name, description, keywords

@bahmutov   @confooca   #confoo

@bahmutov   @confooca   #confoo

const Generator = require('yeoman-generator')
const g = Generator.extend({
  copyNpmrc () {
    this.fs.copy(this.templatePath('npmrc'), 
      this.destinationPath('.npmrc'))
  },
  gitOrigin () {
    const done = this.async()
    originUrl()
      .then(url => {
        // remember url
        done()
      })
  }
})
module.exports = g

Optimize for iteration

Final Thoughts

Solid project means remembering a LOT of options

Node & NPM are great

@bahmutov   @confooca   #confoo

Final Thoughts

Checklists help

@bahmutov   @confooca   #confoo

Final Thoughts

Automate it 🤖

Thank you 👏

A Simple Node Checklist

Gleb Bahmutov, PhD

A Simple Node Checklist to Follow

By Gleb Bahmutov

A Simple Node Checklist to Follow

Checklists have been known to reduce errors during air flights and surgeries. Why don't we use one for our JavaScript projects? I want my NPM module to be useful, robust, and well-documented. Here is my recipe for a module that everyone can rely on: a simple checklist. Rather than forcing everyone to do the same, the checklist only reminds me to do unit testing, publish smaller module, have good API examples, and lots of other useful things. ConFoo 2018

  • 4,262