Gleb Bahmutov PRO
JavaScript ninja, image processing expert, software quality fanatic
March 7, 2018 @ 14:00
Mont-Royal
C / C++ / C# / Java / CoffeeScript / JavaScript / Node / Angular / Vue / Cycle.js / functional
JavaScript ninja, image processing expert, software quality fanatic, Microsoft MVP
Cypress.io open source E2E test runner
JavaScript ninja, image processing expert, software quality fanatic, Microsoft MVP
70 blog posts about Node
160 blog posts about JavaScript
tip: reload page to get new funny definition of "NPM"
2000 years ago ....
"Divide and Rule (Conquer)"
Name and description
CI and other badges
Requirements and install
Example
Links and license
I need a library to do X...
Qt, GTK, .... autoconf, Makefiles
Java Server
Swing UI
Client Browser
JS UI
undefined is not a function
aka OSS heaven
source: http://www.modulecounts.com
source: https://libraries.io
source: https://libraries.io
source: https://npms.io
mkdir foo-bar && cd foo-bar
npm init
name: (foo-bar)
version: (1.0.0)
description: Example module
...
$ 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"
}
$ npm home debug
$ npm issues debug
{
"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!
{
"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"
}
}
What are these?!
{
"bin": {
"foo": "src/index.js"
}
}
$ ./node_modules/.bin/foo
$ $(npm bin)/foo
Creating executable scripts
{
"scripts": {
"start": "foo"
}
}
{
"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 ...
{
"engines": {
"node": ">=6",
"npm": ">=3"
}
}
Generates warning
$ node -v
v4.2.0
$ npm i
WARN: engine foo@1.0.0: wanted ...
{
"engines": {
"node": ">=6",
"npm": ">=3"
}
}
engine-strict=true
.npmrc
Stops npm install
For the users of the package
For development
local only
global + profile + local
$ cat .npmrc
registry=http://registry.npmjs.org/
save-exact=true
progress=false
package-lock=false
$ npm config ls -l
shows all defaults
$ 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"
I'm forgetting something ...
Human brain "glazes" over routine details
Going through a list helps
✅ fill all package.json fields
✅ catch simple errors using a linter
ESLint, Standard, Xo
✅ stop arguing about style
Prettier is an opinionated code formatter
One JavaScript Style to Rule Them All
$ npm home prettier
$ npm home standard
{
"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
// index.js
const add = (a, b) => a + b
const sub = (a, b) => {
return a + b
}
module.exports.add = ad
module.exports.sub = sub
> 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.
✅ start unit testing right away
Tape, Mocha, Ava, Jest, ...
Delightful JavaScript Testing
$ 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)
})
})
$ 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.
Never "npm publish" yourself
✅ CI + semantic release
✅ git hooks
✅ commit message convention
✅ use logging library
✅ crash reporting
✅ secure dependencies
✅ check missing dependencies
$ npm install -g yo generator-node-bahmutov
git init
git remote add origin <remote git>
yo node-bahmutov
Answer 3 questions: name, description, keywords
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
By Gleb Bahmutov
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
JavaScript ninja, image processing expert, software quality fanatic