Modern JavaScript

#boscc

Gleb Bahmutov, PhD

Boston Code Camp 2018

undefined is not a function

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

About: @bahmutov

A software product

User

Data

Servers

UI

C++ / C# Java

SilverLight

Java

A software product

User

Data

Servers

UI

JavaScript

JavaScript

Desktop

Deploy

Mobile app

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

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

JS in the Browser: 4 places

Server

browser

Web Workers

ServiceWorker

(JavaScript)

Transforms

the response

Transforms

the request

DevTools

JavaScript vs Images

Most of the pages are images

But browsers choke on JS

Website is so slow!

Solution: put your JavaScript into images 🤔

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')
})
server.listen(port, hostname, () => {
  console.log(`Server running at 
    http://${hostname}:${port}/`)
})

Node Adoption: Everyone

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 👍

Cypress GUI is a web app itself running as Electron application

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!

All our build scripts are Nodejs files

I don't want to learn your cryptic script commands

  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

Creating your own module

$ mkdir my-app
$ cd my-app
$ git init
$ npm init -y
$ npm publish
$ 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"
}
$ 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

$ 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

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

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

#boscc

Gleb Bahmutov, PhD

Boston Code Camp 2018

Thank You