Open-source project
lastmjs/zwitterion avatar
lastmjs/zwitterion

Zwitterion: a dev server that serves TypeScript, Rust, C and Wasm as if they were scripts

A web dev server that lets you import anything*

592 stars14 forksTypeScriptMIT

At a glance

What is it?
Zwitterion is a web development server that compiles on the fly, so an HTML page can import a TypeScript file which imports a Rust or C module and it just works in the browser. It is deliberately not a bundler, and the README says plainly that running it in production is not recommended.
Who is it for?
Zwitterion suits a developer who wants to write a page in TypeScript today and port a hot function to Rust or C tomorrow without touching the build setup, and who wants a static build they can upload to a CDN. It is the wrong choice if you need a production dev server, because the README says not to run it in production for performance and security reasons, and it is the wrong choice if you need import maps or HTTP2, which the README lists as upcoming features.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 178 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The promise is that an HTML file can point at a TypeScript file

Zwitterion describes itself as a web dev server that lets you import anything, and the asterisk in the tagline leads to a precise expansion. If by anything you mean JavaScript ES2015+, TypeScript, JSON, JSX, TSX, AssemblyScript, Rust, C, C++, WebAssembly, and in the future anything that compiles to JavaScript or WebAssembly. The design goal is stated as being an instant replacement for your current web development static file server. The example that makes it concrete is an HTML file whose script tag points at app.ts with the module type, where app.ts imports from a TypeScript module, and where the browser is expected to run it all without a build step in between. The README's phrasing is that you write your source code in any supported language and run it in the browser, and that you get back to the good old days of web development. The subject matter is a little whimsical, but the mechanism underneath is a real compilation server rather than a joke, and the language list in the topics, from AssemblyScript and C through es5, es6, es7, esnext, Rust, spa, TypeScript, wasm and webassembly, matches the claim.

It compiles at request time instead of bundling ahead of time

The single most important design decision is stated bluntly in the README: Zwitterion is NOT a bundler, and it eschews bundling for a simpler experience. That single decision explains most of its behaviour. A bundler walks your import graph, transforms everything, and emits a graph of chunks that the browser loads. Zwitterion instead leaves the import graph in the browser and transforms one file at a time as the browser requests it, so the module graph on the wire is the module graph in your source tree, and a change to one file does not invalidate the whole build. The dependencies tell you what that costs. The TypeScript compiler is present and is used for transpilation, including for plain JavaScript, and there is a Babel core with the syntax plugins for dynamic import and export extensions. AssemblyScript is there for the AssemblyScript path, wabt is the WebAssembly binary toolkit used for the Wat path, as-bind is the AssemblyScript binding layer, and ws handles the websocket side. The server itself is a ws server, commander supplies the command line, chokidar watches files for changes, and whatwg-url parses request URLs. There is no bundler in that list.

Install it locally, or globally, and it is just an executable

Installation is npm in both flavours. Locally, in the directory you want to serve files from:

bash
npm install zwitterion

json

The local install is then run by reaching its executable directly from the terminal, which the README gives as node_modules/.bin/zwitterion, or from an npm script:

json
{
  ...
  "scripts": {
    "start": "zwitterion"
  }
  ...
}

The global install is npm install -g zwitterion, and then the command is simply zwitterion, with the same npm script form available. There is no configuration file to write and no port to set for the basic case, and the package.json bin entry maps the zwitterion name to ./bin.js. Two details in the repository are worth noting for anyone reading the source. The npm start and npm test scripts both run ts-node with the transpile-only flag against src/app.ts and test/test.ts, with the port 5050 passed on the command line, which tells you the server is written in TypeScript and that the port is configurable by flag. The package version is 0.47.0, the licence is MIT, and the last push to the repository was on 2026-04-29. The project publishes no GitHub releases, and it is on npm.

Production means a static build, not a running server

The README is unusually direct about deployment. It is recommended to use Zwitterion in production by creating a static build of your project, and a static build essentially runs all relevant files through Zwitterion and copies those and all other files in your project to a dist directory, which you can then upload to a Content Delivery Network or another static file hosting service. You may also use a running Zwitterion server in production, but for performance and potential security reasons it is not recommended. The static build is created with the --build-static option:

bash
zwitterion --build-static

