Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

⏳ A higher order component for loading components with promises.

License

NotificationsYou must be signed in to change notification settings

slorber/react-loadable

 
 

Repository files navigation

IMPORTANT: this is a minimalistic fork to solve thecomponentWillMount warning for thefacebook/docusaurus project.

v6.0 also adds support for React 18.3 / React 19, and removes the useless prop-type dependency.

We don't plan to support/maintain this project, just get rid of the deprecation warnings for our own needs. It is provided as-is, and we won't accept PRs.


React Loadable

A higher order component for loading components with dynamic imports.

Install

yarn add react-loadable

Example

importLoadablefrom'react-loadable';importLoadingfrom'./my-loading-component';constLoadableComponent=Loadable({loader:()=>import('./my-component'),loading:Loading,});exportdefaultclassAppextendsReact.Component{render(){return<LoadableComponent/>;}}

Happy Customers:

Users

If your company or project is using React Loadable, please open a PR and addyourself to this list (in alphabetical order please)

Also See:

  • react-loadable-visibility - Building on top of and keeping the same API asreact-loadable, this library enables you to load content that is visible on the screen.

  • react-loadable-ssr-addon - Server Side Render add-on forreact-loadable. Discover & load automatically dynamically all files dependencies, e.g. splitted chunks, css, etc.



GUIDE

Guide

So you've got your React app, you're bundling it with Webpack, and things aregoing smooth. But then one day you notice your app's bundle is getting so bigthat it's slowing things down.

It's time to start code-splitting your app!

A single giant bundle vs multiple smaller bundles

Code-splitting is the process of taking one large bundle containing your entireapp, and splitting them up into multiple smaller bundles which contain separateparts of your app.

This might seem difficult to do, but tools like Webpack have this built in, andReact Loadable is designed to make it super simple.

Route-based splitting vs. Component-based splitting

A common piece of advice you will see is to break your app into separate routesand load each one asynchronously. This seems to work well enough for many apps–as a user, clicking a link and waiting for a page to load is a familiarexperience on the web.

But we can do better than that.

Using most routing tools for React, a route is simply a component. There'snothing particularly special about them (Sorry Ryan and Michael– you're what'sspecial). So what if we optimized for splitting around components instead ofroutes? What would that get us?

Route vs. component centric code splitting

As it turns out: Quite a lot. There are many more places than just routes whereyou can pretty easily split apart your app. Modals, tabs, and many more UIcomponents hide content until the user has done something to reveal it.

Example: Maybe your app has a map buried inside of a tab component. Whywould you load a massive mapping library for the parent route every time whenthe user may never go to that tab?

Not to mention all the places where you can defer loading content until higherpriority content is finished loading. That component at the bottom of your pagewhich loads a bunch of libraries: Why should that be loaded at the same time asthe content at the top?

And because routes are just components, we can still easily code-split at theroute level.

Introducing new code-splitting points in your app should be so easy that youdon't think twice about it. It should be a matter of changing a few lines ofcode and everything else should be automated.

Introducing React Loadable

React Loadable is a small library that makes component-centric code splittingincredibly easy in React.

Loadable is a higher-order component (a function that creates a component)which lets you dynamically load any module before rendering it into your app.

Let's imagine two components, one that imports and renders another.

importBarfrom'./components/Bar';classFooextendsReact.Component{render(){return<Bar/>;}}

Right now we're depending onBar being imported synchronously viaimport,but we don't need it until we go to render it. So why don't we just defer that?

Using adynamic import (a tc39 proposal currently at Stage 3)we can modify our component to loadBar asynchronously.

classMyComponentextendsReact.Component{state={Bar:null};componentWillMount(){import('./components/Bar').then(Bar=>{this.setState({Bar:Bar.default});});}render(){let{Bar}=this.state;if(!Bar){return<div>Loading...</div>;}else{return<Bar/>;};}}

But that's a whole bunch of work, and it doesn't even handle a bunch of cases.What about whenimport() fails? What about server-side rendering?

