Plugins
Extend the app with plugins — defined inline in your bundle, or loaded at runtime from a URL.
On this page: Embedded (inline) plugin · External plugin
Embedded (inline) plugin
Register a plugin defined inline in your code — here adding a rubber-band menu item.
Plugins extend the app 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-published plugin looks identical — you
import the class and pass it the same way.
This one adds a “console.log the selected region” item to the linear genome view’s rubber-band menu; click and drag on the ruler to see it.
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: plugin development guide.
View source — 77 lines
import Plugin from '@jbrowse/core/Plugin'
import { extendViewType } from '@jbrowse/core/pluggableElementTypes'
import { JBrowse } from '@jbrowse/react-app2'
import type PluginManager from '@jbrowse/core/PluginManager'
const base = 'https://jbrowse.org/code/jb2/main/test_data/volvox'
const assemblies = [{ name: 'volvox', uri: `${base}/volvox.2bit` }]
const tracks = [
{
type: 'AlignmentsTrack',
trackId: 'volvox_cram',
name: 'volvox-sorted.cram',
assemblyNames: ['volvox'],
adapter: { type: 'CramAdapter', uri: `${base}/volvox-sorted.cram` },
},
]
class HighlightRegionPlugin extends Plugin {
name = 'HighlightRegionPlugin'
install(pluginManager: PluginManager) {
extendViewType(pluginManager, 'LinearGenomeView', stateModel =>
stateModel.extend(self => {
const superItems = self.rubberBandMenuItems
return {
views: {
rubberBandMenuItems() {
return [
...superItems(),
{
label: 'Console log selected region',
onClick: () => {
const { leftOffset, rightOffset } = self
console.log(
JSON.stringify(
self.getSelectedRegions(leftOffset, rightOffset),
),
)
},
},
]
},
},
}
}),
)
}
configure() {}
}
// #region usePlugin
export default function EmbeddedPlugin() {
return (
<JBrowse
assemblies={assemblies}
tracks={tracks}
// the class itself, not a definition to fetch — an embedded app has no
// config.json to list plugins in
plugins={[HighlightRegionPlugin]}
views={[
{
type: 'LinearGenomeView',
init: {
assembly: 'volvox',
loc: 'ctgA:1..50000',
tracks: ['volvox_cram'],
},
},
]}
/>
)
}
// #endregionExternal plugin
Load a plugin at runtime from a URL with loadPlugins.
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, which go to createViewState as
plugins. This demo loads the UCSC plugin from unpkg and shows a UCSCAdapter
track on hg19.
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.
loadPlugins takes the same entries a JBrowse Web config.json lists under
plugins, so a fetched config is
just loadPlugins(config.plugins ?? [], { baseUri: configUrl }) — pass
baseUri so a relative plugin URL resolves against the config rather than your
app. Unlike JBrowse Web, the embedded app never fetches them for you:
createViewState is synchronous and loading a plugin is not, so run
loadPlugins in an effect and render once it resolves.
For plugins you author or npm install, pass the class directly — see
embedded plugins. The
plugin store lists what’s published.
View source — 107 lines
import { useEffect, useState } from 'react'
import { ErrorMessage } from '@jbrowse/core/ui'
import {
JBrowseApp,
createViewState,
destroyViewState,
loadPlugins,
} from '@jbrowse/react-app2'
type ViewState = ReturnType<typeof createViewState>
// loadPlugins fetches plugins at runtime from a URL (here the UCSC plugin from
// unpkg), so you don't have to bundle them. Pass the records it returns to
// createViewState unchanged — each one 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.
//
// 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 [viewState, setViewState] = useState<ViewState>()
const [error, setError] = useState<unknown>()
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 ViewState | undefined,
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
;(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
}
mount.engine = createViewState({
config: {
assemblies: [
{
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',
},
},
],
tracks: [
{
type: 'FeatureTrack',
trackId: 'segdups_ucsc_hg19',
name: 'UCSC SegDups',
assemblyNames: ['hg19'],
adapter: { type: 'UCSCAdapter', track: 'genomicSuperDups' },
},
],
defaultSession: {
name: 'External plugin example',
views: [
{
id: 'view1',
type: 'LinearGenomeView',
init: {
assembly: 'hg19',
loc: '1:2,467,681..2,667,681',
tracks: ['segdups_ucsc_hg19'],
},
},
],
},
},
plugins,
})
setViewState(mount.engine)
} catch (e) {
console.error(e)
setError(e)
}
})()
return () => {
mount.unmounted = true
if (mount.engine) {
destroyViewState(mount.engine)
}
}
}, [])
return error ? (
<ErrorMessage error={error} />
) : viewState ? (
<JBrowseApp viewState={viewState} />
) : null
}