DEV Community

loading...
Cover image for πŸ“¦ React-Imported-Component v6

πŸ“¦ React-Imported-Component v6

Anton Korzunov
Reinventing the wheels.
・5 min read

Long story short - nobody cares about version 1 and 2 - by that time the library, I am going to talk about, even had a different name. Version 3 never existed and the difference between 4 and 5 was a forwardRef, or React 16 support, which is a good reason for a breaking change. So, in short, v6 is actually 3rd iteration on the API.

Bump to a version 6 was also driven by new React features. This time by hooks, making React-imported-component the first code-splitting library with exposed hook API, as well as the first Create-React-App compatible one, thanks to the babel macros support.

Stop stop stop, why not just Lazy?

Server Side Rendering, tracking, and advanced API. Here is the comparison table:

Comparison table

Ready? Jump in!

GitHub logo theKashey / react-imported-component

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

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

Build status 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 βœ… ❌ ❌ ❌ ❌ ❌ 😹
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…

useImported hook

Lazy-loading is not only about React.lazy and Components - behind any variant there is nothing more but a dynamic import, which able to load absolute everything. And all you need - is a proper "React integration" to manage loading states.

// a STATIC table with imports
const languages = {
  'en': () => import('./i18n/en'), // it's probably a json...
  'de': () => import('./i18n/de'),
}
// just a helper function
const pickLanguage = (lng) => languages[lng];
// your component
const MyI18nProvider = ({lng, children}) => {
  // let's pick and provide correct import function for our language
  const {
   imported: messages = {} // defaulting to empty object
  } = useImported(pickLanguage(lng));

  // set messages to the Provider
  return <I18nProvider value={messages}>{children}</I18nProvider>
}

That's all. The only problem is with default {}, unique on every render, so better extract them to a separate variable outside of this component. However, this is not something you should worry about.

useImported could load whatever you want, and any other API exposed by react-imported-component is built on it.

importedModule and ImportedModule

useImported is not always super handfull, sometimes something more declarative could be preferred.
So there is a helper to import anything you want via react render props interface. For the majority, this pattern is better known as loadable.lib (however it was first introduced for imported)

import {importedModule, ImportedModule} from 'react-imported-component';

// you can use it to codesplit and  use `momentjs`, no offence :)
const Moment = importedModule(() => import('moment'));

<Moment fallback="long time ago">
  {(momentjs /* default imports are auto-imported*/) => momentjs(date).fromNow()}
</Moment>

// or, again, translations

<ImportedModule
 import={() => import('./i18n/en')}
 // fallback="..." // will throw to the Suspense boundary without fallback provided
>
 {(messages) => <I18nProvider value={messages}>{children}</I18nProvider> }
</ImportedModule>

imported and lazy

There is also two "common" APIs - imported and lazy.
Where lazy - quacks like a React.lazy, and is lazy in production, while imported is, well, old good imported with API compatible with the first generation of code splitting libraries.

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

Extra stuff

Create React App support

There are 3 things you should know about CRA:

  • it's hard to change the configuration of your project, and you are encouraged not to do it
  • it supports SSR or Prerendering
  • that's makes better code splitting a bit complicated

However, while other SSR friendly code-splitting solutions require babel and webpack plugins to work - react-imported-component don't need anything from webpack, it's bundler independent, and provides a babel macro, the only thing which works out of the box with CRA.

Just use react-imported-component/macro, and call it a day

import {imported} from "react-imported-component/macro";
imported(()=>import('./aGoodDay'));

Bundler independency

This means again 3 different things:

  • it's parcel bundler compatible, or rollup, or systemjs. It does not matter what bundler you use - it would work.
  • it's react-snap - compatible. "Usage tracking" never stops, and after rendering your page in a headless browser you might ask it - "which chunks you need to render the same again", and be given a list. As well it works with unmanaged "real" imports. It basically does not matter.
  • it was not so efficient as code splitting solutions with more deeper integrations, so they could flush used chunks during SSR. And, as I said - it was.

Loading orchestration

The first way to make script rehydration is to make the loading process more efficient. imported-component provides a separate entry point - /boot to kick off initialization process before the main script evaluation, thus load deferred scripts a bit early. A great solution for CRA or Parcel, where you might not know real names of chunks files(without extra plugins installed) to inline into HTML.

import "../async-requires";
import {injectLoadableTracker} from "react-imported-component/boot";
// ^ just 1kb

// injects runtime
injectLoadableTracker('importedMarks');