Instead you can useLoadable to abstract away the problem.

importLoadablefrom'react-loadable';constLoadableBar=Loadable({loader:()=>import('./components/Bar'),loading(){return<div>Loading...</div>}});classMyComponentextendsReact.Component{render(){return<LoadableBar/>;}}

Automatic code-splitting onimport()

When you useimport() with Webpack 2+, it willautomatically code-split foryou with no additional configuration.

This means that you can easily experiment with new code splitting points justby switching toimport() and using React Loadable. Figure out what performsbest for your app.

Creating a great "Loading..." Component

Rendering a static "Loading..." doesn't communicate enough to the user. Youalso need to think about error states, timeouts, and making it a niceexperience.

functionLoading(){return<div>Loading...</div>;}Loadable({loader:()=>import('./WillFailToLoad'),// oh no!loading:Loading,});

To make this all nice, yourloading component receives acouple different props.

Loading error states

When yourloader fails, yourloading componentwill receive anerror prop which will be anError object (otherwise itwill benull).

functionLoading(props){if(props.error){return<div>Error!<buttononClick={props.retry}>Retry</button></div>;}else{return<div>Loading...</div>;}}

AvoidingFlash Of Loading Component

Sometimes components load really quickly (<200ms) and the loading screen onlyquickly flashes on the screen.

A number of user studies have proven that this causes users to perceive thingstaking longer than they really have. If you don't show anything, users perceiveit as being faster.

So your loading component will also get apastDelay propwhich will only be true once the component has taken longer to load than a setdelay.

functionLoading(props){if(props.error){return<div>Error!<buttononClick={props.retry}>Retry</button></div>;}elseif(props.pastDelay){return<div>Loading...</div>;}else{returnnull;}}

This delay defaults to200ms but you can also customize thedelay inLoadable.

Loadable({loader:()=>import('./components/Bar'),loading:Loading,delay:300,// 0.3 seconds});

Timing out when theloader is taking too long

Sometimes network connections suck and never resolve or fail, they just hangthere forever. This sucks for the user because they won't know if it shouldalways take this long, or if they should try refreshing.

Theloading component will receive atimedOut prop which will be set totrue when theloader has timed out.

functionLoading(props){if(props.error){return<div>Error!<buttononClick={props.retry}>Retry</button></div>;}elseif(props.timedOut){return<div>Taking a long time...<buttononClick={props.retry}>Retry</button></div>;}elseif(props.pastDelay){return<div>Loading...</div>;}else{returnnull;}}

However, this feature is disabled by default. To turn it on, you can pass atimeout option toLoadable.

Loadable({loader:()=>import('./components/Bar'),loading:Loading,timeout:10000,// 10 seconds});

Customizing rendering

By defaultLoadable will render thedefault export of the returned module.If you want to customize this behavior you can use therender option.

Loadable({loader:()=>import('./my-component'),render(loaded,props){letComponent=loaded.namedExport;return<Component{...props}/>;}});

Loading multiple resources

Technically you can do whatever you want withinloader() as long as itreturns a promise andyou're able to render something.But writing it out can be a bit annoying.

To make it easier to load multiple resources in parallel, you can useLoadable.Map.

Loadable.Map({loader:{Bar:()=>import('./Bar'),i18n:()=>fetch('./i18n/bar.json').then(res=>res.json()),},render(loaded,props){letBar=loaded.Bar.default;leti18n=loaded.i18n;return<Bar{...props}i18n={i18n}/>;},});

When usingLoadable.Map therender() method is required. Itwill be passed aloaded param which will be an object matching the shape ofyourloader.

Preloading

As an optimization, you can also decide to preload a component before it getsrendered.

For example, if you need to load a new component when a button gets pressed,you could start preloading the component when the user hovers over the button.

The component created byLoadable exposes astaticpreload method which does exactly this.

constLoadableBar=Loadable({loader:()=>import('./Bar'),loading:Loading,});classMyComponentextendsReact.Component{state={showBar:false};onClick=()=>{this.setState({showBar:true});};onMouseOver=()=>{LoadableBar.preload();};render(){return(<div><buttononClick={this.onClick}onMouseOver={this.onMouseOver}>          Show Bar</button>{this.state.showBar&&<LoadableBar/>}</div>)}}



SERVER SIDE RENDERING

Server-Side Rendering

When you go to render all these dynamically loaded components, what you'll getis a whole bunch of loading screens.

This really sucks, but the good news is that React Loadable is designed tomake server-side rendering work as if nothing is being loaded dynamically.

Here's our starting server usingExpress.

importexpressfrom'express';importReactfrom'react';importReactDOMServerfrom'react-dom/server';importAppfrom'./components/App';constapp=express();app.get('/',(req,res)=>{res.send(`    <!doctype html>    <html lang="en">      <head>...</head>      <body>        <div>${ReactDOMServer.renderToString(<App/>)}</div>        <script src="/dist/main.js"></script>      </body>    </html>  `);});app.listen(3000,()=>{console.log('Running on http://localhost:3000/');});

Preloading all your loadable components on the server

The first step to rendering the correct content from the server is to make surethat all of your loadable components are already loaded when you go to renderthem.

To do this, you can use theLoadable.preloadAllmethod. It returns a promise that will resolve when all your loadablecomponents are ready.

Loadable.preloadAll().then(()=>{app.listen(3000,()=>{console.log('Running on http://localhost:3000/');});});

Picking up a server-side rendered app on the client

This is where things get a little bit tricky. So let's prepare ourselveslittle bit.

In order for us to pick up what was rendered from the server we need to haveall the same code that was used to render on the server.

To do this, we first need our loadable components telling us which modules theyare rendering.

Declaring which modules are being loaded

There are two options inLoadable andLoadable.Map which are used to tell us which modules ourcomponent is trying to load:opts.modules andopts.webpack.

Loadable({loader:()=>import('./Bar'),modules:['./Bar'],webpack:()=>[require.resolveWeak('./Bar')],});

But don't worry too much about these options. React Loadable includes aBabel plugin to add them for you.

Just add thereact-loadable/babel plugin to your Babel config:

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

Now these options will automatically be provided.

For typescript you can usereact-loadable-ts-transformer which is a ts analog of react-loadable/babel plugin.

Finding out which dynamic modules were rendered

Next we need to find out which modules were actually rendered when a requestcomes in.

For this, there isLoadable.Capture component which canbe used to collect all the modules that were rendered.

importLoadablefrom'react-loadable';app.get('/',(req,res)=>{letmodules=[];lethtml=ReactDOMServer.renderToString(<Loadable.Capturereport={moduleName=>modules.push(moduleName)}><App/></Loadable.Capture>);console.log(modules);res.send(`...${html}...`);});

Mapping loaded modules to bundles

In order to make sure that the client loads all the modules that were renderedserver-side, we'll need to map them to the bundles that Webpack created.

This comes in two parts.

First we need Webpack to tell us which bundles each module lives inside. Forthis there is theReact Loadable Webpack plugin.

Import theReactLoadablePlugin fromreact-loadable/webpack and include itin your webpack config. Pass it afilename for where to store the JSON dataabout our bundles.

// webpack.config.jsimport{ReactLoadablePlugin}from'react-loadable/webpack';exportdefault{plugins:[newReactLoadablePlugin({filename:'./dist/react-loadable.json',}),],};

Then we'll go back to our server and use this data to convert our modules tobundles.

To convert from modules to bundles, import thegetBundlesmethod fromreact-loadable/webpack and the data from Webpack.

importLoadablefrom'react-loadable';import{getBundles}from'react-loadable/webpack'importstatsfrom'./dist/react-loadable.json';app.get('/',(req,res)=>{letmodules=[];lethtml=ReactDOMServer.renderToString(<Loadable.Capturereport={moduleName=>modules.push(moduleName)}><App/></Loadable.Capture>);letbundles=getBundles(stats,modules);// ...});

We can then render these bundles into<script> tags in our HTML.

It is important that the bundles are includedbefore the main bundle, so thatthey can be loaded by the browser prior to the app rendering.

However, as the Webpack manifest (including the logic for parsing bundles) lives inthe main bundle, it will need to be extracted into its own chunk.

This is easy to do with theCommonsChunkPlugin

// webpack.config.jsexportdefault{plugins:[newwebpack.optimize.CommonsChunkPlugin({name:'manifest',minChunks:Infinity})]}

Notice: As of Webpack 4 the CommonsChunkPlugin has been removed and the manifest doesn't need to be extracted anymore.

letbundles=getBundles(stats,modules);res.send(`  <!doctype html>  <html lang="en">    <head>...</head>    <body>      <div>${html}</div>      <script src="/dist/manifest.js"></script>      <script src="/dist/main.js"></script>${bundles.map(bundle=>{return`<script src="/dist/${bundle.file}"></script>`// alternatively if you are using publicPath option in webpack config// you can use the publicPath value from bundle, e.g:// return `<script src="${bundle.publicPath}"></script>`}).join('\n')}      <script>window.main();</script>    </body>  </html>`);

Preloading ready loadable components on the client

We can use theLoadable.preloadReady() method on theclient to preload the loadable components that were included on the page.

LikeLoadable.preloadAll(), it returns a promise,which on resolution means that we can hydrate our app.

// src/entry.jsimportReactfrom'react';importReactDOMfrom'react-dom';importLoadablefrom'react-loadable';importAppfrom'./components/App';window.main=()=>{Loadable.preloadReady().then(()=>{ReactDOM.hydrate(<App/>,document.getElementById('app'));});};

Now server-side rendering should work perfectly!



API DOCS

API Docs

Loadable

A higher-order component for dynamicallyloading a module beforerendering it, aloading component is renderedwhile the module is unavailable.

constLoadableComponent=Loadable({loader:()=>import('./Bar'),loading:Loading,delay:200,timeout:10000,});

This returns aLoadableComponent.

Loadable.Map

A higher-order component that allows you to load multiple resources in parallel.

Loadable.Map'sopts.loader accepts an object of functions, andneeds aopts.render method.

Loadable.Map({loader:{Bar:()=>import('./Bar'),i18n:()=>fetch('./i18n/bar.json').then(res=>res.json()),},render(loaded,props){letBar=loaded.Bar.default;leti18n=loaded.i18n;return<Bar{...props}i18n={i18n}/>;}});

When usingLoadable.Map therender() method'sloaded param will be anobject with the same shape as yourloader.

Loadable andLoadable.Map Options

opts.loader

A function returning a promise that loads your module.

Loadable({loader:()=>import('./Bar'),});

When using withLoadable.Map this accepts an object of thesetypes of functions.

Loadable.Map({loader:{Bar:()=>import('./Bar'),i18n:()=>fetch('./i18n/bar.json').then(res=>res.json()),},});

When using withLoadable.Map you'll also need to pass aopts.render function.

opts.loading

ALoadingComponent that renders while a module isloading or when it errors.

Loadable({loading:LoadingComponent,});

This option is required, if you don't want to render anything, returnnull.

Loadable({loading:()=>null,});

opts.delay

Time to wait (in milliseconds) before passingprops.pastDelay to yourloadingcomponent. This defaults to200.

Loadable({delay:200});

Read more about delays.

opts.timeout

Time to wait (in milliseconds) before passingprops.timedOut to yourloading component.This is turned off by default.

Loadable({timeout:10000});

Read more about timeouts.

opts.render

A function to customize the rendering of loaded modules.

Receivesloaded which is the resolved value ofopts.loaderandprops which are the props passed to theLoadableComponent.

Loadable({render(loaded,props){letComponent=loaded.default;return<Component{...props}/>;}});

opts.webpack

An optional function which returns an array of Webpack module ids which you canget withrequire.resolveWeak.

Loadable({loader:()=>import('./Foo'),webpack:()=>[require.resolveWeak('./Foo')],});

This option can be automated with theBabel Plugin.

opts.modules

An optional array with module paths for your imports.

Loadable({loader:()=>import('./my-component'),modules:['./my-component'],});

This option can be automated with theBabel Plugin.

LoadableComponent

This is the component returned byLoadable andLoadable.Map.

constLoadableComponent=Loadable({// ...});

Props passed to this component will be passed straight through to thedynamically loaded component viaopts.render.

LoadableComponent.preload()

This is a static method onLoadableComponent which canbe used to load the component ahead of time.

constLoadableComponent=Loadable({...});LoadableComponent.preload();

This returns a promise, but you should avoid waiting for that promise toresolve to update your UI. In most cases it creates a bad user experience.

Read more about preloading.

LoadingComponent

This is the component you pass toopts.loading.

functionLoadingComponent(props){if(props.error){// When the loader has erroredreturn<div>Error!<buttononClick={props.retry}>Retry</button></div>;}elseif(props.timedOut){// When the loader has taken longer than the timeoutreturn<div>Taking a long time...<buttononClick={props.retry}>Retry</button></div>;}elseif(props.pastDelay){// When the loader has taken longer than the delayreturn<div>Loading...</div>;}else{// When the loader has just startedreturnnull;}}Loadable({loading:LoadingComponent,});

Read more about loading components

props.error

AnError object passed toLoadingComponent when theloader has failed. When there is no error,null ispassed.

functionLoadingComponent(props){if(props.error){return<div>Error!</div>;}else{return<div>Loading...</div>;}}

Read more about errors.

props.retry

A function prop passed toLoadingComponent when theloader has failed, used to retry loading the component.

functionLoadingComponent(props){if(props.error){return<div>Error!<buttononClick={props.retry}>Retry</button></div>;}else{return<div>Loading...</div>;}}

Read more about errors.

props.timedOut

A boolean prop passed toLoadingComponent after a settimeout.

functionLoadingComponent(props){if(props.timedOut){return<div>Taking a long time...</div>;}else{return<div>Loading...</div>;}}

Read more about timeouts.

props.pastDelay

A boolean prop passed toLoadingComponent after a setdelay.

functionLoadingComponent(props){if(props.pastDelay){return<div>Loading...</div>;}else{returnnull;}}

Read more about delays.

Loadable.preloadAll()

This will call all of theLoadableComponent.preload methods recursivelyuntil they are all resolved. Allowing you to preload all of your dynamicmodules in environments like the server.

Loadable.preloadAll().then(()=>{app.listen(3000,()=>{console.log('Running on http://localhost:3000/');});});

It's important to note that this requires that you declare all of your loadablecomponents when modules are initialized rather than when your app is beingrendered.

Good:

// During module initialization...constLoadableComponent=Loadable({...});classMyComponentextendsReact.Component{componentDidMount(){// ...}}

Bad:

// ...classMyComponentextendsReact.Component{componentDidMount(){// During app render...constLoadableComponent=Loadable({...});}}

Note:Loadable.preloadAll() will not work if you have more than onecopy ofreact-loadable in your app.

Read more about preloading on the server.

Loadable.preloadReady()

Check for modules that are already loaded in the browser and call the matchingLoadableComponent.preload methods.

Loadable.preloadReady().then(()=>{ReactDOM.hydrate(<App/>,document.getElementById('app'));});

Read more about preloading on the client.

Loadable.Capture

A component for reporting which modules were rendered.

Accepts areport prop which is called for everymoduleName that isrendered via React Loadable.

letmodules=[];lethtml=ReactDOMServer.renderToString(<Loadable.Capturereport={moduleName=>modules.push(moduleName)}><App/></Loadable.Capture>);console.log(modules);

Read more about capturing rendered modules.

Babel Plugin

Providingopts.webpack andopts.modules forevery loadable component is a lot of manual work to remember to do.

Instead you can add the Babel plugin to your config and it will automate it foryou:

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

Input

importLoadablefrom'react-loadable';constLoadableMyComponent=Loadable({loader:()=>import('./MyComponent'),});constLoadableComponents=Loadable.Map({loader:{One:()=>import('./One'),Two:()=>import('./Two'),},});

Output

importLoadablefrom'react-loadable';importpathfrom'path';constLoadableMyComponent=Loadable({loader:()=>import('./MyComponent'),webpack:()=>[require.resolveWeak('./MyComponent')],modules:[path.join(__dirname,'./MyComponent')],});constLoadableComponents=Loadable.Map({loader:{One:()=>import('./One'),Two:()=>import('./Two'),},webpack:()=>[require.resolveWeak('./One'),require.resolveWeak('./Two')],modules:[path.join(__dirname,'./One'),path.join(__dirname,'./Two')],});

Read more about declaring modules.

Webpack Plugin

In order tosend the right bundles downwhen rendering server-side, you'll need the React Loadable Webpack plugin to provide you with a mapping of modules to bundles.

// webpack.config.jsimport{ReactLoadablePlugin}from'react-loadable/webpack';exportdefault{plugins:[newReactLoadablePlugin({filename:'./dist/react-loadable.json',}),],};

This will create a file (opts.filename) which you can import to map modulesto bundles.

Read more about mapping modules to bundles.

getBundles

A method exported byreact-loadable/webpack for converting modules tobundles.

import{getBundles}from'react-loadable/webpack';letbundles=getBundles(stats,modules);

Read more about mapping modules to bundles.



FAQ

FAQ

How do I avoid repetition?

Specifying the sameloading component ordelay every time you useLoadable() gets repetitive fast. Instead you can wrapLoadable with yourown Higher-Order Component (HOC) to set default options.

importLoadablefrom'react-loadable';importLoadingfrom'./my-loading-component';exportdefaultfunctionMyLoadable(opts){returnLoadable(Object.assign({loading:Loading,delay:200,timeout:10000,},opts));};

Then you can just specify aloader when you go to use it.

importMyLoadablefrom'./MyLoadable';constLoadableMyComponent=MyLoadable({loader:()=>import('./MyComponent'),});exportdefaultclassAppextendsReact.Component{render(){return<LoadableMyComponent/>;}}

Unfortunately at the moment using wrapped Loadable breaksreact-loadable/babel so in such case you have to add required properties (modules,webpack) manually.

importMyLoadablefrom'./MyLoadable';constLoadableMyComponent=MyLoadable({loader:()=>import('./MyComponent'),modules:['./MyComponent'],webpack:()=>[require.resolveWeak('./MyComponent')],});exportdefaultclassAppextendsReact.Component{render(){return<LoadableMyComponent/>;}}

How do I handle other styles.css or sourcemaps.map with server-side rendering?

When you callgetBundles, it may return file types other thanJavaScript depending on your Webpack configuration.

To handle this, you should manually filter down to the file extensions thatyou care about:

letbundles=getBundles(stats,modules);letstyles=bundles.filter(bundle=>bundle.file.endsWith('.css'));letscripts=bundles.filter(bundle=>bundle.file.endsWith('.js'));res.send(`  <!doctype html>  <html lang="en">    <head>      ...${styles.map(style=>{return`<link href="/dist/${style.file}" rel="stylesheet"/>`}).join('\n')}    </head>    <body>      <div>${html}</div>      <script src="/dist/main.js"></script>${scripts.map(script=>{return`<script src="/dist/${script.file}"></script>`}).join('\n')}    </body>  </html>`);

About

⏳ A higher order component for loading components with promises.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript100.0%

[8]ページ先頭

©2009-2025 Movatter.jp