Skip to content

Building a Custom Library

This page goes over creating a custom Mol* based library usable inside a <script> tag in an HTML page using the esbuild tool.

  • Create a new npm/yarn package
  • Install molstar and esbuild packages
mkdir molstar-lib
cd molstar-lib
npm init
npm install molstar
npm install esbuild --save-dev

Create new file src/index.ts (or .js if you don’t want to use TypeScript):

import { DefaultPluginSpec, PluginSpec } from 'molstar/lib/mol-plugin/spec';
import { PluginContext } from 'molstar/lib/mol-plugin/context';
export async function initViewer(element: string | HTMLDivElement, options?: { spec?: PluginSpec }) {
const parent = typeof element === 'string' ? document.getElementById(element)! as HTMLDivElement : element;
const canvas = document.createElement('canvas') as HTMLCanvasElement;
parent.appendChild(canvas);
const spec = options?.spec ?? DefaultPluginSpec();
const plugin = new PluginContext(spec);
await plugin.init();
plugin.initViewer(canvas, parent);
return plugin;
}
export async function loadStructure(
plugin: PluginContext,
url: string,
options?: { format?: string, isBinary?: boolean }
) {
const data = await plugin.builders.data.download(
{ url, isBinary: options?.isBinary }
);
const trajectory = await plugin.builders.structure.parseTrajectory(
data,
options?.format ?? 'mmcif' as any
);
const preset = await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
return preset;
}

Add new commands to the scripts section of the package.json file

"scripts": {
"build": "esbuild src/index.ts --bundle --outfile=./build/js/index.js --global-name=molstarLib",
"watch": "esbuild src/index.ts --bundle --outfile=./build/js/index.js --global-name=molstarLib --watch"
}

and run the command npm run build (or watch for interactive development experience). This will create build/js/index.js file which can be imported with a <script> tag and the exported functions called view the molstarLib prefix (you can customize this parameter).

Create file build/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>Mol* Library Example</title>
</head>
<style>
#viewer {
position: absolute;
width: 800px;
height: 600px;
}
</style>
<script type="text/javascript" src="./js/index.js"></script>
<body>
<div id="viewer"></div>
<script type="text/javascript">
async function init() {1
const plugin = await molstarLib.initViewer("viewer");
await molstarLib.loadStructure(
plugin,
"https://models.rcsb.org/4hhb.bcif",
{ isBinary: true }
);
}
init();
</script>
</body>
</html>

After opening index.html in a browser, you should see

lib-example

The above example does not make use of the default Mol* React UI and any UI components are therefore the author’s responsibility. The below examples show how to (re)use the Mol* React UI.

  • Create src/ui.tsx:

    import React from 'react';
    import { createRoot } from 'react-dom/client';
    import { DefaultPluginUISpec, PluginUISpec } from 'molstar/lib/mol-plugin-ui/spec';
    import { PluginUIContext } from 'molstar/lib/mol-plugin-ui/context';
    import { Plugin } from 'molstar/lib/mol-plugin-ui/plugin';
    export async function initViewerUI(element: string | HTMLDivElement, options?: { spec?: PluginUISpec }) {
    const parent = typeof element === 'string' ? document.getElementById(element)! as HTMLDivElement : element;
    const spec = { ...DefaultPluginUISpec(), ...options?.spec };
    const plugin = new PluginUIContext(spec);
    await plugin.init();
    createRoot(parent).render(<Plugin plugin={plugin} />)
    return plugin;
    }
    export async function loadStructure(plugin: PluginUIContext, url: string, options?: { format?: string, isBinary?: boolean }) {
    const data = await plugin.builders.data.download({ url, isBinary: options?.isBinary });
    const trajectory = await plugin.builders.structure.parseTrajectory(data, options?.format ?? 'mmcif' as any);
    await plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
    }
  • Create src/style.scss:

    @use '../node_modules/molstar/lib/mol-plugin-ui/skin/light.scss';
  • Create build/ui.html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <title>Mol* UI Library Example</title>
    </head>
    <link rel="stylesheet" type="text/css" href="css/style.css" />
    <style>
    #viewer {
    position: absolute;
    inset: 0;
    }
    </style>
    <script type="text/javascript" src="./js/ui.js"></script>
    <body>
    <div id="viewer"></div>
    <script type="text/javascript">
    async function init() {
    const plugin = await molstarLib.initViewerUI("viewer", {
    spec: {
    layout: {
    initial: {
    isExpanded: true,
    showControls: true,
    },
    },
    }
    });
    await molstarLib.loadStructure(plugin, "https://models.rcsb.org/4hhb.bcif", { isBinary: true });
    }
    init();
    </script>
    </body>
    </html>
  • Install sass: npm install sass -save-dev (or use esbuild plugin and import the scss file in ui.tsx)

  • Add scripts to package.json:

    "build-ui": "esbuild src/ui.tsx --bundle --outfile=./build/js/ui.js --global-name=molstarLib",
    "css": "sass src/style.scss ./build/css/style.css"
  • Run npm run build-ui and npm run css (skip if using esbuild-sass-plugin)

  • Opening build/ui.html: ui-example