The FOUR Labyrinths of JavaScript

(adapted from Clojure for the Brave and True)

Overview

The Four Labyrinths

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

The FirES of Functional Programming

composability

immutability

purity


3 "T"s of Functional Programming ("3TFP")

composability

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
            

composability

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
            

immutability

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'];
            

immutability

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]
            

Minimize Mutation

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}!`;
            

Minimize Mutation

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'
    };
            

Isolate Mutation

...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);
            

immutability

Thoughts on performance
  • "Interface vs Implementation"
  • Browser interpreters get faster every day
  • Is "push" really the bottle-neck for your app?
  • Find balance between development time and runtime

purity

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
            

One more thing...

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));
        

The Trials of Tooling

analyze → compile → optimize

Analyze

"Lint"

JavaScript ESLint
CSS Stylelint
HTML HTMLHint
JSON jsonlint
a11y (url) pa11y or a11y
a11y (any) AccessSniff

Compile

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

Optimize

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

Getting Started — 3 Easy Steps

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
            
Babel

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);
    };
            
Babel

Configuration

Create .babelrc file...


    {
      "presets": ["env", "minify"],
    }
            

...OR add to package.json


    {
      "babel": {
        "presets": ["env", "minify"]
    }
            
Babel

Usage — Choose your "vehicle"

CLI babel-cli
Browserify babelify
Webpack babel-loader
Grunt grunt-babel
Gulp gulp-babel
PostCSS

PostCSS

A tool for transforming CSS with JavaScript

Yep.

bundling and testing are the topics of the next two classes

The ObstaclES of Organization

module format — module bundler

Module Formats

  • CommonJS (CJS)
  • Asynchronous Module Definition (AMD)
  • Universal Module Definition (UMD)
  • ECMAScript Module (ESM)
  • WebAssembly Module (WASM)
CommonJS
  • Compile step required
  • No boilerplate
  • Works with Jest & Flow
  • Extended with transpilation
  • optimize/bundle → multiple
RequireJS

AMD

ESM


  • Limited support
  • No boilerplate
  • Simplified syntax
  • Works with Jest & Flow
  • Extended with transpilation
  • optimize/bundle → multiple

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);
            

UMD

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 */}));
        

module format — module bundler

Module Bundlers

r.js — browserify — webpack

Module Bundler — r.js

Bundle Require.js AMD modules

AMD r.js / almond.js
CJS nope
ESM nope
WASM nope

Module Bundler — browserify

Process modules with transforms

AMD deamdify
CJS browserify-shim
ESM nope
WASM rustify

Module Bundler — webpack

Process modules with loaders & plugins

AMD built in
CJS built in
ESM built in
WASM wasm-loader

The Tribulations of Testing

> Not another "test pyramid" talk

All the things

without Jest

Or just Jest (no AMD)

without Jest

A Note on Jasmine

Jasmine is basically:

  • a testing framework with zero dependencies
  • + Mocha without CLI support and a weaker API
  • + Chai with limited choice and less customizability
  • + Sinon with 90% fewer features
> Jest was built on Jasmine

Mocha + Chai + Sinon + Istanbul


    {
        "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",
        }
    }
            

Jest


    {
        "dependencies": {
            "jest": "^20.0.4"
        }
    }
            
### You might not need [sinon](http://sinonjs.org/)
### You might not need [chai](http://chaijs.com/)

References