A collection of Molstar molecular visualization applications built for Val Town, using JSR imports and TSX.
This Val serves four different Molstar visualization applications:
- Viewer 🧬 - Standard Mol* viewer for visualizing molecular structures
- Docking Viewer 🔬 - Specialized viewer for molecular docking results
- Mesoscale Explorer 🔭 - Explore mesoscale biological structures
- MVS Stories 📖 - Interactive molecular visualization stories
Following the Val Town blog pattern with routes at the top level:
MolstarViewer/
├── index.tsx # Main Hono app (entry point)
├── routes/ # Route handlers
│ ├── home.tsx # Homepage listing all apps
│ ├── viewer.tsx # Viewer app route
│ ├── docking-viewer.tsx # Docking viewer route
│ ├── mesoscale-explorer.tsx # Mesoscale explorer route
│ └── mvs-stories.tsx # MVS stories route
├── frontend/ # Client-side TSX components
│ ├── apps/ # Individual app components
│ │ ├── viewer.tsx
│ │ ├── docking-viewer.tsx
│ │ ├── mesoscale-explorer.tsx
│ │ └── mvs-stories.tsx
│ ├── styles/ # CSS files
│ │ └── molstar.css # Compiled Molstar styles
│ └── README.md
├── shared/ # Shared types and utilities
│ ├── types.ts
│ └── README.md
├── utils/ # Utility functions
└── README.md # This file
- Runtime: Val Town (Deno-based)
- Framework: Hono v4 from JSR
- UI: React 18.2.0 via esm.sh
- Molstar: JSR package
jsr:@zachcp/molstar@5.3.17 - Language: TypeScript/TSX
All dependencies are imported using the JSR protocol:
import { Viewer } from "jsr:@zachcp/molstar@5.3.17";
Available exports from jsr:@zachcp/molstar:
.(main export - includes Viewer class)./plugin-ui./plugin./plugin-state./canvas3d./data./state./task./util./extensions
import { Hono } from "jsr:@hono/hono@4";
JSR (JavaScript Registry) provides native Deno/TypeScript support and works seamlessly in Val Town's runtime environment.
The top-level index.tsx is the main Hono app that imports and mounts all routes:
/** @jsxImportSource https://esm.sh/react@18.2.0 */
import { Hono } from "jsr:@hono/hono@4";
import homeRoutes from "./routes/home.tsx";
import viewerRoute from "./routes/viewer.tsx";
// ... other routes
const app = new Hono();
app.onError((err, c) => {
throw err; // Unwrap errors for debugging
});
app.route("/", homeRoutes);
app.route("/", viewerRoute);
// ... mount other routes
export default app.fetch;
Each route is a separate Hono app that handles its specific path:
/** @jsxImportSource https://esm.sh/react@18.2.0 */
import { Hono } from "jsr:@hono/hono@4";
import { renderToString } from "https://esm.sh/react-dom@18.2.0/server?deps=react@18.2.0,react-dom@18.2.0";
import ViewerApp from "../frontend/apps/viewer.tsx";
const app = new Hono();
app.get("/viewer", async (c) => {
const html = renderToString(<ViewerApp />);
return c.html(html);
});
export default app;
Each app is a self-contained TSX component that returns a complete HTML document:
/** @jsxImportSource https://esm.sh/react@18.2.0 */
export default function ViewerApp({ molstarCss }: { molstarCss: string }) {
return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<title>Mol* Viewer</title>
<style dangerouslySetInnerHTML={{ __html: molstarCss }} />
</head>
<body>
<div id="molstar-container">Loading Mol* Viewer...</div>
<script type="module">
{`
import { Viewer } from "jsr:@zachcp/molstar@5.3.17";
window.addEventListener("DOMContentLoaded", async () => {
const container = document.getElementById("molstar-container");
if (container) {
const viewer = await Viewer.create(container, {
layoutIsExpanded: true,
layoutShowControls: true,
});
}
});
`}
</script>
</body>
</html>
);
}
- Routes use
renderToStringfrom React to server-render components - CSS is injected inline from compiled Molstar stylesheets
- Client-side Molstar initialization happens via inline module scripts
- This avoids issues with JSR imports in server-side contexts
The project is currently scaffolded with:
- ✅ Top-level route structure (Val Town blog style)
- ✅ Main Hono app in
index.tsx - ✅ Separate route files for each app
- ✅ TSX components for each app
- ✅ Viewer app fully functional with JSR imports
- ✅ Compiled Molstar CSS integration
- ⏳ Docking viewer (scaffolded)
- ⏳ Mesoscale explorer (scaffolded)
- ⏳ MVS Stories (scaffolded)
- CSS Handling: Molstar CSS is read from
frontend/styles/molstar.cssand injected inline - Client-Side Initialization: Molstar viewer is initialized in browser using inline
<script type="module"> - JSR Imports: Work only in client-side context, not in server-side rendering
- Error Handling:
app.onErrorunwraps Hono errors to show full stack traces
- Implement remaining apps:
- Docking Viewer: Load and compare docking results
- Mesoscale Explorer: Load mesoscale structures
- MVS Stories: Story navigation and playback
- Add interactive controls for each viewer
- Add loading states and error handling in UI
- Implement app-specific features:
- Viewer: Load PDB structures by ID
- Add URL parameters for structure loading
- Add preset views and controls
- Mol* Website
- Val Town Documentation
- JSR Package Registry
- Hono Framework
- Val Town Blog Example - Structure inspiration
- All components use the
/** @jsxImportSource https://esm.sh/react@18.2.0 */pragma - JSR imports use the
jsr:protocol (e.g.,jsr:@zachcp/molstar@5.3.17) - Routes are separate Hono apps that get mounted in main
index.tsx - Server-side rendering uses
renderToStringfrom React - Client-side scripts are injected using template literals in JSX
- Molstar CSS is loaded server-side and injected inline
- No
backend/folder - routes at top level like Val Town blog - Main entry point is
index.tsx(notbackend/index.tsx) - Use
readFileutility for reading project files - JSR imports work seamlessly in Val Town's Deno runtime
When adding new features:
- Create a new route file in
routes/if adding a new page - Create corresponding component in
frontend/apps/for the UI - Mount the route in
index.tsx - Keep shared types in
shared/types.ts - Use JSR imports for Molstar dependencies
- Follow Val Town best practices from
AGENTS.md
Copyright (c) 2018-2025 mol* contributors, licensed under MIT