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:
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 importsconstlanguages={'en':()=>import('./i18n/en'),// it's probably a json...'de':()=>import('./i18n/de'),}// just a helper functionconstpickLanguage=(lng)=>languages[lng];// your componentconstMyI18nProvider=({lng,children})=>{// let's pick and provide correct import function for our languageconst{imported:messages={}// defaulting to empty object}=useImported(pickLanguage(lng));// set messages to the Providerreturn<I18nProvidervalue={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 :)constMoment=importedModule(()=>import('moment'));<Momentfallback="long time ago">{(momentjs/* default imports are auto-imported*/)=>momentjs(date).fromNow()}</Moment>
// or, again, translations<ImportedModuleimport={()=>import('./i18n/en')}// fallback="..." // will throw to the Suspense boundary without fallback provided>{(messages)=><I18nProvidervalue={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.
constComponent=importedComponent(()=>import('./Component'),{LoadingComponent:Spinner,// what to display during the loadingErrorComponent: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
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 runtimeinjectLoadableTracker('importedMarks');// give browser a tick to kick off script loadingPromise.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
not requiring main in a promise, as in the example above (just moved it to the catch, all code still in the bundle)
Or you will more believe in Profiler flame graphs?
Notice small "time box" on the left, and microtasks given to browser, to kick off network stuff on the right.
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:
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