npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@dgieselaar/loadable-components

v2.2.1

Published

React code splitting made easy.

Downloads

10

Readme

Build Status Code Coverage Version MIT License Small size

PRs Welcome Chat

Watch on GitHub Star on GitHub Tweet

Read the intro blogpost

npm install loadable-components

Webpack allows modern code splitting via the dynamic import syntax. Loadable Components makes it possible to use that awesome feature with React. It is compatible with react-router and server side rendering. The API is designed to be as simple as possible to avoid useless complexity and boilerplate.

We use it in production on smooth-code.com, it's open source https://github.com/smooth-code/website.

Motivation

Splitting your React application and rendering it server-side is complicated. Several have tried, react-router gave up, today only next.js is doing it right. First I decided to not do it (afraid by react-router 😱) on my website. But then I think "Fuck code splitting shouldn't be a problem today, let's do it.".

I tried several solutions, react-async-components, react-loadable and for each of them server-side rendering was very complicated. I decided to create Loadable Components with for main goal: reducing API in order to make it as easier as possible for the developer. I inspired from Styled Components and Apollo for the API.

Getting started

// Routes.js
import loadable from 'loadable-components'

export const Home = loadable(() => import('./Home'))
export const About = loadable(() => import('./About'))
export const Contact = loadable(() => import('./Contact'))
// App.js
import React from 'react'
import { Route } from 'react-router'
import * as Routes from './Routes'

export default () => (
  <div>
    <Route exact path="/" component={Routes.Home} />
    <Route path="/about" component={Routes.About} />
    <Route path="/contact" component={Routes.Contact} />
  </div>
)

Custom loading

It is possible to add a custom loading component, by default it will render nothing:

Using a component:

const Loading = () => <div>Loading...</div>

const Home = loadable(() => import('./Home'), {
  LoadingComponent: Loading,
})

Or using render props:

import React from 'react'

const Home = loadable(() => import('./Home'), {
  render: ({ Component, loading, ownProps }) => {
    if (loading) return <div>Loading...</div>
    return <Component {...ownProps} />
  },
})

Error handling

You can configure the component rendered when an error occurs during loading, by default it will render nothing:

Using a component:

const ErrorDisplay = ({ error }) => <div>Oups! {error.message}</div>

const Home = loadable(() => import('./Home'), {
  ErrorComponent: ErrorDisplay,
})

Or using render props:

import React from 'react'

const Home = loadable(() => import('./Home'), {
  render: ({ Component, error, ownProps }) => {
    if (error) return <div>Oups! {error.message}</div>
    return <Component {...ownProps} />
  },
})

Delay

To avoid flashing a loader if the loading is very fast, you could implement a minimum delay. There is no built-in API in loadable-components but you could do it using p-min-delay.

import loadable from 'loadable-components'
import pMinDelay from 'p-min-delay'

// Wait a minimum of 200ms before loading home.
export const Home = loadable(pMinDelay(() => import('./Home'), 200))

If you want to avoid these delay server-side:

import loadable from 'loadable-components'
import pMinDelay from 'p-min-delay'

const delay = promise => {
  if (typeof window === 'undefined') return promise
  return pMinDelay(promise, 200)
}

export const Home = loadable(delay(() => import('./Home')))

Timeout

Infinite loading is not good for user experience, to avoid it implementing a timeout is a good workaround. You can do it using a third party module like promise-timeout:

import loadable from 'loadable-components'
import { timeout } from 'promise-timeout'

// Wait a maximum of 2s before sending an error.
export const Home = loadable(timeout(() => import('./Home'), 2000))

Loading multiple resources in parallel

Since loadable-components accepts a simple callback function it is easy to load multiple resource in parallel. Simply do it in JavaScript!

import React from 'react'
import loadable from 'loadable-components'

const What = loadable(async () => {
  const [{ default: Books }, { default: books }] = await Promise.all([
    import('./Books'),
    import('./books.json'),
  ])

  return props => <Books {...props} books={books} />
})

Prefetching

To enhance user experience you can fetch routes before they are requested by the user.

Prefetch on route loading

import React from 'react'
import { Route } from 'react-router'
import * as Routes from './Routes'

// Prefetch contact component
Routes.Contact.load()

const App () => (
  <div>
    <Route exact path="/" component={Routes.Home} />
    <Route path="/about" component={Routes.About} />
    <Route path="/contact" component={Routes.Contact} />
  </div>
)

Prefetch on hover

import React from 'react'
import { Contact } from './Routes'

const Links = () => (
  <div>
    <Link to="/contact" onMouseOver={Contact.load}>
      Contact
    </Link>
  </div>
)

Server-side rendering

First create a Routes.js containing all your loadable routes:

// Routes.js
import loadable from 'loadable-components'

export const Home = loadable(() => import('client/Home'))

You can use them in your application:

// App.js
import React from 'react'
import { Home } from './Routes'

const App = () => (
  <div>
    <Route exact path="/" component={Home} />
  </div>
)

export default App

Then bootstrap your application client-side using loadComponents:

// main.js
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import { loadComponents } from 'loadable-components'
import App from './App'

// Load all components needed before starting rendering
loadComponents().then(() => {
  ReactDOM.render(
    <BrowserRouter>
      <App />
    </BrowserRouter>,
    document.getElementById('main'),
  )
})

The only thing you have to do on the server is calling getLoadableState() and inserting the loadable state in your html:

// server.js
import React from 'react'
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router'
import { getLoadableState } from 'loadable-components/server'
import App from './App'

const context = {}

const app = (
  <StaticRouter location={...} context={context}>
    <App />
  </StaticRouter>
)

