Development Workflow#
Mol* Linker is written in TypeScript and uses a two-stage build pipeline:
- Type checking —
tsc --noEmitvalidates the entire codebase against strict TypeScript rules without producing any output files. - Bundling —
esbuildtakes each entry-point module and bundles it with all its dependencies into a single, self-contained JavaScript file per page. - Assembly —
assemble.jscopies the bundled JS, static HTML/CSS, icons, and the correct browser manifest into a browser-specific output folder.
This separation means the TypeScript compiler is a pure quality gate, and esbuild handles the actual output — giving you both type safety and fast, modern output with zero runtime overhead.
Environment Setup#
The project uses pixi to manage all tooling dependencies (Node.js, TypeScript, esbuild) in a reproducible, isolated environment. This means contributors do not need to install anything globally.
# Clone the repo
git clone https://github.com/MartinBaGar/molstar_linker.git
cd molstar_linker
# Install the pixi environment (only needed once)
pixi installDirectory Structure#
Molstar_Linker/
├── src/ ← TypeScript source (never loaded by the browser directly)
│ ├── types.ts ← shared interfaces and message protocol types
│ ├── config.ts ← AppConfig: targets, RepSchema, presets, getDefaults()
│ ├── permissions.ts
│ ├── mvs-builder.ts
│ ├── background.ts
│ ├── content.ts
│ ├── sandbox.ts
│ ├── viewer.ts
│ ├── popup.ts
│ └── options.ts
├── public/ ← static assets, version-controlled
│ ├── viewer.html / sandbox.html / popup.html / options.html
│ ├── popup.css / options.css
│ ├── icons/
│ └── lib/ ← molstar.js and molstar.css (third-party, not compiled)
├── manifests/ ← browser-specific manifests
│ ├── chrome.json
│ └── firefox.json
├── dist/ ← gitignored, generated by the build
│ ├── chrome/
│ └── firefox/
├── releases/ ← hand-managed zips for store uploads
├── build.mjs ← esbuild bundler
├── assemble.js ← copies JS + statics into dist/chrome or dist/firefox
├── tsconfig.json ← type-checker config (noEmit: true)
└── package.jsonBuild Commands#
All commands are run via pixi (recommended) or npm directly.
Local testing#
pixi run npm run build:chrome # type-check → bundle → assemble dist/chrome/
pixi run npm run build:firefox # type-check → bundle → assemble dist/firefox/
pixi run npm run build # both browsers at once- Load
dist/chromeas an Unpacked Extension inchrome://extensions/. - Load
dist/firefoxas a Temporary Add-on inabout:debugging, selectingmanifest.json.
Type checking only (no output)#
pixi run npm run watch # tsc --noEmit -w, re-checks on every file saveThis is the recommended mode while editing — fast feedback on type errors without a full build cycle.
Packaging a release#
Zip each browser folder after a clean build:
pixi run npm run build
cd dist
zip -r ../releases/molstar_linker_chrome-vX.Y.Z.zip chrome/
zip -r ../releases/molstar_linker_firefox-vX.Y.Z.zip firefox/Key Files Explained#
tsconfig.json#
Sets noEmit: true — tsc is used exclusively for type checking, not for producing output. esbuild handles compilation and bundling. Strict mode and noImplicitAny are both enabled.
build.mjs#
Runs esbuild against the six entry points (background, content, sandbox, viewer, popup, options). Each entry point is bundled with all its TypeScript imports into a single flat JS file in dist/. Shared modules (config, permissions, mvs-builder, types) are not separate output files — they are inlined into whichever entry points import them.
assemble.js#
Takes the browser name as an argument (chrome or firefox), cleans the target output folder, copies the six bundled JS files from dist/, copies static assets from public/, and copies the correct manifest from manifests/. The result is a complete, self-contained loadable extension folder.
manifests/chrome.json vs manifests/firefox.json#
Chrome requires Manifest V3 (service_worker, action, strict CSP). Firefox requires Manifest V2 (background.scripts, browser_action, permissive CSP for unsafe-eval). Keeping them separate is honest about the platform divergence rather than maintaining one manifest with conditional logic.
Adding a New Module#
- Create
src/your-module.tswith a named export. - Import it in whichever entry point needs it — esbuild will bundle it automatically.
- If the module introduces shared types, add interfaces to
src/types.ts. - Run
pixi run npm run watchto verify no type errors.
There is no need to update any HTML files, manifests, or the assemble script when adding internal modules.