• Stars
    star
    1,447
  • Rank 31,252 (Top 0.7 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 7 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Resolve components asynchronously, with support for code splitting and advanced server side rendering use cases.

react-async-component ๐Ÿ“ฌ

Resolve components asynchronously, with support for code splitting and advanced server side rendering use cases.

npm MIT License Travis Codecov

const AsyncProduct = asyncComponent({
  resolve: () => System.import('./Product'),
  LoadingComponent: ({ productId }) => <div>Loading {productId}</div>, // Optional
  ErrorComponent: ({ error }) => <div>{error.message}</div> // Optional
});

<AsyncProduct productId={1} /> // ๐Ÿš€

TOCs

Introduction

This library does not require that you use either Webpack or Babel. Instead it provides you a generic and "pure" Javascript/React API which allows for the expression of lazy-loaded Components. It's Promise-based API naturally allows you to take advantage of modern code splitting APIs (e.g import(), System.import, require.ensure).

Features

  • Supports any major code splitting API.
  • Show a LoadingComponent until your component is resolved.
  • Show an ErrorComponent if your component resolution fails.
  • Prevents flash-of-content by tracking already resolved Components.
  • Full server side rendering support, allowing client side state rehydration, avoiding React checksum errors.

Usage

Imagine you had the following Product component:

export default function Product({ id }) {
  return <div>Product {id}</div>
}

To make this asynchronous create a new file that wraps it with asyncComponent, like so:

import { asyncComponent } from 'react-async-component';

export default asyncComponent({
  resolve: () => System.import('./Product')
});

I recommend that you use the following folder/file structure:

 |- components
    |- AsyncProduct
       |- index.js   // contains asyncComponent
       |- Product.js // The component you want resolved asynchronously

Now, you can simply import AsyncProduct anywhere in your application and use it exactly as you would any other component.

For example:

import AsyncProduct from './components/AsyncProduct'

export default function MyApp() {
  return (
    <div>
      <h1>Welcome to My App</h1>
      <AsyncProduct id={1337} />
    </div>
  )
}

API

asyncComponent(config)

The asynchronous component factory. Config goes in, an asynchronous component comes out.

Arguments

  • config (Object) : The configuration object for the async Component. It has the following properties available:
    • resolve (() => Promise) : A function that should return a Promise that will resolve the Component you wish to be async.
    • LoadingComponent (Component, Optional, default: null) : A Component that will be displayed until your async Component is resolved. All props will be passed to it.
    • ErrorComponent (Component, Optional, default: null) : A Component that will be displayed if any error occurred whilst trying to resolve your component. All props will be passed to it as well as an error prop containing the Error.
    • name (String, Optional, default: 'AsyncComponent') : Use this if you would like to name the created async Component, which helps when firing up the React Dev Tools for example.
    • autoResolveES2015Default (Boolean, Optional, default: true) : Especially useful if you are resolving ES2015 modules. The resolved module will be checked to see if it has a .default and if so then the value attached to .default will be used. So easy to forget to do that. ๐Ÿ˜€
    • env (String, Optional) : Provide either 'node' or 'browser' so you can write your own environment detection. Especially useful when using PhantomJS or ElectronJS to prerender the React App.
    • serverMode (Boolean, Optional, default: 'resolve') : Only applies for server side rendering applications. Please see the documentation on server side rendering. The following values are allowed.
      • 'resolve' - Your asynchronous component will be resolved and rendered on the server. It's children will be checked to see if there are any nested asynchronous component instances, which will then be processed based on the serverMode value that was associated with them.
      • 'defer' - Your asynchronous component will not be rendered on the server, instead deferring rendering to the client/browser.
      • 'boundary' - Your asynchronous component will be resolved and rendered on the server. However, if it has a nested asynchronous component instance within it's children that component will be ignored and treated as being deferred for rendering in the client/browser instead (it's serverMode will be ignored). We highly recommend that you consider using defer as much as you can.

Returns

A React Component.

Examples

