├── .babelrc ├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README-old.md ├── README.md ├── __fixtures__ ├── component.es6.js └── component.js ├── __tests__ ├── __snapshots__ │ └── test.js.snap └── test.js ├── babel.ts ├── example ├── client.js ├── components │ ├── App.js │ ├── Descendant.js │ ├── Example.js │ ├── ExampleNested.js │ ├── Loading.js │ └── Sibling.js └── server.js ├── index.tsx ├── lib.ts ├── package.json ├── webpack.config.js ├── webpack.ts └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env" 5 | ], 6 | ["@babel/preset-react", { 7 | "runtime": "automatic" 8 | }], 9 | "@babel/preset-typescript" 10 | ], 11 | "plugins": [ 12 | "@babel/plugin-proposal-class-properties", 13 | "@babel/plugin-transform-object-assign", 14 | "@babel/plugin-transform-async-to-generator" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = tab 6 | tab_width = 2 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | *.log 4 | coverage 5 | example/dist 6 | /babel.js 7 | /babel.d.ts 8 | /webpack.js 9 | /webpack.d.ts 10 | /lib.js 11 | /lib.d.ts 12 | /index.js 13 | /index.d.ts 14 | /esm 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10' 4 | - '12' 5 | - '14' 6 | cache: yarn 7 | script: yarn build && yarn test --runInBand --coverage 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.5.0 2 | Breaking change: Target ESNext to simplify output 3 | Support both ESM and CJS via conditional exports 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-present Jamie Kyle 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README-old.md: -------------------------------------------------------------------------------- 1 | ![React Loadable](http://thejameskyle.com/img/react-loadable-header.png) 2 | 3 | > A higher order component for loading components with dynamic imports. 4 | 5 | ## Install 6 | 7 | ```sh 8 | yarn add react-loadable 9 | ``` 10 | 11 | ## Example 12 | 13 | ```js 14 | import Loadable from 'react-loadable'; 15 | import Loading from './my-loading-component'; 16 | 17 | const LoadableComponent = Loadable({ 18 | loader: () => import('./my-component'), 19 | loading: Loading, 20 | }); 21 | 22 | export default class App extends React.Component { 23 | render() { 24 | return ; 25 | } 26 | } 27 | ``` 28 | 29 | ## Happy Customers: 30 | 31 | - ["I'm obsessed with this right now: CRA with React Router v4 and react-loadable. Free code splitting, this is so easy."](https://twitter.com/matzatorski/status/872059865350406144) 32 | - ["Oh hey - using loadable component I knocked 13K off my initial load. Easy win!"](https://twitter.com/AdamRackis/status/846593080992153600) 33 | - ["Had a look and its awesome. shaved like 50kb off our main bundle."](https://github.com/quran/quran.com-frontend/pull/701#issuecomment-287908551) 34 | - ["I've got that server-side rendering + code splitting + PWA ServiceWorker caching setup done 😎 (thanks to react-loadable). Now our frontend is super fast."](https://twitter.com/mxstbr/status/922375575217627136) 35 | - ["Using react-loadable went from 221.28 KB → 115.76 KB @ main bundle. Fucking awesome and very simple API."](https://twitter.com/evgenyrodionov/status/958821614644269057) 36 | - ["We've reduced our entry chunk by a lot & reduced initial load time by ~50%!"](https://github.com/jamiebuilds/react-loadable/pull/181) 37 | - ["React-loadable is killer! We've decreased our load size by over 50kb with only 2 files! Can't wait to see how much lower it will go."](https://github.com/jamiebuilds/react-loadable/pull/180/) 38 | 39 | ## Users 40 | 41 | - [AdHawk / Flooring Stores](https://www.flooringstores.com) 42 | - [Akutbolig.dk](https://www.akutbolig.dk) 43 | - [Analog.Cafe](https://www.analog.cafe) 44 | - [Ambrosus](https://ambrosus.com) 45 | - [Appbase.io](https://github.com/appbaseio/reactivesearch) 46 | - [Atlassian](https://www.atlassian.com/) 47 | - [BBC News](https://github.com/BBC-News/simorgh) 48 | - [Blytzpay](https://www.blytzpay.com) 49 | - [ClearTax](https://cleartax.in) 50 | - [Cloudflare](https://www.cloudflare.com) 51 | - [Chibaki](https://chibaki.co) 52 | - [Compass](https://compass.com) 53 | - [Curio](https://www.curio.org) 54 | - [Delivery.com](https://www.delivery.com) 55 | - [Doctor.com](https://www.doctor.com/) 56 | - [Dollar Shave Club](https://github.com/dollarshaveclub) 57 | - [Dresez](https://dresez.pk/) 58 | - [Edcast](https://www.edcast.com/) 59 | - [Evidation Health](https://evidation.com/) 60 | - [Flexport](https://flexport.com/) 61 | - [Flyhomes](https://flyhomes.com) 62 | - [Gogo](https://gogoair.com) 63 | - [Gofore](https://gofore.com/en/home/) 64 | - [Graana](https://www.graana.com/) 65 | - [Localie](https://localie.co/en) 66 | - [MediaTek MCS-Lite](https://github.com/MCS-Lite) 67 | - [NiYO Solutions Inc.](https://www.goniyo.com/) 68 | - [Officepulse](https://www.officepulse.in/) 69 | - [PageSpeed Green](https://pagespeed.green/) 70 | - [Perx](https://www.perxtech.com/) 71 | - [Plottu](https://public.plottu.com) 72 | - [reformma](https://reformma.com.br) 73 | - [Render](https://render.com) 74 | - [Shift](https://shift.com) 75 | - [Snipit](https://snipit.io) 76 | - [Spectrum.chat](https://spectrum.chat) 77 | - [Superblocks](https://superblocks.com) 78 | - [Sprint Boards](https://sprintboards.io) 79 | - [Talentpair](https://talentpair.com) 80 | - [Tinder](https://tinder.com/) 81 | - [Unsplash](https://unsplash.com/) 82 | - [Wave](https://waveapps.com/) 83 | - [WUZZUF](https://wuzzuf.net/) 84 | - [Wxb](https://wxb.com/wxpush) 85 | 86 | > _If your company or project is using React Loadable, please open a PR and add 87 | > yourself to this list (in alphabetical order please)_ 88 | 89 | ## Also See: 90 | 91 | - [`react-loadable-visibility`](https://github.com/stratiformltd/react-loadable-visibility) - Building on top of and keeping the same API as `react-loadable`, this library enables you to load content that is visible on the screen. 92 | 93 | - [`react-loadable-ssr-addon`](https://github.com/themgoncalves/react-loadable-ssr-addon) - Server Side Render add-on for `react-loadable`. Discover & load automatically dynamically all files dependencies, e.g. splitted chunks, css, etc. 94 | 95 |

