Writing modern SPA applications

part 1: tooling and environment

ES6 - what's new?

let

(function() {
  var foo = 'A'
  if(foo !== false) {
    var foo;
    console.log(foo) // this will print "A" to console
  }
})()

(function() {
  let foo = 'A'
  if(foo) {
    let foo;
    console.log(foo) // this will print "undefined" to console
  }
})()

const

const foo = {};

foo = 4 // this will crash
foo.a = 4 // this will not

FAT ARROW SYNTAX

function Something() {
  setTimeout(() => console.log(this), 1) // will log correct instance Something() {}
  setTimeout(function() { console.log(this) }, 1) // will log Window
}

const instance = new Something()





const foo = () => { 123 } // <- this works like normal function 
                          // and will return `undefined`

const bar = () => ( 123 ) // <- this works like expression and will return `123`

const bar = () => 123     // <- notice that () are optional in some cases, 
                          // for example arithmetric operations or simple values

PROMISES

  • Promises (or Futures, in other languages) are incomplete value objects.
     
  • They can be in either of three states: pending, resolved or rejected.
     
  • When the asynchronous operation ends it can either resolve or reject a Promise, passing a value (or error) to it.
     
  • Promises are one of the simplest synchronisation constructs.

PROMISES

// using callbacks
getUser(1, {
  success: (user) => {
    this.user = user;
  },
  failure: (error) => {
    this.error = error;
  }
);

// using promises
getUser(1)
  .then((user) => {
    this.user = user
  })
  .catch(error) => {
    this.error = error;
  })

PROMISES CAN BE

CHAINED

PROMISE CHAINING

// using callbacks
doSomething('1', function(resultA) {
  thenDoSomething(resultA, function(resultB) {
    andSomethingElse(resultB, function(resultC) {
      // WE NEED TO GO DEEPER
    })
  })
})

// using promise chaining
doSomething('1')
  .then(thenDoSomething)
  .then(andSomethingElse)
  .then((result) => {
    // WE NEED TO GO DEEPER
  })

PROMISES CAN BE

SYNCHRONISED

PROMISE.ALL

const getUsers = fetch('/users')
const getProjects = fetch('/projects')

Promise.all([ getUsers, getProjects ]).then((results) => {
  // this can be simplified with destructuring, we'll get there
  const users = results[0]
  const projects = results[1]
})

PROMISES CAN BE

WRITTEN LIKE SYNCHRONOUS CODE

(wait, what?)

ASYNC/AWAIT SYNTAX

async function getResult() {
  const resultA = await doSomething('1')
  const resultB = await thenDoSomething(resultA)
  const resultC = await andSomethingElse(resultB)
  // WE NEED TO GO DEEPER
}


try {
  const response = await axios('/users')
  console.log(response.data)
} catch(error) {
  if(error.response && error.response.code === 404) {
    console.log("Not found")
  }
}

DESTRUCTURING

  • Simplifies working with hashes (objects)
     
  • Work both with {} objects and [] arrays
     
  • You can pass default parameters

DESTRUCTURING - simple case

const object = { foo: 1, bar: 2}
const { foo, bar } = object

// this is equivalent of

const foo = object.foo
const bar = object.bar

DESTRUCTURING - NESTED

const object = { a: { b: 1 } }
const { a: { b, c }} = object 

// this is equivalent of

const b = object.a.b
const c = object.a.c

// for most cases this is more readable

const { b, c } = object.a

DESTRUCTURING - ARRAYS

const list = [1, 2]
const [ foo, bar ] = list

// this is equivalent of

const foo = list[0]
const bar = list[1]

DESTRUCTURING - DEFAULTS

const emptyObject = {}
const { foo = 1, bar } = emptyObject

// this is (more or less) equivalent of

const foo = typeof emptyObject.foo !== 'undefined' ? emptyObject.foo : 1
const bar = emptyObject.bar

DESTRUCTURING - DIFFERENT NAMES

const emptyObject = { first: 1, last: 2 }
const { first: firstNumber, last: lastNumber } = emptyObject

console.log(firstName, lastName) // will log 1 2
console.log(first, last) // this will throw TypeError because first and last
                         // are not defined

PROPERTY SHORTHAND AND COMPUTED PROPERTY NAMES

const name = "Me"
const date = Date.now

// explicitly stating key is unnecessary if it's the same as variable name
const user = { name, date }



const key = "Key"
const anotherKey = "anotherKey"