LoadingComponent
export default asyncComponent({
  resolve: () => import('./Product'),
  LoadingComponent: ({ id }) => <div>Loading product {id}</div>
})
ErrorComponent
export default asyncComponent({
  resolve: () => import('./Product'),
  ErrorComponent: ({ error }) => <div>{error.message}</div>
})
Named chunks
export default asyncComponent({
  resolve: () => new Promise(resolve =>
    // Webpack's code splitting API w/naming
    require.ensure(
      [],
      (require) => {
        resolve(require('./Product'));
      },
      'ChunkName'
    )
  )
})

<AsyncComponentProvider />

Currently only useful when building server side rendering applications. Wraps your application allowing for efficient and effective use of asynchronous components.

Props

  • asyncContext (Object) : Used so that you can gain hooks into the context for server side rendering render tracking and rehydration. See the createAsyncContext helper for creating an instance.
  • rehydrateState (Object, Optional) : Used on the "browser-side" of a server side rendering application (see the docs). This allows you to provide the state returned by the server to be used to rehydrate the client appropriately.

createAsyncContext()

Creates an asynchronous context for use by the <AsyncComponentProvider />. The context is an object that exposes the following properties to you:

  • getState() (() => Object) : A function that when executed gets the current state of the <AsyncComponentProvider />. i.e. which async components resolved / failed to resolve etc. This is especially useful for server sider rendering applications where you need to provide the server rendered state to the client instance in order to ensure the required asynchronous component instances are resolved prior to render.

Server Side Rendering

NOTE: This section only really applies if you would like to have control over the behaviour of how your asyncComponent instances are rendered on the server. If you don't mind your asyncComponents always being resolved on the client only then you need not do any of the below. In my opinion there is great value in just server rendering your app shell and having everything else resolve on the client, however, you may have very strict SEO needs - in which case, we have your back.

This library has been designed for interoperability with react-async-bootstrapper.

react-async-bootstrapper allows us to do a "pre-render parse" of our React Element tree and execute an asyncBootstrap function that are attached to a components within the tree. In our case the "bootstrapping" process involves the resolution of asynchronous components so that they can be rendered "synchronously" by the server. We use this 3rd party library as it allows interoperability with other libraries which also require a "bootstrapping" process (e.g. data preloading as supported by react-jobs).

Firstly, install react-async-bootstrapper:

npm install react-async-bootstrapper

Now, let's configure the "server" side. You could use a similar express (or other HTTP server) middleware configuration:

import React from 'react'
import { renderToString } from 'react-dom/server'
import { AsyncComponentProvider, createAsyncContext } from 'react-async-component' // ๐Ÿ‘ˆ
import asyncBootstrapper from 'react-async-bootstrapper' // ๐Ÿ‘ˆ
import serialize from 'serialize-javascript'

import MyApp from './shared/components/MyApp'

export default function expressMiddleware(req, res, next) {
  //    Create the async context for our provider, this grants
  // ๐Ÿ‘‡ us the ability to tap into the state to send back to the client.
  const asyncContext = createAsyncContext()

  // ๐Ÿ‘‡ Ensure you wrap your application with the provider.
  const app = (
    <AsyncComponentProvider asyncContext={asyncContext}>
      <MyApp />
    </AsyncComponentProvider>
  )

  // ๐Ÿ‘‡ This makes sure we "bootstrap" resolve any async components prior to rendering
  asyncBootstrapper(app).then(() => {
      // We can now render our app ๐Ÿ‘‡
      const appString = renderToString(app)

      // Get the async component state. ๐Ÿ‘‡
      const asyncState = asyncContext.getState()

      const html = `
        <html>
          <head>
            <title>Example</title>
          </head>
          <body>
            <div id="app">${appString}</div>
            <script type="text/javascript">
              // Serialise the state into the HTML response ๐Ÿ‘‡
              window.ASYNC_COMPONENTS_STATE = ${serialize(asyncState)}
            </script>
          </body>
        </html>`

      res.send(html)
    })
}

Then on the "client" side you would do the following:

import React from 'react'
import { render } from 'react-dom'
import { AsyncComponentProvider, createAsyncContext } from 'react-async-component' // ๐Ÿ‘ˆ
import asyncBootstrapper from 'react-async-bootstrapper' // ๐Ÿ‘ˆ
import MyApp from './components/MyApp'

// ๐Ÿ‘‡ Get any "rehydrate" state sent back by the server
const rehydrateState = window.ASYNC_COMPONENTS_STATE

//   Ensure you wrap your application with the provider,
// ๐Ÿ‘‡ and pass in the rehydrateState.
const app = (
  <AsyncComponentProvider  rehydrateState={rehydrateState}>
    <MyApp />
  </AsyncComponentProvider>
)

//   We run the bootstrapper again, which in this context will
//   ensure that all components specified by the rehydrateState
// ๐Ÿ‘‡ will be resolved prior to render.
asyncBootstrapper(app).then(() => {
  // ๐Ÿ‘‡ Render the app
  render(app, document.getElementById('app'))
});

SSR AsyncComponent Resolution Process

It is worth us highlighting exactly how we go about resolving and rendering your asyncComponent instances on the server. This knowledge will help you become aware of potential issues with your component implementations as well as how to effectively use our provided configuration properties to create more efficient implementations.

When running react-async-bootstrapper on the server the helper has to walk through your react element tree (depth first i.e. top down) in order to discover all the asyncComponent instances and resolve them in preparation for when you call the ReactDOM.renderToString. As it walks through the tree it has to call the componentWillMount method on your Components and then the render methods so that it can get back the child react elements for each Component and continue walking down the element tree. When it discovers an asyncComponent instance it will first resolve the Component that it refers to and then it will continue walking down it's child elements (unless you set the configuration for your asyncComponent to not allow this) in order to try and discover any nested asyncComponent instances. It continues doing this until it exhausts your element tree.

Although this operation isn't as expensive as an actual render as we don't generate the DOM it can still be quite wasteful if you have a deep tree. Therefore we have provided a set of configuration values that allow you to massively optimise this process. See the next section below.

SSR Performance Optimisation

As discussed in the "SSR AsyncComponent Resolution Process" section above it is possible to have an inefficient implementation of your asyncComponent instances. Therefore we introduced a new configuration object property for the asyncComponent factory, called serverMode, which provides you with a mechanism to optimise the configuration of your async Component instances. Please see the API documentation for more information.

Understand your own applications needs and use the options appropriately . I personally recommend using mostly "defer" and a bit of "boundary". Try to see code splitting as allowing you to server side render an application shell to give the user perceived performance. Of course there will be requirements otherwise (SEO), but try to isolate these components and use a "boundary" as soon as you feel you can.

Demo

You can see a "live" version here. This is a deployment of the "React, Universally" starter kit that makes use of this library. Open the network tab and then click the menu items to see the asynchronous component resolving in action.

FAQs

Let me know if you have any...

More Repositories

1

easy-peasy

Vegetarian friendly state for React
JavaScript
5,022
star
2

react-sizeme

Make your React Components aware of their width and height!
JavaScript
1,932
star
3

react-universally

A starter kit for universal react applications.
JavaScript
1,699
star
4

react-tree-walker

Walk a React (or Preact) element tree, executing a "visitor" function against each element.
JavaScript
346
star
5

react-component-queries

Provide props to your React Components based on their Width and/or Height.
JavaScript
183
star
6

react-jobs

Asynchronously resolve data for your components, with support for server side rendering.
JavaScript
167
star
7

react-async-bootstrapper

Execute a bootstrap method on your React/Preact components. Useful for data prefetching and other activities.
JavaScript
118
star
8

code-split-component

Declarative code splitting for your Wepback bundled React projects, with SSR support.
JavaScript
116
star
9

prisma-pg-jest

Example showcasing how to use Prisma + Postgres + Jest, where each test has its own unique DB context
TypeScript
106
star
10

