How we went from loading 30mb of JSON in 5minutes to loading it in under 1 minute with the help of web workers.
We are building an application with full offline functionality with the help of Create React App, redux-offline, and indexDB. We have service workers enabled which handle persisting all the HTML, JS, and CSS for offline use. Redux-offline helps persist our redux state to indexDB. When the app initially loads after the user logs in, we gather all the data necessary for the user to work offline. The amount of data required for this has grown over the years and we are at a point where the initial load can take upwards of 5 minutes on a good internet connection. A lot of this time is spent deserializing the roughly 30mb worth of JSON bodies from the ~20 HTTP requests. This shows up in Chrome dev tools on the network tab as "content downloading". In other words we are running into throughput capacity. More about that here: https://developers.google.com/web/tools/chrome-devtools/network/understanding-resource-timing#hitting_throughput_capacity
The low hanging fruit here is to do everything we can to reduce the amount of data loaded. We have worked hard to eliminate duplicates and only send what is needed. Now we are at a point where we need to figure out how to best handle 30mb of JSON. We plan to solve this with 2 basic solutions. 1. take advantage of multithreading via web workers. 2. being smarter about lazy loading and or give the user control of what they want to be able to do offline.
Part of the goal here is to avoid ejecting CRA. There are a few plugins mentioned below that help work with WebWorkers, they are worth checking out, but they are not a huge value add over the native implementation.
Here is a great tutorial for adding this in CRA without ejecting: https://www.newline.co/fullstack-react/articles/introduction-to-web-workers-with-react/
However, since I am using Typescript and quickly run into "no-restricted-globals" when I try to use "self". Fortunately this can be safely ignored.
self.addEventListener("message", e => { // eslint-disable-line no-restricted-globals
Here is the gist:
App.tsx
componentDidMount() {
this.worker = new WebWorker(worker);
setTimeout(() => {
console.log('posting fetch users');
this.worker.postMessage('Fetch Users');
}, 3000);
this.worker.addEventListener('message', (event: any) => {
console.log('recieved message', event);
});
workerSetup.ts
This is the key to avoiding ejecting the CRA app.
export default class WebWorker {
constructor(worker: any) {
const code = worker.toString();
const blob = new Blob(['('+code+')()']);
return new Worker(URL.createObjectURL(blob));
}
}
worker.ts
declare function postMessage(message: any): void;
export default () => {
self.addEventListener("message", e => { // eslint-disable-line no-restricted-globals
if (!e) return;
const users = [];
const userDetails = {
name: "Jane Doe",
email: "[email protected]",
id: 1,
dateJoined: 0
};
for (let i = 0; i < 1000000; i++) {
userDetails.id = i++;
userDetails.dateJoined = Date.now();
users.push(userDetails);
}
postMessage(users);
});
};
After we implemented this we also found that there was a bottleneck with how redux-persist was putting everything inside a single key in indexDB. We ended up downgrading to redux-persist v4 which out of the box stores each root reducer in a separate key in indexDB.
resources:
Browser limits for large JSON: https://joshzeigler.com/technology/web-development/how-big-is-too-big-for-json
Threads supports web workers in Node: https://github.com/andywer/threads.js
Promise and web workers: https://github.com/nolanlawson/promise-worker
Interesting example of 1GB of JSON: https://uptech.team/blog/filter-1gb-json-on-frontend-and-not-crash-browser
WebWorkers and Typescript: https://itnext.io/typescript-web-workers-with-angular-cli-6-19129b299d69
Best solution for loading a web working in React without ejecting: https://stackoverflow.com/questions/47475360/creating-a-web-worker-inside-react
Full Example of Web Workers in in React: https://www.newline.co/fullstack-react/articles/introduction-to-web-workers-with-react/