Kit Randel
on 9 August 2018
Conference Report: Fullstack 2018 London
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:
- Use multiple frameworks on the same page without refreshing the page (React, AngularJS, Angular, etc.)
- Write code using a new framework, without rewriting your existing app
- Lazy load code for improved initial load time.
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.