JBrowse 2 · Linear Genome View examples

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} />
}