Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/theKashey/react-imported-component
βοΈπ¦Bundler-independent solution for SSR-friendly code-splitting
https://github.com/theKashey/react-imported-component
async-components code-splitting loader parcel-bundler react server-side-rendering ssr webpack
Last synced: 3 months ago
JSON representation
βοΈπ¦Bundler-independent solution for SSR-friendly code-splitting
- Host: GitHub
- URL: https://github.com/theKashey/react-imported-component
- Owner: theKashey
- License: mit
- Created: 2017-07-20T12:49:45.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2024-03-20T11:17:40.000Z (8 months ago)
- Last Synced: 2024-04-15T08:08:46.399Z (7 months ago)
- Topics: async-components, code-splitting, loader, parcel-bundler, react, server-side-rendering, ssr, webpack
- Language: TypeScript
- Homepage:
- Size: 6.48 MB
- Stars: 655
- Watchers: 12
- Forks: 39
- Open Issues: 43
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
- awesome-list - react-imported-component - independent solution for SSR-friendly code-splitting | theKashey | 577 | (TypeScript)
README
IMPORTED COMPONENT β
Code splitting which always works*
SSR-friendly code splitting compatible with any platform.
Deliver a better experience within a single import.
> \* It's really will never let you down. All credits to your bundler.
π [Usage](#usage) | [API](#api) | [Setup](#setup) | [SSR](#ssr) | [CCS](#css) [Concurrent loading](#concurrent-loading) | [Webpack/Parcel](#bundler-integration)
| Library | Suspense | SSR | Hooks | Library | Non-modules | import(`./${value}`) | babel-macro | webpack only |
| ------------------- | :------: | :-: | :---: | :-----: | :---------: | :------------------: | :---------: | :----------: |
| React.lazy | β | β | β | β | β | β | πΉ | no-ssr |
| react-loadable | β | β | β | β | β | β | β | πΏ |
| @loadable/component | β | β | β | β | β | β | β | πΏ |
| imported-component | β | β | β | β | β | β | β | πΈ |> Read more about [what this table displays](#comparisonLegend)
Key features:
- 1οΈβ£ Single source of truth - your **bundler drives** everything
- π **library** level code **splitting**
- π§οΈ Hybrid and **Prerendering** compatible
- π‘ **TypeScript** bindings
- βοΈ **React.Lazy** underneath (if hot module updates are disabled)
- π Async on client, sync on server. Supports **Suspense** (even on server side)
- π¦ could work with **any bundler** - webpack, rollup, parcel or puppeteer - it does not matter
- π€ΉββοΈ working as well with any `import` you may provideOther features:
- π₯ Hot-Module-Replacement/React-Hot-Loader friendly
- βοΈ support forwardRef
- βοΈ React 16/Async/Hooks ready
- π HOC, Component, Hooks API
- π³ stream rendering support
- π₯ partial hydration out of the box
- π¦ 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**π Better than [React.Lazy](https://reactjs.org/docs/code-splitting.html#reactlazy):
- It **IS** Lazy, just with some stuff around\*
- SSR, Prerendering and Preloading support
- With or without Suspense, and easier Error cases supportπ Better than others:
- Not bound to webpack
- Easy way to use per-browser(modern/legacy) bundles - you down have to mess with actual browser support
- Strong typing
- Working with any imports, even native, external or derived ones.π Client-side module resolution
- Loads chunks only after the `main one`, as long as loader code is bundled inside the main chunk, so it should be loaded first.
- Not an issue with the `progressive hydration`, and might provide a better UX via feature detection.
- Provides π¨βπ¬ technological workaround - [see here](#concurrent-loading)π¦ Optional bundler integration for the best experience
- prefetching backed by webpack `stat.json` and `asset.json`
- `parcel-manifest.json` supportπ―ββοΈWorks better in pair
- [react-prerendered-component](https://github.com/theKashey/react-prerendered-component) for prerendering, partial hydration and react fragment caching
- [used-style](https://github.com/theKashey/used-styles) for CSS and critical CSS extraction
- [devolution](https://github.com/theKashey/devolution) for shipping legacy/modern bundles
- [webpack-imported](https://github.com/theKashey/webpack-imported) for deep `webpack` integration, the only bundler dependent tool in this list.# Usage
## Server side
Just a proper setup and a bit of magic
## Client side
### Component
`imported` provides 2 common ways to define a component, which are more different inside than outside
- using `pre-lazy` API.
```javascript
import importedComponent from 'react-imported-component';
const Component = importedComponent( () => import('./Component'));const Component = importedComponent( () => import('./Component'), {
LoadingComponent: Spinner, // what to display during the loading
ErrorComponent: FatalError // what to display in case of error
});Component.preload(); // force preload
// render it
```
- using `lazy` API. It's almost the same `React.lazy` outside, and exactly the same inside.
```js
import { lazy, LazyBoundary } from 'react-imported-component';
const Component = lazy(() => import('./Component'));const ClientSideOnly = () => (
);// or let's make it SSR friendly
const ServerSideFriendly = () => (
{' '}
// LazyBoundary is Suspense* on the client, and "nothing" on the server
);
````LazyBoundary` is a `Suspense` on Client Side, and `React.Fragment` on Server Side. Don't forget - "dynamic" imports are sync on a server.
Example: [React.lazy vs Imported-component](https://codesandbox.io/s/wkl95r0qw8)
### Hook
However, you may not load only components - you may load anything
```js
import {useImported} from 'react-imported-component'const MyCalendarComponent = () => {
const {
imported: moment,
loading
} = useImported(() => import("moment"));return loading ? "..." : today is {moment(Date.now).format()}
}// or we could make it a bit more interesting...
const MyCalendarComponent = () => {
const {
imported: format = x => "---", // default value is used while importing library
} = useImported(
() => import("moment"),
moment => x => moment(x).format // masking everything behind
);return today is {format(Date.now())
}
```What you could load using `useImported`? Everything - `imported` itself is using it to import components.
> `useImported` is an excellent example for loading translations, which are usually a simple json, in a _trackable_ way.
> π‘ 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.
π€ Keep in mind - **everything here is using `useImported`**, and **you can build whatever you need** using just it.
### Module
A slim helper to help handle `modules`, you might require using `useImported` in a component way
```js
import { importedModule, ImportedModule } from 'react-imported-component';const Moment = importedModule(() => import('moment'));
{(momentjs /* default imports are auto-imported*/) => momentjs(date).fromNow()}
;
```> Yes, this example was taken from [loadable.lib](https://www.smooth-code.com/open-source/loadable-components/docs/library-splitting/)
Can I also use a ref, populated when the library is loaded? No, you cant. Use `useImported` for any special case like this.
Plus, there is a Component helper:
```js
import('moment').then(({momentDefault})=> momentDefault(date).fromNow()}
fallback="long time ago"
>
{(fromNow) => fromNow()}```
> ImportedModule will throw a promise to the nearest Suspense boundary if no `fallback` provided.
## Babel macro
If you could not use babel plugin, but do have `babel-plugin-macro` (like CRA) - consider using macro API:
```js
import { imported, lazy, useImported } from 'react-imported-component/macro';
// notice - there is no default import here
```### Indirect usage
Just importing `react-imported-component/macro` would enable babel transformation for the current file.
If 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)# API
> Don't forget - there are TS typings provided.
### Code splitting components
> import {\*} from 'react-imported-component';
##### importedComponent
- `importedComponent(importFunction, [options]): ComponentLoader` - main API, default export, HOC to create imported component.
- `importFunction` - function which resolves with Component to be imported.
- `options` - optional settings
- `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)
- `options.LoadingComponent` - component to be shown in Loading state
- `options.ErrorComponent` - component to be shown in Error state. Will re-throw error if ErrorComponent is not set. Use ErrorBoundary to catch it.
- `options.onError` - function to consume the error, if one will thrown. Will rethrow a real error if not set.
- `options.exportPicker` - function to pick `not default` export from a `importFunction`
- `options.render(Component, state, props)`- function to render the result. Could be used to tune the rendering.- [static] `.preload` - static method to preload components.
##### lazy
- `lazy(importFunction)` - helper to mimic **React.lazy** behavior
##### useImported
- `useImported(importFunction, [exportPicker], [options])` - code splitting hook
- `importFunction` - a function which resolves to `default` or `wildcard` import(T | {default:T})
- `[exportPicker]` - function to pick "T" from the import
- `[options]` - options to the hook- `[options.import]` - controls import. Hooks would be executed only if this is not false
- `[options.track]` - ability to disable server-side usage tracking.`useImported` returns complex object(ImportedShape):
- `imported` - the imported resource
- `error` - error (if present)
- `loading` - is it loading right now?
- `loadable` - the underlying `Loadable` object
- `retry` - retry action (in case of error)Hints:
- use `options.import=false` to perform conditional import - `importFunction` would not be used if this option set to `false.
- use `options.track=true` to perform SSR only import - to usage would be tracked if this option set to `false.##### ImportedController
- `` - a controller for Suspense Hydration. **Compulsory** for async/lazy usecases
##### Misc
There is also API method, unique for imported-component, which could be useful on the client side
- `addPreloader(fn):fn` - adds a function, result of which would be _awaited_ when any component is loaded. Returns cancel method.
### Server side API
> import {\*} from 'react-imported-component/server';
- `whenComponentsReady():Promise` - will be resolved, when all components are loaded. Usually on the next "Promise" tick.
- `drainHydrateMarks([stream])` - returns the currently used marks, and clears the list.
- `printDrainHydrateMarks([stream])` - print our the `drainHydrateMarks`.#### Stream API
- `createLoadableStream` - creates a steam
- `ImportedStream` - wraps another component with import usage tracker.
- `createLoadableTransformer` - creates nodejs StreamTransformer
- `getLoadableTrackerCallback` - helper factory for the stream transformer### Client side API
> import {\*} from 'react-imported-component/boot';
- `whenComponentsReady():Promise`, will be resolved, when all (loading right now) marks are loaded.
- `rehydrateMarks([marks]):Promise`, loads _marked_ async chunks.
- `injectLoadableTracker` - helper factory for the stream transformer### Types
#### Loadable
All imports inside library are converted into `Loadable` object, and it's often accessible from outside via
`useImported().loadable`, `useLoadable`(not documented), `getLoadable`(not documented). Even if it's documented from TS point of view -
let's keep all fields in a secret, except one:- `resolution` - promise reflecting resolution of this loadable object
## In short
1. Add `babel` plugin
2. Run `yarn imported-components src src/imported.js` to extract all your imports into a `run time chunk` (aka async-requires).
3. Replace `React.lazy` with our `lazy`, and `React.Suspense` with our `LazyBoundary`. Literraly [monkey-patch React to do so](#monkey-patch)
4. Add `printDrainHydrateMarks` to the server code.
5. Add `rehydrateMarks` to the client code
6. Done. Just read the rest of readme for details.There are examples for webpack, parcel, and react-snap. Just follow them.
## 1. Configure babel plugin
**On the server**:
```json
{
"plugins": ["react-imported-component/babel", "babel-plugin-dynamic-import-node" /* might be optional for babel 7*/]
}
```**On the client**:
```json
{
"plugins": ["react-imported-component/babel"]
}
```Imported-Component will hook into dynamic imports, providing extra information about files you want to load.
## 2. Add one more command into package.json
CLI command `imported-components [sources ROOT] [targetFile.js]` (use .ts for TypeScript)
```js
"generate-imported-component": "imported-components src src/imported.js"
```When you will execute this command - all `imports` among your codebase would be found and extracted to a file provided.
This will gave ability to orchestrate code-splitting later.If you need to search inside more that one top-level directory - just define more command, saving information into more than one target file.
> The current implementation will discover and use all `imports`, even // commented ones
> π‘ Feel free to **.gitignore** these autogenerated files
## 3. Start using `imported`, `lazy` or `useImported`
Without you using API provided nothing would work.
## 4. Add server side tracking
There are two ways to do it - in a single threaded way, and async
#### Single threaded
```js
import { printDrainHydrateMarks, drainHydrateMarks } from 'react-imported-component';
// this action will "drain" all currently used(by any reason) marks
// AND print a script tag
const html = renderToString() + printDrainHydrateMarks();// OR return list of usedmarks, and yet again CLEAR the marks list.
const html = renderToString() + 'const marks=' + JSON.stringify(drainHydrateMarks()) + '';
```#### renderToStream or async render
```js
import {createLoadableStream} from 'react-imported-component/server';let importedStream = createLoadableStream();
// ImportedStream is a async rendering "provider"
const stream = renderToStream(
);// you'd then pipe the stream into the response object until it's done
stream.pipe(res, { end: false });// and finalize the response with closing HTML
stream.on('end', () =>
// print marks used in the file
res.end(`${printDrainHydrateMarks(importedStream)}