Discover React Suspense

Watch screencast [en] / Watch talk [fr]

With React v16, the Facebook team shipped a new version of the core called “Fiber”. Thanks to this full rewrite, we’ve already seen new features like Fragments, Portals and ErrorBoundaries. Some other features are still under development:

  • Suspense
  • Time Slicing

For the last few months, engineers from facebook have made a few demonstrations of the possibilities of these features. Few examples are available (at the time of the writing, those are still unstable/undocumented APIs).

After the talk of Ryan Florence at React Rally presenting Suspense and how @reach/router is ready for it, I decided to make a little project:

  • to dive into those new concepts
  • to share code examples
  • to expose a simple tool that could help explain the benefits of using suspense

TRY OUT

What problems does Suspense solves ?

  • It lets you start rendering before all data is ready (rendering can be paused when data starts loading and resumed when it finishes)
  • It lets you avoid a cascade of spinners, that may cause flashes due to unintentional reflows and repaints
  • Code splitting becomes a built-in feature, easy to use (you also can pause rendering while loading script)

What this means:

  • Move the fetching of data closer to where it will be rendered – data encapsulation
  • Get rid of loading states – cleaner state in components (maybe even stateless in some use cases – no more need of componentDidMount lifecycle hook to go fetch data)
  • Finer grain control over spinners (thanks to <Placeholder>)

How to test it today

Suspense is still under active development, be aware that the APIs might change and be unstable, though it’s advanced enough to hack a project with. You’ll need to make your own build of React.

Tophe

This was a short introduction to React Suspense, go further by watching the video and play online with topheman/react-fiber-experiments.

Resources:

CircleCI – How to use the latest version of docker-compose / docker-engine ?

On my latest project topheman/docker-experiments, I’ve been using CircleCI to build docker images of my app on the CI and run unit tests through them, using the same docker-compose file configuration as I’m using on my local machine. Though, at my first build I encountered the following error:

ERROR: Version in “./docker-compose.yml” is unsupported. You might be seeing this error because you’re using the wrong Compose file version. Either specify a supported version (“2.0”, “2.1”, “3.0”, “3.1”, “3.2”) and place your service definitions under the `services` key, or omit the `version` key and place your service definitions at the root of the file to use version 1.

For more on the Compose file format versions, see https://docs.docker.com/compose/compose-file/

This means that the docker-compose file format I’m using (v3.4) isn’t supported by the docker-engine version used on the default setup of CircleCIsee compose and docker matrix compatibility.

Fortunately, on CircleCI, in machine executor mode, you can change/customize the image your VM will be running (by default: circleci/classic:latest) – see the list of images available. All I had to do was switching the image name and I was able to use an other version of docker-compose / docker-engine on the CI:

version: 2
jobs:
  build:
-    machine: true
+    machine:
+      image: circleci/classic:201808-01

You can see the modifications in that commit.

More posts to come about docker, meanwhile, you can checkout topheman/docker-experiments.

Resources:

Continuous deployment with Travis CI

This post is inspired by the notes I published on my project topheman/npm-registry-browser where you can find an application of the continuous deployment I will be writing about.

Before starting

  • I’m assuming that you have a build step (like npm run build) that creates a build folder containing anything that should be deployed
  • I will assume that you already have a test suite running on Travis CI
  • I will use github pages as hosting for my production server
  • I will use surge.sh as hosting for my staging server (BONUS)
  • The API server is not part of the flow I’m describing (hosted on another server)

I’m using the github flow which is very suited for continuous deployment (this is not a requirement). Reminder:

  • Anything on master is deployable
  • Working on a feature / a fix ? Create a branch
  • Commit to that branch locally and regularly push to the same named branch on the server
  • Open a pull-request when need feedback or ready to merge
  • Once reviewed and approved, merge to master
  • Once merged to master, it should be deployable

Goal

Here is the Continuous Deployment workflow we will setup (this is a little opiniated, you might have slightly different needs, the use cases are wide enough so that you can choose to either blindly follow or adapt):

  • Only tagged commits pushed on master will deploy to production. That way, we can choose when we want to ship to production, all we’ll have to do is tag a commit and push it.
  • Each time we ship to production, we’ll also upload the generated build folder to github to make it available in the releases section
    • teams won’t need the whole toolchain to retrieve a specific build (a simple download will do the job)
    • if a weird bug is filled for a specific version, we track the exact files that were deployed on the server
  • Each push on master will deploy to staging. That way, the QA team will have access to the latest features and will be able to report bugs that we’ll fix before finally shipping to production.

Of course, these deployments, will only happen if all the tests pass first. We won’t deploy any failing build.

Setup

Create a github token

First, you will need to create a github token to:

  • deploy on github pages
  • upload your generated build files to the releases section

1) Go to https://github.com/settings/tokens and generate a token that has at least the scope public_repo.
2) Go to https://travis-ci.org/<owner>/<project>/settings (the dashboard of your project on Travis CI) and add the token as the env variable GITHUB_TOKEN.

Deploy production to github pages

In your .travis.yml file, you will add the following:

before_deploy:
  - npm run build
deploy:
  - provider: pages
    skip_cleanup: true # Don't re-run the tests
    github_token: $GITHUB_TOKEN
    keep_history: true
    local_dir: build
    on:
      tags: true
      branch: master

You can do some deployment tests on a feature branch, just temporarily update the on section.

To check if it works on the master branch, you just have to push a tag. Example:

git tag tag-test-release-production
git push --tags origin master

Once it’s done, you can clean the tag locally (and remotly) like that (if you wish):

