Before you go, check out these stories!

0
Hackernoon logoReact and Code Splitting made easy by@antonkorzunov

React and Code Splitting made easy

Author profile picture

@antonkorzunovAnton Korzunov

Code splitting always was a thing. From the times, when you were adding tons of different script tags, till RequireJS was emerged.

Code splitting always was here, were supported, used, and let’s skip the question about “how” it was supported and used. Anyway — bringing the code from overseas and performing a customs clearance — that is discussable.

Importing components….

So, let’s skip the history lesson and warp directly to the start of an action.

Webpack 2 comes out and gives an import.

Code splitting become a thing (yet again) and we got bunch of “React-loaders” next week. And the first question you better ask is simple

Why do I need “loader”?

So, you should be ok with having npm package or React component just for everything. Why don’t to have a special component for a loader?

The simplest code for such component is short and simple.

What else? This component is actually does some state handling, and provision your application with code splitting. You can use it as is.

Just add some error handling, ability to retry loading, precaching, testing, displaying something while you load the stuff, or fail to do it – and you will get a real react component. The thing you should probably write once, or even better don’t spend your time and use existing solution — some library from an open source.

Ok then. How to pick that library?

If you will go to github and search for a library – you will found a few of them, and them will have to pick the one you gonna to use.

What’s the difference? How to choose?

First of all — ignore the stars count, commits, activity and so on. “Loader” is not a rocket, as we just found – this is 22 lines of a sparse code.

The Second — look on API. You are going to use it, and API should “fit” you. Actually there is 2 “forms” of API.

1. loadable(() => import('./Home'))
2. loadable({ loader: () => import('./Home') })

You can found the first one in react-imported-component, react-universal-component and loadable-components, while the second one — react-loadable and react-async-component.

There is also a slight difference in “extra” options, you can pass into the loader

  1. Loadable-components — you can specify what to display while loading Loading, in case of Error, or how to display a Component.
const Home = loadable(() => import('./Home'), {
LoadingComponent: Loading,
ErrorComponent: ErrorDisplay,
render: ({ Component, loading, ownProps }) => {...})
});

Loadable-components also proposes to handle delays and timeouts with third party tools.

2. React-imported-component has actually the same API, just introduces additional onError property. It is better to refer to Loadable-components readme to undestand how to handle React-imported-component.

3. React-async-component yet again is the same. With a lot of interesting information asked and answered in issues.

4. React-loadable is slightly different, first accepting more that just “import” function, to be resolved by final component(ie it could handle “Maps”), mixing LoadingComponent and ErrorComponent in one, and having build in “delay” to reduce flashing of loading “spinners”.

Loadable({
loader: () => import('./WillFailToLoad'), // oh no!
loading: (props) => {
if (props.error) {
return <div>Error!</div>;
} else {
return <div>Loading...</div>;
}
}
});

5. React-universal-component also not differs a lot from all others. The only one with real dynamic dynamic imports.

const Component = universal(props => import(`./${props.page}`))
PS: this approach may have great side effects, be aware.

They all do their job well, and if you want to learn more about codesplitting itself — refer to the articles about these loaders.

PS: actually those articles are mostly about the “import” thing.

And feel free to can pick “Loader”, or even use your own one, if you are building pure front-end SPA, just cos….

Client side code splitting AND server side rendering

On face value they all have similarities, especially when dealing with client side only. However, I personally find that the real distinction between each solution becomes apparent when you are trying to execute server side rendering. — react-async-loader, issue #37

The client side code splitting is a super easy thing. The “good” client side code splitting in much more complex thing…. But you still can write everything you need in an hour, or two. And the result could be even better then all the libraries above. Yeah, writing “loader” for client side is easy.

Server side and code splitting? Huh? No one actually solved the problem.

The problem

You are rendering your application on Server, and sending the Whole html to the Client. Next, Client rehydrates the code, replacing dead html by React representation, making it alive. Easy without code splitting.

The problem #1 is — due to code splitting client does not have ALL required code to fully rehydrate server-rendered response. And the page, which were just ok-ish a second ago, got replaced by spinners, and seconds after it yet again ok-ish. It it will render as much it could, load the deferred parts, and re-render the rest. Thats how things works!

You have fucked up all the work you done on SSR, made a customer angry, or even caused seizures due to blinking content and spinning loaders.
Rule #1 — first load all “spare” parts. Only next rehydrate content to make rehydration invisible to the customer.

The problem #2 is — server side React rendering is synchronous. “Code-splitted” component are “async”hronous. If you will render them on server — you will render nothing, or “LoadingComponent”, but not the React Component you have to render. It is async, and will be ready to be rendered just after you send result to the client. Bingo! First price!

Rule #2 — async on client. Sync on server. Don’t mix.

Those problems are entangled — you have to know what you “will” load during the rendering, “did” load it, and let the client side know how to repeat.

Maybe Lyft’s universal-async-component is the cheatest one. They are using “string-replace-loader” to replace “import” by their stuff making it literally sync on server, next extracting the “used” scripts from webpack stats. 50 lines of code for everything.

