react-imported-component

by theKashey

βœ‚οΈπŸ“¦Bundler-independent solution for SSR-friendly code-splitting

495 Stars 35 Forks Last release: about 1 month ago (v6.3.13) MIT License 422 Commits 72 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

IMPORTED COMPONENT βœ‚

Code splitting which always works*


imported components

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

npm downloads bundle size

* It's really will never let you down. All credits to your bundler.

πŸ‘‰ Usage | API | Setup | SSR | CCS Concurrent loading | Webpack/Parcel

| 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

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 provide

Other 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:

  • 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

πŸ“¦ Optional bundler integration for the best experience

  • prefetching backed by webpack
    stat.json
    and
    asset.json
  • parcel-manifest.json
    support

πŸ‘―β€β™€οΈWorks better in pair

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...></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 = () => ( );

// 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

Hook

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

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

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:

 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:
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

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

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
    . Literraly monkey-patch React to do so
  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:

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

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

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() + '';

renderToStream or async render

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

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)}

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.