react-imported-component
I will import your component, and help to handle it
Last updated 5 years ago by kashey .
MIT · Repository · Bugs · Original npm · Tarball · package.json
$ npm install react-imported-component 
SYNC missed versions from official npm registry.

IMPORTED COMPONENT ✂


imported components

SSR-friendly code splitting compatible with any platform.
Deliver a better experience within a single import.

Build status npm downloads bundle size

👉 Usage | API | SSR | Concurrent loading

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

Other features:

  • 🔥 Hot-Module-Replacement/React-Hot-Loader friendly
  • ⛓️ support forwardRef
  • ⚛️ Rect 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

👍 Better than React.Lazy:

  • It IS Lazy, just with some stuff around*
  • SSR, Prerendering and Preloading support.
  • With or without Suspense, and easier Error cases support.

👍 Better than Loadable-Component:

  • Not bound to webpack.
  • Easy way to use per-browser(modern/legacy) bundles.
  • Strong typing.
  • Prerendering support

👌 Not as good with

  • 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

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.
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
<Component... />
  • using lazy API. It's almost the same React.lazy outside, and exactly the same inside.
import {lazy, LazyBoundary} from 'react-imported-component'
const Component = lazy(() => import('./Component'));

const ClientSideOnly = () => (
    <Suspense>
     <Component />
    </Suspense> 
);

// or let's make it SSR friendly
const ServerSideFriendly = () => (
    <LazyBoundary> // LazyBoundary is Suspense on the client, and "nothing" on the server
      <Component />
    </LazyBoundary> 
)

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

Hook

However, you may not load only components - you may load anything

import {useImported} from 'react-imported-component'

const MyCalendarComponent = () => {
  const {
      moment,
      loading
    } = useImported(() => import("moment"));
  
  return loading ? "..." : <span>today is {moment(Date.now).format()}</span>
}

// 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 <span>today is {format(Date.now())</span>
}

What you could load using useImported? Everything - imported itself is using it to import components.

API

Code splitting components

import {*} from 'react-imported-component';

  • 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.

    • 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(importFunction) - helper to mimic React.lazy behavior

  • useImported(importFunction, exportPicker?) - code splitting hook

    • importFunction - a function which resolves to default or wildcard import(T | {default:T})
    • exportPicker - function to pick "T" from the import

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

Setup

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.
  4. Add printDrainHydrateMarks to the server code 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:

{
  "plugins": ["react-imported-component/babel", "babel-plugin-dynamic-import-node"/* might be optional for babel 7*/]
}

On the client:

{
  "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)

 "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.

The current implementation will discover and use all imports, even // commented ones

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

  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(<YourApp />) + printDrainHydrateMarks();
  
  // OR return list of usedmarks, and yet again CLEAR the marks list.
  const html = renderToString(<YourApp />) + "<script>const marks="+JSON.stringify(drainHydrateMarks())+"</script>";

renderToStream or async render

import {createLoadableStream} 'react-imported-component';

let importedStream = createLoadableStream();
// ImportedStream is a async rendering "provider"
const stream = renderToStream(
  <ImportedStream stream={importedStream}>
    <YourApp />
  </ImportedStream>
);

// 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)}</body></html>`),
)

However, the idea is just to use streams to separate renders

const html = renderToString(<ImportedStream stream={importedStream}><YourApp /></ImportedStream>) + printDrainHydrateMarks(importedStream);

5. Add rehydrateMarks to the client code

Before rendering your application you have to ensure - all parts are loaded. rehydrateMarks will load everything you need, and provide a promise to await.

  import { rehydrateMarks } from 'react-imported-component';

  // this will trigger all marked imports, and await for competition.
  rehydrateMarks().then(() => {
    // better
    ReactDOM.hydrate(<App />, document.getElementById('main'));
    // or
    ReactDOM.render(<App />, document.getElementById('main'));
  });

rehydrateMarks accepts a list of marks from a server side(drainHydrateMarks), loads all necessary chunks and then resolves.

A VERY IMPORTANT MOMENT - Concurrent Loading

All other code splitting libraries are working a bit differently - they amend webpack building process, gathering information about how the final chunks are assembled, and injects the real scripts and styles to the server response, thus all scripts, used to render something on the Server would be loaded in a parallel in on Client. Literally - they are defined in the HTML. React-imported-component is different, it starts "working" when the bundle is loaded, thus loading of chunks is deferred.

In the normals conditions react-imported-component would be "slower" than a "webpack" library.

However, it is not a problem, as long as (for now), script execution is single trhreaded, and even you if can load multiple scripts simultaneously - you can't run them in parallel*.

Just change your entry point, to utilize this limitation.

Let's call it - Scheduler optimization.

