Skip to main content

Your submission was sent successfully! Close

Thank you for signing up for our newsletter!
In these regular emails you will find the latest updates from Canonical and upcoming events where you can meet our team.Close

Thank you for contacting us. A member of our team will be in touch shortly. Close

  1. Blog
  2. Article

Kit Randel
on 9 August 2018


I recently attended Fullstack 2018, “The Conference on JavaScript, Node & Internet of Things” with my colleagues from the Canonical Web Team in London. Fullstack attempts to cover the full spectrum of the JS ecosystem – frontend, backend, IoT, machine learning and a number of other topics. While I attended a broad range of talks, I’ll just mention those that I think are most pertinent to the work we are doing currently in the web team.

A Thunk, a Saga and an Epic walk into a bar

This talk by Artur Daschevici focused on three different approaches to managing side effects in Redux, in order of relative complexity: thunks, sagas and epics.

Thunks

Some terminology first – a thunk, sometimes called a nullary function in functional programming, is essentially a pattern for lazy evaluation. The term thunk originates from languages like Haskell and Scheme, although confusingly a thunk is sometimes called a promise in Scheme!

In JS a thunk basically looks something like this:

function outerFn() {
  return function() {
    doSomething();
  };
}

That’s all well and good, but why do we need them? In redux, reducer functions are supposed to remain pure, which means no mutation of state, and no side effects. The arguments for this are described in the redux documentation, but basically impure reducers break time travel and replay, one of the advantages of using Redux in the first place.

Without thunks, that leaves you in a weird place, you can never actually do anything, you can’t perform I/O, you can’t call an API. Thunks allow you to perform some impure action, and then dispatch an action e.g.

// a thunkified action creator
function getBurlyWombats() {
  return (dispatch, getState) => {
    return axios.get(‘/wombats?type=burly’).then(() => {
      // since redux passes dispatch as the first arg to thunks
      // we can then conveniently dispatch the action once this
      // promise resolves and we have *lots* of burly wombats.
      dispatch(receiveBurlyWombats());
    });
  }
}

Redux-saga

Redux-saga is an alternative to the redux-thunk middleware, which attempts to reduce some of the complexity of chaining asynchronous actions which can devolve into callback hell with redux-thunk. Redux-saga employs generators to allow you to write async actions in a more synchronous style. Another advantage of this approach is that the async “thread of execution”, can effectively be paused or cancelled using a normal redux action, which could provide some nice UX opportunities for long running tasks. Redux-saga also makes error handling quite a bit cleaner.

function* getBurlyWombats() {
  try {
    const burlyWombats = yield call(api.getBurlyWombats);
    yield put({type: “GET_BURLY_WOMBATS_SUCCEEDED”, wombats: burlyWombats});
  } catch (e) {
    yield put({type: “GET_BURLY_WOMBATS_FAILED”, message: e.message});
  }
}

Redux-observable & epics

Redux-observable, developed by Netflix, is built on RxJS, and introduces an entirely new programming model (reactive programming), which the speaker suggested was potentially overkill for anything other than the most complex use-cases. Redux-observable provides many powerful tools for managing asynchronous streams, but the speaker seemed to feel that this translated to many additional ways to shoot yourself in the foot.

Ultimately, the speaker seemed to think that redux-saga offers the best balance of power, without introducing significant complexity. Worth also noting that redux-logic seems to be gaining some traction (also based on RxJS but suggests that it offers a simpler abstraction) but was not mentioned in this talk.

Links

Surprisingly pain-free end-to-end testing with Jest and Puppeteer

Matt Zeunert spoke about using Puppeteer and Jest for integration tests. Puppeteer is a node library, officially maintained by the Chrome team, for managing chrome (headless by default) and has a number of features:

  • Generates screenshots and PDFs of pages.
  • Crawl a SPA and generate pre-rendered content (i.e. “SSR”).
  • Automate form submission, UI testing, keyboard input, etc.
  • Create an up-to-date, automated testing environment. Run your tests directly in the latest version of Chrome using the latest JavaScript and browser features.
  • Capture a timeline trace of your site to help diagnose performance issues.

Using Puppeteer with Jest is relatively straightforward to setup and is well documented.

