twoslash

Markup format that runs real TypeScript compiler checks over code samples to extract types, errors, and completions.

Library
npm
v0.3.9
938stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
58/100Fair
Development Activity32
Maintenance64
Community48
Maturity48
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
85/100Excellent
Architecture80
Code Quality85
Innovation85
Learning Curve90

Twoslash is the successor to the original @typescript/twoslash project used by the TypeScript team to power rich, compiler-verified code samples on the TypeScript website and handbook. Instead of relying on hand-annotated syntax highlighting, Twoslash spins up a real TypeScript language service against your code sample and pulls out hover types, diagnostics, autocomplete suggestions, and even emitted JavaScript directly from the compiler.

Authors write plain TypeScript with inline sigil comments — ^? to query a type, ^| for completions, @errors: to assert diagnostics, ---cut--- to trim setup code from the rendered output — and Twoslash returns a structured result that integration packages like the Shiki-based renderer turn into annotated HTML. The core twoslash package is renderer-agnostic; a family of sibling packages (twoslash-vue, twoslash-svelte, twoslash-cdn, twoslash-eslint, twoslash-remote) plug the same engine into different frameworks and workflows.

What You Get

  • Structured type/hover/diagnostic extraction via createTwoslasher and twoslasher functions
  • Inline notation system for cutting code, overriding compiler flags, and asserting expected errors
  • Cached language-service instances for 5-20x faster repeated runs across many samples
  • Compatibility layer (twoslasherLegacy) for migrating from @typescript/twoslash
  • Renderer-agnostic core consumed by Shiki, Vue, Svelte, CDN, and ESLint integrations

Common Use Cases

  • Generating compiler-verified code samples for documentation sites and handbooks
  • Asserting expected TypeScript errors in blog posts and tutorials so they don’t rot
  • Building custom syntax-highlighting pipelines that need real hover/type info
  • Testing that documented code samples still compile against current TypeScript versions

Under The Hood

Architecture Twoslash’s core lives in packages/twoslash/src/core.ts, which exports createTwoslasher — a factory that stands up a cached map of @typescript/vfs virtual TypeScript environments keyed by a hash of compilerOptions (getObjectHash from utils.ts), so repeated calls against the same tsconfig reuse an already-populated language service instead of re-parsing lib files. The public twoslasher(code, ext, options) entry point runs a fixed pipeline over each call: find inline flag notations (findFlagNotations) and apply them to compilerOptions/handbookOptions, validate there are no unknown flags (unless noErrorValidation), split the code into virtual files (splitFiles) written into the VFS, extract cut ranges and position queries via twoslash-protocol helpers, then walk the language service for diagnostics/hovers/completions and finally re-map all positions back through removeCodeRanges once cuts are applied. index.ts is a thin wrapper around core.ts that injects the real typescript module and cwd and re-exports a legacy.ts shim (convertLegacyOptions/convertLegacyReturn) so the newer core can still satisfy the original @typescript/twoslash consumers. Because compilerOptions resolution and position-remapping are centralized in this single pipeline, changing the core cut-then-remap abstraction would ripple through every sibling package that consumes the shared TwoslashReturn shape from twoslash-protocol.

Tech Stack The monorepo is pnpm-workspace and catalog managed (pnpm-workspace.yaml, pnpm@11.5.1), built with unbuild per-package (build.config.ts) and typechecked/linted via @antfu/eslint-config (eslint.config.js) with simple-git-hooks plus lint-staged enforcing eslint —fix pre-commit. The twoslash package itself only depends on @typescript/vfs (for the in-memory TypeScript environment/System) and the sibling twoslash-protocol workspace package (shared types and position utilities), with typescript itself declared as a broad peer dependency so consumers bring their own compiler version. Tests run on Vitest with a separate bench script for performance benchmarking, and docs are a VitePress site that doubles as the integration-test surface for real-world usage examples.

Code Quality The test suite is large and fixture-driven — the test directory has well over a hundred files including example fixtures (README-facing samples) and edge-case fixtures (completions, cut-file errors, custom paths), each asserting the exact TwoslashReturn shape Vitest produces, giving strong regression coverage for the notation parser and position remapping. Source is fully typed TypeScript with a well-defined public type surface (CreateTwoslashOptions, TwoslashExecuteOptions, TwoslashReturn), errors are raised through a dedicated TwoslashError class carrying a title/description/recommendation triple rather than being swallowed, and CI runs separate lint, typecheck-plus-build, and test jobs on every push and pull request. Naming is consistent throughout, and lint-staged plus the shared eslint config enforce style automatically pre-commit.

API Design Twoslash’s public API is deliberately small — two functions (createTwoslasher, twoslasher) cover both the cached and one-shot use cases, and the type-queries-as-inline-comments notation means a documentation author never touches the JS API directly, only writes plain TypeScript with sigil comments that any Markdown-based doc tool can embed. The library’s genuine differentiator versus hand-rolled syntax highlighters is that every rendered artifact — hover type, diagnostic, completion, emitted file — is produced by an actual running TypeScript language service rather than a static grammar, so documentation samples are guaranteed to compile and stay accurate as both the user’s code and the TypeScript compiler evolve, the same guarantee the TypeScript team relies on for the official handbook. The compatibility layer is a good developer-experience signal too: it lets consumers of the predecessor package adopt the rewritten core without a breaking migration.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search