// Extract loadable state from application tree
getLoadableState(app).then(loadableState => {
  const html = renderToString(app)
  // Insert style tag into page
  const page = `
    <!doctype html>
    <html>
    <head></head>
    <body>
      <div id="main">${html}</div>
      ${loadableState.getScriptTag()}
    </body>
    </html>
  `
})

Configuring Babel

Server-side rendering requires to specify which modules are loaded into your loadable callback:

import loadable from 'loadable-components'

const AsyncComponent = loadable(() => import('./MyComponent'), {
  modules: ['./MyComponent'],
})

As you can see this is relatively boring and can be automated using our babel plugin loadable-components/babel.

Dynamic import syntax is natively supported by Webpack / Parcel but not by node. That's why you have to configure Babel differently for server and client:

On the server:

{
  "plugins": ["loadable-components/babel", "babel-plugin-dynamic-import-node"]
}

On the client:

{
  "plugins": ["loadable-components/babel"]
}

To have a different configuration for client and server, you can use Babel env option.

Snapshoting

An alternative to server-side rendering is snapshoting. Basically, you crawl your React website locally and you generate HTML pages.

You need to instruct your snapshot solution to save state of Loadable Components to the window in the end.

getState() will return {__LOADABLE_STATE__: {...} }, and this should be converted to <script>window.__LOADABLE_STATE__ = {...}</script> in the resulting html.

For example, to do this with react-snap you can use following code:

import { getState } from 'loadable-components'

// Set up for react-snap.
window.snapSaveState = () => getState()

Hot Reloading

Loadable Components is Hot Reload friendly, it works out of the box with React Hot Loader.

API Reference

loadable

This is the default export. It's a factory used to create a loadable component. Props are passed to the loaded component.

Arguments

  1. getComponent (Function): Function to load component asynchronously.
  2. options (Object): Facultative options to configure component behavior.

options

  1. ErrorComponent (ReactComponent): Component rendered when an error occurs, take two props: error and ownProps.
  2. LoadingComponent (ReactComponent): Component rendered during loading, take the same props from loadable component.
  3. render (Function): If specified this function is called with in render with an object: { loading, error, ownProps, Component }. It takes precedence over ErrorComponent and LoadingComponent.
  4. modules (Object): This options is only required if you do server-side rendering. It can be automated using babel plugin loadable-components/babel.
import loadable from 'loadable-components'

const MyLoadableComponent = loadable(() => import('./MyComponent'), {
  ErrorComponent: ({ error }) => <div>{error.message}</div>,
  LoadingComponent: () => <div>Loading...</div>,
})

loadComponents

This method is only required if you use server-side rendering. It loads components used in the page that has been rendered server-side.

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import { loadComponents } from 'loadable-components'
import App from './App'

// Load all components needed before starting rendering
loadComponents().then(() => {
  ReactDOM.render(
    <BrowserRouter>
      <App />
    </BrowserRouter>,
    document.getElementById('main'),
  )
})

getLoadableState

This method is only required if you use server-side rendering. It loads components recursively and extract a loadable state from a React tree.

import React from 'react'
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router'
import { getLoadableState } from 'loadable-components/server'
import App from './App'

const app = (
  <StaticRouter>
    <App />
  </StaticRouter>
)

// Extract loadable state from application tree
getLoadableState(app).then(loadableState => {
  const html = renderToString(<YourApp />)
  // Insert style tag into page
  const page = `
    <!doctype html>
    <html>
    <head></head>
    <body>
      <div id="main">${html}</div>
      ${loadableState.getScriptTag()}
    </body>
    </html>
  `
})

A loadable state has two methods to extract state:

  • loadableState.getScriptTag(): Returns a string representing a script tag.
  • loadableState.getScriptElement(): Returns a React element.

Interoperability

You can implement a loadable component by your own. To do it you have to add LOADABLE Symbol to your component:

import React from 'react'
import { LOADABLE, componentTracker } from 'loadable-components'

class ComponentWithTranslations extends React.Component {
  // Required
  static componentId = componentTracker.track(ComponentWithTranslations);

  static async load = () => {
    const response = await fetch('/translations.json')
    const translations = await response.json()
    ComponentWithTranslations.translations = translations
    return translations
  }

  static [LOADABLE] = () => ({
    componentId: ComponentWithTranslations.componentId,
    load: ComponentWithTranslations.load,
  })

  state = { translations: ComponentWithTranslations.translations }

  componentWillMount() {
    ComponentWithTranslations[LOADABLE].load()
    .then(translations => this.setState({ translations }))
  }

  render() {
    const { translations = { hello = 'hello' } } = this.props;

    return <div>{hello}</div>
  }
}

Other solutions

react-loadable offers an elegant API to load a component and enhance it. It supports a lot of features like delay and timeout. I chose to not implement it because it delay can be done in LoadingComponent and timeout can be done in getComponent function.

react-async-component offers a simple API, very similar to loadable-components API.

react-code-splitting is the basic approach of an async component, it doesn't support LoadingComponent, ErrorComponent and server-side rendering.

The main difference between these two libraries is the server-side rendering approach:

  • react-loadable requires a webpack plugin and a babel plugin. I think it's too complicated and we should not rely on it.
  • react-async-component has a better approach, analyzing tree + context, it also rely on another library. I like the idea but not the API.

Loadable Components has a simpler approach, it relies on dynamic-import-specification and assumes that it is working for node and Webpack. Then it analyzes the tree server-side and waiting for every modules to be loaded. Client-side it loads modules before rendering the application. The API is as simple as possible, no context, no magic variable.

Inspirations

MIT