An example puppeteer tests looks something like:

test('can logout', async () => {
await page.click('[data-testid="userMenuButton"]')
  await page.waitForSelector('[data-testid="userMenuOpen"]')
  await page.click('[data-testid="logoutLink"]')
  await page.waitForSelector('[data-testid="userLoginForm"]')
})

Matt seemed to think that Puppeteer tests tended to be more reliable than Selenium/Webdriver tests, which as we all know are prone to flakiness.

Perhaps the only disadvantage of using Puppeteer exclusively, is the lack of cross browser support, which service like BrowserStack can help address.

Links

Micro Frontends – a microservice approach to the modern web

Ivan Jovanovic explored the notion of applying the architectural pattern of microservices to frontend development, decomposing frontend code along boundaries (pages, sections), even going as far as to advocate for mixing and composing different frameworks. Ivan suggested that this approach would primarily suit clients maintained by large teams, but that it could also be applied to a project transitioning to a new framework.

External app bootstrapping

The first case covered was that of an external, independently deployed app. The speaker suggested that the simplest means of integration in this case was communication via the global window object, followed by a slightly more sophisticated approach using an event bus library called eev, a ‘micro-library’ (< 500 bytes minified) with no dependencies.

Using eev is as simple as:

  window.e = new Eev();

  // Add a handler
  window.e.on('wrangle-wombats', (num) => {
  alert(`Wrangled ${num} wombats.`);
});
  // Emit an event
window.e.emit('wrangle-wombats', 10);

single-spa

The primary tool for achieving this goal is single-spa, which describes itself as a JavaScript Metaframework. It suggests you can:

single-spa isn’t opinionated about how applications communicate – apps can communicate and share state via the window object, an event bus like eev, or a global state store like redux.

Creating a single-spa app looks something like:

  import * as singleSpa from 'single-spa';

const appName = 'app1';
const loadingFunction = () => import('./app1/app1.js');

// routing to determine which app is loaded
const activityFunction = location => location.pathname.startsWith('/app1');

singleSpa.registerApplication(appName, loadingFunction, activityFunction);
singleSpa.start();

Links

Lightning Talk: Introduction to ReasonML

Jack Lewin gave a fascinating lightning talk about ReasonML, a new programming language developed at Facebook by the team that created React. ReasonML is an OCaml variant (strongly typed functional language, but with excellent type inference such that it feels like a dynamic language), and has native JavaScript interop. This means that you can drop raw JS into ReasonML modules, and use libraries from npm. Facebook has recently rewritten over 50% of Messenger in ReasonML, and the language appears to be gaining traction at a number of other companies.

Compared to TypeScript, ReasonML offers immutable types by default (TypeScript needs to be used in conjunction with immutable.js), runtime validation (you get json-schema for free without a library essentially), pattern matching (a powerful technique which obviates the need for complex type checking you would need in TypeScript), and an elegant module system.

An example of Reason’s elegantly minimalistic module system:

  /* FileA.re. This typically compiles to module FileA below */
  let a = 1;
  let b = 2;

  /* FileB.re */
  /* Maps FileA's implementation to a new API */
  let alpha = FileA.a;
  let beta = FileA.b;

ReasonML & React

If one was pursuing type safety in React, there’s a strong argument for ReasonML as an alternative to TypeScript, as the author of React itself is maintaining the react bindings.

Reason-react has the additional benefit of providing support for redux like stateful components and reducers natively.

Links

Related posts


Maximilian Blazek
6 November 2024

Designing Canonical’s Figma libraries for performance and structure

Design Article

How Canonical’s Design team rebuilt their Figma libraries, with practical guidelines on structure, performance, and maintenance processes. ...


Julie Muzina
13 August 2024

Visual Testing: GitHub Actions Migration & Test Optimisation

Design Article

What is Visual Testing? Visual testing analyses the visual appearance of a user interface. Snapshots of pages are taken to create a “baseline”, or the current expectation of how each page should appear. Proposed changes are then compared against the baseline. Any snapshots that deviate from the baseline are flagged for review. For example ...


Ana Sereijo
19 April 2024

Let’s talk open design

Design Article

Why aren’t there more design contributions in open source? Help us find out! ...