Simply static render

  1. Split your app into boot and main parts
  2. rehydrate at the boot
 // index.js (boot)
 import './src/imported'; // the file generated by "generate-imported-component" (.2)
 import {rehydrateMarks} from 'react-imported-component/boot';
  
 rehydrateMarks(); // just start loading what's needed
  
 // load/execute the rest after letting the browser kick off chunk loading
 // for example wrapping it in two Promises (1ms timeout or setImmediate)
 Promise.resolve().then(() =>
   Promise.resolve().then(() => {
     // load the rest
     require('./main');
     // don't forget to `await rehydrateMarks()` before render
   })
 );

This will just start loading extra chunks before the main bundle got completely parsed and executed.

Stream render

See examples/SSR/parcel-react-ssr/server-stream for details

  1. Add your main bundle to the head, using async script tag. Not defer! We have to do it async
  2. Add loadableTracker at server side
import {createLoadableTransformer, getLoadableTrackerCallback} from 'react-imported-component/server';
const importedTracker = createLoadableTransformer(
  loadableStream, // stream to observe 
  getLoadableTrackerCallback() // helper factory to create global tracker.
);

// pipe result of `renderToStream` throught it
const reactRenderStream = ReactDOM.renderToNodeStream(...).pipe(importedTracker);
  1. Add loadableTracker at client side
 // index.js
 import './src/imported'; // the file generated by "generate-imported-component" (.2)
 import {injectLoadableTracker} from 'react-imported-component/boot';
  
 injectLoadableTracker();
  
 // load the rest after letting the browser kick off chunk loading
 // for example wrapping it in two Promises (1ms timeout or setImmediate)
 Promise.resolve().then(() =>
   Promise.resolve().then(() => {
     require('./main')
   })
 );

This "hack", will first introduce all possible imports to the imported-component, then gave it a "tick" to start loading required once, and only then execute the rest of the bundle. While the rest(99%) of the bundle would make CPU busy - chunks would be loaded over the network.

This is utilizing the differences between parse(unenviable) phase of script, and execute one.

Cooking receipts

Partial hydration

Just wrap "partial hydrated" component with another ImportedStream so everything needed for it would be not automatically loaded with the main stream.

Hybrid render (CSR with prerendering)

This library could support hybrid rendering (aka pre-rendering) compatible in two cases:

Works better in pair (boiled-place-less code splitting)

You might not need to wait for all the chunks to be loaded before you can render you app - just use react-prerendered-component.

import imported from 'react-imported-component';
import {PrerenderedComponent} from "react-prerendered-component";

const AsyncComponent = imported(() => import('./myComponent.js'));

<PrerenderedComponent
  // component will "go live" when chunk loading would be done
  live={AsyncComponent.preload()}
>
  // until component is not "live" prerendered HTML code would be used
  // that's why you need to `preload`
  <AsyncComponent/>
</PrerenderedComponent>

React-prerendered-component is another way to work with code splitting, which makes everything far better.

CSS Support

CSS-in-JS Support

First class. Literally CSS-in-JS library, like styled-component will do it by themselves, and there is nothing to be managed by this library

Static CSS Support

This library does not support CSS as CSS, as long it's bundler independent. However, there is a bundler independent way to support CSS:

  1. Configure you bundler, and server side rendering to emit the right classNames (just remove style-loader from webpack configuration)
  2. Use used-styles to inject used css files to the resulting HTML.

In short (streamed example is NOT short)

  const markup = ReactDOM.renderToString(<App />)
  const usedStyles = getUsedStyles(markup, lookup);

If you need streamed example with reduced TTFB - please refer to used-styles documentation, or our parcel-bundler stream server example.

Timeout to display "spinners"

There is no build in timeouts to display Error or Loading states. You could control everything by yourself

  • use react-delay, p-delay, p-timeout, or Suspense :P.

Component loader

You may use component api if you need it by any reason.

import {ComponentLoader} from 'react-imported-component';

const MyPage = () => (
   <ComponentLoader
       loadable={() => import('./Page.js')}
       // all fields are optional, and matches the same field of importedComponent.
       LoadingComponent={Loading}
       ErrorComponent={Error}
       onError
       
       exportPicker
       render
       async                 
   />
);

SSR (Server side rendering)

It was usually a headache - async components and SSR, which is currently sync. React-imported-component break this cycle, making ServerSide rendering sync, and providing comprehensive ways to rehydrate rendered tree on client. It will detect server-side environment and precache all used components.

Bundler independent SSR

It 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.

  • You might even dont have any separated chunk on the server side - it would still works.
  • You might even ship module/nomodule scripts, using, for example, devolution - no additional configuration would be required.

Not using React.Lazy with React-Hot-Loader

