rdkit/rdkit-js

Offline usage examples

MichelML opened this issue · 5 comments

Add examples on how to use rdkit-js in a offline-first app

see context here #150 (comment)

I started experimenting with using the module in a browser-less / Node.js only setting. I've managed to load the module relatively easily and do some basic things with it.

I'm a little unsure as to what would count as an "offline-first" app. Just some bare .js / .ts files, without any browser interaction or HTML?

Edit:

Very basic example

// index.js
import initRDKitModule from '@rdkit/rdkit'

initRDKitModule()
.then((rdkit) => {
    console.log(rdkit.version())
})

// console output
> 2022.03.5

Just being able to do server-side rendering would be very useful, both of depictions (particularly SVGs), and also (and likely easier) for molecule canonization.

Showcase here. I'm building a plugin for the note-taking app Obsidian, aiming to render SMILES strings as chemical structures. Repo link

Challenges

  • Obsidian is built on Electron, so I can't access local assets directly via file paths
  • The plugins are recommended to write in TypeScript
  • I want to implement asynchronous loading to ensure that the structures are rendered correctly, rather than invoking an undefined window.RDKit when the notes are ready

Solutions

  • Create an object URL from local wasm file (indirectly calling fs and conducted a binary read), then provide it to param locateFile for initRDKitModule()
  • Follow the instructions in TypeScript/README.md and create a type definition
  • Asynchronously read .wasm file as binary and .js script as text. The former is turned into an object URL and loaded. Create a script tag and set .js text as its innerHTML
    • I used unpkg distributions as a backup. The wasm URL works fine for locateFile, but I can't simply set the URL of .js script as the src attribute for a script tag. It's still necessary to conduct an async fetch and set the innerHTML as the response text.

@Acylation You may want to look at how I deal with this in rdkit-structure-renderer. There are several examples of plain HTML pages that bootstrap themselves by loading the bundle through a script tag:
https://github.com/rdkit/rdkit-structure-renderer/blob/master/index.html
https://github.com/rdkit/rdkit-structure-renderer/blob/master/api_examples/svg_using_bundle.html
Structures are rendered as soon as the bundle has loaded and initialized.

@ptosco yes using onload callback here is quite reasonable and elegant. As I want to get a return value from the async onload (comparing to calling window.RDKit directly, this can ensure that I get a initialized reference), I need to do some "promisify". Following are the two versions for loading the package and they both work well.

Thanks a lot for providing these efficient examples!

Before

const loadRDKitUnpkg = async () => {
	const rdkitBundler = document.createElement('script');
        document.body.appendChild(rdkitBundler);
	console.log('Fetching RDKit.js from unpkg...');

        //requestUrl() is an API that fetching js scripts as text asynchoronously, based on `fetch()` API
	rdkitBundler.innerHTML = await requestUrl( 'https://unpkg.com/@rdkit/rdkit/dist/RDKit_minimal.js').text;

	const RDKit = await window.initRDKitModule({
		locateFile: () => 'https://unpkg.com/@rdkit/rdkit/dist/RDKit_minimal.wasm',
	});
	console.log('RDKit.js has been successfully loaded.');
	return RDKit;
};

After

const loadRDKitUnpkg = async () => {
	const rdkitBundler = document.createElement('script');
        document.body.appendChild(rdkitBundler);
	console.log('Fetching RDKit.js from unpkg...');

	rdkitBundler.src = 'https://unpkg.com/@rdkit/rdkit/dist/RDKit_minimal.js';
	return new Promise<RDKitModule>((resolve, reject) => {
		rdkitBundler.onload = async () => {
			const RDKit = await window.initRDKitModule({
				locateFile: () => 'https://unpkg.com/@rdkit/rdkit/dist/RDKit_minimal.wasm',
			});
			console.log('RDKit.js has been successfully loaded.');
			resolve(RDKit);
			rdkitBundler.onerror = (error) => {
				reject(error);
			};
		};
	});
};