⚛️ Inside the Code Split
Tags: react, codesplitting, lazy
Published: 2019-10-04 01:21:43

---
title: ⚛️ Inside the Code Split
published: true
description: technical details about the implementation of different libraries
tags: react, codesplitting, lazy
cover_image: https://thepracticaldev.s3.amazonaws.com/i/mueilw1gqyt726g4sirh.jpg
series: A split second later 
---

...the previous part was about theoretical aspects behind code splitting, and now it's time to jump into technical details.

> Stop! What are you going to tell us about? `React.Lazy`?

Well, React itself provides the only one way to code split - `React.lazy`. And it replaces a dozen other OSS solutions, existed before it. Have you ever wonder __Why__?

## What makes Lazy so special?

In the beginning, there was a Component, and the Component has a State. In terms of code splitting it was `"Loading"`, `"Loaded"`, or `"Error"` states. And everything was good, except it was a `local state`, this was `this.state`.

> It might sound link a normal state-full component, but this little implementation detail was creating... 🌊waves🌊

So - once you will nest one _loadable_ inside another - you will see a __loading spinner__ from the top component, and then from the nested one. I could not remember the _official_ name of the problem, but it was something like __Death By Thousand Flickering Spinners__. A terrible thing, and very popular.

And `lazy` was created to fix it, although haven't - but `Suspense` did.

`Suspense` played the role of a single _boundary_ which shall display _something_ till _anything_ inside is not ready to be displayed. Suspense is no more than a boundary of _communication protocol_ (where the "protocol" is nothing more than throwing Promises).

> So, what `lazy` did? - it gave us more stable "boundaries", and removed the Flickering Spinners Threat. Our applications were saved yet again.

## What's the problem with Lazy?

