Gleb Bahmutov PRO
JavaScript ninja, image processing expert, software quality fanatic
#boscc
Gleb Bahmutov, PhD
Boston Code Camp 2018
undefined is not a function
CoffeeScript then JavaScript
C++ / C# Java
SilverLight
Java
JavaScript
JavaScript
<div onclick="alert('hi')">Click me</div>
<script src="app.js"></script>
<div id="app" />
Single Page Applications (SPA)
React, Angular, Vue, Ember
JScript
VBScript
Dart
Flash
Silverlight
Java
Replacing mobile applications
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
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
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
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
Build cross platform desktop apps with JavaScript, HTML, and CSS
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
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!
I don't want to learn your cryptic script commands
Browser, server, mobile, shell scripts
Let's look at the JavaScript language
EcmaScript (ES) = standard
JavaScript = implementation
* "JavaScript" is a trademark of Oracle Co
*
All modern browsers
var x = 'foo'
var y = 42
function add(a, b) {
return a + b
}
add(x, y)
// EcmaScript has no input / output ⚠️
const x = 'foo'
[1, 2, 3].map(x => x + x)
console.log(`x = ${x}`)
// and lots of other things...
[1, 2, 3].includes(2) // true
2 ** 3 // 8
async function userName () {
try {
const user = await fetch()
return user.name
} catch (err) {
// deal with error
}
}
Anyone can propose a new feature
I want JavaScript to have X
an idea is born
Discussion, demos, polyfills
Feature gets a "champion"
TC39 committee: about 25 members
Mozilla, Google, Apple, Intel, universities
Draft and precise spec language is being developed
All semantics, syntax and API are completed described
Feature has tests
Two reference implementations
Stage 4 features as of March
become part of the standard published in June
try {
eval('2 ** 8')
} catch () {
// ** is not supported
}
// ** is ✅
*
* except for (maybe) ES6 modules
Detect and polyfill only what's missing with modernizr.com
<script src="polyfills.js"></script>
<script>
[1, 2, 3].includes(2) // works now
</script>
Does not work with every feature
<script src="polyfills.js"></script>
<script>
2 ** 3
// SyntaxError
</script>
Rewrite code to older standard
2 ** 3 // app.js
babel app.js
Math.pow(2, 3)
ES2016
ES5
* not every feature can be transpiled
*
aka OSS heaven
source: http://www.modulecounts.com
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
Use static linters to catch simple mistakes
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 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.
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()
})
// add.js
function add (a, b) {
return a + b
}
module.exports = add
// index.js
const add = require('./add')
add(2, 3) // 5
add.js sub.js index.js
<script src="bundle.js"></script>
bundle.js
Huge JS bundles because have to include everything, even if code is unused
// 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
* 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
$ 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
// index.js
import {add} from './add'
import {sub} from './sub'
console.log(add(2, 3))
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>
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
#boscc
Gleb Bahmutov, PhD
Boston Code Camp 2018
By Gleb Bahmutov
Let us look at JavaScript - the language that runs the Internet. We will learn how new features are added to the language, how the modern syntax looks, and the main tools for testing, transpiling and bundling JavaScript applications. Anyone looking to start with JS or upgrade development skills will benefit from this presentation. Presented at Boston Code Camp 2017.
JavaScript ninja, image processing expert, software quality fanatic