WebWorker Guide
While isomorphic-git
tries not to block the main thread, it still does on occasion.
This can cause your webapp to stutter or even freeze up briefly!
To achieve buttery smooth performance, you'll eventually want to move all your isomorphic-git
usage off of the main thread.
Introduction
WebWorkers live in a separate operating system thread from the main JS thread and communicate using the worker.postMessage() method. Code in the worker thread does not have access to heap objects in the main thread. So all objects sent via postMessage need to be serialized before being sent. Technically, this is done using the structured clone algorithm.
What kinds of Objects can be sent using postMessage
? Functions cannot. Therefore objects with methods cannot.
JSON objects can. Date
objects and RegExp
objects can. Also Uint8Array
objects and Map
objects and Set
objects.
So basically the types of objects you can send to a worker are a superset of JSON but a subset of full JavaScript objects.
For convenience, throughout the rest of this document I will refer to objects that can be serialized via the structured clone algorithm as "cloneable" objects.
Because of foresight and planning, most isomorphic-git
functions are:
- async
- have a single, cloneable object as their argument.
The plugins (excepting fs
plugin) also follow this pattern.
This greatly simplifies the job of mapping isomorphic-git
calls to a remote procedure call (RPC) API using postMessage
.
If you already have a WebWorker in your project and a preferred way to communicating with it, then mapping isomorphic-git
calls is easy.
If you don't already have a preferred WebWorker RPC solution, then the easiest way is to use the MagicPortal which was designed specifically for isomorphic-git
so it's a perfect match!
The examples below will use MagicPortal.
Implementation
In the main thread, we acquire a proxy isomorphic-git we can use.
// main.js
;(async () => {
let worker = new Worker("./worker.js")
const portal = new MagicPortal(worker)
// Acquire it from the worker
const git = await portal.get('git')
})();
In the worker, we expose isomorphic-git
to the main thread.
// worker.js
importScripts([
"https://isomorphic-git.org/js/browserfs.js",
"https://unpkg.com/isomorphic-git",
"https://unpkg.com/magic-portal",
])
BrowserFS.configure({ fs: 'IndexedDB', options: {} }, function (err) {
if (err) return console.log(err)
// Initialize isomorphic-git
const fs = BrowserFS.BFSRequire('fs')
git.plugins.set('fs', fs)
// Expose it to the main thread
const portal = new MagicPortal(self);
portal.set('git', git)
})
Plugins
All plugins have to be setup in the worker, because the plugin functions have to live in the same thread as isomorphic-git
.
Therefore, if your plugin needs to interact with the main thread, you have to ALSO proxy your plugin!
// main.js
const credentialManager = {
async fill({ url }) {
let username = window.prompt("Username:")
let password = window.prompt("Password:")
return { username, password }
},
async approved({ url, auth }) {
return
},
async rejected({ url, auth }) {
window.alert('Authentication rejected')
return
}
}
portal.set('credentialManager', credentialManager, {void: ['approved', 'rejected']})
// worker.js
let credentialManager = await portal.get('credentialManager')
git.plugins.set('credentialManager', credentialManager)
Example
Here is a complete example that runs git in a WebWorker. The worker exposes 'git' to the main thread, and the main thread exposes the 'emitter' and 'credentialManager' plugins (which need access to the DOM) to the worker.
Gist: https://gist.github.com/wmhilton/c311070034bc8d99e3e82d97636d0fd8