Plugins & accounts
Plugins loaded at runtime or defined inline, authenticated data via internet accounts, and the web worker RPC.
External plugin
Plugins load at runtime from a URL — the model JBrowse Web uses for
community-published plugins. loadPlugins fetches the bundles and returns one
{ plugin, definition } record each; hand those to createViewState as
plugins.
Pass the records through unchanged rather than mapping to p.plugin. The
definition is the plugin’s URL, and that is what the
RPC worker uses to load its own copy. Without it
the plugin is registered on the main thread only, and a track that needs it
fails inside the worker with an unknown-type error naming nothing about the real
cause.
loadPlugins is async, so run it in an effect and render the view once it
resolves. For plugins you author or npm install, pass the class directly — see
inline plugins. The
plugin store lists what’s published.
View source — 89 lines
import { useEffect, useState } from 'react'
import { ErrorBanner } from '@jbrowse/core/ui'
import {
JBrowseLinearGenomeView,
createViewState,
destroyViewState,
loadPlugins,
} from '@jbrowse/react-linear-genome-view2'
import type { ViewModel } from '@jbrowse/react-linear-genome-view2'
// Building the engine yourself means owning its lifetime: React unmounting this
// component does not stop the engine's RPC worker threads or its autoruns, so
// the effect below destroys whatever it built. That is not just tidiness —
// React StrictMode mounts, unmounts and mounts again in development, so without
// it every page visit leaves a whole worker pool behind.
export default function WithExternalPlugin() {
const [error, setError] = useState<unknown>()
const [viewState, setViewState] = useState<ViewModel>()
useEffect(() => {
// one box rather than two `let`s, because the cleanup below assigns from a
// separate call and the compiler's narrowing doesn't see through that
const mount = {
unmounted: false,
engine: undefined as ViewModel | undefined,
}
void (async () => {
try {
const plugins = await loadPlugins([
{
name: 'UCSC',
url: 'https://unpkg.com/jbrowse-plugin-ucsc@^1/dist/jbrowse-plugin-ucsc.umd.production.min.js',
},
])
// the fetch can land after this effect was already torn down (in
// StrictMode it usually does); building an engine now would leave one
// that nothing destroys
if (mount.unmounted) {
return
}
const state = createViewState({
assembly: {
name: 'hg19',
aliases: ['GRCh37'],
uri: 'https://jbrowse.org/genomes/hg19/fasta/hg19.fa.gz',
refNameAliases: {
uri: 'https://s3.amazonaws.com/jbrowse.org/genomes/hg19/hg19_aliases.txt',
},
},
// pass the records through unchanged: each pairs the plugin class
// with the definition it was loaded from, and that definition is what
// lets the RPC worker load the same plugin on its side
plugins,
tracks: [
{
type: 'FeatureTrack',
trackId: 'segdups_ucsc_hg19',
name: 'UCSC SegDups',
assemblyNames: ['hg19'],
adapter: { type: 'UCSCAdapter', track: 'genomicSuperDups' },
},
],
location: '1:2,467,681..2,667,681',
})
state.session.view.showTrack('segdups_ucsc_hg19')
mount.engine = state
setViewState(state)
} catch (e) {
setError(e)
}
})()
return () => {
mount.unmounted = true
if (mount.engine) {
destroyViewState(mount.engine)
}
}
}, [])
return error ? (
<ErrorBanner error={error} />
) : !viewState ? (
<div>Loading...</div>
) : (
<JBrowseLinearGenomeView viewState={viewState} />
)
}Inline plugins
Plugins extend the view with new track types, adapters, renderers, view types
and menu items. The simplest form: define a Plugin subclass in your own source
and pass the class in plugins. An npm-installed plugin looks identical — you
import the class and pass it the same way.
Plugins can also be loaded from a URL at runtime. If you enable the web worker RPC, a plugin has to be registered in the worker as well as the main thread. Authoring is covered in the plugin development guide.
View source — 64 lines
import Plugin from '@jbrowse/core/Plugin'
import { extendViewType } from '@jbrowse/core/pluggableElementTypes'
import {
JBrowseLinearGenomeView,
useCreateViewState,
} from '@jbrowse/react-linear-genome-view2'
import type PluginManager from '@jbrowse/core/PluginManager'
class HighlightRegionPlugin extends Plugin {
name = 'HighlightRegionPlugin'
install(pluginManager: PluginManager) {
extendViewType(pluginManager, 'LinearGenomeView', stateModel =>
stateModel.extend(self => {
const superRubberBandMenuItems = self.rubberBandMenuItems
return {
views: {
rubberBandMenuItems() {
return [
...superRubberBandMenuItems(),
{
label: 'Console log selected region',
onClick: () => {
const { leftOffset, rightOffset } = self
console.log(
self.getSelectedRegions(leftOffset, rightOffset),
)
},
},
]
},
},
}
}),
)
}
configure() {}
}
export default function WithInlinePlugins() {
const state = useCreateViewState({
assembly: {
name: 'volvox',
uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
},
plugins: [HighlightRegionPlugin],
tracks: [
{
type: 'FeatureTrack',
trackId: 'volvox_gff3',
name: 'Volvox genes',
assemblyNames: ['volvox'],
adapter: {
type: 'Gff3TabixAdapter',
uri: 'https://jbrowse.org/code/jb2/main/test_data/volvox/volvox.sort.gff3.gz',
},
},
],
location: 'ctgA:1105..1221',
})
return <JBrowseLinearGenomeView viewState={state} />
}Internet accounts (authentication)
Internet accounts override fetch per track. The common case is a bearer
token for protected files — Google Drive, signed-URL S3, an internal endpoint
behind auth. Any track whose file locations carry
internetAccountId: 'manualGoogleEntry' routes through the matching account,
which prompts for a token and adds Authorization: Bearer <token> to each
request.
DropboxOAuthInternetAccount and GoogleDriveOAuthInternetAccount are not
supported in the embedded LGV — they need app-level control over redirects and
popups that only full JBrowse Web has. Run the OAuth flow in your host app and
pass the resulting token to ExternalTokenInternetAccount.
Despite the name these are a general fetch override: a custom account type can rewrite URLs, add caching, or proxy through your backend. Slots: ExternalTokenInternetAccount, HTTPBasicInternetAccount.
View source — 40 lines
import {
JBrowseLinearGenomeView,
useCreateViewState,
} from '@jbrowse/react-linear-genome-view2'
export default function WithInternetAccounts() {
const state = useCreateViewState({
assembly: {
name: 'volvox',
uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
},
tracks: [
{
type: 'QuantitativeTrack',
trackId: 'google_bigwig',
name: 'Google Drive BigWig',
assemblyNames: ['volvox'],
adapter: {
type: 'BigWigAdapter',
bigWigLocation: {
locationType: 'UriLocation',
uri: 'https://www.googleapis.com/drive/v3/files/1PIvZCOJioK9eBL1Vuvfa4L_Fv9zTooHk?alt=media',
internetAccountId: 'manualGoogleEntry',
},
},
},
],
location: 'ctgA:1105..1221',
internetAccounts: [
{
type: 'ExternalTokenInternetAccount',
internetAccountId: 'manualGoogleEntry',
name: 'Google Drive Manual Token Entry',
description: 'Manually enter a token to access Google Drive files',
tokenType: 'Bearer',
},
],
})
return <JBrowseLinearGenomeView viewState={state} />
}Web worker RPC
By default all parsing and rendering runs on the main thread, which hitches on
large alignments datasets. A makeWorkerInstance factory moves it off. Under
Vite/ESM, construct the worker from the package’s ?worker entry; under
webpack/CRA, import the prebuilt
@jbrowse/react-linear-genome-view2/esm/makeWorkerInstance instead.
import RpcWorker from '@jbrowse/react-linear-genome-view2/esm/rpcWorker?worker'
createViewState({ assembly, tracks, makeWorkerInstance: () => new RpcWorker() })
It is off by default only because of bundler requirements — enable it whenever your toolchain allows:
- webpack: set
output.publicPath: 'auto'so workers resolve their own URL (guide). - Vite and other ESM bundlers: handled natively.
The worker is a separate JavaScript realm with its own plugin registry, so a
plugin contributing anything that runs there
— an adapter, usually — must be registered there too. It loads its own copy from
the URL in the definition each loadPlugins record carries, which is why
those records are passed through unmapped.
One caveat: a UMD plugin loads in the worker via importScripts, which
module workers don’t support. A Vite build with worker.format: 'es' therefore
can’t load UMD plugins worker-side; a classic worker (what the prebuilt
makeWorkerInstance produces) can. ESM plugins are unaffected. The rpc config
block is RpcOptions.
View source — 30 lines
import {
JBrowseLinearGenomeView,
useCreateViewState,
} from '@jbrowse/react-linear-genome-view2'
// Vite/Astro apps construct the RPC worker with Vite's `?worker` suffix. (With
// a webpack/CRA setup you'd instead import the package's prebuilt
// `@jbrowse/react-linear-genome-view2/esm/makeWorkerInstance`.)
import RpcWorker from '@jbrowse/react-linear-genome-view2/esm/rpcWorker?worker'
export default function WithWebWorker() {
const state = useCreateViewState({
assembly: {
name: 'volvox',
uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
},
tracks: [
{
trackId: 'volvox_gff3',
name: 'Volvox genes',
uri: 'https://jbrowse.org/code/jb2/main/test_data/volvox/volvox.sort.gff3.gz',
},
],
init: { loc: 'ctgA:1105..1221', tracks: ['volvox_gff3'] },
// supplying makeWorkerInstance is enough — the RPC default driver
// switches to WebWorkerRpcDriver automatically (no defaultDriver config
// needed)
makeWorkerInstance: () => new RpcWorker(),
})
return <JBrowseLinearGenomeView viewState={state} />
}