UNPKG

37.7 kBMarkdownView Raw
1<div align="center">
2 <h1>IMPORTED COMPONENT ✂</h1>
3 <h2>Code splitting which always works<sup>*</sup></h2>
4 <br/>
5 <img src="./assets/imported-logo.png" alt="imported components" width="409" align="center">
6 <br/>
7 <br/>
8 SSR-friendly code splitting compatible with any platform.
9 <br/>
10 Deliver a better experience within a single import.
11 <br/>
12 <br/>
13
14 <a href="https://www.npmjs.com/package/react-imported-component">
15 <img src="https://img.shields.io/npm/v/react-imported-component.svg?style=flat-square" />
16 </a>
17
18 <a href="https://travis-ci.org/github/theKashey/react-imported-component">
19 <img src="https://travis-ci.org/theKashey/react-imported-component.svg" />
20 </a>
21
22 <a href="https://www.npmjs.com/package/react-imported-component">
23 <img src="https://img.shields.io/npm/dm/react-imported-component.svg" alt="npm downloads">
24 </a>
25
26 <a href="https://bundlephobia.com/result?p=react-imported-component">
27 <img src="https://img.shields.io/bundlephobia/minzip/react-imported-component.svg" alt="bundle size">
28 </a>
29
30 <img src="https://badges.greenkeeper.io/theKashey/react-imported-component.svg" />
31
32 <br/>
33</div>
34
35> <sup>\*</sup> It's really will never let you down. All credits to your bundler.
36
37👉 [Usage](#usage) | [API](#api) | [Setup](#setup) | [SSR](#ssr) | [CCS](#css) [Concurrent loading](#concurrent-loading) | [Webpack/Parcel](#bundler-integration)
38
39| Library | Suspense | SSR | Hooks | Library | Non-modules | import(`./${value}`) | babel-macro | webpack only |
40| ------------------- | :------: | :-: | :---: | :-----: | :---------: | :------------------: | :---------: | :----------: |
41| React.lazy | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 😹 | no-ssr |
42| react-loadable | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 😿 |
43| @loadable/component | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | 😿 |
44| imported-component | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | 😸 |
45
46> Read more about [what this table displays](#comparisonLegend)
47
48Key features:
49
50- 1️⃣ Single source of truth - your **bundler drives** everything
51- 📖 **library** level code **splitting**
52- 🧙️ Hybrid and **Prerendering** compatible
53- 💡 **TypeScript** bindings
54- ⚛️ **React.Lazy** underneath (if hot module updates are disabled)
55- 🌟 Async on client, sync on server. Supports **Suspense** (even on server side)
56- 📦 could work with **any bundler** - webpack, rollup, parcel or puppeteer - it does not matter
57- 🤹‍♂️ working as well with any `import` you may provide
58
59Other features:
60
61- 🔥 Hot-Module-Replacement/React-Hot-Loader friendly
62- ⛓️ support forwardRef
63- ⚛️ React 16/Async/Hooks ready
64- 🛠 HOC, Component, Hooks API
65- 🐳 stream rendering support
66- 👥 partial hydration out of the box
67- 📦 and yes - this is the only **parcel-bundler compatible** SSR-friendly React code splitting library, as well as a perfect solution for **Create React App**
68
69👍 Better than [React.Lazy](https://reactjs.org/docs/code-splitting.html#reactlazy):
70
71- It **IS** Lazy, just with some stuff around<sup>\*</sup>
72- SSR, Prerendering and Preloading support
73- With or without Suspense, and easier Error cases support
74
75👍 Better than others:
76
77- Not bound to webpack
78- Easy way to use per-browser(modern/legacy) bundles - you down have to mess with actual browser support
79- Strong typing
80- Working with any imports, even native, external or derived ones.
81
82👌 Client-side module resolution
83
84- Loads chunks only after the `main one`, as long as loader code is bundled inside the main chunk, so it should be loaded first.
85- Not an issue with the `progressive hydration`, and might provide a better UX via feature detection.
86- Provides 👨‍🔬 technological workaround - [see here](#concurrent-loading)
87
88📦 Optional bundler integration for the best experience
89
90- prefetching backed by webpack `stat.json` and `asset.json`
91- `parcel-manifest.json` support
92
93👯‍♀️Works better in pair
94
95- [react-prerendered-component](https://github.com/theKashey/react-prerendered-component) for prerendering, partial hydration and react fragment caching
96- [used-style](https://github.com/theKashey/used-styles) for CSS and critical CSS extraction
97- [devolution](https://github.com/theKashey/devolution) for shipping legacy/modern bundles
98- [webpack-imported](https://github.com/theKashey/webpack-imported) for deep `webpack` integration, the only bundler dependent tool in this list.
99
100<a name="usage"/>
101
102# Usage
103
104## Server side
105
106Just a proper setup and a bit of magic
107
108## Client side
109
110### Component
111
112`imported` provides 2 common ways to define a component, which are more different inside than outside
113
114- using `pre-lazy` API.
115
116```javascript
117import importedComponent from 'react-imported-component';
118const Component = importedComponent( () => import('./Component'));
119
120const Component = importedComponent( () => import('./Component'), {
121 LoadingComponent: Spinner, // what to display during the loading
122 ErrorComponent: FatalError // what to display in case of error
123});
124
125Component.preload(); // force preload
126
127// render it
128<Component... />
129```
130
131- using `lazy` API. It's almost the same `React.lazy` outside, and exactly the same inside.
132
133```js
134import { lazy, LazyBoundary } from 'react-imported-component';
135const Component = lazy(() => import('./Component'));
136
137const ClientSideOnly = () => (
138 <Suspense>
139 <Component />
140 </Suspense>
141);
142
143// or let's make it SSR friendly
144const ServerSideFriendly = () => (
145 <LazyBoundary>
146 {' '}
147 // LazyBoundary is Suspense* on the client, and "nothing" on the server
148 <Component />
149 </LazyBoundary>
150);
151```
152
153`LazyBoundary` is a `Suspense` on Client Side, and `React.Fragment` on Server Side. Don't forget - "dynamic" imports are sync on a server.
154
155Example: [React.lazy vs Imported-component](https://codesandbox.io/s/wkl95r0qw8)
156
157### Hook
158
159However, you may not load only components - you may load anything
160
161```js
162import {useImported} from 'react-imported-component'
163
164const MyCalendarComponent = () => {
165 const {
166 imported: moment,
167 loading
168 } = useImported(() => import("moment"));
169
170 return loading ? "..." : <span>today is {moment(Date.now).format()}</span>
171}
172
173// or we could make it a bit more interesting...
174
175const MyCalendarComponent = () => {
176 const {
177 imported: format = x => "---", // default value is used while importing library
178 } = useImported(
179 () => import("moment"),
180 moment => x => moment(x).format // masking everything behind
181 );
182
183 return <span>today is {format(Date.now())</span>
184}
185```
186
187What you could load using `useImported`? Everything - `imported` itself is using it to import components.
188
189> `useImported` is an excellent example for loading translations, which are usually a simple json, in a _trackable_ way.
190
191> 💡 did you know that there is another hook based solution to load _"something might might need"_? The [use-sidecar](https://github.com/theKashey/use-sidecar) pattern.
192
193🤔 Keep in mind - **everything here is using `useImported`**, and **you can build whatever you need** using just it.
194
195### Module
196
197A slim helper to help handle `modules`, you might require using `useImported` in a component way
198
199```js
200import { importedModule, ImportedModule } from 'react-imported-component';
201
202const Moment = importedModule(() => import('moment'));
203
204<Moment fallback="long time ago">
205 {(momentjs /* default imports are auto-imported*/) => momentjs(date).fromNow()}
206</Moment>;
207```
208
209> Yes, this example was taken from [loadable.lib](https://www.smooth-code.com/open-source/loadable-components/docs/library-splitting/)
210
211Can I also use a ref, populated when the library is loaded? No, you cant. Use `useImported` for any special case like this.
212
213Plus, there is a Component helper:
214
215```js
216<ImportedModule
217 // import is just a _trackable_ promise - do what ever you want
218 import={() => import('moment').then(({momentDefault})=> momentDefault(date).fromNow()}
219 fallback="long time ago"
220>
221 {(fromNow) => fromNow()}
222</ImportedModule>
223```
224
225> ImportedModule will throw a promise to the nearest Suspense boundary if no `fallback` provided.
226
227## Babel macro
228
229If you could not use babel plugin, but do have `babel-plugin-macro` (like CRA) - consider using macro API:
230
231```js
232import { imported, lazy, useImported } from 'react-imported-component/macro';
233// notice - there is no default import here
234```
235
236### Indirect usage
237
238Just importing `react-imported-component/macro` would enable babel transformation for the current file.
239If you have `imported` definition in one file, and use it from another - just `import "react-imported-component/macro"` in that another file. See [#142](https://github.com/theKashey/react-imported-component/issues/142)
240
241<a name="api"/>
242
243# API
244
245> Don't forget - there are TS typings provided.
246
247### Code splitting components
248
249> import {\*} from 'react-imported-component';
250
251##### importedComponent
252
253- `importedComponent(importFunction, [options]): ComponentLoader` - main API, default export, HOC to create imported component.
254
255 - `importFunction` - function which resolves with Component to be imported.
256 - `options` - optional settings
257 - `options.async` - activates react suspense support. Will throw a Promise in a Loading State - use it with Suspense in a same way you use **React.lazy**. See [working with Suspense](working-with-suspense)
258 - `options.LoadingComponent` - component to be shown in Loading state
259 - `options.ErrorComponent` - component to be shown in Error state. Will re-throw error if ErrorComponent is not set. Use ErrorBoundary to catch it.
260 - `options.onError` - function to consume the error, if one will thrown. Will rethrow a real error if not set.
261 - `options.exportPicker` - function to pick `not default` export from a `importFunction`
262 - `options.render(Component, state, props)`- function to render the result. Could be used to tune the rendering.
263
264 - [static] `.preload` - static method to preload components.
265
266##### lazy
267
268- `lazy(importFunction)` - helper to mimic **React.lazy** behavior
269
270##### useImported
271
272- `useImported(importFunction, [exportPicker], [options])` - code splitting hook
273
274 - `importFunction` - a function which resolves to `default` or `wildcard` import(T | {default:T})
275 - `[exportPicker]` - function to pick "T" from the import
276 - `[options]` - options to the hook
277
278 - `[options.import]` - controls import. Hooks would be executed only if this is not false
279 - `[options.track]` - ability to disable server-side usage tracking.
280
281`useImported` returns complex object(ImportedShape):
282
283- `imported` - the imported resource
284- `error` - error (if present)
285- `loading` - is it loading right now?
286- `loadable` - the underlying `Loadable` object
287- `retry` - retry action (in case of error)
288
289Hints:
290
291- use `options.import=false` to perform conditional import - `importFunction` would not be used if this option set to `false.
292- use `options.track=true` to perform SSR only import - to usage would be tracked if this option set to `false.
293
294##### ImportedController
295
296- `<ImportedController>` - a controller for Suspense Hydration. **Compulsory** for async/lazy usecases
297
298##### Misc
299
300There is also API method, unique for imported-component, which could be useful on the client side
301
302- `addPreloader(fn):fn` - adds a function, result of which would be _awaited_ when any component is loaded. Returns cancel method.
303
304### Server side API
305
306> import {\*} from 'react-imported-component/server';
307
308- `whenComponentsReady():Promise` - will be resolved, when all components are loaded. Usually on the next "Promise" tick.
309- `drainHydrateMarks([stream])` - returns the currently used marks, and clears the list.
310- `printDrainHydrateMarks([stream])` - print our the `drainHydrateMarks`.
311
312#### Stream API
313
314- `createLoadableStream` - creates a steam
315- `ImportedStream` - wraps another component with import usage tracker.
316- `createLoadableTransformer` - creates nodejs StreamTransformer
317- `getLoadableTrackerCallback` - helper factory for the stream transformer
318
319### Client side API
320
321> import {\*} from 'react-imported-component/boot';
322
323- `whenComponentsReady():Promise`, will be resolved, when all (loading right now) marks are loaded.
324- `rehydrateMarks([marks]):Promise`, loads _marked_ async chunks.
325- `injectLoadableTracker` - helper factory for the stream transformer
326
327### Types
328
329#### Loadable
330
331All imports inside library are converted into `Loadable` object, and it's often accessible from outside via
332`useImported().loadable`, `useLoadable`(not documented), `getLoadable`(not documented). Even if it's documented from TS point of view -
333let's keep all fields in a secret, except one:
334
335- `resolution` - promise reflecting resolution of this loadable object
336
337<a name="setup"/>
338
339# Setup
340
341## In short
342
3431. Add `babel` plugin
3442. Run `yarn imported-components src src/imported.js` to extract all your imports into a `run time chunk` (aka async-requires).
3453. Replace `React.lazy` with our `lazy`, and `React.Suspense` with our `LazyBoundary`. Literraly [monkey-patch React to do so](#monkey-patch)
3464. Add `printDrainHydrateMarks` to the server code.
3475. Add `rehydrateMarks` to the client code
3486. Done. Just read the rest of readme for details.
349
350There are examples for webpack, parcel, and react-snap. Just follow them.
351
352## 1. Configure babel plugin
353
354**On the server**:
355
356```json
357{
358 "plugins": ["react-imported-component/babel", "babel-plugin-dynamic-import-node" /* might be optional for babel 7*/]
359}
360```
361
362**On the client**:
363
364```json
365{
366 "plugins": ["react-imported-component/babel"]
367}
368```
369
370Imported-Component will hook into dynamic imports, providing extra information about files you want to load.
371
372## 2. Add one more command into package.json
373
374CLI command `imported-components [sources ROOT] [targetFile.js]` (use .ts for TypeScript)
375
376```js
377 "generate-imported-component": "imported-components src src/imported.js"
378```
379
380When you will execute this command - all `imports` among your codebase would be found and extracted to a file provided.
381This will gave ability to orchestrate code-splitting later.
382
383If you need to search inside more that one top-level directory - just define more command, saving information into more than one target file.
384
385> The current implementation will discover and use all `imports`, even // commented ones
386
387> 💡 Feel free to **.gitignore** these autogenerated files
388
389## 3. Start using `imported`, `lazy` or `useImported`
390
391Without you using API provided nothing would work.
392
393## 4. Add server side tracking
394
395There are two ways to do it - in a single threaded way, and async
396
397#### Single threaded
398
399```js
400import { printDrainHydrateMarks, drainHydrateMarks } from 'react-imported-component';
401// this action will "drain" all currently used(by any reason) marks
402// AND print a script tag
403const html = renderToString(<YourApp />) + printDrainHydrateMarks();
404
405// OR return list of usedmarks, and yet again CLEAR the marks list.
406const html = renderToString(<YourApp />) + '<script>const marks=' + JSON.stringify(drainHydrateMarks()) + '</script>';
407```
408
409#### renderToStream or async render
410
411```js
412import {createLoadableStream} from 'react-imported-component/server';
413
414let importedStream = createLoadableStream();
415// ImportedStream is a async rendering "provider"
416const stream = renderToStream(
417 <ImportedStream stream={importedStream}>
418 <YourApp />
419 </ImportedStream>
420);
421
422// you'd then pipe the stream into the response object until it's done
423stream.pipe(res, { end: false });
424
425// and finalize the response with closing HTML
426stream.on('end', () =>
427 // print marks used in the file
428 res.end(`${printDrainHydrateMarks(importedStream)}</body></html>`),
429)
430```
431
432However, the idea is just to use `streams` to separate renders
433
434```js
435const html =
436 renderToString(
437 <ImportedStream stream={importedStream}>
438 <YourApp />
439 </ImportedStream>
440 ) + printDrainHydrateMarks(importedStream);
441```
442
443## 5. Add `rehydrateMarks` to the client code
444
445Before rendering your application you have to ensure - all parts are loaded.
446`rehydrateMarks` will load everything you need, and provide a promise to await.
447
448```js
449import { rehydrateMarks, ImportedController } from 'react-imported-component';
450
451// this will trigger all marked imports, and await for competition.
452rehydrateMarks().then(() => {
453 // better (note ImportedController usage)
454 ReactDOM.hydrate(
455 <ImportedController>
456 <App />
457 </ImportedController>,
458 document.getElementById('main')
459 );
460 // or
461 ReactDOM.render(<App />, document.getElementById('main'));
462});
463```
464
465`rehydrateMarks` accepts a list of `marks` from a server side(`drainHydrateMarks`), loads all
466necessary chunks and then resolves.
467
468<a name="concurrent-loading"/>
469
470## A VERY IMPORTANT MOMENT - Concurrent Loading
471
472All other code splitting libraries are working a bit differently - they amend `webpack` building process,
473gathering information about how the final chunks are assembled, and **injects the real scripts and styles** to the server response,
474thus all scripts, used to render something on the Server would be loaded in a parallel in on Client.
475Literally - they are defined in the HTML.
476`React-imported-component` is different, it starts "working" when the bundle is loaded, thus
477**the loading of chunks is deferred**.
478
479> 💡 In the normals conditions `react-imported-component` would be "slower" than a "webpack" library. Refer to bundle integration section.
480
481However, it is not a problem, as long as (for now), script execution is single threaded, and even you if can **load** multiple scripts
482simultaneously - you can't **run** them in parallel\*.
483
484And there a way to utilize this limitation - just change your entry point, .
485
486And let's call it - a **Scheduler optimization**. See **loading prediction** section for more details.
487
488#### Scheduler optimization + simply static render
489
4901. Split your app into `boot` and `main` parts
4912. `rehydrate` at the boot
492
493```js
494// index.js (boot)
495import './src/imported'; // the file generated by "generate-imported-component" (.2)
496import { rehydrateMarks } from 'react-imported-component/boot';
497
498rehydrateMarks(); // just start loading what's needed
499
500// load/execute the rest after letting the browser kick off chunk loading
501// for example wrapping it in two Promises (1ms timeout or setImmediate)
502Promise.resolve().then(() =>
503 Promise.resolve().then(() => {
504 // load the rest
505 require('./main'); // <--- your main scripts
506 // ! and don't forget to `await rehydrateMarks()` before render
507 })
508);
509
510// main.js
511rehydrateMarks().then(() => {
512 ReactDOM.hydrate(<App />, document.getElementById('root'));
513});
514```
515
516This will just start loading extra chunks before the main bundle got completely parsed and executed.
517
518#### Defer till DOMReady
519
520> 💡 Another perfect option would be to wait till DomReady event.
521
522```js
523// index.js (boot)
524import './src/imported'; // the file generated by "generate-imported-component" (.2)
525import { rehydrateMarks } from 'react-imported-component/boot';
526
527rehydrateMarks(); // just start loading what's needed
528
529const startApp = () => require('./main'); // <--- your main scripts
530
531// it's "not safe" to start you application before DOM is "ready"
532if (document.readyState === 'loading') {
533 document.addEventListener('DOMContentLoaded', startApp);
534} else {
535 startApp();
536}
537```
538
539#### Scheduler optimization + stream render
540
541> See examples/SSR/parcel-react-ssr/server-stream for details
542
5431. Add your main bundle to the `head`, using **async** script tag. Not defer! We have to do it async
5442. Add `loadableTracker` at server side
545
546```js
547import {createLoadableTransformer, getLoadableTrackerCallback} from 'react-imported-component/server';
548const importedTracker = createLoadableTransformer(
549 loadableStream, // stream to observe
550 getLoadableTrackerCallback() // helper factory to create global tracker.
551);
552
553// pipe result of `renderToStream` throught it
554const reactRenderStream = ReactDOM.renderToNodeStream(...).pipe(importedTracker);
555```
556
5573. Add `loadableTracker` at client side
558
559```js
560// index.js
561import './src/imported'; // the file generated by "generate-imported-component" (.2)
562import { injectLoadableTracker } from 'react-imported-component/boot';
563
564injectLoadableTracker();
565
566// load the rest after letting the browser kick off chunk loading
567// for example wrapping it in two Promises (1ms timeout or setImmediate)
568Promise.resolve().then(() =>
569 Promise.resolve().then(() => {
570 require('./main');
571 })
572);
573```
574
575This "hack", will first introduce all possible `imports` to the `imported-component`, then gave it a "tick"
576to start loading required once, and only then execute the rest of the bundle.
577While the rest(99%) of the bundle would make CPU busy - chunks would be loaded over the network.
578
579> 💡This is utilizing the differences between `parse`(unenviable) phase of script, and execute(more expensive) one.
580
581# Cooking receipts
582
583<a name="monkey-patch"/>
584
585## Replace React.lazy by lazy
586
587```js
588import React from 'react';
589import { lazy, LazyBoundary } from 'react-imported-component';
590React.lazy = lazy;
591React.Suspense = LazyBoundary;
592```
593
594That's all the work required to get it working.
595
596## Partial hydration
597
598Just wrap "partial hydrated" component with _another_ `ImportedStream` so everything needed for it would be not automatically loaded with the main stream.
599
600## Hybrid render (CSR with prerendering)
601
602This library could support hybrid rendering (aka pre-rendering) compatible in two cases:
603
604- pre-render supports `state hydration`, like `getState` in [react-snap](https://github.com/stereobooster/react-snap). See our [example](https://github.com/theKashey/react-imported-component/tree/master/examples/hybrid/react-snap).
605- for [rendertron](https://github.com/GoogleChrome/rendertron) or [https://prerender.io](https://prerender.io) follow `react-snap` example, just dump `state` using `setTimeout`.
606- You may use `react-prerendered-component` to maintain a component state until async chunk is not loaded. See example below.
607
608### Works better in pair (boiled-place-less code splitting)
609
610You might not need to wait for all the chunks to be loaded before you can render you app -
611just use [react-prerendered-component](https://github.com/theKashey/react-prerendered-component).
612
613```js
614import imported from 'react-imported-component';
615import { PrerenderedComponent } from 'react-prerendered-component';
616
617const AsyncComponent = imported(() => import('./myComponent.js'));
618
619<PrerenderedComponent
620 // component will "go live" when chunk loading would be done
621 live={AsyncComponent.preload()}
622>
623 // until component is not "live" prerendered HTML code would be used // that's why you need to `preload`
624 <AsyncComponent />
625</PrerenderedComponent>;
626```
627
628`React-prerendered-component` is another way to work with code splitting, which makes everything far better.
629
630## Timeout to display "spinners"
631
632There is no build in timeouts to display Error or Loading states. You could control everything by yourself
633
634- use react-delay, p-delay, p-timeout, or `Suspense` :P.
635
636## Component loader
637
638You may use component api if you need it by any reason.
639
640```js
641import { ComponentLoader } from 'react-imported-component';
642
643const MyPage = () => (
644 <ComponentLoader
645 loadable={() => import('./Page.js')}
646 // all fields are optional, and matches the same field of importedComponent.
647 LoadingComponent={Loading}
648 ErrorComponent={Error}
649 onError
650 exportPicker
651 render
652 async
653 />
654);
655```
656
657<a name="css"/>
658
659## CSS Support
660
661### CSS-in-JS Support
662
663Out-of-the-box. Literally. CSS-in-JS library, like `styled-component` will do it by themselves, and there is nothing
664to be managed by this library.
665
666### Static CSS Files Support
667
668> `imported` could knew only about JS you've used, not the CSS that js've used...
669
670This library **does not** support CSS as CSS, as long it's bundler independent and such deep integration is not possible.
671Even if [deep bundler integration](#bundler) is next following this section - the recomended solution is to skip `css` part of it, and use
672a more _bundler independent_ way to support CSS:
673
6741. Configure you bundler, and server side rendering to emit the right `classNames` (just remove `style-loader` from webpack configuration)
6752. Use `used-styles` to inject used **css files** to the resulting HTML.
676
677In short (streamed example is NOT short)
678
679```js
680const lookup = discoverProjectStyles('./dist');
681// ....
682const markup = ReactDOM.renderToString(<App />);
683const usedStylesAsCSSFiles = getUsedStyles(markup, lookup);
684// generate `link` (better rel='preload') to load CSS files
685
686const usedStylesAsStyles = getCriticalStyles(markup, lookup);
687// extract critical CSS and inline in to the server response
688```
689
690If you need _stream render_ example with **reduced TTFB** -
691please refer to [used-styles](https://github.com/theKashey/used-styles) documentation, or our [parcel-bundler stream server example](https://github.com/theKashey/react-imported-component/tree/master/examples/SSR/parcel-react-ssr/stream-server).
692
693#### Critical style extraction and Preloader
694
695With the critical style extracting you might not need the "real" and "full" styles on the page load -
696only `js` is really required for hydration.
697Thus - you might skip loading styles for the initial render, however still need them for the next render.
698However - your bundler might think that you have loaded them, this is not true.
699To handle this case you need `addPreloader` method
700
701```js
702// load initial bundle
703// await dom ready
704// -> HYDRATE APP <-
705// add preloader
706
707import { addPreloader } from 'react-imported-component/boot';
708
709addPreloader(() => {
710 // check that styles are loaded
711 // or return Promise if they are not
712});
713
714// any not yet loaded ImportedComponent
715// will await for it's own `import` as well as for
716// promise returned from preloaders
717```
718
719See critical css example for details
720
721#### Create React App
722
723Use `react-imported-component/macro` for CRA compatible SSR code splitting without ejecting.
724
725> It is safe to always use `react-imported-component/macro` without `babel-plugin-macros`, BUT with `react-hot-loader/babel` enabled, as long as it will remove all `macros`.
726
727<a name="bundler-integration"/>
728
729## Bundler integration
730
731Keep in mind - you dont "need" this. It will just make integration slightly better in terms of prefetching (which affects network recourse priority) and thus startup time.
732
733### Webpack integration
734
735You might preload/prefetch used styles and scripts, which were defined with `webpackChunkName`
736
737```js
738// get mark somehow (drainHydrateMarks(importedStream))
739import { getMarkedChunks } from 'react-imported-component/server';
740
741const chunkNames = getMarkedChunks(marks);
742```
743
744#### Via webpack-imported
745
746[webpack-imported](https://github.com/theKashey/webpack-imported) - provides `webpack` plugin to get data from the build, as well as to use it.
747Supports prefetching and critical style _guidance_.
748
749```js
750import { WebpackImport } from 'webpack-imported/react';
751import importedStat from 'build/imported.json';
752
753<WebpackImport stats={importedStat} chunks={getMarkedChunks(drainHydrateMarks())} />;
754```
755
756#### Via stat.json
757
758If you do have only `stat.json` - it could be converted into "importedStat" by `webpack-imported` without adding plugin.
759
760Alternatively - you can discover **all** resources you have to preload
761using [flush-webpack-chunks](https://github.com/faceyspacey/webpack-flush-chunks)
762
763```js
764const { js, styles } = flushChunks(webpackStats, {
765 chunkNames,
766});
767
768const prefetch = (targets, as) => targets.map(url => `<link as="${as}" rel="preload" href="${url}" />`).join('');
769
770res.send(prefetch(scripts, 'script'));
771res.send(prefetch(stylesheets, 'style'));
772```
773
774**DO NOT** actually **load** reported resources - only preload them, and let webpack do rest.
775
776#### Via assets.json
777
778You you are using [assets-webpack-plugin](https://github.com/ztoben/assets-webpack-plugin) then you have only list of assets, without dependencies between.
779That's enought.
780
781```js
782const prefetchChunks = (chunks, assets) =>
783 chunks
784 .map(chunk =>
785 [
786 assets[chunk].js && `<link rel="preload" as="script" href="${assets[chunk].js}" />`,
787 assets[chunk].css && `<link rel="preload" as="style" href="${assets[chunk].css}" />`,
788 ].join('')
789 )
790 .join('');
791
792res.send(prefetchChunks(chunkNames, assets));
793```
794
795#### Preloading
796
797Proper preloading is available **only** with bundler integration. Calling `component.preload` will not
798only `preload` it, but will `execute` as well.
799
800> `.preload` is equal do loading script as usual.
801
802If you seek the best experience you might want to prefetch or preload only (network only), not load (network and CPU).
803
804In order to prefetch(or preload) scripts - use `webpack-imported`, as seen in examples above.
805
806You need SSR step in order to use preloading!
807
808##### Step 1 - expose known chunks
809
810In order to prefetch or preload you should know scripts locations. This information is stored inside webpack, and you cannot access it from user code.
811As a result we have to duplicate this information:
812
813```js
814import { importAssets } from 'webpack-imported';
815// react-imported-component data file
816import applicationImports from '../async-requires';
817// webpack-imported data file
818import importedChunks from '../build/imported.json';
819// 👆 this file is generated during the build, you cannot use it "IN" the build
820// that's why this is Server Side only component
821
822export const ScriptLocations = () => (
823 <script
824 dangerouslySetInnerHTML={{
825 __html: `
826 window.KNOWN_SCRIPT_MARKS = ${JSON.stringify(
827 applicationImports.reduce((acc, [, chunkName]) => {
828 if (chunkName) {
829 acc[chunkName] = importAssets(importedChunks, chunkName).raw.load;
830 }
831 return acc;
832 }, {})
833 )};
834 `,
835 }}
836 />
837);
838```
839
840Then add `<ScriptLocations/>` somewhere in your SSR-ed `head`.
841
842##### Step 2 - preload
843
844Then you will be able to `prefetch(() => import('./home')`.
845
846```js
847import { getMarkedChunks, loadableResource } from 'react-imported-component';
848
849// this function is partially duplicating webpack internals
850export const prefetch = importCallback => {
851 // get a "lodable" attached for a given import
852 const loadable = loadableResource(importCallback);
853 if (typeof document !== 'undefined') {
854 const prefix = __webpack_public_path__;
855 if (loadable) {
856 // get chunks used in loadable
857 const chunks = getMarkedChunks(loadable.mark);
858 chunks.forEach(chunk => {
859 // note the usage of KNOWN_SCRIPT_MARKS
860 const { js = [], css = [] } = window.KNOWN_SCRIPT_MARKS[chunk];
861 js.forEach(script => {
862 const link = document.createElement('link');
863
864 link.rel = 'prefetch';
865 link.as = 'script';
866
867 link.href = prefix + script;
868 document.head.appendChild(link);
869 });
870 css.forEach(style => {
871 const link = document.createElement('link');
872
873 link.rel = 'prefetch';
874 link.as = 'style';
875
876 link.href = prefix + style;
877 document.head.appendChild(link);
878 });
879 });
880 }
881 }
882};
883```
884
885### Parcel integration
886
887Use `parcel-manifest` and `getMarkedFileNames`(instead of `getMarkedChunks`) to find which files were required and has to be imported.
888Keep in mind - the base path would be different and you have to `resolve` the _right_ files in to the _right_ files (`/app/index` to `index.js`).
889
890### CRA integration
891
892Consider using [concurrent-loading](#concurrent-loading) technique only.
893
894## React-snap
895
896`react-imported-component` is compatible with [react-snap](https://github.com/stereobooster/react-snap) out of the box.
897Works even better with `react-prerendered-component`.
898
899## Webpack-external-import (or native import)
900
901`react-imported-component` is compatible with [webpack-external-import](https://github.com/ScriptedAlchemy/webpack-external-import),
902as long as it executes `imports`, thus executes custom logic
903
904## `useImported` and `Suspense`
905
906`useImported` is not supposed to be used with Suspense (by design), while it could
907
908```js
909const MyComponent = () => {
910 const { loading, error, loadable, imported } = useImported(() => import('...'));
911
912 if (loading) throw loadable.resolution; // throw to the nearest Suspense boundary
913 if (error) throw error; // throw to the nearest Error boundary
914
915 // do something with `imported` value
916};
917```
918
919## SSR (Server side rendering)
920
921It was usually a headache - async components and SSR, which is currently sync.
922React-imported-component break this cycle, making ServerSide rendering sync, and providing
923comprehensive ways to rehydrate rendered tree on client.
924It will detect server-side environment and precache all used components.
925
926### Server Side Auto Import
927
928On the server side `imported` **would auto-import** any found import. As long as not all imports could be executed at Server Side you might
929pop out this feature using a "magic comment".
930
931```js
932import(/* client-side */ './file');
933```
934
935Or file filtering
936
937```js
938import { setConfiguration } from 'react-imported-component';
939
940setConfiguration({
941 fileFilter: fileName => file.indexOf('/client') !== 0,
942});
943```
944
945### .imported.js
946
947There is possibility to finely control both with files are scanned, which imports are added and which chunks would be generated(webpackonly)
948via `.imported.js` at the root directory
949
950> 💩 .js, not .ts
951
952```js
953// 👉 use provided helper for documentation and type safety
954const { configure } = require('react-imported-component');
955
956modules.export = configure({
957 testFile: fileName => true | false,
958 testImport: (targetFile, sourceFile) => true | false,
959 clientSideOnly: (targetFile, sourceFile, sourceComments) => true | false,
960 shouldPrefetch: (targetFile, sourceFile, sourceComments) => true | false,
961 shouldPreload: (targetFile, sourceFile, sourceComments) => true | false,
962 chunkName: (targetFile, sourceFile, sourceComments) => string | null | undefined,
963});
964```
965
966None of those methods are required.
967
968`.imported` could be used to:
969
970- remove some files or imports
971- autogenerate chunk names or prefetch/preload
972
973### Bundler independent SSR
974
975It does not matter how do you bundle your application - it could be even browser. The secrect sause is a **cli** command, to extract all your imports into imports map, and use it later to load chunks by request.
976
977- You might even dont have any separated chunk on the server side - it would still works.
978- You might even ship module/nomodule scripts, using, for example, [devolution](https://github.com/theKashey/devolution) - no additional configuration would be required.
979
980## Why you need SSR
981
982In case of imported component SSR is a "dry run" of your application - an easy way to discover required pieces
983powered by zero latency(you are already on the server) and super fast speed connection (all scripts are in memory).
984
985However - you dont need SSR to get the same benefits on pure ClientSideRendered solutions - _prediction_ would be enought.
986The common approach is to
987
988- load first part of components, including Providers, which would load something they need. Like translations.
989- then hit, and load the current route
990- then do the same with the sub route
991
992This causes effect known as `loading waves`, the effect SSR could mitigate almost in full.
993
994However, nothing stops you from loading translation data in the parallel to the routes, and loading
995route and sub route in the same time, not sequentially.
996You can have backend-for-frontend, why not to have frontend-for-frontend?
997Just handle route, cookies, and whatever you could handle, outside of React. [Redux-first-router](https://github.com/faceyspacey/redux-first-router) and principles behind it
998are the great example of this idealogy.
999
1000### Not using React.Lazy with React-Hot-Loader
1001
1002There is design limitation with React.lazy support from RHL size, so they could not be reloaded without
1003state loss if `lazy` is created not in the user space. At it would be created inside imported.
1004
1005If React-Hot-Loader is detected `lazy` switches to `imported async` mode, this behaves absolutely the same.
1006
1007<a name="comparisonLegend" />
1008
1009### Comparison table legend
1010
1011- `Library` - the library name
1012- `Suspense` - does it support Suspense feature
1013- `SSR` - does it support SSR
1014- `Hooks` - does it have hooks API
1015- `Library` - does it support _library_, not Component level splitting
1016- `Non-modules` - could it "import" generic promise, not a real dynamic module import
1017- `import('./${value}')` - does it support a _full dynamic import_ - ability to import any file.
1018- `babel macro` - ability to work with babel-plugin-macros
1019- `webpack only` - is this solution hard bould to webpack
1020
1021## Featured in
1022
1023- https://medium.com/hackernoon/react-and-code-splitting-made-easy-f118befb5168
1024- https://dev.to/thekashey/react-imported-component-v6-4304
1025
1026## Other loaders
1027
1028Another loaders exist, and the only difference is in API, and how they manage (or not manage) SSR.
1029
1030- (no SSR) React.Lazy
1031- (webpack only) With [react-loadable](https://github.com/thejameskyle/react-loadable)
1032- (not compatible with hooks) [react-async-component](https://github.com/ctrlplusb/react-async-component)
1033- (webpack only) [loadable-components](https://github.com/smooth-code/loadable-components)
1034- (webpack only) [react-universal-component](https://github.com/faceyspacey/react-universal-component)
1035
1036- (not a component loader) [use-sidecar](https://github.com/theKashey/use-sidecar)
1037
1038## Licence
1039
1040MIT