96 |
97 |
98 | GUIDE 99 |
100 |
101 | Guide 102 |

103 | 104 | So you've got your React app, you're bundling it with Webpack, and things are 105 | going smooth. But then one day you notice your app's bundle is getting so big 106 | that it's slowing things down. 107 | 108 | It's time to start code-splitting your app! 109 | 110 | ![A single giant bundle vs multiple smaller bundles](http://thejameskyle.com/img/react-loadable-split-bundles.png) 111 | 112 | Code-splitting is the process of taking one large bundle containing your entire 113 | app, and splitting them up into multiple smaller bundles which contain separate 114 | parts of your app. 115 | 116 | This might seem difficult to do, but tools like Webpack have this built in, and 117 | React Loadable is designed to make it super simple. 118 | 119 | ### Route-based splitting vs. Component-based splitting 120 | 121 | A common piece of advice you will see is to break your app into separate routes 122 | and load each one asynchronously. This seems to work well enough for many apps– 123 | as a user, clicking a link and waiting for a page to load is a familiar 124 | experience on the web. 125 | 126 | But we can do better than that. 127 | 128 | Using most routing tools for React, a route is simply a component. There's 129 | nothing particularly special about them (Sorry Ryan and Michael– you're what's 130 | special). So what if we optimized for splitting around components instead of 131 | routes? What would that get us? 132 | 133 | ![Route vs. component centric code splitting](http://thejameskyle.com/img/react-loadable-component-splitting.png) 134 | 135 | As it turns out: Quite a lot. There are many more places than just routes where 136 | you can pretty easily split apart your app. Modals, tabs, and many more UI 137 | components hide content until the user has done something to reveal it. 138 | 139 | > **Example:** Maybe your app has a map buried inside of a tab component. Why 140 | > would you load a massive mapping library for the parent route every time when 141 | > the user may never go to that tab? 142 | 143 | Not to mention all the places where you can defer loading content until higher 144 | priority content is finished loading. That component at the bottom of your page 145 | which loads a bunch of libraries: Why should that be loaded at the same time as 146 | the content at the top? 147 | 148 | And because routes are just components, we can still easily code-split at the 149 | route level. 150 | 151 | Introducing new code-splitting points in your app should be so easy that you 152 | don't think twice about it. It should be a matter of changing a few lines of 153 | code and everything else should be automated. 154 | 155 | ### Introducing React Loadable 156 | 157 | React Loadable is a small library that makes component-centric code splitting 158 | incredibly easy in React. 159 | 160 | `Loadable` is a higher-order component (a function that creates a component) 161 | which lets you dynamically load any module before rendering it into your app. 162 | 163 | Let's imagine two components, one that imports and renders another. 164 | 165 | ```js 166 | import Bar from './components/Bar'; 167 | 168 | class Foo extends React.Component { 169 | render() { 170 | return ; 171 | } 172 | } 173 | ``` 174 | 175 | Right now we're depending on `Bar` being imported synchronously via `import`, 176 | but we don't need it until we go to render it. So why don't we just defer that? 177 | 178 | Using a **dynamic import** ([a tc39 proposal currently at Stage 3](https://github.com/tc39/proposal-dynamic-import)) 179 | we can modify our component to load `Bar` asynchronously. 180 | 181 | ```js 182 | class MyComponent extends React.Component { 183 | state = { 184 | Bar: null 185 | }; 186 | 187 | componentWillMount() { 188 | import('./components/Bar').then(Bar => { 189 | this.setState({ Bar: Bar.default }); 190 | }); 191 | } 192 | 193 | render() { 194 | let {Bar} = this.state; 195 | if (!Bar) { 196 | return
Loading...
; 197 | } else { 198 | return ; 199 | }; 200 | } 201 | } 202 | ``` 203 | 204 | But that's a whole bunch of work, and it doesn't even handle a bunch of cases. 205 | What about when `import()` fails? What about server-side rendering? 206 | 207 | Instead you can use `Loadable` to abstract away the problem. 208 | 209 | ```js 210 | import Loadable from 'react-loadable'; 211 | 212 | const LoadableBar = Loadable({ 213 | loader: () => import('./components/Bar'), 214 | loading() { 215 | return
Loading...
216 | } 217 | }); 218 | 219 | class MyComponent extends React.Component { 220 | render() { 221 | return ; 222 | } 223 | } 224 | ``` 225 | 226 | ### Automatic code-splitting on `import()` 227 | 228 | When you use `import()` with Webpack 2+, it will 229 | [automatically code-split](https://webpack.js.org/guides/code-splitting/) for 230 | you with no additional configuration. 231 | 232 | This means that you can easily experiment with new code splitting points just 233 | by switching to `import()` and using React Loadable. Figure out what performs 234 | best for your app. 235 | 236 | ### Creating a great "Loading..." Component 237 | 238 | Rendering a static "Loading..." doesn't communicate enough to the user. You 239 | also need to think about error states, timeouts, and making it a nice 240 | experience. 241 | 242 | ```js 243 | function Loading() { 244 | return
Loading...
; 245 | } 246 | 247 | Loadable({ 248 | loader: () => import('./WillFailToLoad'), // oh no! 249 | loading: Loading, 250 | }); 251 | ``` 252 | 253 | To make this all nice, your [loading component](#loadingcomponent) receives a 254 | couple different props. 255 | 256 | #### Loading error states 257 | 258 | When your [`loader`](#optsloader) fails, your [loading component](#loadingcomponent) 259 | will receive an [`error`](#propserror) prop which will be an `Error` object (otherwise it 260 | will be `null`). 261 | 262 | ```js 263 | function Loading(props) { 264 | if (props.error) { 265 | return
Error!
; 266 | } else { 267 | return
Loading...
; 268 | } 269 | } 270 | ``` 271 | 272 | #### Avoiding _Flash Of Loading Component_ 273 | 274 | Sometimes components load really quickly (<200ms) and the loading screen only 275 | quickly flashes on the screen. 276 | 277 | A number of user studies have proven that this causes users to perceive things 278 | taking longer than they really have. If you don't show anything, users perceive 279 | it as being faster. 280 | 281 | So your loading component will also get a [`pastDelay` prop](#propspastdelay) 282 | which will only be true once the component has taken longer to load than a set 283 | [delay](#optsdelay). 284 | 285 | ```js 286 | function Loading(props) { 287 | if (props.error) { 288 | return
Error!
; 289 | } else if (props.pastDelay) { 290 | return
Loading...
; 291 | } else { 292 | return null; 293 | } 294 | } 295 | ``` 296 | 297 | This delay defaults to `200ms` but you can also customize the 298 | [delay](#optsdelay) in `Loadable`. 299 | 300 | ```js 301 | Loadable({ 302 | loader: () => import('./components/Bar'), 303 | loading: Loading, 304 | delay: 300, // 0.3 seconds 305 | }); 306 | ``` 307 | 308 | #### Timing out when the `loader` is taking too long 309 | 310 | Sometimes network connections suck and never resolve or fail, they just hang 311 | there forever. This sucks for the user because they won't know if it should 312 | always take this long, or if they should try refreshing. 313 | 314 | The [loading component](#loadingcomponent) will receive a 315 | [`timedOut` prop](#propstimedout) which will be set to `true` when the 316 | [`loader`](#optsloader) has timed out. 317 | 318 | ```js 319 | function Loading(props) { 320 | if (props.error) { 321 | return
Error!
; 322 | } else if (props.timedOut) { 323 | return
Taking a long time...
; 324 | } else if (props.pastDelay) { 325 | return
Loading...
; 326 | } else { 327 | return null; 328 | } 329 | } 330 | ``` 331 | 332 | However, this feature is disabled by default. To turn it on, you can pass a 333 | [`timeout` option](#optstimeout) to `Loadable`. 334 | 335 | ```js 336 | Loadable({ 337 | loader: () => import('./components/Bar'), 338 | loading: Loading, 339 | timeout: 10000, // 10 seconds 340 | }); 341 | ``` 342 | 343 | ### Customizing rendering 344 | 345 | By default `Loadable` will render the `default` export of the returned module. 346 | If you want to customize this behavior you can use the 347 | [`render` option](#optsrender). 348 | 349 | ```js 350 | Loadable({ 351 | loader: () => import('./my-component'), 352 | render(loaded, props) { 353 | let Component = loaded.namedExport; 354 | return ; 355 | } 356 | }); 357 | ``` 358 | 359 | ### Loading multiple resources 360 | 361 | Technically you can do whatever you want within `loader()` as long as it 362 | returns a promise and [you're able to render something](#customizing-rendering). 363 | But writing it out can be a bit annoying. 364 | 365 | To make it easier to load multiple resources in parallel, you can use 366 | [`Loadable.Map`](#loadablemap). 367 | 368 | ```js 369 | Loadable.Map({ 370 | loader: { 371 | Bar: () => import('./Bar'), 372 | i18n: () => fetch('./i18n/bar.json').then(res => res.json()), 373 | }, 374 | render(loaded, props) { 375 | let Bar = loaded.Bar.default; 376 | let i18n = loaded.i18n; 377 | return ; 378 | }, 379 | }); 380 | ``` 381 | 382 | When using `Loadable.Map` the [`render()` method](#optsrender) is required. It 383 | will be passed a `loaded` param which will be an object matching the shape of 384 | your `loader`. 385 | 386 | ### Preloading 387 | 388 | As an optimization, you can also decide to preload a component before it gets 389 | rendered. 390 | 391 | For example, if you need to load a new component when a button gets pressed, 392 | you could start preloading the component when the user hovers over the button. 393 | 394 | The component created by `Loadable` exposes a 395 | [static `preload` method](#loadablecomponentpreload) which does exactly this. 396 | 397 | ```js 398 | const LoadableBar = Loadable({ 399 | loader: () => import('./Bar'), 400 | loading: Loading, 401 | }); 402 | 403 | class MyComponent extends React.Component { 404 | state = { showBar: false }; 405 | 406 | onClick = () => { 407 | this.setState({ showBar: true }); 408 | }; 409 | 410 | onMouseOver = () => { 411 | LoadableBar.preload(); 412 | }; 413 | 414 | render() { 415 | return ( 416 |
417 | 422 | {this.state.showBar && } 423 |
424 | ) 425 | } 426 | } 427 | ``` 428 | 429 |

430 |
431 |
432 | SERVER SIDE RENDERING 433 |
434 |
435 | Server-Side Rendering 436 |

437 | 438 | When you go to render all these dynamically loaded components, what you'll get 439 | is a whole bunch of loading screens. 440 | 441 | This really sucks, but the good news is that React Loadable is designed to 442 | make server-side rendering work as if nothing is being loaded dynamically. 443 | 444 | Here's our starting server using [Express](https://expressjs.com/). 445 | 446 | ```js 447 | import express from 'express'; 448 | import React from 'react'; 449 | import ReactDOMServer from 'react-dom/server'; 450 | import App from './components/App'; 451 | 452 | const app = express(); 453 | 454 | app.get('/', (req, res) => { 455 | res.send(` 456 | 457 | 458 | ... 459 | 460 |
${ReactDOMServer.renderToString()}
461 | 462 | 463 | 464 | `); 465 | }); 466 | 467 | app.listen(3000, () => { 468 | console.log('Running on http://localhost:3000/'); 469 | }); 470 | ``` 471 | 472 | ### Preloading all your loadable components on the server 473 | 474 | The first step to rendering the correct content from the server is to make sure 475 | that all of your loadable components are already loaded when you go to render 476 | them. 477 | 478 | To do this, you can use the [`Loadable.preloadAll`](#loadablepreloadall) 479 | method. It returns a promise that will resolve when all your loadable 480 | components are ready. 481 | 482 | ```js 483 | Loadable.preloadAll().then(() => { 484 | app.listen(3000, () => { 485 | console.log('Running on http://localhost:3000/'); 486 | }); 487 | }); 488 | ``` 489 | 490 | ### Picking up a server-side rendered app on the client 491 | 492 | This is where things get a little bit tricky. So let's prepare ourselves 493 | little bit. 494 | 495 | In order for us to pick up what was rendered from the server we need to have 496 | all the same code that was used to render on the server. 497 | 498 | To do this, we first need our loadable components telling us which modules they 499 | are rendering. 500 | 501 | #### Declaring which modules are being loaded 502 | 503 | There are two options in [`Loadable`](#loadable) and 504 | [`Loadable.Map`](#loadablemap) which are used to tell us which modules our 505 | component is trying to load: [`opts.modules`](#optsmodules) and 506 | [`opts.webpack`](#optswebpack). 507 | 508 | ```js 509 | Loadable({ 510 | loader: () => import('./Bar'), 511 | modules: ['./Bar'], 512 | webpack: () => [require.resolveWeak('./Bar')], 513 | }); 514 | ``` 515 | 516 | But don't worry too much about these options. React Loadable includes a 517 | [Babel plugin](#babel-plugin) to add them for you. 518 | 519 | Just add the `react-loadable/babel` plugin to your Babel config: 520 | 521 | ```json 522 | { 523 | "plugins": [ 524 | "react-loadable/babel" 525 | ] 526 | } 527 | ``` 528 | 529 | Now these options will automatically be provided. 530 | 531 | For typescript you can use [react-loadable-ts-transformer](https://github.com/stushurik/react-loadable-ts-transformer) which is a ts analog of react-loadable/babel plugin. 532 | 533 | #### Finding out which dynamic modules were rendered 534 | 535 | Next we need to find out which modules were actually rendered when a request 536 | comes in. 537 | 538 | For this, there is [`Loadable.Capture`](#loadablecapture) component which can 539 | be used to collect all the modules that were rendered. 540 | 541 | ```js 542 | import Loadable from 'react-loadable'; 543 | 544 | app.get('/', (req, res) => { 545 | let modules = []; 546 | 547 | let html = ReactDOMServer.renderToString( 548 | modules.push(moduleName)}> 549 | 550 | 551 | ); 552 | 553 | console.log(modules); 554 | 555 | res.send(`...${html}...`); 556 | }); 557 | ``` 558 | 559 | #### Mapping loaded modules to bundles 560 | 561 | In order to make sure that the client loads all the modules that were rendered 562 | server-side, we'll need to map them to the bundles that Webpack created. 563 | 564 | This comes in two parts. 565 | 566 | First we need Webpack to tell us which bundles each module lives inside. For 567 | this there is the [React Loadable Webpack plugin](#webpack-plugin). 568 | 569 | Import the `ReactLoadablePlugin` from `react-loadable/webpack` and include it 570 | in your webpack config. Pass it a `filename` for where to store the JSON data 571 | about our bundles. 572 | 573 | ```js 574 | // webpack.config.js 575 | import { ReactLoadablePlugin } from 'react-loadable/webpack'; 576 | 577 | export default { 578 | plugins: [ 579 | new ReactLoadablePlugin({ 580 | filename: './dist/react-loadable.json', 581 | }), 582 | ], 583 | }; 584 | ``` 585 | 586 | Then we'll go back to our server and use this data to convert our modules to 587 | bundles. 588 | 589 | To convert from modules to bundles, import the [`getBundles`](#getbundles) 590 | method from `react-loadable/webpack` and the data from Webpack. 591 | 592 | ```js 593 | import Loadable from 'react-loadable'; 594 | import { getBundles } from 'react-loadable/webpack' 595 | import stats from './dist/react-loadable.json'; 596 | 597 | app.get('/', (req, res) => { 598 | let modules = []; 599 | 600 | let html = ReactDOMServer.renderToString( 601 | modules.push(moduleName)}> 602 | 603 | 604 | ); 605 | 606 | let bundles = getBundles(stats, modules); 607 | 608 | // ... 609 | }); 610 | ``` 611 | 612 | We can then render these bundles into ` 646 | 647 | ${bundles.map(bundle => { 648 | return `` 649 | // alternatively if you are using publicPath option in webpack config 650 | // you can use the publicPath value from bundle, e.g: 651 | // return `` 652 | }).join('\n')} 653 | 654 | 655 | 656 | `); 657 | ``` 658 | 659 | #### Preloading ready loadable components on the client 660 | 661 | We can use the [`Loadable.preloadReady()`](#loadablepreloadready) method on the 662 | client to preload the loadable components that were included on the page. 663 | 664 | Like [`Loadable.preloadAll()`](#loadablepreloadall), it returns a promise, 665 | which on resolution means that we can hydrate our app. 666 | 667 | ```js 668 | // src/entry.js 669 | import React from 'react'; 670 | import ReactDOM from 'react-dom'; 671 | import Loadable from 'react-loadable'; 672 | import App from './components/App'; 673 | 674 | window.main = () => { 675 | Loadable.preloadReady().then(() => { 676 | ReactDOM.hydrate(, document.getElementById('app')); 677 | }); 678 | }; 679 | 680 | ``` 681 | 682 |