react-virtual-container

Optimise your React apps by only rendering components when in proximity to the viewport.
JavaScript
53
star
11

cra-monorepo

Example of a now 2.0 monorepo containing Create React App and Node Lambdas
TypeScript
52
star
12

vercel-node-server

An unofficial package allowing you to create http.Server instances of your Vercel Node lambdas.
TypeScript
50
star
13

react-injectables

Explicitly inject Components into any part of your React render tree.
JavaScript
43
star
14

easy-peasy-typescript

An example of using Easy Peasy with Typescript
TypeScript
33
star
15

react-universally-skinny

A "when size matters" adaptation of the react-universally boilerplate.
JavaScript
30
star
16

lerna-cola

Superpowers for your Lerna monorepos.
JavaScript
23
star
17

gun-most

Extends gunDB with the ability to chain into most.js observables.
JavaScript
14
star
18

i-theme

A minimalist theme for vscode
12
star
19

preact-compat-hmr

Example HMR with preact, preact-compat and webpack 2.
JavaScript
12
star
20

prisma2-template

A scratchpad for Prisma2
JavaScript
12
star
21

vercel-node-dev

An unofficial development CLI for Vercel applications targeting the Node.js runtime.
TypeScript
10
star
22

npm-library-starter

A starter kit for npm libraries.
JavaScript
9
star
23

vue-zod-form

A composition based API forms helper for Vue 3 projects that utilise TypeScript.
Vue
9
star
24

vue3-typescript-strategy

An example of a sound TS experience in Vue 3 via .tsx files
TypeScript
8
star
25

cinderella

[WIP] A tiny transformation library.
JavaScript
8
star
26

demo-react-async-and-jobs

Demo on how to use react-async-component and react-jobs together in an SSR project.
JavaScript
7
star
27

react-universally-opinionated

[WIP] An opinionated version of React, Universally.
JavaScript
7
star
28

simee

Ultra simple symlinking of local npm packages.
JavaScript
7
star
29

remix-aws-edge

An adapter for running Remix on AWS CloudFront Lambda@Edge
TypeScript
6
star
30

easy-peasy-hot-reload

Easy Peasy hot reloading example
HTML
6
star
31

templatiser

Apply templates to a file tree.
JavaScript
4
star
32

react-hey-fela

Alternative React bindings for Fela
JavaScript
4
star
33

react-app-rewire-modernizr

Adds the modernizr-loader to your react-app-rewired config.
JavaScript
3
star
34

reason-advent-2017

https://adventofcode.com solved using ReasonML
OCaml
2
star
35

code-split-match

A Webpack 2 code splitting Match component for React Router v4.
2
star
36

comickult.holding

RxJS powered holding page for comickult.
JavaScript
2
star
37

flowcheatsheet

An interactive cheat sheet for Facebook's Flow type system.
JavaScript
2
star
38

lerna-cola-sample

A sample application showcasing Lerna Cola
JavaScript
2
star
39

react-app-rewire-css-blocks

Adds support for CSS Blocks to your react-app-rewired config.
JavaScript
2
star
40

react-universally-archive

JavaScript
2
star
41

now-dev-issue

JavaScript
1
star
42

reason-react-gof

[WIP] Conway's Game of Life implemented using ReasonML/Bucklescript/React.
OCaml
1
star
43

sst-cors-example

An example of CORS configuration in the context of a Serverless Stack application.
TypeScript
1
star
44

react-responsive-element

A responsive element component for React web applications.
1
star
45

monilla

A CLI to manage monorepos with predictability and stability.
TypeScript
1
star
46

approachable-fp-in-scala

An Approachable Guide to Functional Programming in Scala
1
star
47

react-router-bundlesize

Debugging react-router bundle size optimisation.
JavaScript
1
star
48

blank

Blank project for fiddling with JS whilst having all the tooling I like
JavaScript
1
star
49

splice

A malleable content management system.
1
star
50

react-fractal-grid

A fractal grid Component set for React.
1
star