(adapted from Clojure for the Brave and True)
the Fires of Functional Programming
composability — immutability — purity
the Trials of Tooling
analyze → compile → optimize
the Obstacles of Organization
module format — module bundler
the Tribulations of Testing
libraries — frameworks — runners
OO makes code understandable by encapsulating moving parts. FP makes code understandable by minimizing moving parts.
— Michael Feathers (@mfeathers) November 3, 2010
build functions with functions
// create functions
const trimStr = str => str.trim();
const toUpper = str => str.toUpperCase();
// ...and compose!
toUpper(trimStr(' hello world '));// HELLO WORLD
// libraries like lodash can provide functions for you
const {flow, toUpper, trim} = require('lodash');
const loud = flow(trim, toUpper);
// code code code
loud(' hello world ');// HELLO WORLD
minimize moving parts
function add(a, b) {return a + b;}
const thisSum = add(9000, 1);// 9001
const thatSum = add(9000, 2);// 9002
const someSum = add(9000, 3);// 9003
// what if you need to change 9000?
// add9000 encapsulates 9000 and has fewer parameters
const add9000 = num => add(9000, num);
// code code code
const thisSum = add9000(1);// 9001
const thatSum = add9000(2);// 9002
const someSum = add9000(3);// 9003
favor immutable patterns — concat
// LESS FP: mutation with push
const hobbits = [];
hobbits.push('Bilbo');
hobbits.push('Frodo');
// code code code
hobbits.push('Samwise');
// MORE FP: avoid mutation with concat
const baggins = ['Bilbo', 'Frodo'];
// code code code
const hobbits = baggins.concat('Samwise');
// or with ES6 spread operator
const hobbits = [...baggins, 'Samwise'];
favor immutable patterns — map
let numbers = [1, 2, 3, 4, 5];
// LESS FP: imperative mutation with for
const squared = [];
for (let i = 0; i < numbers.length; i++) {
squared[i] = numbers[i] ** 2;
}
// MORE FP: functional immutability with map
const squared = numbers.map(num => num ** 2);
// squared === [1, 4, 9, 16, 25]
use string interpolation
const name = 'Carl';
// LESS FP: mutation and no string interpolation
let greeting = 'Hello';// const CANNOT be used here!
greeting += ' ';
greeting += name;
greeting += '!'// "Hello Carl!"
// MORE FP: no mutation with string interpolation
const greeting = `hello ${name}!`;
group object property assignment operations
// LESS FP
// object declaration followed by property assignments
const book = {};
o.title = 'The Dream Machine';
o.author = 'M. Waldrop';
// MORE FP
// object declaration combined with property assignments
const book = {
title: 'The Dream Machine',
author: 'M. Waldrop'
};
...when you cannot avoid mutation
const {assign} = Object;
const powerLevel = 9001;
const person = {powerLevel};
// LESS FP: multiple property assignment operations
person.name = 'Kakarot';
person.race = 'Saiyan';
// MORE FP: single property assignment operation
const features = {
name: 'Kakarot',
race: 'Saiyan'
};
assign(person, features);
Thoughts on performance
balance arity and state
let foo = () => console.log('bar');
let obj = {foo};
// NOT PURE (obj is a "free variable")
let triggerFooNotPure = () => obj.foo();
// PURE (no free variables)
let triggerFooPure = obj => obj.foo();
// pure functions generally have higher "arity"
triggerFooNotPure();// 0-ary
triggerFooPure(obj);// 1-ary
Don't use filter
when you should use forEach
// BAD
[1, 2, 3, 4, 5].filter(val => console.log(val));
// GOOD
[1, 2, 3, 4, 5].forEach(val => console.log(val));
JS Functional Programming Array.prototype cheat sheet pic.twitter.com/diGW4FaOso
— Jason Wohlgemuth (@jhwohlgemuth) May 11, 2017
"Lint"
JavaScript | → ESLint |
CSS | → Stylelint |
HTML | → HTMLHint |
JSON | → jsonlint |
a11y (url) | → pa11y or a11y |
a11y (any) | → AccessSniff |
Precompile for runtime and transpile for developers
trans | ES6+ | → Babel |
trans | JSX | → Babel |
trans | CSS | → PostCSS |
pre | HTML | → Handlebars or JST |
trans | HTML | → EJS or pug |
Reduce size and increase performance
min | ES5 | → uglify-js |
min | ES6+ | → babel-minify or uglify-es |
min | CSS | → cssnano (PostCSS) |
min | HTML | → html-minifier |
perf | JS | → Benchmark.js |
1. Install tool
npm install eslint --save-dev
2. Configure tool (add to workflow)
{
"scripts": {
"lint": "eslint ./src/*.js"
}
}
3. Use tool
npm run lint
Global — Use CLI
>> npm install eslint --global
>> eslint ./src/*.js --config /path/to/.eslintrc.js
Local (project) — Use npm scripts
>> npm install eslint --save-dev
{
"scripts": {
"lint": "eslint ./src/*.js"
}
}
>> npm run lint
Use tomorrow's JavaScript today!
ES6+ goes in...
const squared = val => val ** 2;
...and ES5 comes out!
"use strict";
var squared = function squared(val) {
return Math.pow(val, 2);
};
Configuration
Create .babelrc
file...
{
"presets": ["env", "minify"],
}
...OR add to package.json
{
"babel": {
"presets": ["env", "minify"]
}
Usage — Choose your "vehicle"
CLI | → babel-cli |
Browserify | → babelify |
Webpack | → babel-loader |
Grunt | → grunt-babel |
Gulp | → gulp-babel |
A tool for transforming CSS with JavaScript
AMD → browser
define((require, exports, module) => {
const {add, partial} = require('lodash');
module.exports = partial(add, 1);
});
CJS → Node.js
const {add, partial} = require('lodash');
module.exports = partial(add, 1);
ESM → browser / Node.js
import {add, partial} from 'lodash';
export partial(add, 1);
Support AMD + CJS + Global
// Module has lodash dependency and is named "foo"
(function(root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['lodash'], function(_) {
return (root.foo = factory(root, _));
});
} else if (typeof exports === 'object') {
const _ = require('lodash');
module.exports = factory(root, _);
} else {
root.foo = factory(root, _);
}
}(this, function(root, _) {/* module code */}));
Bundle Require.js AMD modules
AMD | → r.js / almond.js |
CJS | → nope |
ESM | → nope |
WASM | → nope |
Process modules with transforms
AMD | → deamdify |
CJS | → browserify-shim |
ESM | → nope |
WASM | → rustify |
Process modules with loaders & plugins
AMD | → built in |
CJS | → built in |
ESM | → built in |
WASM | → wasm-loader |
Jasmine is basically:
{
"dependencies": {
"chai": "^4.0.1",
"chai-shallow-deep-equal": "^1.4.6",
"istanbul": "^0.4.4",
"mocha": "^3.4.2",
"mocha-lcov-reporter": "^1.0.0",
"mocha-unfunk-reporter": "^0.4.0",
"nyc": "^11.0.2",
}
}
{
"dependencies": {
"jest": "^20.0.4"
}
}