Dr Gleb Bahmutov PhD

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

NERD Summit 2018

Modern JavaScript

About: @bahmutov

  • Startups / medium size / MathWorks
  • VP of Eng at Cypress.io

"Testing, the way it should be"

  • Computer vision background (C/C++/C#)

CoffeeScript then JavaScript

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

@bahmutov                 @NERDSummit               #nerds18

A software product

User

Data

Servers

UI

C++ / C# Java

SilverLight

Java

JavaScript

About 8 years ago

Happily coding in C++ / C#

I need a library to do X...

@bahmutov                 @NERDSummit               #nerds18

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

Qt, GTK, .... autoconf, Makefiles

@bahmutov                 @NERDSummit               #nerds18

Incredibly fragile

@bahmutov                 @NERDSummit               #nerds18

Let's do everything in JavaScript!

@bahmutov                 @NERDSummit               #nerds18

undefined is not a function

@bahmutov                 @NERDSummit               #nerds18

ES5 got wide adoption

= browsers are mostly the same!

+ Node.js for the server

About 5 years ago

@bahmutov                 @NERDSummit               #nerds18

A software product

User

Data

Servers

UI

JavaScript

JavaScript

We are going to cover:

  1. Where is JavaScript?
  2. JavaScript versions
  3. Main tools

JavaScript around us

  • Browser

  • Server

  • Desktop

  • Mobile

  • Gray areas:

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

<div onclick="alert('hi')">Click me</div>

Modern JavaScript

in the Browser

<script src="app.js"></script>
<div id="app" />

Single Page Applications (SPA)

React, Angular, Vue, Ember

Modern JavaScript

in the Browser

No longer relevant

JScript

VBScript

Dart

Flash

Silverlight

Java

@bahmutov                 @NERDSummit               #nerds18

Replacing mobile applications

Modern JavaScript

Browser/mobile

Two choices

Progressive Web App

I don't want to download 200MB app to read tweets

I can't afford to download 200MB app to read tweets

me

billions of people

Progressive Web Apps

  • Visit URL in the browser
  • Save on desktop
  • Acts like an app:
    • fast, offline, notifications

2nd: ReactNative

Code Web App convert to Native app

@bahmutov                 @NERDSummit               #nerds18

JS in the Browser: 4 places

browser

JS in the Browser: 4 places

browser

Web Workers

JS in the Browser: 4 places

Server

browser

Web Workers

ServiceWorker

(JavaScript)

Transforms

the response

Transforms

the request

JS in the Browser: 4 places

Server

browser

Web Workers

ServiceWorker

(JavaScript)

Transforms

the response

Transforms

the request

DevTools

  • WebWorkers

  • ServiceWorker

  • DevTools code snippets

node sever.js -p 3000

Modern JavaScript

on Server

const http = require('http')
const hostname = '127.0.0.1'
const port = 3000

const server = http.createServer((req, res) => {
  res.statusCode = 200
  res.setHeader('Content-Type', 'text/plain')
  res.end('Hello World\n')
})

@bahmutov                 @NERDSummit               #nerds18

server.listen(port, hostname, () => {
  console.log(`Server running at 
    http://${hostname}:${port}/`)
})

Node Adoption: Everyone

@bahmutov                 @NERDSummit               #nerds18

@bahmutov                 @NERDSummit               #nerds18

Modern JavaScript

on Desktop

Build cross platform desktop apps with JavaScript, HTML, and CSS

Cypress.io E2E Test Runner

Platform CI Output Binary
Darwin Buildkite Electron
Linux CircleCI Electron
Windows AppVeyor Electron

Qt, Java, ... Node 👍

@bahmutov                 @NERDSummit               #nerds18

Cypress GUI is a web app itself running as Electron application

@bahmutov                 @NERDSummit               #nerds18

Debug using same tool

Platform Debug
Web app DevTools
Node DevTools
Electron DevTools

@bahmutov                 @NERDSummit               #nerds18

Shell scripts ➡ Nodejs scripts

if [ -d "foo" ] then:
  cp -r foo bar
fi
const shell = require('shelljs')
if (fs.existsSync('foo')) {
  shell.cp('-r', 'foo', 'bar')
}

Works (Linux)

Mostly works (Darwin)

🔥  (Windows)

Just works!

@bahmutov                 @NERDSummit               #nerds18

All our build scripts are Nodejs files

I don't want to learn your cryptic script commands

@bahmutov                 @NERDSummit               #nerds18

  1. Where is JavaScript?
    • Browser, server, mobile, shell scripts

  2. JavaScript versions
  3. Main tools

Modern JavaScript

Let's look at the JavaScript language

Modern JavaScript

  • 1996: Netscape asks Ecma to make a "standard" JavaScript specifications
  • 1997 Ecma publishes EcmaScript standard

Modern JavaScript

EcmaScript (ES) = standard

JavaScript = implementation

* "JavaScript" is a trademark of Oracle Co

*

Modern JavaScript

  • June 2011: EcmaScript 5.1 (ES5)

All modern browsers

Modern JavaScript

  • June 2011: EcmaScript 5.1 (ES5)
var x = 'foo'
var y = 42
function add(a, b) {
  return a + b
}
add(x, y)
// EcmaScript has no input / output ⚠️

Modern JavaScript

  • June 2015: EcmaScript 2015 (ES6)
const x = 'foo'
[1, 2, 3].map(x => x + x)
console.log(`x = ${x}`)
// and lots of other things...

Modern JavaScript

  • June 2016: EcmaScript 2016
[1, 2, 3].includes(2) // true
2 ** 3 // 8

Modern JavaScript

  • June 2017: EcmaScript 2017
async function userName () {
  try {
    const user = await fetch()
    return user.name
  } catch (err) {
    // deal with error
  }
}

Modern JavaScript

  • June 2015: EcmaScript 2015 (ES6)
  • June 2016: EcmaScript 2016 (ES7)
  • June 2017: EcmaScript 2017
  • June 2018: ...

How an idea becomes an EcmaScript feature

Anyone can propose a new feature

How an idea becomes an EcmaScript feature

I want JavaScript to have X

Stage 0

an idea is born

How an idea becomes an EcmaScript feature

Discussion, demos, polyfills

Feature gets a "champion"

Stage 1: proposal

How an idea becomes an EcmaScript feature

TC39 committee: about 25 members

Mozilla, Google, Apple, Intel, universities

Stage 1: proposal

How an idea becomes an EcmaScript feature

Draft and precise spec language is being developed

Stage 2: draft

How an idea becomes an EcmaScript feature

All semantics, syntax and API are completed described

Stage 3: candidate

How an idea becomes an EcmaScript feature

Feature has tests

Two reference implementations

Stage 4: finished

How an idea becomes an EcmaScript feature

Stage 4 features as of March

become part of the standard published in June

What does a browser support?

try {
  eval('2 ** 8')
} catch () {
  // ** is not supported
}
// ** is ✅

What does a browser support?

  • Evergreen browsers are 100% ES2015/2016/2017 compatible

*

* except for (maybe) ES6 modules

What does a browser support?

  • Mobile browsers really patchy

1: Adding polyfills

Detect and polyfill only what's missing with modernizr.com

<script src="polyfills.js"></script>
<script>
[1, 2, 3].includes(2) // works now
</script>

1: Adding polyfills

Does not work with every feature

<script src="polyfills.js"></script>
<script>
2 ** 3
// SyntaxError
</script>

2: Transpiling

Rewrite code to older standard

2 ** 3 // app.js
babel app.js
Math.pow(2, 3)

ES2016

ES5

* not every feature can be transpiled

*

Talk progress

  1. Where is JavaScript?
  2. JavaScript versions
  3. Main tools

NPM Registry

aka OSS heaven

source: https://npms.io

@bahmutov                 @NERDSummit               #nerds18

Creating your own module

$ mkdir my-app
$ cd my-app
$ git init
$ npm init -y
$ npm publish

@bahmutov                 @NERDSummit               #nerds18

$ npm init -y
Wrote to my-app/package.json
{
  "name": "my-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

@bahmutov                 @NERDSummit               #nerds18

$ yo node-bahmutov
{
  "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"
  }
}

Yeoman

@bahmutov                 @NERDSummit               #nerds18

$ yo node-bahmutov
{
  "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"
  }
}

