πŸ“¦ React-Imported-Component v6

thekashey

Anton Korzunov

Posted on October 4, 2019

πŸ“¦ React-Imported-Component v6

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.

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…

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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... />
Enter fullscreen mode Exit fullscreen mode

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'));
Enter fullscreen mode Exit fullscreen mode

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')
  })
);
Enter fullscreen mode Exit fullscreen mode

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!

code splitting, prefetching, and resource management.

WebpackPlugin + ServerSide API + React Components(separate entrypoint)

Server side API

Webpack plugin

const {ImportedPlugin} = require('webpack-imported');

module.exports = {
  plugins: [
    new ImportedPlugin('imported.json')
  ]
};
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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"
…
Enter fullscreen mode Exit fullscreen mode

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}
/>
Enter fullscreen mode Exit fullscreen mode

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



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

(without any puppeteer involved)



Build Status NPM version

πŸ‘‹Version 3 migration notice: import { discoverProjectStyles } from 'used-styles/node'. That's it


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.

Read more about critical style extraction and this library: https://dev.to/thekashey/optimising-css-delivery-57eh

  • πŸš€ Super Fast - no browser, no jsdom, no runtime transformations
  • πŸ’ͺ API - it's no more than an API - integrates with everything
  • 🀝 Works with strings and streams
  • ⏳ Helps preloading for the "real" style files

Works in two modes:

  • πŸš™ inlines style rules required to render given HTML - ideal for the first time visitor
  • πŸ‹οΈβ€β™€οΈinlines style files required to render given HTML - ideal for the second time visitor (and code splitting)

Critical style extraction:

  • 🧱 will load…

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

πŸ’– πŸ’ͺ πŸ™… 🚩
thekashey
Anton Korzunov

Posted on October 4, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related