There is design limitation with React.lazy support from RHL size, so they could not be reloaded without state loss if lazy is created not in the user space. At it would be created inside imported.

If React-Hot-Loader is detected lazy switches to imported async mode, this behaves absolutely the same.

Other loaders

Another loaders exists, and the only difference is in API, and how they manage (or not manage) SSR.

Licence

MIT

Current Tags

  • 6.3.12                                ...           beta (4 years ago)
  • 6.5.4                                ...           latest (9 months ago)
  • 6.2.4                                ...           next (5 years ago)

80 Versions

  • 6.5.4                                ...           9 months ago
  • 6.5.3                                ...           3 years ago
  • 6.5.2                                ...           3 years ago
  • 6.5.0                                ...           3 years ago
  • 6.4.1                                ...           4 years ago
  • 6.4.0                                ...           4 years ago
  • 6.3.13                                ...           4 years ago
  • 6.3.12                                ...           4 years ago
  • 6.3.11                                ...           4 years ago
  • 6.3.10                                ...           4 years ago
  • 6.3.9                                ...           4 years ago
  • 6.3.7                                ...           4 years ago
  • 6.3.6                                ...           4 years ago
  • 6.3.5                                ...           4 years ago
  • 6.3.4                                ...           5 years ago
  • 6.3.3                                ...           5 years ago
  • 6.3.2                                ...           5 years ago
  • 6.3.1                                ...           5 years ago
  • 6.3.0                                ...           5 years ago
  • 6.2.4                                ...           5 years ago
  • 6.2.3                                ...           5 years ago
  • 6.2.2                                ...           5 years ago
  • 6.2.1                                ...           5 years ago
  • 6.2.0                                ...           5 years ago
  • 6.1.1                                ...           5 years ago
  • 6.1.0                                ...           5 years ago
  • 6.0.5                                ...           5 years ago
  • 6.0.4                                ...           5 years ago
  • 6.0.3                                ...           5 years ago
  • 6.0.2                                ...           5 years ago
  • 6.0.1                                ...           5 years ago
  • 6.0.0                                ...           5 years ago
  • 6.0.0-beta.6                                ...           5 years ago
  • 6.0.0-beta.5                                ...           5 years ago
  • 6.0.0-beta.4                                ...           5 years ago
  • 6.0.0-beta.3                                ...           5 years ago
  • 6.0.0-beta.2                                ...           5 years ago
  • 6.0.0-beta.1                                ...           5 years ago
  • 5.5.3                                ...           6 years ago
  • 5.5.1                                ...           6 years ago
  • 5.5.0                                ...           6 years ago
  • 5.4.1                                ...           6 years ago
  • 5.4.0                                ...           6 years ago
  • 5.3.0                                ...           6 years ago
  • 5.2.5                                ...           6 years ago
  • 5.2.4                                ...           6 years ago
  • 5.2.3                                ...           6 years ago
  • 5.2.2                                ...           6 years ago
  • 5.2.1                                ...           6 years ago
  • 5.2.0                                ...           6 years ago
  • 5.1.3                                ...           6 years ago
  • 5.1.2                                ...           6 years ago
  • 5.1.1                                ...           6 years ago
  • 5.1.0                                ...           6 years ago
  • 5.0.1                                ...           6 years ago
  • 5.0.0                                ...           6 years ago
  • 4.6.2                                ...           6 years ago
  • 4.6.1                                ...           7 years ago
  • 4.6.0                                ...           7 years ago
  • 4.5.0                                ...           7 years ago
  • 4.4.2                                ...           7 years ago
  • 4.4.1                                ...           7 years ago
  • 4.4.0                                ...           7 years ago
  • 4.3.3                                ...           7 years ago
  • 4.3.2                                ...           7 years ago
  • 4.3.1                                ...           7 years ago
  • 4.3.0                                ...           7 years ago
  • 4.2.1                                ...           7 years ago
  • 4.2.0                                ...           7 years ago
  • 4.1.1                                ...           7 years ago
  • 4.1.0                                ...           7 years ago
  • 4.0.1                                ...           7 years ago
  • 4.0.0                                ...           7 years ago
  • 2.0.1                                ...           7 years ago
  • 2.0.0                                ...           7 years ago
  • 1.0.4                                ...           7 years ago
  • 1.0.3                                ...           7 years ago
  • 1.0.2                                ...           7 years ago
  • 1.0.1                                ...           7 years ago
  • 1.0.0                                ...           7 years ago
Maintainers (1)
Downloads
Total 3
Today 0
This Week 0
This Month 1
Last Day 0
Last Week 0
Last Month 0
Dependencies (5)
Dependents (1)

© 2010 - cnpmjs.org x YWFE | Home | YWFE