generator-node-bahmutov

@bahmutov                 @NERDSummit               #nerds18

Best practices for NPM + Node projects

@bahmutov                 @NERDSummit               #nerds18

Formatting and linting

  • Enforce consistent code style using a formatting tool like "prettier"
  • Catch simple errors using a linter like "standard"

JavaScript is scripting language ⚠️

Use static linters to catch simple mistakes

Good auto-formatting

Prettier

Prettier is an opinionated code formatter

Static source linter
Standard

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 lint
// 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.

Unit testing JavaScript

Write tests first

Run tests often

Tape, QUnit, 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.
 PASS  ./test.js
  add
    ✓ adds numbers (7ms)
  sub
    ✓ subs numbers (1ms)

Snapshot Summary
 › 2 snapshots written in 1 test suite.
it('adds numbers', () => {
  expect(add(1, 2)).toMatchSnapshot()
  expect(add(100, -50)).toMatchSnapshot()
})
$ cat __snapshots__/test.js.snap 
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`add adds numbers 1`] = `3`;

exports[`add adds numbers 2`] = `50`;

Saved snapshot files

expect(value).toMatchSnapshot()
    
Received value does not match stored snapshot 1.
    
    - 3
    + 21
it('adds numbers', () => {
  expect(add(1, 20)).toMatchSnapshot()
  expect(add(100, -50)).toMatchSnapshot()
})
  • There are plenty of testing tools and frameworks
  • Snapshot testing is becoming mainstream