React-lodable are doing almost the same, but using babel plugin to “transfer” information about used imports, and providing the sync requires next to async imports.

Universal-component went the similar and the different way. They doing “the same” passing down the “decrypted” information via babel-plugin-universal-import, but they are passing down that information “within” the “import”.

And All the libraries listed above next will access the webpack stat information, to figure out which extra scripts user have to load prior of main context.

Pros: They do the job.
Cons: webpack only, will synchronously import extra scripts before the main. Could be affected by some optimizations webpack could introduce in a future.

Loadable and async components are super different — they literraly “renders the react tree” on the server side, to provision the “async-rendered” stuff to the final result. This does not require any webpack or babel plugin, but could affect the performance of a page rendering. You have to perform a client side rendering on the client side, re-rendering everything a few times.

Next they do almost the same on front end. But there is a problem — you could import only one “level” of imports at once. If you have a async Page, which loads the async Widget — they will have first load Page, and only after it they will be able to load the Widget, and get to code to do it.

PS: Loadable-components has a babel plugin to mitigate(a bit) this problem.

Pros: Could work with any bundler, even without extra configuration
Cons: Slow async SSR, slow “wave affected” front-end rendering.

React-imported-component is something in between. It is still always async, but making SSR rendering synchronous by simple executing imports and memoizing the results before the first user could generate the page. Just a simple assumptions, that Promise will got executed before all that socket stuff, needed to accept the first client request to renderer something.

Next, it will track the usage of imports, and try to replay them before the client-side rehydration.

Pros: Did it’s job
Cons: not webpack only, not wave affected, not slow, and not gonna break in a future.

Wat?

There is one thing, I forgot to mention — HOW these loaders making “sync” requests, and understands what client have to load prior to rehydration.

They use babel, to find the “import” and use the string inside the import as a “mark”, and as a filename to require the real file. Like

const LoadableComponent = Loadable({
loader: () => import('./my-component'),
loading: Loading,
});

But! If you will just move import off that function

const loader = () => import('./my-component');
const LoadableComponent = Loadable({
loader,
loading: Loading,
});

It will break the stuff. And this is known issue. And this is issue only for SSR.

If you want to have loader as a part of UI library or UI kit, I mean — standard site wide “Loading” or “Error”, analytics, monitoring — you will be unable to wrap “the Loader” by helper function or convert into HOC. It will stop properly SSR, as long it will lose the track of used components.

Actually this is not the “common” issue:

  • react-universal-component replaces import by universalImport, enchanted by sync and async “versions” of an import.
  • react-imported-component do almost the same, replacing by “findable” wrapper.
  • universal-async-component also replaces import by their wrapper.

So — they all will be able to understand whats happening and use “indirect” imports. “The issues” is “the issue” only for react-loadable and loadable-components. And webpack v4 support for mjs files could break them both due the current realisation.

The second important thing — if you want to load module “A” — webpack may load “B”, ”C” and “D”. Due to CommonChunk, AsyncChunk, Webpack v4 AutoMagicChunking, or anything else they will invent in a future. It is not easy to undestand (and predict) all the magic from the webpack stat file, thats why due to “unexpected” ConcatenationPlugin universal-async-component might be already broken.

React-imported-component changes this. A bit. This is not good and not final solution, but may work just here just now for everyone.

  1. It casts the “import function” you provided to string, and RegExping imports inside it. So you can pass “import” from anywhere, but you still can not use complex function, as long everything except the loader function body will be invisible to imported component.
    The difference from react-universal-component and universal-async-component, which also “hacks” the import is simple — imported component does not execute the “import function” each time. Only one during the startup, keeping it async, and letting you put ANY logic inside (like a bunch of imports, awaits, resulting a new component at the end).
  2. It searches for imports across your codebase, resulting a single “lookup” file, with each and every import in your project. Next it could use it to import something by “mark”(import name) to reduce “loading waves”.

And that loading will be async. And loaded AFTER the main bundle. Is there any difference between “when” to load stuff — synchroniusly before the “main” or asynchroniusly after it? Sure the second variant will be faster, ${numberOfExtraScripts} times faster.

Pros: Could work with any bundler.
Cons: Require separate step to “extract” imports from code base. And babel.

There is a lot of loaders, so give it a try to this new one. Actually it is not new, as long it was born as a “react-hot-component-loader” for React-Hot-Loader almost a year ago. And also feel free to try all the other libraries I’ve listed above. Just because they are all different, and each of them could be a better fit for you. And all of them will get better and evolve.

PS: As react-imported-components just did (this is version 4 already)

And what about code splitting itself? Just do it! Not much. Mostly on logical, not component layers.

The key idea — load “splitted” code in the parallel with “data”. Or you will have first to load the code, which will start loading the data. We are fighting for the customer experience, not for code splitting itself.

Wanna more?

Tags

Become a Hackolyte

Level up your reading game by joining Hacker Noon now!