Skip to content

Creating Plugin Instance

Intro

What is a plugin? A plugin is a collection of modules that provide functionality to the Mol* UI. The plugin is responsible for managing the state of the viewer, internal and user interactions. It has been a previous point of confusion for new users of Mol* to associate the viewer part of the library with what is further referred to as the plugin. These two are closely connected in the molstar-plugin-ui module, which is the user-facing part of the library and ultimately provides the viewer, but they are ultimately distinct.

It is recommended that you inspect the general class structure of PluginInitWrapper, PluginUIContext and PluginUIComponent to better understand the flow of data and events in the plugin. A passing analogy is that a PluginContext is the engine that powers computation, rendering, events and subscriptions inside the molstar UI. All UI components depend on PluginContext.

There are 4 basic ways of instantiating the Mol* plugin.

Viewer wrapper

  • The most basic usage is to use the Viewer wrapper. This is best suited for use cases that do not require custom behavior and are mostly about just displaying a structure.
  • See Viewer class is defined in src/apps/viewer/app.ts for available methods
  • See options.ts for available plugin options
  • See embedded.html and mvs.html for example usage
  • Importing molstar.js will expose molstar.lib namespace that allow accessing various functionality without a bundler such as WebPack or esbuild. See the mvs example above for basic usage.
  • Alternative color themes can be used by importing theme/dark.css (or light/blue) instead of molstar.css

molstar.js and molstar.css sources

  • Download molstar NPM package and use the files from build/viewer diractory
  • Use jsdelivr CDN
  • <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.js" />
  • <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.css" />
  • @latest can be replaced by a specific Mol* version, e.g., @5.4.2
  • Clone & build the GitHub repository
  • This option allows for quite straightforward extension customization, e.g., not including movie export, which reduces the bundle size by ~0.5MB

Bundle size

By default, the Viewer includes all the available extensions. This increases the bundle size significantly, especially by including the mp4-export, which is responsible for almost 0.5MB of compressed bundle size. It is quite easy to reduce this bundle size by cloning the Mol* repository, editing extensions.ts and rebuilding it with npm run build:apps. The new build will be available in the build/viewer directory (the JS file you will find there is uncompressed, but your hosting setup should include automatic gzip compression, significantly reducing the size).

Alternatively, you can explore building your own "viewer" using the base Mol* library. For this, see the options below.

Example

<style>
    #app {
        position: absolute;
        left: 100px;
        top: 100px;
        width: 800px;
        height: 600px;
    }
</style>
<!-- 
    molstar.js and .css are obtained from
    - the folder build/viewer after cloning and building the molstar package 
    - from the build/viewer folder in the Mol* NPM package
-->
<link rel="stylesheet" type="text/css" href="./molstar.css" />
<script type="text/javascript" src="./molstar.js"></script>

<div id="app"></div>

<script type="text/javascript">
    molstar.Viewer.create('app', {
        layoutIsExpanded: false,
        layoutShowControls: false,
        layoutShowRemoteState: false,
        layoutShowSequence: true,
        layoutShowLog: false,
        layoutShowLeftPanel: true,

        viewportShowExpand: true,
        viewportShowSelectionMode: false,
        viewportShowAnimation: false,

        pdbProvider: 'rcsb',
        emdbProvider: 'rcsb',
    }).then(viewer => {
        viewer.loadPdb('7bv2');
        viewer.loadEmdb('EMD-30210', { detail: 6 });
    });
</script>

Using WebPack/esbuild/...

When using WebPack (or other bundler) with the Mol* NPM package installed, the viewer class can be imported using

import { Viewer } from 'molstar/lib/apps/viewer/app'

function initViewer(target: string | HTMLElement) {
    return Viewer.create(target, { /* options */}) // returns a Promise
}

PluginContext with built-in React UI

import { DefaultPluginUISpec, PluginUISpec } from 'molstar/lib/mol-plugin-ui/spec';
import { createPluginUI } from 'molstar/lib/mol-plugin-ui';
import { renderReact18 } from 'molstar/lib/mol-plugin-ui/react18';
import { PluginConfig } from 'molstar/lib/mol-plugin/config';

const MySpec: PluginUISpec = {
    ...DefaultPluginUISpec(),
    config: [
        [PluginConfig.VolumeStreaming.Enabled, false]
    ]
}

async function createPlugin(parent: HTMLElement) {
    const plugin = await createPluginUI({
      target: parent,
      spec: MySpec,
      render: renderReact18
    });

    const data = await plugin.builders.data.download({ url: '...' }, { state: { isGhost: true } });
    const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
    await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');

    return plugin;
}

createPlugin(document.getElementById('app')!); // app is a <div> element with position: relative

To use the plugin (with the React UI) inside another React app:

A single-plugin setup is shown the example below. In order to initialize multiple plugins, each with its own context and viewport, some extra steps are required (docs section to be added).