The output lands in a directory called dist, in the directory you ran the command from. There is one deployment detail that will bite you if nobody tells you. You will probably need to add the application/javascript MIME type to your hosting provider for your TypeScript, AssemblyScript, Rust, Wasm and Wat files, because a static host that serves them as text or as octet-stream will not execute them. That is the cost of the approach, and it is a hosting configuration problem rather than a code problem. The alternative to a static build is to keep the server running, which the README discourages on both performance and security grounds, and the single page application routing behaviour, where the server returns index.html on unhandled routes, is one of the reasons it is not designed to face the public internet.

Bare imports and per-language compiler options are the two knobs

Two features do the work of making a bundler unnecessary. The first is bare imports, listed as support for import * as stuff from 'library'; instead of import * as stuff from '../node_modules/library/index.js';. In other words, the server resolves the package name the way a bundler would and serves the file, so you do not have to write relative paths into node_modules. The second is that each language has its own transformation path, and for the JavaScript and TypeScript paths the compiler options are the same mechanism. By default the TypeScript compiler's compilerOptions are set to a module and target of ES2015, and JavaScript transpilation is done by the TypeScript compiler, which is why a plain .js file and a .ts file behave the same way here. You can override those options by creating a .json file with your own compilerOptions and pointing Zwitterion at it with the --tsc-options-file command line option:

bash
zwitterion --tsc-options-file tsc-options.json

The options are specified as a JSON object, and the README links the TypeScript handbook for the full list. Rust, C and C++ support is described as basic, and the upcoming features list names more reliable Rust, C and C++ integration, including automatic local Rust installation during npm installation, along with import maps and HTTP2 optimizations.

The dependency list dates the tool, and that matters for a compiler

There is a limitation here that is easy to miss and worth stating plainly. This is a tool whose job is running a compiler, and the compilers it drives are pinned to specific old versions in package.json. The TypeScript dependency is 3.9.9, Babel core is 7.8.3, AssemblyScript is 0.18.11, ws is 7.4.3, commander is 6.2.1, chokidar is 3.5.1 and TypeScript itself is 3.9.9. The default target of ES2015 that the README documents is consistent with that era of the toolchain. For a server that transforms code on every request, the version of the compiler is not an incidental detail, it is the product. A newer language feature that the pinned TypeScript does not know about will not compile, and a browser that supports a syntax the pinned compiler does not emit will not run the result. The repository does carry renovate.json, which is the configuration for automated dependency updates, and the npm version badge and dependency status badges in the README suggest the maintainers track upstream releases. But the last push was on 2026-04-29 and the package version is 0.47.0, so anyone adopting this today should check whether the pinned compiler versions match the syntax their codebase actually uses, rather than assuming the toolchain tracks the current TypeScript release.

Editorial conclusion

Zwitterion suits a developer who wants to write a page in TypeScript today and port a hot function to Rust or C tomorrow without touching the build setup, and who wants a static build they can upload to a CDN. It is the wrong choice if you need a production dev server, because the README says not to run it in production for performance and security reasons, and it is the wrong choice if you need import maps or HTTP2, which the README lists as upcoming features. Verify first that the language you want to import is on the supported list, that the bare import resolution works with your node_modules layout, and that a static build produces the MIME types your host needs before you rely on it.

Frequently asked questions

What languages can I import in Zwitterion?

The README lists JavaScript ES2015+, TypeScript, JSON, JSX, TSX, AssemblyScript, Rust, C, C++, WebAssembly Text Format and WebAssembly, with Rust, C and C++ described as basic support. The tagline adds that in the future anything compiling to JavaScript or WebAssembly is intended to work.

Is Zwitterion a bundler?

No. The README states explicitly that Zwitterion is NOT a bundler and eschews bundling for a simpler experience, and it is designed to be an instant replacement for a static file server rather than a build step in front of one.

Can I run Zwitterion in production?

You can, but the README does not recommend it, citing performance and potential security reasons. The recommended path is a static build with zwitterion --build-static, which produces a dist directory you upload to a CDN or static host, adding the application/javascript MIME type for the compiled files.

How do I change the TypeScript compiler options in Zwitterion?

Create a JSON file with your own compilerOptions and point Zwitterion at it with the --tsc-options-file option, for example zwitterion --tsc-options-file tsc-options.json. The defaults set module and target to ES2015.

Official sources

  1. Issues
  2. lastmjs/zwitterion on GitHub
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lastmjs-zwitterion.svg)](https://hysenlabs.com/projects/lastmjs-zwitterion)

Was this useful?