git tag --delete tag-test-release-production
git push --delete origin tag-test-release-production

Upload artefacts to github releases

Since we can only upload one file at a time, we will start by compressing the build folder into an archive. Update the before_deploy section of your .travis.yml file:

before_deploy:
  - npm run build
  - tar czvf build.tar.gz -C build .

Then, add a new provider to the deploy section:

deploy:
  - provider: releases
    api_key: $GITHUB_TOKEN
    file: "./build.tar.gz"
    skip_cleanup: true
    on:
      tags: true
      branch: master

Deploy to staging using surge

As you already saw, you can use multiple providers, but you can also trigger them on different occasions. Say you would like to deploy on a staging server each time someone pushes on master ? Here is an example of how to do it with surge.sh (a static site hosting solution – you may use a similar one):

Add the 2 following env vars to the travis settings of your repo:

  • SURGE_LOGIN: Set it to the email address you use with Surge
  • SURGE_TOKEN: Set it to your login token (get it by running surge token)

Add the following to the deploy section of your .travis.yml (specify your domain name):

deploy:
  - provider: surge
    project: ./build/
    domain: example.surge.sh
    skip_cleanup: true

Notes

A lot of providers are supported by Travis CI, the ones exposed in this article are just examples (as is the workflow). If you are deploying to an unsupported hosting service (or you have some custom workflow), you can make your own deploy.sh script.

Deploy hooks aren’t executed on PRs. You don’t have to worry about malicious deployment or leaking tokens to untrusted forks: both encypted travis variables and environment values set via repo settings are not provided to untrusted builds, triggered by pull requests from another repository (source).

You could use the after_deploy hook to make a curl request to make sure the deployment went through (if you have some metadatas such as the git hash or the date of the generated build, you can be 100% sure it has been deployed) – see how to inject metadatas into your generated build.

I’m using everything I described above in my project topheman/npm-registry-browser.

Add metadatas to your build files

Have you ever wondered if the file you’re been served from the server is up to date ? Why would you ?…

Not so long ago, you might have been programming in php and when something went wrong, the first thing you would say was “Try to clear your browser’s cache”.

Now that our assets filenames are hashed by default by bundlers like webpack, you could think that we shouldn’t run into such problems anymore. Since the filename of js/html (or any other files) will change based on its content, it will force the browser to retrieve the freshest version.

But between you and the server, there could be proxies / caching systems (like varnish / memcached). And more recently, on the client-side, we have ServiceWorkers that let us enable caching strategies.

So, to be 100% sure of which version of the site I’m running, for a few years now, I’ve been using a little routine that adds metadatas into the main generated files, based on:

  • infos inside the package.json (name, description, version, author, license)
  • git hash
  • date of the generation of the build

Example on an index.html:

<html>...</html><!--!
 * 
 * my-react-app-starter
 * 
 * create-react-app based project with a few pre-configured / installed features such as eslint or prettier
 * 
 * @version v1.0.0 - 2018-06-14T17:28:30+02:00
 * @revision #640ba4d - https://github.com/topheman/my-react-app-starter/tree/640ba4df20e3452acd2dd10b0f746a8300af0749
 * @author Christophe Rosset <tophe@topheman.com> (http://labs.topheman.com/)
 * @copyright 2018(c) Christophe Rosset <tophe@topheman.com> (http://labs.topheman.com/)
 * @license MIT
 * 
-->

That way, you can be sure of date / version (and other infos) of the generated file you’re been served with a simple curl.

Implementation

Whether you use create-react-app or have direct access to the webpack.config, first, copy/paste this common.js file to the root of your project, then:

npm install --save-dev moment git-rev-sync

Using create-react-app

I’ll explain bellow the implementation that you can see in this diff.

1) Copy/paste the following in a bin/expand-metadatas.js file in your project:

const { getBanner, getInfos } = require("../common");

process.env.REACT_APP_METADATAS_BANNER_HTML = getBanner("formatted");

process.env.REACT_APP_METADATAS_VERSION = getInfos().pkg.version;

2) Add --require ./bin/expand-metadatas.js to your package.json, that way, react-scripts will require bin/expand-metadatas.js before running and inject REACT_APP_METADATAS_BANNER_HTML and REACT_APP_METADATAS_VERSION as env vars:

"scripts": {
  "start": "react-scripts --require ./bin/expand-metadatas.js start",
  "build": "react-scripts --require ./bin/expand-metadatas.js build",
  "test": "react-scripts --require ./bin/expand-metadatas.js test --env=jsdom",
  "eject": "react-scripts eject"
}

3) At the end of public/index.html, append:

<!--!%REACT_APP_METADATAS_BANNER_HTML%
-->

You can also access process.env.REACT_APP_METADATAS_VERSION in the code of your app (to show the version number for example).

Using the webpack.config

Like in topheman/webpack-babel-starter, you can require common.js from your webpack.config and:

– access getBanner / getBannerHtml / getInfos
– use the return values with the HtmlWebpackPlugin

Conclusion

Next time your build is deployed on a server and you’re being told that your release doesn’t work, a simple curl will settle it … The front is not the problem ! 😉

Resources:

Pourquoi réaliser topheman/npm-registry-browser ? vidéo talk (fr)

Dans un post précédent, j’ai présenté la démarche que j’ai suivie pour mon dernier projet en React topheman/npm-registry-browser.

Il y a quelques jours, j’ai fait un talk à ce propos au meetup ReactJS chez Toucan Toco. Le meetup était enregistré, vous pouvez visionner mon talk (30min) en première partie de la vidéo :

Ressources :