683 | Now server-side rendering should work perfectly! 684 |

685 | 686 |

687 |
688 |
689 | API DOCS 690 |
691 |
692 | API Docs 693 |

694 | 695 | ### `Loadable` 696 | 697 | A higher-order component for dynamically [loading](#optsloader) a module before 698 | [rendering](#optsrender) it, a [loading](#opts.loading) component is rendered 699 | while the module is unavailable. 700 | 701 | ```js 702 | const LoadableComponent = Loadable({ 703 | loader: () => import('./Bar'), 704 | loading: Loading, 705 | delay: 200, 706 | timeout: 10000, 707 | }); 708 | ``` 709 | 710 | This returns a [LoadableComponent](#loadablecomponent). 711 | 712 | ### `Loadable.Map` 713 | 714 | A higher-order component that allows you to load multiple resources in parallel. 715 | 716 | Loadable.Map's [`opts.loader`](#optsloader) accepts an object of functions, and 717 | needs a [`opts.render`](#optsrender) method. 718 | 719 | ```js 720 | Loadable.Map({ 721 | loader: { 722 | Bar: () => import('./Bar'), 723 | i18n: () => fetch('./i18n/bar.json').then(res => res.json()), 724 | }, 725 | render(loaded, props) { 726 | let Bar = loaded.Bar.default; 727 | let i18n = loaded.i18n; 728 | return ; 729 | } 730 | }); 731 | ``` 732 | 733 | When using `Loadable.Map` the `render()` method's `loaded` param will be an 734 | object with the same shape as your `loader`. 735 | 736 | ### `Loadable` and `Loadable.Map` Options 737 | 738 | #### `opts.loader` 739 | 740 | A function returning a promise that loads your module. 741 | 742 | ```js 743 | Loadable({ 744 | loader: () => import('./Bar'), 745 | }); 746 | ``` 747 | 748 | When using with [`Loadable.Map`](#loadablemap) this accepts an object of these 749 | types of functions. 750 | 751 | ```js 752 | Loadable.Map({ 753 | loader: { 754 | Bar: () => import('./Bar'), 755 | i18n: () => fetch('./i18n/bar.json').then(res => res.json()), 756 | }, 757 | }); 758 | ``` 759 | 760 | When using with `Loadable.Map` you'll also need to pass a 761 | [`opts.render`](#optsrender) function. 762 | 763 | #### `opts.loading` 764 | 765 | A [`LoadingComponent`](#loadingcomponent) that renders while a module is 766 | loading or when it errors. 767 | 768 | ```js 769 | Loadable({ 770 | loading: LoadingComponent, 771 | }); 772 | ``` 773 | 774 | This option is required, if you don't want to render anything, return `null`. 775 | 776 | ```js 777 | Loadable({ 778 | loading: () => null, 779 | }); 780 | ``` 781 | 782 | #### `opts.delay` 783 | 784 | Time to wait (in milliseconds) before passing 785 | [`props.pastDelay`](#propspastdelay) to your [`loading`](#optsloading) 786 | component. This defaults to `200`. 787 | 788 | ```js 789 | Loadable({ 790 | delay: 200 791 | }); 792 | ``` 793 | 794 | [Read more about delays](#avoiding-flash-of-loading-component). 795 | 796 | #### `opts.timeout` 797 | 798 | Time to wait (in milliseconds) before passing 799 | [`props.timedOut`](#propstimedout) to your [`loading`](#optsloading) component. 800 | This is turned off by default. 801 | 802 | ```js 803 | Loadable({ 804 | timeout: 10000 805 | }); 806 | ``` 807 | 808 | [Read more about timeouts](#timing-out-when-the-loader-is-taking-too-long). 809 | 810 | #### `opts.render` 811 | 812 | A function to customize the rendering of loaded modules. 813 | 814 | Receives `loaded` which is the resolved value of [`opts.loader`](#optsloader) 815 | and `props` which are the props passed to the 816 | [`LoadableComponent`](#loadablecomponent). 817 | 818 | ```js 819 | Loadable({ 820 | render(loaded, props) { 821 | let Component = loaded.default; 822 | return ; 823 | } 824 | }); 825 | ``` 826 | 827 | #### `opts.webpack` 828 | 829 | An optional function which returns an array of Webpack module ids which you can 830 | get with `require.resolveWeak`. 831 | 832 | ```js 833 | Loadable({ 834 | loader: () => import('./Foo'), 835 | webpack: () => [require.resolveWeak('./Foo')], 836 | }); 837 | ``` 838 | 839 | This option can be automated with the [Babel Plugin](#babel-plugin). 840 | 841 | #### `opts.modules` 842 | 843 | An optional array with module paths for your imports. 844 | 845 | ```js 846 | Loadable({ 847 | loader: () => import('./my-component'), 848 | modules: ['./my-component'], 849 | }); 850 | ``` 851 | 852 | This option can be automated with the [Babel Plugin](#babel-plugin). 853 | 854 | ### `LoadableComponent` 855 | 856 | This is the component returned by `Loadable` and `Loadable.Map`. 857 | 858 | ```js 859 | const LoadableComponent = Loadable({ 860 | // ... 861 | }); 862 | ``` 863 | 864 | Props passed to this component will be passed straight through to the 865 | dynamically loaded component via [`opts.render`](#optsrender). 866 | 867 | #### `LoadableComponent.preload()` 868 | 869 | This is a static method on [`LoadableComponent`](#loadablecomponent) which can 870 | be used to load the component ahead of time. 871 | 872 | ```js 873 | const LoadableComponent = Loadable({...}); 874 | 875 | LoadableComponent.preload(); 876 | ``` 877 | 878 | This returns a promise, but you should avoid waiting for that promise to 879 | resolve to update your UI. In most cases it creates a bad user experience. 880 | 881 | [Read more about preloading](#preloading). 882 | 883 | ### `LoadingComponent` 884 | 885 | This is the component you pass to [`opts.loading`](#optsloading). 886 | 887 | ```js 888 | function LoadingComponent(props) { 889 | if (props.error) { 890 | // When the loader has errored 891 | return
Error!
; 892 | } else if (props.timedOut) { 893 | // When the loader has taken longer than the timeout 894 | return
Taking a long time...
; 895 | } else if (props.pastDelay) { 896 | // When the loader has taken longer than the delay 897 | return
Loading...
; 898 | } else { 899 | // When the loader has just started 900 | return null; 901 | } 902 | } 903 | 904 | Loadable({ 905 | loading: LoadingComponent, 906 | }); 907 | ``` 908 | 909 | [Read more about loading components](#creating-a-great-loading-component) 910 | 911 | #### `props.error` 912 | 913 | An `Error` object passed to [`LoadingComponent`](#loadingcomponent) when the 914 | [`loader`](#optsloader) has failed. When there is no error, `null` is 915 | passed. 916 | 917 | ```js 918 | function LoadingComponent(props) { 919 | if (props.error) { 920 | return
Error!
; 921 | } else { 922 | return
Loading...
; 923 | } 924 | } 925 | ``` 926 | 927 | [Read more about errors](#loading-error-states). 928 | 929 | #### `props.retry` 930 | 931 | A function prop passed to [`LoadingComponent`](#loadingcomponent) when the 932 | [`loader`](#optsloader) has failed, used to retry loading the component. 933 | 934 | ```js 935 | function LoadingComponent(props) { 936 | if (props.error) { 937 | return
Error!
; 938 | } else { 939 | return
Loading...
; 940 | } 941 | } 942 | ``` 943 | 944 | [Read more about errors](#loading-error-states). 945 | 946 | #### `props.timedOut` 947 | 948 | A boolean prop passed to [`LoadingComponent`](#loadingcomponent) after a set 949 | [`timeout`](#optstimeout). 950 | 951 | ```js 952 | function LoadingComponent(props) { 953 | if (props.timedOut) { 954 | return
Taking a long time...
; 955 | } else { 956 | return
Loading...
; 957 | } 958 | } 959 | ``` 960 | 961 | [Read more about timeouts](#timing-out-when-the-loader-is-taking-too-long). 962 | 963 | #### `props.pastDelay` 964 | 965 | A boolean prop passed to [`LoadingComponent`](#loadingcomponent) after a set 966 | [`delay`](#optsdelay). 967 | 968 | ```js 969 | function LoadingComponent(props) { 970 | if (props.pastDelay) { 971 | return
Loading...
; 972 | } else { 973 | return null; 974 | } 975 | } 976 | ``` 977 | 978 | [Read more about delays](#avoiding-flash-of-loading-component). 979 | 980 | ### `Loadable.preloadAll()` 981 | 982 | This will call all of the 983 | [`LoadableComponent.preload`](#loadablecomponentpreload) methods recursively 984 | until they are all resolved. Allowing you to preload all of your dynamic 985 | modules in environments like the server. 986 | 987 | ```js 988 | Loadable.preloadAll().then(() => { 989 | app.listen(3000, () => { 990 | console.log('Running on http://localhost:3000/'); 991 | }); 992 | }); 993 | ``` 994 | 995 | It's important to note that this requires that you declare all of your loadable 996 | components when modules are initialized rather than when your app is being 997 | rendered. 998 | 999 | **Good:** 1000 | 1001 | ```js 1002 | // During module initialization... 1003 | const LoadableComponent = Loadable({...}); 1004 | 1005 | class MyComponent extends React.Component { 1006 | componentDidMount() { 1007 | // ... 1008 | } 1009 | } 1010 | ``` 1011 | 1012 | **Bad:** 1013 | 1014 | ```js 1015 | // ... 1016 | 1017 | class MyComponent extends React.Component { 1018 | componentDidMount() { 1019 | // During app render... 1020 | const LoadableComponent = Loadable({...}); 1021 | } 1022 | } 1023 | ``` 1024 | 1025 | > **Note:** `Loadable.preloadAll()` will not work if you have more than one 1026 | > copy of `react-loadable` in your app. 1027 | 1028 | [Read more about preloading on the server](#preloading-all-your-loadable-components-on-the-server). 1029 | 1030 | ### `Loadable.preloadReady()` 1031 | 1032 | Check for modules that are already loaded in the browser and call the matching 1033 | [`LoadableComponent.preload`](#loadablecomponentpreload) methods. 1034 | 1035 | ```js 1036 | Loadable.preloadReady().then(() => { 1037 | ReactDOM.hydrate(, document.getElementById('app')); 1038 | }); 1039 | ``` 1040 | 1041 | [Read more about preloading on the client](#waiting-to-render-on-the-client-until-all-the-bundles-are-loaded). 1042 | 1043 | ### `Loadable.Capture` 1044 | 1045 | A component for reporting which modules were rendered. 1046 | 1047 | Accepts a `report` prop which is called for every `moduleName` that is 1048 | rendered via React Loadable. 1049 | 1050 | ```js 1051 | let modules = []; 1052 | 1053 | let html = ReactDOMServer.renderToString( 1054 | modules.push(moduleName)}> 1055 | 1056 | 1057 | ); 1058 | 1059 | console.log(modules); 1060 | ``` 1061 | 1062 | [Read more about capturing rendered modules](#finding-out-which-dynamic-modules-were-rendered). 1063 | 1064 | ## Babel Plugin 1065 | 1066 | Providing [`opts.webpack`](#optswebpack) and [`opts.modules`](#optsmodules) for 1067 | every loadable component is a lot of manual work to remember to do. 1068 | 1069 | Instead you can add the Babel plugin to your config and it will automate it for 1070 | you: 1071 | 1072 | ```json 1073 | { 1074 | "plugins": ["react-loadable/babel"] 1075 | } 1076 | ``` 1077 | 1078 | **Input** 1079 | 1080 | ```js 1081 | import Loadable from 'react-loadable'; 1082 | 1083 | const LoadableMyComponent = Loadable({ 1084 | loader: () => import('./MyComponent'), 1085 | }); 1086 | 1087 | const LoadableComponents = Loadable.Map({ 1088 | loader: { 1089 | One: () => import('./One'), 1090 | Two: () => import('./Two'), 1091 | }, 1092 | }); 1093 | ``` 1094 | 1095 | **Output** 1096 | 1097 | ```js 1098 | import Loadable from 'react-loadable'; 1099 | import path from 'path'; 1100 | 1101 | const LoadableMyComponent = Loadable({ 1102 | loader: () => import('./MyComponent'), 1103 | webpack: () => [require.resolveWeak('./MyComponent')], 1104 | modules: [path.join(__dirname, './MyComponent')], 1105 | }); 1106 | 1107 | const LoadableComponents = Loadable.Map({ 1108 | loader: { 1109 | One: () => import('./One'), 1110 | Two: () => import('./Two'), 1111 | }, 1112 | webpack: () => [require.resolveWeak('./One'), require.resolveWeak('./Two')], 1113 | modules: [path.join(__dirname, './One'), path.join(__dirname, './Two')], 1114 | }); 1115 | ``` 1116 | 1117 | [Read more about declaring modules](#declaring-which-modules-are-being-loaded). 1118 | 1119 | ## Webpack Plugin 1120 | 1121 | In order to [send the right bundles down](#mapping-loaded-modules-to-bundles) 1122 | when rendering server-side, you'll need the React Loadable Webpack plugin  1123 | to provide you with a mapping of modules to bundles. 1124 | 1125 | ```js 1126 | // webpack.config.js 1127 | import { ReactLoadablePlugin } from 'react-loadable/webpack'; 1128 | 1129 | export default { 1130 | plugins: [ 1131 | new ReactLoadablePlugin({ 1132 | filename: './dist/react-loadable.json', 1133 | }), 1134 | ], 1135 | }; 1136 | ``` 1137 | 1138 | This will create a file (`opts.filename`) which you can import to map modules 1139 | to bundles. 1140 | 1141 | [Read more about mapping modules to bundles](#mapping-loaded-modules-to-bundles). 1142 | 1143 | ### `getBundles` 1144 | 1145 | A method exported by `react-loadable/webpack` for converting modules to 1146 | bundles. 1147 | 1148 | ```js 1149 | import { getBundles } from 'react-loadable/webpack'; 1150 | 1151 | let bundles = getBundles(stats, modules); 1152 | ``` 1153 | 1154 | [Read more about mapping modules to bundles](#mapping-loaded-modules-to-bundles). 1155 | 1156 |

