Writing a no-build plugin
The following guide will provide a short tutorial on how to create a single page no-build plugin for JBrowse 2.
Pre-requisites
- you can run an instance of JBrowse 2 on the web, see any of our quickstart guides for details
- a stable and recent version of node
- basic familiarity with the command line and navigating the file system
What is the difference between a no-build plugin and a regular plugin?
A "regular" JBrowse plugin often uses our plugin template
https://github.com/GMOD/jbrowse-plugin-template which uses rollup
to compile
extra dependencies that your plugin might use.
In contrast, "no-build" plugins have no build step and can be hand edited. This can be useful for adding extra jexl config callbacks for making extra config callbacks or similar modifications.
Writing a "no-build" plugin
Example use case: Adding a jexl callback function which you can use in your config
A common method for a no-build plugin might be making a custom function that you
can use to simplify jexl
callbacks in your config. We will create a file
myplugin.js
, which will export a single class.
myplugin.js
export default class MyPlugin {
name = 'MyPlugin'
version = '1.0'
install(pluginManager) {
pluginManager.jexl.addFunction('customColor', feature => {
if (feature.get('type') === 'exon') {
return 'red'
} else if (feature.get('type') === 'CDS') {
return 'green'
}
})
}
configure(pluginManager) {}
}
Put this file myplugin.js
in the same folder as your config file, and then,
you can refer to this plugin and the custom function you added in your
config.json
.
{
"plugins": [
{
"name": "MyPlugin",
"esmLoc": {
"uri": "myplugin.js"
}
}
],
"tracks": []
}
Example use case: Adding a global menu item
Another example of a no-build plugin is to add menu items or minor extension
points. Here, we're going to add a menu item using the configure
method in the
plugin class.
myplugin.js
// ...
configure(pluginManager) {
// this is called in the web worker as well, which does not have a
// rootModel, so check for existence of pluginManager.rootModel before
// continuing
if (pluginManager.rootModel) {
// adding a new menu to the top toolbar
pluginManager.rootModel.insertMenu('Citations', 4)
// appending a menu item to the new menu
pluginManager.rootModel.appendToMenu('Citations', {
label: 'Cite this JBrowse session',
onClick: (session) => { /* do nothing for now, see below for example */ }
})
}
}
// ...
Importing with jbrequire
Because our plugin is not going to be built with any dependencies, the process for referencing external libraries is a little different.
If a package you need to use is found within the JBrowse core project, a special
function jbrequire
can provide your plugin access to these packages. Click
here
for a full list of packages accessible through jbrequire
. Using jbrequire
might look like this:
const { types } = pluginManager.jbrequire('mobx-state-tree')
which would provide the functionality of mobx-state-tree through that value.
Complete example
Example
esmplugin.js
export default class MyPlugin {
name = 'MyPlugin'
version = '1.0'
install(pluginManager) {
// here, we use jbrequire to reference packages exported through JBrowse
const { ConfigurationSchema } = pluginManager.jbrequire(
'@jbrowse/core/configuration',
)
const WidgetType = pluginManager.jbrequire(
'@jbrowse/core/pluggableElementTypes/WidgetType',
)
const { ElementId } = pluginManager.jbrequire(
'@jbrowse/core/util/types/mst',
)
const { types } = pluginManager.jbrequire('mobx-state-tree')
const React = pluginManager.jbrequire('react')
// this is our react component
const CiteWidget = props => {
// React.createElement can be used to add html to our widget component.
// We write out raw React.createElement code because JSX requires a build
// step and can't be used very easily in the no build plugin context
const header = React.createElement(
'h1',
null,
'Cite this JBrowse session',
)
const content = React.createElement(
'p',
null,
`Diesh, Colin, et al. "JBrowse 2: A modular genome browser with views of synteny and structural variation. bioRxiv. 2022.`,
)
return React.createElement('div', null, [header, content])
}
// we're adding a widget that we can open upon clicking on our menu item
pluginManager.addWidgetType(() => {
// adding a widget to the plugin
return new WidgetType({
name: 'CiteWidget',
heading: 'Cite this JBrowse session',
configSchema: ConfigurationSchema('CiteWidget', {}),
stateModel: types.model('CiteWidget', {
id: ElementId,
type: types.literal('CiteWidget'),
}),
// we're going to provide this component ourselves
ReactComponent: CiteWidget,
})
})
}
configure(pluginManager) {
if (pluginManager.rootModel) {
pluginManager.rootModel.insertMenu('Citations', 4)
pluginManager.rootModel.appendToMenu('Citations', {
label: 'Cite this JBrowse session',
onClick: session => {
// upon clicking on this menu item, we need to add and show our new widget
const widget = session.addWidget('CiteWidget', 'citeWidget', {
view: self,
})
session.showWidget(widget)
},
})
}
}
}
Then in your config you can reference it using the "esmLoc" function
{
"plugins": [
{
"name": "MyPlugin",
"esmLoc": {
"uri": "esmplugin.js"
}
}
],
"tracks": []
}
Result
With JBrowse running and the "Citation" plugin from above added to your config, your JBrowse session should look like the following:
Conclusion and next steps
Congratulations! You built and ran a single file no-build plugin in JBrowse.
Have some questions? Contact us through our various communication channels.
Footnote 1: JSX syntax
You can see from the above that writing React code is a little cumbersome as JSX syntax is not really supported in the no build plugins since JSX generally requires a build step.
If you are writing module code or writing a plugin that has dependencies, you can try our https://github.com/GMOD/jbrowse-plugin-template which has a build step, bundler, and type checking with typescript
Footnote 2: UMD vs ESM module syntax
This guide was updated in 2024 to use "ESM" modules which export a simple class in response to browsers increased support of importing pure ESM modules. However, for maximum legacy browser compatibility, you can also use "UMD" modules also
For an example of UMD, see https://github.com/GMOD/jbrowse-components/blob/76ce3660c9192f071d23e2478c756fff42ec533a/test_data/volvox/umd_plugin.js#L1-L127 (it uses a function that defines a specific global variable rather than exporting a class)