// give browser a tick to kick off script loading
Promise.resolve().then(() =>
  Promise.resolve().then(() => { 
    // the rest of your application
    // imported with a little "pause"
    require('./main')
  })
);

This works quite simple and interesting - you js got parsed, got evaluated, but not fully executed. Then imported will trigger importing loading the required chunks, just calling imports it shall call. Then your application will continue the execution.

Let me provide two lighthouse snapshots(4x slowdown, 3Mb JS bundle) to explain the idea:

  • requiring main in a promise, as in the example above full script evaluation
  • not requiring main in a promise, as in the example above (just moved it to the catch, all code still in the bundle) boot bundle

Or you will more believe in Profiler flame graphs?

first chunk
Notice small "time box" on the left, and microtasks given to browser, to kick off network stuff on the right.

full time

And notice how small was that "time box".

This makes all those things, like script preloading and prefetching, not as important - it would be not so bad even without them. So you might have good SSR with CRA out of the box.

Deep Webpack integration

However, the best results require a more fine-grained approach. And another change for v6 - a separate package for webpack integration - will be able to help. Named quite obviously:

GitHub logo theKashey / webpack-imported

πŸ“stats-webpack-plugin and πŸ’©webpack-flush-chunks had a baby!

webpack-imported

We'll get your asses imported in a right way.

πŸ“ stats-webpack-plugin and πŸ’©webpack-flush-chunks had a baby!

Server side API

Webpack plugin

const {ImportedPlugin} = require('webpack-imported')
module.exports = {
  plugins: [
    new ImportedPlugin('imported.json')
  ]
};

This will output imported.json as a one of the emitted assets, with all the information carefully sorted.

Stat.json

If you have only stat.json generated somehow you can convert into into "imported" format

import {importStats} from "webpack-imported"
import stats from 'build/stats.json'
const importedStat = importStats(stats);

SSR API

  • importedAssets(stats, chunks, [tracker]) - return all assets associated with provided chunks. Could be provided a tracker to prevent duplications between runs.
  • createImportedTracker() - creates a duplication prevention tracker
import {importedAssets} from "webpack-imported"
import importedStat from "build/imported.json"; 
…

A webpack plugin to gather the data, and clientside API, including React binding, to handle everything out of the box.

import importedData from 'build/imported.json';

<WebpackImport
   stats={importedData}
   chunks={getMarkedChunks(marks)}
   publicPath={importedData.config.publicPath}
   crossOrigin={CDN_ANONYMOUS}
/>

CSS

And CSS is also handled in the best possible way - with critical style extraction working out of the box for a plain CSS files.

GitHub logo theKashey / used-styles

πŸ“All the critical styles you've used to render a page.

used-style


Get all the styles, you have used to render a page

Build Status NPM version

Bundler and framework independent CSS part of SSR-friendly code splitting

Detects used css files from the given HTML, and/or inlines critical styles Supports sync or stream rendering.

Code splitting

This is all about code splitting, Server Side Rendering and React, even if React has nothing to do with this library.

Code splitting is a good feature, and SSR is also awesome, but then you have to load all the used scripts on the client, before making a page alive Everybody is talking not about .css, but only about .js.

That's done, in a different ways. That's not a big task, as long as the usage of code splitted blocks is trackable - you are using it, and components defined inside.

CSS is harder - you might just use random classes and what next? You are…

Streaming

All used libraries have Streamig-friendly API, and able to provide the best TTFB you are looking for.

Why Why Why

If you are looking for answers Why To Code Split, What to Code Split and When to Code Split, as well as how to optimize JS or CSS delivery.

Conclusion

  • hooks API
  • loading modules
  • loading components​
  • with server-side tracking
  • babel macro support
  • not bound to webpack, however best of class support for a deeper browser integration
  • build just for you!

πŸ‘‰ https://github.com/theKashey/react-imported-component

Discussion (4)

Collapse
marais profile image
Marais Rossouw

Great read mate! Think you can do a "How to read profiler flame graphs" article as well? Super keen to see if i can leverage this for relay's operationLoader, where it needs an api to async load something, and then also synchronously. Ie, check if the module has already been resolved and downloaded, and if so return it statically, alleviating the need for the promise overhead. In certain cases, this could mean huge perf improvements.

Collapse
thekashey profile image
Anton Korzunov Author

πŸ˜… that's probably a missing piece.
one does not simply

Collapse
lineldcosta profile image
lineldcosta

Hello,

Any sample repo available which uses react-imported-component.

Thanks,

Collapse
thekashey profile image
Anton Korzunov Author