1157 |
1158 |
1159 | FAQ 1160 |
1161 |
1162 | FAQ 1163 |

1164 | 1165 | ### How do I avoid repetition? 1166 | 1167 | Specifying the same `loading` component or `delay` every time you use 1168 | `Loadable()` gets repetitive fast. Instead you can wrap `Loadable` with your 1169 | own Higher-Order Component (HOC) to set default options. 1170 | 1171 | ```js 1172 | import Loadable from 'react-loadable'; 1173 | import Loading from './my-loading-component'; 1174 | 1175 | export default function MyLoadable(opts) { 1176 | return Loadable(Object.assign({ 1177 | loading: Loading, 1178 | delay: 200, 1179 | timeout: 10000, 1180 | }, opts)); 1181 | }; 1182 | ``` 1183 | 1184 | Then you can just specify a `loader` when you go to use it. 1185 | 1186 | ```js 1187 | import MyLoadable from './MyLoadable'; 1188 | 1189 | const LoadableMyComponent = MyLoadable({ 1190 | loader: () => import('./MyComponent'), 1191 | }); 1192 | 1193 | export default class App extends React.Component { 1194 | render() { 1195 | return ; 1196 | } 1197 | } 1198 | ``` 1199 | 1200 | Unfortunately at the moment using wrapped Loadable breaks [react-loadable/babel](#babel-plugin) so in such case you have to add required properties (`modules`, `webpack`) manually. 1201 | 1202 | ```js 1203 | import MyLoadable from './MyLoadable'; 1204 | 1205 | const LoadableMyComponent = MyLoadable({ 1206 | loader: () => import('./MyComponent'), 1207 | modules: ['./MyComponent'], 1208 | webpack: () => [require.resolveWeak('./MyComponent')], 1209 | }); 1210 | 1211 | export default class App extends React.Component { 1212 | render() { 1213 | return ; 1214 | } 1215 | } 1216 | ``` 1217 | 1218 | ### How do I handle other styles `.css` or sourcemaps `.map` with server-side rendering? 1219 | 1220 | When you call [`getBundles`](#getbundles), it may return file types other than 1221 | JavaScript depending on your Webpack configuration. 1222 | 1223 | To handle this, you should manually filter down to the file extensions that 1224 | you care about: 1225 | 1226 | ```js 1227 | let bundles = getBundles(stats, modules); 1228 | 1229 | let styles = bundles.filter(bundle => bundle.file.endsWith('.css')); 1230 | let scripts = bundles.filter(bundle => bundle.file.endsWith('.js')); 1231 | 1232 | res.send(` 1233 | 1234 | 1235 | 1236 | ... 1237 | ${styles.map(style => { 1238 | return `` 1239 | }).join('\n')} 1240 | 1241 | 1242 |
${html}
1243 | 1244 | ${scripts.map(script => { 1245 | return `` 1246 | }).join('\n')} 1247 | 1248 | 1249 | `); 1250 | ``` 1251 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A bug-free and actively maintained version of react-loadable 2 | 3 | Check the old readme [here](https://github.com/react-loadable/revised/blob/master/README-old.md). 4 | 5 | The new package name: `@react-loadable/revised`. 6 | 7 | # Background 8 | 9 | There are several bugs in the original `react-loadable` package. 10 | The author abandoned it a long time ago. 11 | This is a revised and actively maintained version of the original package. 12 | 13 | # Usage 14 | 15 | Exported APIs 16 | 17 | ```javascript 18 | import babelPlugin from '@react-loadable/revised/babel' 19 | import {ReactLoadablePlugin} from '@react-loadable/revised/webpack' 20 | import loadable, {preloadReady, preloadAll, Capture} from '@react-loadable/revised' 21 | import {Capture} from '@react-loadable/revised' 22 | ``` 23 | 24 | - `babelPlugin`: babel plugin. 25 | - `ReactLoadablePlugin`: webpack plugin. 26 | - `loadable`: the main component to wrap your component. 27 | - `preloadReady`: to load the pre-loaded components, used in client. 28 | - `preloadAll`: to load all, used in server. 29 | - `getBundles`: determine which bundles are required. 30 | - `Capture`: the wrapper context, used in server, to capture the pre-loaded components. 31 | 32 | ## Babel config 33 | 34 | Include `'@react-loadable/revised/babel'` in your babel plugin list. 35 | This is required for both client and server builds. 36 | 37 | In `.babelrc`: 38 | 39 | ```json 40 | { 41 | "plugins": [ 42 | ["@react-loadable/revised/babel", { "absPath": true }] 43 | ] 44 | } 45 | ``` 46 | 47 | The babel plugin finds all calls to `loadable({loader() {}, ...})`, 48 | scans all `import()` call in `loader`'s body, 49 | and inject the module identifiers to the object passed to `loadable()` for later uses. 50 | 51 | For example, it transforms: 52 | 53 | ```javascript 54 | loadable({ 55 | loader() { 56 | return import('./ExampleNested') 57 | }, 58 | loading: Loading, 59 | }) 60 | ``` 61 | 62 | into: 63 | 64 | ```javascript 65 | loadable({ 66 | loader() { 67 | return import('./ExampleNested') 68 | }, 69 | modules: ['./ExampleNested'], 70 | webpack() { 71 | return [require.resolveWeak('./ExampleNested')] // will be evaluated by Webpack to ['./example/components/ExampleNested.js'] 72 | }, 73 | loading: Loading, 74 | }) 75 | ``` 76 | 77 | ## Webpack config 78 | 79 | Webpack plugin is required **only** in your client build. 80 | Include it in the webpack config's plugin list. 81 | 82 | [See example](https://github.com/react-loadable/revised/blob/93f97770cc2825ae7cd6c443ab59641ee1b5a146/webpack.config.js#L47) 83 | 84 | ```javascript 85 | const {writeFile} = require('fs/promises') 86 | 87 | plugins: [ 88 | new ReactLoadablePlugin({ 89 | async callback(manifest) { 90 | // save the manifest somewhere to be read by the server 91 | await writeFile( 92 | path.join(__dirname, 'dist/react-loadable.json'), 93 | JSON.stringify(manifest, null, 2) 94 | ) 95 | }, 96 | absPath: true, 97 | }), 98 | ] 99 | ``` 100 | 101 | ## In react code 102 | 103 | Wrap your split components with `loadable({loader() {}, ...})` to get the lazily loadable component. 104 | [Sample code](https://github.com/react-loadable/revised/blob/1add49804cc246dd91f9600f0ad5bc49a276b791/example/components/Example.js#L5): 105 | 106 | ```javascript 107 | const LoadableNested = loadable({ 108 | loader() { 109 | return import('./ExampleNested') 110 | }, 111 | loading: Loading 112 | }) 113 | ``` 114 | 115 | **Note**: you must call `loadable({...})` at the top-level of the module. 116 | Otherwise, make sure to call them all (via importing) before calling `preloadAll()` or `preloadReady()`. 117 | 118 | ## In server side 119 | 120 | - Call and await for `preloadAll()` **once** in the server side to pre-load all the components. 121 | - For example: [when the server starts serving](https://github.com/react-loadable/revised/blob/fbccbfed39a1e8dbf799e69311c7366c78649b01/example/server.js#L66). 122 | 123 | ```javascript 124 | await preloadAll() 125 | app.listen(3000, () => { 126 | console.log('Running on http://localhost:3000/') 127 | }) 128 | ``` 129 | 130 | - Load the exported `react-loadable.json` file, which is generated by the webpack plugin, to get the manifest. 131 | 132 | ```javascript 133 | // in production, this should be cached in the memory to reduce IO calls. 134 | const getStats = () => JSON.parse(fs.readFileSync(path.resolve(__dirname, 'dist/react-loadable.json'), 'utf8')) 135 | ``` 136 | 137 | - Wrap the rendered component with `Capture` to capture the pre-loaded components. 138 | 139 | [See example](https://github.com/react-loadable/revised/blob/fbccbfed39a1e8dbf799e69311c7366c78649b01/example/server.js#L49) 140 | 141 | ```javascript 142 | const modules = [] // one list for one request, don't share 143 | const body = ReactDOMServer.renderToString( 144 | modules.push(moduleName)}> 145 | 146 | 147 | ) 148 | ``` 149 | 150 | - After rendering the component, use `getBundles()` to determine which bundles are required. 151 | 152 | ```javascript 153 | const {assets, preload, prefetch} = getBundles(getStats(), modules) 154 | ``` 155 | 156 | - Inject the required bundles and rendered `body` to the html document and returns to the client. 157 | 158 | ```javascript 159 | const Links = ({assets, prefetch}) => { 160 | const urls = assets.filter(file => file.endsWith('.css')) 161 | return prefetch 162 | ? urls.map((url, index) => ) 163 | : urls.map((url, index) => ) 164 | } 165 | const Scripts = ({assets, prefetch}) => { 166 | const urls = assets.filter(file => file.endsWith('.js')) 167 | return prefetch 168 | ? urls.map((url, index) => ) 169 | : urls.map((url, index) =>