Well - the interface. `Lazy` is not compatible with SSR, and, well, with tests, __by design__. And the design is following:
- having `React.lazy(() => import('./something'))
- execute `() => import('./something')`
- (throw the promise up to the `Suspense`)
- once resolved - render Lazy with resolved information.

To explain why this simple sequence, which works just perfectly in runtime, is not the best option for test/SSR, I have to ask you one question - _"Did you ever wonder - why `lazy` would not repeat the cycle again and again?"._ "What" will _monitor_ the _fulfilment_ of a given promise? 

Long story short - `lazy` itself, [inside the object](https://github.com/facebook/react/blob/56636353d8a0bf51c0eee1bbef004c90d6788999/packages/shared/ReactLazyComponent.js#L44) returned by `React.lazy`. `const Lazy = React.lazy(...)` is not only a _Component_ - it's also a _State_. Well, `typeof Lazy === "object"`, JFYI.

>So `lazy` is a local state variable from one point of view. Or component with a _static_ state from another.

And what this point and SSR have in common? Let's call that _something_ - a __synchronicity__. 
How to compact __4__ steps above into __one__? As long as _asynchronous_ rendering is absolutely 100% normal for Client-Side Render - that's (yet) absolutely not acceptable for the Server Side Rendering (or tests).

> Basically you have to call "renderToString" multiple times, until all `lazy` components would be __resolved prior to the rendering__.

Is there any good way to handle `lazy` on SSR? Well, of course, they are:
- it's `synchronous thenables`. Ie _thenables_(a base interface for a Promise, just `.then`), which [don't have to "wait"](https://github.com/facebook/react/pull/14626), and resolves _ synchronously_, giving React ability to instantly use then. (but that's not how Promises were supposed to work)
- already resolved ones. Does not matter why, and without any explanation for the "how". [Merged in React](https://github.com/facebook/react/pull/16383) just a month ago and not yet (16.9) published (and not yet 16.10 documented). 

> And this is exactly the way "react-loadable" was working all this time.

However - even if these two abilities are making `lazy` more or less compatible with (synchronous) testing infrastructure - you may manually _"resolve"_ lazy components before the render (however no interface, like `.preload` was exposed), - it still is not compatible with Server Side Rendering. Well, by design.

# Server Side Rendering?

The problem with SSR is a `hydrate` function - you have to load "everything you need", before rendering on the Client _"the same picture" you have just rendered on the Server_.
- piece a cake - you have to load everything you need do to it, like all chunks.
- piece a cake - you have to know all the chunks you have to load
- piece a cake - you have to track all the chunks you have used
- piece a cake - you have to track all the components you have used during the render and their connections to the chunks...
- don't forget about styles, by the way

🤷‍♂️ Not a big deal, probably 😅

And then, having a list of _things to load_, you have to understand what you actually have loaded them before rendering(hydrating) your App. Like providing `onload` callback to all the places... Not a big deal, again, _probably_ 🤔.

So it's all about _gathering_, _tracking_, _dehydration_ and _hydration_ of "what is needed to render application in some specific state".

While all "lazy loading" solution has almost the same interface, and doing almost the same job - they are managing this moment quite differently.

# So
So let's go thought a few libraries and check how they are doing "that":

### React.lazy(() => import('./a'))
[React.lazy](https://reactjs.org/docs/code-splitting.html) - the "official" component. Easy to use, and paired with `Suspense` and `ErrorBoundary` to handle loading or error cases. 

### reactLoadable(() => import('./a'))
[React-Loadable](https://github.com/jamiebuilds/react-loadable) - yet most popular solution. Has integrated Loading and Error states, with a build-in throttling. Does not support Suspense support, but supports `Import.Maps`.

### loadable(() => import('./a'))
[loadable-components](https://github.com/smooth-code/loadable-components) - SSR friendly solution currently recommended by React. Comes in a form of 4 packages under `@loadable` namespace and has the most detailed information about usage. Supports both Loading/Error components and Suspense.

### `imported(() => import('./a'))` 
[react-imported-component](https://github.com/theKashey/react-imported-component) - solution closer to `@loadable` by interface, and `react-loadable` by technical implementation. The only one(today) build with hooks, and with hooks API exposed to the client side. And, well, I build this guy 👨‍🔬.


***


So, you did `XXX(() => import('./a')`. What would happen next?

### How `lazy` is doing it
Q: Does it do something special?
A: It does not.

Q: It is transforming the code?
A: It does not. `lazy` does not require any babel/webpack magic to work.

Q: What would happen if you request not yet known component?
A: It will call an `import function` to resolve it. And _throw a promise_ just after to _communicate_ - I am not ready.

Q: What would happen if you request already _known_ component?
A: `Lazy` remembers what was loaded, and if something was resolved - it's resolved. So nothing happens - it just renders the Lazy Component.

Q: What would happen on SSR? 
A: It will render all "ready" components, and completely fail in all other cases. However, next run it would work for just requested, and just resolved component, and fail for the following, not _known_ ones. So - it might work, especially with "preheating", but unpredictable.

Q: What could be in the `importer` function
A: Only something resolved to es6 `default`, which is usually a real `dynamic import` called for a module with a default import. However - you may "resolve" it in a way you need - it's just a Promise.

***

### How `react-loadable` is doing it?
Q: Does it do something special?
A: Jump in!

- SSR tracks all used components
- SSR maps components to `chunks`
- SSR sends these `chunks`, as well as their `ids` to the client
- Browser loads all `script` tags injected into HTML
- Every `script` may include `loadable(something)` inside
- Once called - `loadable` adds itself into "known loadables"
- Once everything is loaded, and `preloadReady` is called, `react-loadable` goes thought all "known loadables" and if it seems to be loaded (chunkId is present in `webpack modules`) - calls `init`, effectively preloading (`lodable.preload` does the same) your component
- once all promises are resolved - __you are ready__

> In other words - it "preloads" all "loadables" which could be preloaded, ie both parts - loadable and what it's going to load - are present.

Q: It is transforming the code?
A: Yeah. It does not work (on SSR) without the babel plugin. Plugin's job is to find `import` inside `Loadable` and replace it by an object, containing some webpack specific module resolution things, hepling loadable do the job.

Q: What would happen if you request not yet known component?
A: It will call provided `import function` to resolve it

Q: What would happen if you request already known component?
A: It remembers what it was loaded, and acts like `lazy` - just ready to use.

Q: What would happen on SSR? 
A: `react-loadable.preloadAll` will preload __ALL__ loadables, so they would be ready when you will handle the first request. Without calling this function everything would be broken. However - with calling it everything also might be broken, as long as not all the code should, and could be executed at the Server (and again - it will load EVERYTHING "loadable")

Q: What could be in `importer function`
A: `dynamic import` with any transformation applied(`.then`), as well as `Loadable.map` with any async code inside.

Q: What about bundler integration
A: Provides webpack plugin to read module -> chunk mapping from `stats`, and uses it to map modules to chunks.

***

### How `loadable-components` is doing it?
Q: Does it do something special?
A: Jump in!

- SSR tracks all used components
- SSR maps components to `chunks`
- SSR sends these `chunks`, as well as their `ids` to the client
- Browser loads all `script` tags injected into HTML
> absolutely the same as `react-loadable`
- `Loadable-components` react on every webpack chunk loaded (via webpack plugin), and checks are all requested chunks are loaded.
- Once all are loaded - __you are ready__.

> It does not call the "real" `importer function`, and it is [an issue](https://github.com/smooth-code/loadable-components/issues/400).

Q: It is transforming the code?
A: Yeah. It does not work (on SSR) without the babel plugin. Plugin's job it to find `import` inside `loadable`(just matching the name) and replace it by an object, containing some webpack specific module resolution things. Plus it hooks into `webpack` and [changes `jsonp` callback](https://github.com/smooth-code/loadable-components/blob/master/packages/webpack-plugin/src/index.js#L76) for modules, acquiring visibility over and control of modules loading process.

Q: What would happen if you request not yet known component?
A: `loadable-component` will check `isReady`, which will check the existence of required modules in `webpack cache`, and `requireAsync`(the `import function`) in case it is not.

Q: What would happen if you request already known component?
A: `loadable-component` will call `isReady`, which will check the existence of required module in the webpack cache, and `requireSync` in case it is (call `requireAsync` if not).

Q: What would happen on SSR? 
A: All components would be always `isReady` and always use `requireSync`, which is just a common `nodejs require`.

Q: What could be in `importer function`
A: Only `dynamic import` and nothing more, as long as only "module name" would be used later.

Q: What about bundler integration?
A: Provides webpack plugin to read chunks to assets mapping from `stats`, and uses it to render the right assets during SSR.

***

### How `react-imported-component` is doing it?
Q: Does it do something special?
A: Jump in!

- SSR tracks all used components
- SSR maps components to `marks` - a `crc32` of the text inside `import`
- CLI extracts all `import`s in your code into `async-requires`, like [Gatsby does](https://www.gatsbyjs.org/docs/how-code-splitting-works/#cacheasync-requiresjs)
- SSR sends these `marks`, as well as `async-requires` to the client
- Browser loads all `script` tags injected into HTML
- Imported finds the similarity all known `marks` in `async-requires` and calls real `importers`
- Once all are loaded, and nothing more is pending - __you are ready__.

Q: It is transforming the code?
A: Yeah. It does not work (on SSR) without babel plugin or babel macros. Plugin job it to find all `import`s and inject a `mark` - `/*imported-XXXX-component*/` inside it. Nothing more.

Q: What would happen if you request not yet known component?
A: It will call an `import function` to resolve it

Q: What would happen if you request already known component?
A: It remembers what it was loaded, and acts like `lazy` - just ready to use

Q: What would happen on SSR? 
A: All `imports`, except specially marked ones, would be automatically executed if the server environment is detected. By the time `express` would handle the first request - they would be ready. (you should await for a special function in case of Lambda)

Q: What could be in `importer function`
A: Anything you want, but only requests with a `mark` inside would be properly tracked.

Q: What about bundler integration
A: Provides a helper to map `mark` to `chunk` or `module name`. React-imported-component is actually "bundler", and "environment" independent, and support for more _tight_ integration with your bundler is handled by another package. 

However, as long as the only thing `imported` cares about is a "mark" - it does need any real "bundler" integration, while other SSR friendly solution could not like without it. This make is both CRA compatible(thanks to babel macro), and [react-snap](https://github.com/stereobooster/react-snap) (puppeteer based prerendering) compatible.

***

# But I don't need SSR!

The simple proposition, and the wrong one.

Try to get me right - you might not need SSR, but what is SSR in terms of code splitting, and in terms of this article?
Well, nothing more than a _guidance_, _help_, _instruction_ and _prediction_ of actions to be made before `hydrate` to make your App be able to render the final picture faster.

Fun fact - using code splitting it's really super easy to make things _worse_, and make everything much slower, not faster - loading waves, network underutilization, chunks waiting for other chunks to be loaded first...

With SSR you might render your app much faster - at SSR side all scripts are already loaded, and there is a zero-latency to the backend - and by rendering something on a server you might get information how to prepare frontend to do the same.

Question for you - do you really need SSR for this? Well, let me be honest - it's much _safer_ and much _maintainable_ to use SSR, but it's not required.

Let's imagine you have a site, which serves almost the same, but still different pages for `cats` and `dogs`.

- you will have two `Routes`, one for `cats` and one for `dogs`, and you will load bundle _behind the route_ only then that route would be required (that's how code splitting usually works).

- but then you will have the same page, like `:pet/owner` for the _pet-owner-interface_, also code split, which would be loaded only when hit, and only then the parent `cat`(or `dog`) chunk is loaded, and used to render `:pet/owner` route.

- in "normal" application, with dynamically loaded `i18n` and so on you will face many "waves of loading" of this, greatly delaying the final rendering. Load language, then `:pet` route, then `:pet/owner` route, then something else, there is always something _extra else_...

Would SSR help here? Of course - it will give an instruction to follow, and remove _waving_ at all.

Do you need SSR to solve it? Well, nothing stops you from _predicting_ and _prefetching_ necessary `data` and `chunks` outside of `Route`, outside of `React`, and even outside of your `App`.

> Putting all code splitting "decisions" into "Components" is a big mistake - it's deferring all  "decisions" till the render time. Code spitting only "Components" is also a mistake.

While `React.lazy` could load only "Components", `loadable-components` provides [loadable.lib](https://www.smooth-code.com/open-source/loadable-components/docs/library-splitting/), which would return _a library_ via `renderProps` API, and there is the same helper for [react-loadable](https://github.com/theKashey/react-loadable-library), plus `react-imported-component` provides just an [useImported](https://github.com/theKashey/react-imported-component#hook) hook, which gives you the ability to load whatever you want, whenever you want.

## As a conclusion​

Code splitting is a complex, even _multidimensional_ thing - it starts as flexible boundaries between modules, continues with loading orchestration, with actions you have to do sooner(like prefetching), or later (like deferring side effects), with tracking of actions made and __must__ end with something clearly better than then initial unsplit solution.

Look like it's time to move to the next step - optimising JS delivery.