import { useEffect, createRef } from "react";
import { createPluginUI } from "molstar/lib/mol-plugin-ui";
import { renderReact18 } from "molstar/lib/mol-plugin-ui/react18";
import { PluginUIContext } from "molstar/lib/mol-plugin-ui/context";
/*  Might require extra configuration,
see https://webpack.js.org/loaders/sass-loader/ for example.
create-react-app should support this natively. */
import "molstar/lib/mol-plugin-ui/skin/light.scss";

declare global {
  interface Window {
    molstar?: PluginUIContext;
  }
}


export function MolStarWrapper() {
  const parent = createRef<HTMLDivElement>();

  // In debug mode of react's strict mode, this code will
  // be called twice in a row, which might result in unexpected behavior.
  useEffect(() => {
    // By default, react will call each useEffect twice if using Strict mode in
    // debug build, it is recommended to disable strict mode for this reason if possible
    async function init() {
        window.molstar = await createPluginUI({
          target: parent.current as HTMLDivElement,
          render: renderReact18
        });

        const data = await window.molstar.builders.data.download(
          { url: "https://files.rcsb.org/download/3PTB.pdb" }, /* replace with your URL */
          { state: { isGhost: true } }
        );
        const trajectory =
          await window.molstar.builders.structure.parseTrajectory(data, "pdb");
        await window.molstar.builders.structure.hierarchy.applyPreset(
          trajectory,
          "default"
        );
    }
    init();
    return () => {
      window.molstar?.dispose();
      window.molstar = undefined;
    };
  }, []);

  return <div ref={parent} style={{ width: 640, height: 480 }}/>;
}

Furthermore, if it is desirable in your project to use the molstar's React UI components, but you wish to alter or rearrange the layout, you should take a look at the signatures of PluginUIComponent which every "control" subclasses.

SequenceView , for example, can be used separately from the PluginUI. Yet you would need to pass the PluginUIContext to it in order for it to observe the changes in the state of the plugin. This can be done via a PluginContextContainer:

// your_app.plugin: PluginUIContext
...
<div className="your_custom_ui">
  <PluginContextContainer plugin={your_app.plugin}>
    <SequenceView />
  </PluginContextContainer>
</div>

Directly using Mol* React UI

class MolStarWrapper {
  private resolveInit: () => void;
  initialized = new Promise<boolean>(res => { this.resolveInit = () => res(true); });

  private initCalled = false;
  plugin: PluginUIContext;
  async init() {
    if (this.initCalled) return;
    this.initCalled = true;
    this.plugin = ...;
    await this.plugin.init();
    this.resolveInit();
  }
}

function MolStar({ model }: { model: MolStarWrapper }) {
  const [initialized, setInitialized] = useState(false);
  useEffect(() => {
     async function init() {
       await model.init();
       setInitialized(true);
     }
     init();
  }, [model]);

  if (!initialized) return <>Loading</>;
  return <div style={{ ..., position: 'relative' }}>
    <Plugin plugin={model.plugin} />
  </div>;
}

PluginContext without built-in React UI

  • The PluginContext can be instantiated without using the default React UI.
<div id='molstar-parent' style='position: absolute; top: 0; left: 0; right: 0; bottom: 0'>
    <canvas id='molstar-canvas' style='position: absolute; top: 0; left: 0; right: 0; bottom: 0'></canvas>
</div>
import { DefaultPluginSpec, PluginSpec } from 'molstar/lib/mol-plugin/spec';
import { PluginContext  } from 'molstar/lib/mol-plugin/context';
import { PluginConfig } from 'molstar/lib/mol-plugin/config';

const MySpec: PluginSpec = {
    ...DefaultPluginSpec(),
    config: [
        [PluginConfig.VolumeStreaming.Enabled, false]
    ]
}

async function init() {
    const plugin = new PluginContext(MySpec);
    await plugin.init();

    const canvas = <HTMLCanvasElement> document.getElementById('molstar-canvas');
    const parent = <HTMLDivElement> document.getElementById('molstar-parent');

    if (!(await plugin.initViewerAsync(canvas, parent))) {
        console.error('Failed to init Mol*');
        return;
    }

    // Example url:"https://files.rcsb.org/download/3j7z.pdb" 
    // Example url:"https://files.rcsb.org/download/5AFI.cif" 
    const data = await plugin.builders.data.download({ url: '...' }, { state: { isGhost: true } });
    const trajectory = await plugin.builders.structure.parseTrajectory(data, format); //format is 'mmcif' or 'pdb' etc.
    await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
}

Canvas3D without built-in state management

  • The PluginContext object from the above examples can be completely omitted.
  • See Browser Tests for example usage.
const canvas = document.getElementById('canvas'); // parent <canvas> element
const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas));
canvas3d.animate();
// use the canvas3d object here