Created
July 29, 2022 14:03
-
-
Save brophdawg11/03a475e26922e09aa35ca8b5900a4fb4 to your computer and use it in GitHub Desktop.
React Router 6.4.0 Code Splitting Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Assume you want to do this in your routes, which are in the critical path JS bundle | |
<Route path="lazy" loader={lazyLoader} element={<Lazy />} /> | |
// And assume you have two files containing your actual load and component: | |
// lazy-loader.ts -> exports the loader | |
// lazy-component.ts -> exports the component | |
// We'll render the component via React.lazy() | |
let LazyActual = React.lazy(() => import("./lazy-component")); | |
function Lazy() { | |
return ( | |
<React.Suspense fallback={<p>Loading component...</p>}> | |
<LazyActual /> | |
</React.Suspense> | |
); | |
} | |
// The loader is where things get interesting, we need to load the JS chunk | |
// containing our actual loader code - BUT we also want to get a head start | |
// on downloading the component chunk instead of waiting for React.lazy() to | |
// kick it off. Waterfalls are bad! This opens up the possibility of the | |
// component chunk finishing _before_ the loader chunk + execution. If that | |
// happens we don't want to see a small CLS flicker from Suspense since the | |
// component _is already downloaded_! | |
export async function lazyLoader(...args) { | |
let controller = new AbortController(); | |
/* | |
* Kick off our component chunk load but don't await it | |
* This allows us to parallelize the component download with loader | |
* download and execution. | |
* | |
* Normal React.lazy() | |
* | |
* load loader.ts execute loader() load component.ts | |
* -----------------> -----------------> -----------------> | |
* | |
* Kicking off the component load _in_ your loader() | |
* | |
* load loader.ts execute loader() | |
* -----------------> -----------------> | |
* load component.ts | |
* -----------------> | |
* | |
* Kicking off the component load _alongside_ your loader.ts chunk load | |
* | |
* load loader.ts execute loader() | |
* -----------------> -----------------> | |
* load component.ts | |
* -----------------> | |
*/ | |
import("./lazy-component").then( | |
(componentModule) => { | |
if (!controller.signal.aborted) { | |
// We loaded the component _before_ our loader finished, so we can | |
// blow away React.lazy and just use the component directly. This | |
// avoids the flicker we'd otherwise get since React.lazy would need | |
// to throw the already-resolved promise up to the Suspense boundary | |
// one time to get the resolved value | |
LazyActual = componentModule.default; | |
} | |
}, | |
() => {} | |
); | |
try { | |
// Load our loader chunk | |
let { default: loader } = await import("./lazy-loader"); | |
// Call the loader | |
return await loader(...args); | |
} finally { | |
// Abort the controller when our loader finishes. If we finish before the | |
// component chunk loads, this will ensure we still use React.lazy to | |
// render the component since it's not yet available. If the component | |
// chunk finishes first, it will have overwritten Lazy with the legit | |
// component so we'll never see the suspense fallback | |
controller.abort(); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment