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:
Ready? Jump in!
theKashey / react-imported-component
βοΈπ¦Bundler-independent solution for SSR-friendly code-splitting
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.
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 - not requiring
main
in a promise, as in the example above (just moved it to thecatch
, 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:
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 atracker
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.
theKashey / used-styles
πAll the critical styles you've used to render a page.
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.
βοΈ Code splitting - What, When and Why
Anton Korzunov γ» Oct 4 γ» 12 min read
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!
Top comments (4)
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.
π that's probably a missing piece.
Hello,
Any sample repo available which uses react-imported-component.
Thanks,
There are 6 examples: github.com/theKashey/react-importe...