|
1 |
| -> This is my personal starter kit for npm libraries. |
| 1 | +# react-async-component 😴 |
2 | 2 |
|
3 |
| -# Your Library Name |
| 3 | +Create Components that resolve asynchronously, with support for server side rendering and code splitting. |
4 | 4 |
|
5 |
| -A one liner description of your library. |
6 |
| - |
7 |
| -___BADGES: EDIT THESE TO USE THE URLS FOR EACH SERVICE CONFIGURED FOR YOUR OWN LIBRARY, THEN REMOVE THIS MESSAGE___ |
8 |
| - |
9 |
| -[](http://npm.im/npm-library-starter) |
10 |
| -[](http://opensource.org/licenses/MIT) |
11 |
| -[](https://travis-ci.org/ctrlplusb/npm-library-starter) |
12 |
| -[](https://codecov.io/github/ctrlplusb/npm-library-starter) |
| 5 | +[](http://npm.im/react-async-component) |
| 6 | +[](http://opensource.org/licenses/MIT) |
| 7 | +[](https://travis-ci.org/ctrlplusb/react-async-component) |
| 8 | +[](https://codecov.io/github/ctrlplusb/react-async-component) |
13 | 9 |
|
14 | 10 | ## TOCs
|
15 | 11 |
|
16 | 12 | - [Introduction](#introduction)
|
| 13 | + - [Usage](#usage) |
| 14 | + - [API](#api) |
| 15 | + - [Caveats](#caveats) |
17 | 16 | - [FAQs](#faqs)
|
18 | 17 |
|
19 | 18 | ## Introduction
|
20 | 19 |
|
21 |
| -An introduction to your library... |
| 20 | +This library is an evolution of [`code-split-component`](). Unlike `code-split-component` this library does not require that you use either Webpack or Babel. Instead it provides you a pure Javascript/React API which has been adapted in a manner to make it generically useful for lazy-loaded Components, with support for modern code splitting APIs (e.g `import()`, `System.import`, `require.ensure`). |
22 | 21 |
|
23 |
| -## FAQs |
| 22 | +## Usage |
| 23 | + |
| 24 | +Firstly, you need to use our helper to allow your application to use asynchronous components in an efficient manner. |
| 25 | + |
| 26 | +```jsx |
| 27 | +import { withAsyncComponents } from 'react-async-component'; // 👈 |
| 28 | + |
| 29 | +// Declare your application as normal. |
| 30 | +const app = <MyApp />; |
| 31 | + |
| 32 | +// Then provide it to our helper, which returns a Promise. |
| 33 | +// 👇 |
| 34 | +withAsyncComponents(app).then((result) => { |
| 35 | +// 👆 it resolves a "result" object |
| 36 | + const { |
| 37 | + // ❗️ We return a new version of your app that supports |
| 38 | + // asynchronous components. 💪 |
| 39 | + appWithAsyncComponents |
| 40 | + } = result; |
| 41 | + |
| 42 | + // 🚀 render it! |
| 43 | + ReactDOM.render(appWithAsyncComponents, document.getElementById('app')); |
| 44 | +}); |
| 45 | +``` |
| 46 | + |
| 47 | +Next up, let's make an asynchronous Component! We provide another helper for this. |
| 48 | + |
| 49 | +```jsx |
| 50 | +import { createAsyncComponent } from 'react-async-component'; // 👈 |
| 51 | + |
| 52 | +const AsyncProduct = createAsyncComponent({ |
| 53 | + // Provide a function that will return a Promise that resolves |
| 54 | + // as your Component. |
| 55 | + resolve: function resolveComponent() { |
| 56 | + return new Promise(function (resolve) { |
| 57 | + // The Promise the resolves with a simple require of the |
| 58 | + // `Product` Component. |
| 59 | + resolve(require('./components/Product')); |
| 60 | + }); |
| 61 | + } |
| 62 | +}); |
| 63 | + |
| 64 | +// You can now use the created Component as though it were a |
| 65 | +// "normal" Component, providing it props that will be given |
| 66 | +// to the resolved Component. |
| 67 | +const x = <Product productId={10} /> |
| 68 | +``` |
| 69 | + |
| 70 | +The above may look a tad bit verbose. If you are a fan of anonymous functions then we could provide a more terse implementation: |
| 71 | + |
| 72 | +```jsx |
| 73 | +const AsyncProduct = createAsyncComponent({ |
| 74 | + resolve: () => new Promise(resolve => |
| 75 | + resolve(require('./components/Product')) |
| 76 | + ) |
| 77 | +}); |
| 78 | +``` |
| 79 | + |
| 80 | +Okay, the above may not look terribly useful at first, but it opens up an easy point to integrating code splitting APIs supported by bundlers such as Webpack. We will provide examples of these as well as details on some other useful configuration options within the [`API`](#api) section. |
| 81 | + |
| 82 | +## API |
| 83 | + |
| 84 | +### `createAsyncComponent(config)` |
| 85 | + |
| 86 | +Our async Component factory. Config goes in, an async Component comes out. |
| 87 | + |
| 88 | +#### Arguments |
| 89 | + |
| 90 | + - `config` : _Object_ |
| 91 | + The configuration object for the async Component. It has the following properties available: |
| 92 | + - `resolve` : _Function => Promise<Component>_ |
| 93 | + A function that should return a `Promise` that will resolve the Component you wish to be async. |
| 94 | + - `defer` : _Boolean_ (Optional, default: false) |
| 95 | + Only useful for server side rendering applications. If this is set to true then the async component will only be resolved on the client/browser, not the server. I _highly_ recommend that you consider using this value as much as you can. Try to relieve the load on your server and use server side rendering to provide an application shell for your users. They will still get a perceived performance benefit. |
| 96 | + - `Loading` : _Component_ (Optional, default: null) |
| 97 | + A Component to be displayed whilst your async Component is being resolved. It will be provided the same props that will be provided to your resovled async Component. |
| 98 | + - `es6Aware` : _Boolean_ (Optional, default: true) |
| 99 | + If you are using ES2015 modules with default exports (i.e `export default MyComponent`) then you may need your Component resolver to do syntax such as `require('./MyComp').default`. Forgetting the `.default` can cause havoc with hard to debug error messages. To cover your back we will automatically try to resolve a `.default` on the result that is resolved by your Component. If the `.default` exists it will be used, else we will use the original result. |
| 100 | + |
| 101 | +#### Returns |
| 102 | + |
| 103 | +A React Component. |
| 104 | + |
| 105 | +#### Examples |
| 106 | + |
| 107 | +##### Simple |
| 108 | + |
| 109 | +```jsx |
| 110 | +const AsyncProduct = createAsyncComponent({ |
| 111 | + resolve: () => new Promise(resolve => |
| 112 | + resolve(require('./components/Product')) |
| 113 | + ) |
| 114 | +}); |
| 115 | + |
| 116 | +<AsyncProduct productId={1} /> |
| 117 | +``` |
| 118 | + |
| 119 | +##### With Loading Component |
| 120 | + |
| 121 | +```jsx |
| 122 | +const AsyncProduct = createAsyncComponent({ |
| 123 | + resolve: () => new Promise(resolve => |
| 124 | + resolve(require('./components/Product')) |
| 125 | + ), |
| 126 | + Loading: ({ productId }) => <div>Loading product {productId}</div> |
| 127 | +}); |
| 128 | + |
| 129 | +<AsyncProduct productId={1} /> |
| 130 | +``` |
| 131 | + |
| 132 | +##### Webpack 1/2 `require.ensure` Code Splitting |
| 133 | + |
| 134 | +```jsx |
| 135 | +const AsyncProduct = createAsyncComponent({ |
| 136 | + resolve: () => new Promise(resolve => |
| 137 | + require.ensure([], (require) => { |
| 138 | + resolve(require('./components/Product')); |
| 139 | + }); |
| 140 | + ) |
| 141 | +}); |
24 | 142 |
|
25 |
| -___A common question around your library?___ |
| 143 | +<AsyncProduct productId={1} /> |
| 144 | +``` |
26 | 145 |
|
27 |
| -The answer to the question. |
| 146 | +##### Webpack 2 `import` / `System.import` Code Splitting |
28 | 147 |
|
29 |
| -___A common question around your library?___ |
| 148 | +Note: `System.import` is considered deprecated and will be replaced with `import`, but for now they can be used interchangeably (you may need a Babel plugin for the `import` syntax). |
| 149 | + |
| 150 | +```jsx |
| 151 | +const AsyncProduct = createAsyncComponent({ |
| 152 | + resolve: () => System.import('./components/Product') |
| 153 | +}); |
| 154 | + |
| 155 | +<AsyncProduct productId={1} /> |
| 156 | +``` |
| 157 | + |
| 158 | +#### Defer Loading to the Client/Browser |
| 159 | + |
| 160 | +i.e. The component won't be resolved and rendered in a server side rendering execution. |
| 161 | + |
| 162 | +```jsx |
| 163 | +const AsyncProduct = createAsyncComponent({ |
| 164 | + resolve: () => System.import('./components/Product'), |
| 165 | + defer: true |
| 166 | +}); |
| 167 | +``` |
| 168 | + |
| 169 | +### `withAsyncComponents(element)` |
| 170 | + |
| 171 | +Decorates your application with the ability to use async Components in an efficient manner. It also manages state storing/rehydrating for server side rendering applications. |
| 172 | + |
| 173 | +### Arguments |
| 174 | + |
| 175 | + - `app` _React.Element_ |
| 176 | + The react element representing your application. |
| 177 | + |
| 178 | +### Returns |
| 179 | + |
| 180 | +A promise that resolves in a `result` object. The `result` object will have the following properties available: |
| 181 | + |
| 182 | + - `appWithAsyncComponents` _React.Element_ |
| 183 | + Your application imbued with the ability to use async Components. ❗️Use this when rendering your app. |
| 184 | + - `state` _Object_ |
| 185 | + Only used on the "server" side of server side rendering applications. It represents the state of your async Components (i.e. which ones were rendered) so that the server can feed this information back to the client/browser. |
| 186 | + - `STATE_IDENTIFIER` _String_ |
| 187 | + Only used on the "server" side of server side rendering applications. The identifier of the property you should bind the `state` object to on the `window` object. |
| 188 | + |
| 189 | +### Examples |
| 190 | + |
| 191 | +#### Usage |
| 192 | + |
| 193 | +```jsx |
| 194 | +import React from 'react'; |
| 195 | +import { render } from 'react-dom'; |
| 196 | +import { withAsyncComponents } from 'react-async-component'; // 👈 |
| 197 | +import MyApp from './shared/components/MyApp'; |
| 198 | + |
| 199 | +const app = <MyApp /> |
| 200 | + |
| 201 | +// 👇 run helper on your app. |
| 202 | +withAsyncComponents(app) |
| 203 | + // 👇 and you get back a result object. |
| 204 | + .then((result) => { |
| 205 | + const { |
| 206 | + // ❗️ The result includes a decorated version of your app |
| 207 | + // that will allow your application to use async components |
| 208 | + // in an efficient manner. |
| 209 | + appWithAsyncComponents |
| 210 | + } = result; |
| 211 | + |
| 212 | + // Now you can render the app. |
| 213 | + render(appWithAsyncComponents, document.getElementById('app')); |
| 214 | + }); |
| 215 | +``` |
| 216 | + |
| 217 | +#### Server Side Rendering Usage |
| 218 | + |
| 219 | +When using this helper on the "server" side of your server side rendering applications you should do the following. |
| 220 | + |
| 221 | +> Note: on the "client" side of a server side rendering application you can use the helper in the "nomral" fashion as detailed in the previous example. |
| 222 | +
|
| 223 | +```js |
| 224 | +import React from 'react'; |
| 225 | +import { withAsyncComponents } from 'react-async-component'; // 👈 |
| 226 | +import { renderToString } from 'react-dom/server'; |
| 227 | +import serialize from 'serialize-javascript'; |
| 228 | +import MyApp from './shared/components/MyApp'; |
| 229 | + |
| 230 | +export default function expressMiddleware(req, res, next) { |
| 231 | + const app = <MyApp />; |
| 232 | + |
| 233 | + // 👇 run helper on your app. |
| 234 | + withAsyncComponents(app) |
| 235 | + // 👇 and you get back a result object. |
| 236 | + .then((result) => { |
| 237 | + const { |
| 238 | + // ❗️ The result includes a decorated version of your app |
| 239 | + // that will have the async components initialised for |
| 240 | + // the renderToString call. |
| 241 | + appWithAsyncComponents, |
| 242 | + // This state object represents the async components that |
| 243 | + // were rendered by the server. We will need to send |
| 244 | + // this back to the client, attaching it to the window |
| 245 | + // object so that the client can rehydrate the application |
| 246 | + // to the expected state and avoid React checksum issues. |
| 247 | + state, |
| 248 | + // This is the identifier you should use when attaching |
| 249 | + // the state to the "window" object. |
| 250 | + STATE_IDENTIFIER |
| 251 | + } = result; |
| 252 | + |
| 253 | + const appString = renderToString(appWithAsyncComponents); |
| 254 | + const html = ` |
| 255 | + <html> |
| 256 | + <head> |
| 257 | + <title>Example</title> |
| 258 | + </head> |
| 259 | + <body> |
| 260 | + <div id="app">${appString}</div> |
| 261 | + <script type="text/javascript"> |
| 262 | + window.${STATE_IDENTIFIER} = ${serialize(state)} |
| 263 | + </script> |
| 264 | + </body> |
| 265 | + </html>`; |
| 266 | + res.send(html); |
| 267 | + }); |
| 268 | +} |
| 269 | +``` |
| 270 | + |
| 271 | +## Caveats |
| 272 | + |
| 273 | +At the moment there is one known caveat in using this library: it doesn't support React Hot Loader (RHL). You can still use Webpack's standard Hot Module Replacement, however, RHL does not respond nicely to the architecture of `react-async-component`. |
| 274 | + |
| 275 | +TODO: I'll post up some details why and perhaps we could work to find a solution. |
| 276 | + |
| 277 | +## FAQs |
30 | 278 |
|
31 |
| -The answer to the question. |
| 279 | +> Let me know if you have any... |
0 commit comments