Public
Like
MolstarViewer
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data β all from the browser, and deployed in milliseconds.
Viewing readonly version of main branch: v631View latest version
A single-page application (SPA) for molecular visualization using Molstar, built for Val Town with client-side routing.
This Val serves a React SPA with four different Molstar visualization applications:
- Home - Landing page with app navigation
- 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
Simple SPA structure with all frontend code in one place:
MolstarViewer/
βββ backend/
β βββ index.http.tsx # HTTP entry point (Hono server)
β βββ README.md # Backend documentation
βββ frontend/ # All client-side code
β βββ index.tsx # Client entry: mounts React app
β βββ components/ # React components
β β βββ App.tsx # Main app with client-side routing
β β βββ ErrorBoundary.tsx # Error handling component
β β βββ MolstarViewer.tsx # Reusable Molstar viewer component
β βββ viewer/ # Viewer-specific modules
β βββ docking-viewer/ # Docking-specific modules
β βββ mesoscale-explorer/ # Mesoscale-specific modules
β βββ styles/ # CSS files
β βββ molstar.css # Compiled Molstar styles
βββ index.html # HTML entry point template
- Runtime: Val Town (Deno-based)
- Server: Hono v4 from JSR (static file serving only)
- UI: React 18.2.0 via esm.sh (client-side SPA)
- Routing: Custom client-side routing (History API)
- Molstar: JSR package
jsr:@zachcp/molstar@5.3.17 - Language: TypeScript/TSX
The server is extremely simple - it just serves static files:
import { Hono } from "jsr:@hono/hono@4";
import { serveFile } from "https://esm.town/v/std/utils@85-main/index.ts";
const app = new Hono();
// Unwrap Hono errors to see original error details
app.onError((err, c) => {
throw err;
});
// Serve all frontend files
app.get("/frontend/*", (c) => serveFile(c.req.path, import.meta.url));
// Serve the root HTML file for all routes (SPA)
app.get("*", (c) => serveFile("/frontend/index.html", import.meta.url));
export default app.fetch;
Mounts the React app to the DOM:
import { App } from "/frontend/components/App.tsx";
import { ErrorBoundary } from "/frontend/components/ErrorBoundary.tsx";
import { createRoot } from "https://esm.sh/react-dom@18.2.0/client";
import React from "https://esm.sh/react@18.2.0";
const root = createRoot(document.getElementById("root")!);
root.render(
<React.StrictMode>
<ErrorBoundary>
<App />
</ErrorBoundary>
</React.StrictMode>,
);
Handles client-side routing using the History API:
export function App() {
const [currentPath, setCurrentPath] = useState(window.location.pathname);
const navigate = (path: string) => {
window.history.pushState({}, "", path);
setCurrentPath(path);
};
// Render different views based on currentPath
if (currentPath === "/viewer") {
return <MolstarViewer />;
}
// ... etc
}
Reusable component that dynamically imports and initializes Molstar:
export function MolstarViewer({ layoutIsExpanded, layoutShowControls }) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const { Viewer } = await import("jsr:@zachcp/molstar@5.3.17");
const viewer = await Viewer.create(containerRef.current, {
layoutIsExpanded,
layoutShowControls,
});
}, []);
return <div ref={containerRef} />;
}
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./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.
- β Clean project structure with backend/ and frontend/ folders
- β HTTP entry point properly named (backend/index.http.tsx)
- β Single-page application with client-side routing
- β Hono server serves static files only
- β React app handles all routing client-side
- β Reusable MolstarViewer component
- β Dynamic import of Molstar (code splitting)
- β Error boundary for error handling
- β
All frontend code in
frontend/folder - β Home page with navigation
- β Viewer app fully functional
- β Docking viewer scaffolded
- β Mesoscale explorer scaffolded
- β MVS Stories scaffolded
- SPA Architecture: Server returns
index.htmlfor all routes, React handles routing - Client-Side Routing: Uses History API (
pushState,popstate) - Dynamic Imports: Molstar is loaded dynamically in useEffect for code splitting
- No Server-Side Rendering: Pure client-side React app
- Static File Serving: Server just serves files from
frontend/directory - Error Handling: ErrorBoundary catches React errors, console catches runtime errors
- Add URL parameters for loading specific structures (e.g.,
/viewer?pdb=1ABC) - Implement app-specific features:
- Viewer: Load PDB structures by ID
- Docking Viewer: Load and compare docking results
- Mesoscale Explorer: Load mesoscale structures
- MVS Stories: Story navigation and playback
- Add loading indicators while Molstar initializes
- Optimize bundle size with better code splitting
- Add keyboard shortcuts and controls
- Server is a simple static file server - no server-side routes
- All routing happens client-side using History API
- Molstar is imported dynamically in the browser
- JSR imports work perfectly in client-side
<script type="module">tags - No build step required - TypeScript runs directly in browser via esm.sh
- HTTP handler is
backend/index.http.tsx(the.http.tsxextension is required) - All frontend code in
frontend/directory (including index.html) - Server uses
serveFileutility to serve static files - Client imports use absolute paths starting with
/frontend/ - JSR imports work seamlessly in Val Town's Deno runtime
- Uses
window.history.pushState()for navigation - Listens to
popstateevent for back/forward buttons - Server returns
index.htmlfor all routes (catch-all) - React re-renders based on
currentPathstate
When adding new features:
- New Routes: Add new route logic to
App.tsxcomponent - New Components: Create in
frontend/components/ - Shared Code: Put in
frontend/for easy importing - Use JSR Imports: For Molstar and other Deno-compatible packages
- Follow Val Town Best Practices: See
AGENTS.md
Example adding a new route:
// In App.tsx
if (currentPath === "/my-new-page") {
return (
<div>
<h1>My New Page</h1>
<MolstarViewer />
</div>
);
}
Copyright (c) 2018-2025 mol* contributors, licensed under MIT