// keys can be dynamically calculated from variables now
const myObject = {
  [key]: 'Value',
  [anotherKey]: 'anotherValue'
}

es6 classes

  • Classes are just syntactic sugar over already existing prototype-based functions
     
  • They can inherit from one another...
     
  • ...but inheriting from standard library is still not the best idea
     
  • They can also have properties with getters and setters

classes - constructor

class Model {
  constructor(attributes) {
    this.attributes = attributes
  }
}

const me = new Model({ name: 'Michal' })
console.log(me.attributes) // logs { name: 'Michal' }

classes - INHERITANCE

class Model {
  constructor(attributes) {
    this.attributes = attributes;
  }
}

class User extends Model {
  constructor(attributes) {
    super(attributes);
  }
}

const me = new User({ name: 'Michal' })
console.log(me.attributes) // logs { name: 'Michal' }

classes - GETTERS AND SETTERS

class User extends Model {
  constructor(attributes) {
    this.attributes = attributes;
  }

  get name() {
    return this.attributes.name
  }

  set name(value) {
    return this.attributes.name = value
  }
}

const me = new User({ name: 'Michal' })
console.log(me.name) // logs 'Michal'
me.name = 'Maciek'
console.log(me.name) // logs 'Maciek'

IMPORT/export syntax

  • Official standard for Javascript modules (finally!)
     
  • Works out of the box with Babel and/or Webpack
     
  • They allow multiple named exports from one file (and one default export)

IMPORT/export syntax

export function createUser() {}

const updateUser = () => {}
export { updateUser }

export default MyClass



import React from 'react'
import { FormGroup } from 'react-bootstrap'
import each from 'lodash/each'

import { createUser }, MyClass from './export_example'

decorators

  • Wrappers around class or class method.
     
  • Can be used as cleaner alternative for wrapping class methods in constructor.
     
  • Heavily used by MobX (though not required).

decorators

  • Not really a standard just yet, the specification was heavily changed since they were adopted.
     
  • Original proposal is now called "legacy" and require Babel transform plugin (transform-decorators-legacy).
     
  • Current standard (stage-2) is not yet implemented anywhere.
     
  • This is not a problem as we can treat legacy (stage-0) decorators as any other syntactic sugar applied via Babel.

DECORATORS

import { debounce } from 'lodash-decorators'

class Updater {
  constructor(selector) {
    document.querySelectorAll(selector).addEventListener('change', this.onChange)

    // without decorators we would write this:
    // this.onChange = _.debounce(this.onChange, 200)
  }

  @debounce(500)
  onChange(e) {
    axios.post('/updates', { [e.target.name]: e.target.value })
  }
}

const updater = new Updater('form input[type="text"]')

right, but what about

legacy browsers

BABEL.js

(formerly 6to5)

babel.js

  • Javascript transpiler.
     
  • Converts code from one syntax to another using a set of plugins / transformers.
     
  • Allows heavy configurability by turning on single transformers and broad configuration with presets.
     
  • Can be used as standalone tool using babel-cli npm package.

babel.js presets

  • Sets of predefined or dynamically defined plugins, you don't need to use them but they are wildly useful
     
  • You can use es2015/es2016 directly or use env preset which automatically updates based on browser vendors progress
     
  • stage-0, stage-1 etc. presets add plugins for experimental or unstable features of the language that are still in works and not a fully accepted spec (like decorators)
     
  • There's also preset "react" for JSX 

WEBPACK

WEBPACK

  • Module bundler, used to create javascript "bundles" but also to prepare and handle other assets like images, fonts, CSS etc
     
  • Uses loaders to transform assets in similar way to babel (integration with babel is done with babel-loader)
     
  • Uses plugins to apply changes after transformations (for ex. minification or string replacement) or to create extra files (for ex. HtmlWebpackPlugin)
     
  • It's used both for compilation in development (with live reloading and watching) and production

wait, this is confusing

what's the difference?

BABEL

  • Handles Javascript transformation
     
  • Cares only about Javascript
     
  • Does not resolve import/export (they are just part of language so they end up unchanged in compiled code*)

* unless you use a preset or plugin that transforms them to something else

WEBPACK

  • Transforms files into different files using loaders
     
  • Supports non-javascript files, can pipe them through loaders and plugins
     
  • Creates dependency tree from Javascript files (compiled with babel-loader or not) and other assets
     
  • Resolves import/export syntax

LET'S PAUSE FOR NOW

IT'S Q/A TIME!

Writing modern SPA applications

By Michał Matyas

Writing modern SPA applications

  • 672