Unit testing JavaScript

E2E Testing: Cypress.io

  • npm install -D cypress

  • OSS MIT license

  • fast and flake free

Loading JS Modules

Writing a single JavaScript file is a bad idea, but how should one JS file load another JS file?

CommonJS modules

// add.js
function add (a, b) {
  return a + b
}
module.exports = add

Node implements CommonJS standard

CommonJS modules

// index.js
const add = require('./add')
add(2, 3) // 5

Node loads a file synchronously

Two huge problems

CommonJS modules

  • Browser cannot load files synchronously

Solution: bundling

  add.js
  sub.js
  index.js
<script src="bundle.js"></script>
bundle.js

Second problem

CommonJS modules

  • JS engine cannot tell what a module exports until it runs module's code

Huge JS bundles because have to include everything, even if code is unused

ES6 Modules

// add.js
export function add (a, b) {
  return a + b
}
// index.js
import {add} from './add'
$ node add.js 
./add.js:2
export function add (a, b) {
^^^^^^
SyntaxError: Unexpected token export
  • Node 8 does not implement ES6 modules

  • Browser support is patchy

* Target: Node 10, April 2018

*

$ npm i -S @std/esm
{
  "@std/esm": {
    "esm": "js"
  },
  "scripts": {
    "start": "node -r @std/esm index.js"
  }
}

add to package.json

install dependency

ES6 Modules in Node Today

Building browser bundle

$ npm i -D rollup
{
  "scripts": {
    "build": "rollup index.js -o bundle.js --f iife"
  }
}

add to package.json

install dependency

$ npm run build

build the bundle

Tree-shaking

// index.js
import {add} from './add'
import {sub} from './sub'
console.log(add(2, 3))

The bundle is minimal

Thanks, explicit "import / export" declarations! 

(function () {
'use strict';
function add (a, b) {
  return a + b
}
console.log(add(2, 3));
}());
$ cat bundle.js

There is no "sub" code in the bundle!

<head>
    <!-- in case ES6 modules are supported -->
    <script src="app/index.js" type="module"></script>

    <!-- in case ES6 modules aren't supported -->
    <script src="dist/bundle.js" defer nomodule></script>
  </head>

Browser: ship both bundles

Is this it?

There is so much more

IoT, robots, databases,

WebSockets, Audio and Video, P2P, WebAssembly, VR, compiled to JS languages

JavaScript language is running the modern web

The tools and libraries are incredibly diverse and can satisfy anyone's needs

There is a good process for introducing new language features every year

Modern JavaScript

Thank You

NERD Summit 2018