Development Workflow#

Mol* Linker is written in TypeScript and uses a two-stage build pipeline:

  1. Type checkingtsc --noEmit validates the entire codebase against strict TypeScript rules without producing any output files.
  2. Bundlingesbuild takes each entry-point module and bundles it with all its dependencies into a single, self-contained JavaScript file per page.
  3. Assemblyassemble.js copies 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 install

Directory 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.json

Build 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/chrome as an Unpacked Extension in chrome://extensions/.
  • Load dist/firefox as a Temporary Add-on in about:debugging, selecting manifest.json.

Type checking only (no output)#

pixi run npm run watch    # tsc --noEmit -w, re-checks on every file save

This 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#

  1. Create src/your-module.ts with a named export.
  2. Import it in whichever entry point needs it — esbuild will bundle it automatically.
  3. If the module introduces shared types, add interfaces to src/types.ts.
  4. Run pixi run npm run watch to verify no type errors.

There is no need to update any HTML files, manifests, or the assemble script when adding internal modules.