@swiftuijs/twill

Twill extends TypeScript with Swift-inspired syntax: trailing closures, guards, defer and checked switch expressions. Compiles to ordinary JavaScript.
731
Install
npm install @swiftuijs/twill
Latest Version:0.3.1
Vue:3
License:MIT
Last Updated:Oct 15, 2026
Links: GitHub  ·  npm
Maintainer: evecalm

Twill hummingbird Twill

TypeScript. Clearer flow.

Twill is a TypeScript-based language with Swift-inspired syntax extensions, built by forth.ink. It extends TypeScript and TSX with guards, defer, trailing closures and checked switch expressions. Use TypeScript's type system and existing JavaScript libraries; compile to ordinary JavaScript.

Documentation · Why Twill? · Playground · Getting started · AI skill · VS Code extension · Compatibility

async function readOwned(acquire: () => Promise<TextDocument>) {
  const document = await acquire();
  defer {
    await document.close();
  }

  guard const text = await document.readText() else {
    return 'Missing';
  }
  return text.toUpperCase();
}

The successful path stays in view; release sits next to acquisition. Registration is block-scoped, cleanup is awaited explicitly, and return await keeps an owned resource alive until its work finishes. The output uses ordinary JS control flow.

Release line: 0.2. Twill is an experimental language with a tested compiler, editor and build workflow. Start with one module and evaluate it against your application’s requirements. See support and compatibility.

Why use Twill?

  • Make early exits explicit. guard … else requires an exiting failure path. guard const evaluates once, preserves zero and false, and keeps native TS narrowing.
  • Keep cleanup beside ownership. Register defer immediately after acquisition; reached registrations run on scope exit in reverse order, including failures.
  • Catch omitted business states. Match ordinary TS discriminated unions and bind their payloads. twill check verifies expression exhaustiveness when a variant is added.
  • Keep callbacks and composition readable. Trailing closures work with normal callback APIs. In UI files, component closures become native JSX children or lazy Vue slots.
  • Adopt one module at a time. Two-way TS/JS imports, standard framework APIs, generated declarations and optional native source export keep the integration incremental.

TypeScript already expresses these behaviors with branches, try/finally, arrows and unions. Twill supplies a more explicit syntax for recurring tasks; it earns its place when review clarity outweighs an additional compiler integration. Read the code comparisons and tradeoffs.

Small syntax, existing semantics

const names = users.filter {
  .active;
}.map {
  .name;
};

type Outcome = { kind: 'ok'; value: number } | { kind: 'error'; message: string };
function describe(outcome: Outcome): string {
  return switch (outcome) {
    case { kind: 'ok', value }: value.toFixed(2);
    case { kind: 'error', message }: message;
  };
}

Ordinary closures become arrows; guards become branches. Direct-return switch expressions become native switches. Twill 0.2.0 also removes the IIFE from standalone identifier initializers; other expression contexts retain it. Inline emission introduces no runtime library. Dynamic/async defer and scope-sensitive registrations retain callbacks; dynamic registrations use a local stack; general component child collection uses arrays. These costs are documented and measured.

Twill 0.3.0 emits native try/finally for eligible direct synchronous cleanup and reduces repeated editor/configuration work. The older 0.2.0 retains callback-based cleanup. Dynamic registration, general expression lowering and cold initialization still have measured costs; use the performance guide to choose and profile hot paths.

File Use
.twill TypeScript, including JavaScript syntax with optional types
.twillx TSX, native JSX and component closures
.ts, .tsx, .js, .jsx Ordinary source files, retaining their native syntax

The current parser targets documented TypeScript 5.9 syntax; it does not claim support for every valid TypeScript program. Trailing closures also introduce syntax ambiguity rules. Native TS/JS files keep their usual parsing.

Twill and native files can import each other through the build adapters, virtual checker and Node ESM loader. Your normal tsconfig.json supplies types and JSX settings. No twill.config.json is required. Native tsc cannot parse dialect source; use twill check and standard declaration output.

Try it

The playground runs the actual compiler locally, with live highlighting and generated-source inspection. It does not execute your code or type-check project imports.

Install the compiler as a development dependency in your application. Use Node 24 LTS for a new setup; the package supports ^20.19.0 || >=22.12.0. Run from the directory containing your package.json (run npm init -y first for a new project). Default inline emission introduces no language runtime dependency into your application bundle. The optional runtime in 0.2.0 shares dynamic synchronous cleanup when explicitly selected.

pnpm add -D @swiftuijs/twill@0.3.1
# npm install --save-dev @swiftuijs/twill@0.3.1

Add the Vite adapter to your existing configuration:

import { defineConfig } from 'vite';
import twill from '@swiftuijs/twill/vite';

export default defineConfig({ plugins: [twill()] });

Rename one module to .twill, update its imports, include it in your usual tsconfig and run:

pnpm exec twill check -p tsconfig.json

Follow getting started to complete the editor, formatter and linter setup. For React, use the React Vite adapter to keep Fast Refresh working.

Run a script

For a project-local command, install the compiler above, save this as hello.twill and run npm exec -- twill hello.twill 'hello world' (or pnpm exec twill hello.twill 'hello world'):

#!/usr/bin/env twill
console.log(process.argv.slice(2));

Expected output is [ 'hello world' ]. On Linux/macOS, use chmod +x hello.twill then npm exec --call './hello.twill "hello world"' or pnpm exec ./hello.twill 'hello world' for a local shebang. For direct ./hello.twill execution, install @swiftuijs/twill globally with npm install --global @swiftuijs/twill@0.3.1 and keep twill on PATH. Windows uses the explicit CLI.

Install @swiftuijs/twill-shell in the script's project only when using its subprocess API; a global compiler does not install project imports. Follow shell scripting for complete installation, a portable subprocess example, Node type checking and troubleshooting. Native JS/TS consumers need only the SDK.

A complete working loop

Tool What it provides
Compiler and checker Syntax lowering, original-source diagnostics, native TS types and generated declarations
VS Code extension Completion, signatures, hover, definitions, references, mixed-file rename, safe quick fixes and formatting
Formatter and linter Independent Prettier and ESLint packages, including optional type-aware exhaustive linting
Build and execution Vite, Rollup, esbuild, webpack and Rspack adapters; an opt-in Node ESM loader
Debugging Source maps, generated-source viewer, project diagnostics and original-source Node breakpoints
Web highlighting @swiftuijs/twill-highlight: browser/SSR Shiki integration and portable TextMate grammars
Source export twill export: optional checked source export to formatted native TS/TSX

React children, render props, Vue slots and component libraries use native framework contracts. There are no component registries or Twill-specific wrapping APIs.

SwiftUI.js (@swiftuijs/ui) is our sibling project: a SwiftUI-inspired React component library. Its components work with Twill's native JSX output. Its website covers installation and styles; see its source repository for the components.

Independent examples cover ordinary workflows, mixed sources, cleanup, React, Vue and library output. The React framework source study also compares a pinned client-core rewrite with native JavaScript.

Packages

The compiler includes adapters for your existing bundler:

Host Adapter import Native TS/JSX owner
Vite @swiftuijs/twill/vite Vite
Rollup @swiftuijs/twill/rollup Twill adapter by default
Webpack @swiftuijs/twill/webpack Twill adapter by default
Rspack @swiftuijs/twill/rspack Twill adapter by default
esbuild @swiftuijs/twill/esbuild esbuild

Use @swiftuijs/twill/vite-react for React Fast Refresh. The build guide records tested host versions, watch/incremental workflows and the 0.2.0 configuration-refresh behavior. Builds remain transpile-only; run twill check separately.

Install only the tools your project needs. Keep Twill packages on the same release version.

Package Purpose
@swiftuijs/twill Compiler, twill CLI, checker, build adapters and Node loader
@swiftuijs/twill-formatter Prettier plugin and browser-compatible formatting API
@swiftuijs/twill-linter ESLint flat configurations and source-mapped fixes
@swiftuijs/twill-export Optional twill export command for native TS/TSX output
@swiftuijs/twill-highlight Browser/SSR Shiki integration and TextMate grammars
@swiftuijs/twill-shell Native argv execution, bounded output and owned process teardown
@swiftuijs/twill-runtime Optional, versioned synchronous cleanup helpers

The shell scripting toolkit adds #!/usr/bin/env twill executable scripts and content-validated startup caching in 0.2.0. Install/import @swiftuijs/twill-shell for subprocesses; native JS/TS consumers need no compiler. npm 0.2.0 still uses the retired Rust addon. Twill 0.3.0 simplifies the SDK to Node spawn/streams with zero production dependencies; cancel and await work before worker/process exit. See the version and ownership boundary.

Install the Twill VS Code extension from Marketplace, or run code --install-extension forth-ink.twill. The extension bundles its editing tools. Your application installs the compiler for builds and whole-project checks. See the editor guide for configuration.

Choosing it responsibly

Twill is a useful candidate for nullable workflows, owned resources, typed state handling and callback-heavy code. It is still an experimental dialect. Parser coverage follows a tested TS 5.9 contract; arbitrary workspace TS versions, general editor refactoring, native incremental tsc --build, a CommonJS Twill loader and every SSR framework are outside its current guarantees.

Representative native/dialect bundles are compared for behavior and byte size; release artifacts have enforced size budgets. Synthetic benchmarks do not establish whole-application speed or team productivity. Profile your actual workload and exercise failure paths. See compatibility, performance and gradual adoption.

AI assistance

Install the official Twill AI skill with npx skills add swiftuijs/twill --skill twill. It guides Codex, Claude Code and other Agent Skills clients through application syntax, resource ownership, integrations and verification while distinguishing released features from source prototypes. The skill does not install the compiler or other project tools.

Contributing

The repository is a pnpm workspace with Vite builds. Tooling packages and editor integrations are developed in Twill, exercising the same public adapters, checker and declaration emitter as applications. The compiler remains bootstrapped from JS/TS; see developing tooling in Twill. See the development guide for checkout, dependency and build commands. Compiler, formatter, linter, export, highlight, optional runtime and shell packages, editor packages, examples and the English documentation site own their code and tests.

pnpm check
pnpm editor:test          # requires a graphical host; use xvfb-run -a on headless Linux
pnpm test:browser        # requires Playwright Chromium
pnpm docs:dev

See contributing, architecture, language design and release instructions. MIT licensed.

Added in 0.2.0

Twill 0.2.0 adds if const value = lookup() { use(value); } with branch-local nullish bindings and native destructuring. Group trailing calls in initializers, following Swift’s condition boundary. It emits native branches without a helper, closure or optional wrapper. See syntax and RFC 0018.

Twill 0.2.0 includes associated-value enums and match expressions: match (state) { case State.loaded({ value }): value; default: 0; }. Named payload bindings and erased factory descriptors compile to native switches without calling factories. Native switch statements retain JS fallthrough; match and switch expressions return one result without fallthrough. Use the coordinated 0.2.0 packages and extension. Compiler, formatter, linter, highlighter, export and editor changes are validated together. See syntax and compatibility and RFC 0016.

For Node scripts, Twill 0.2.0 supports #!/usr/bin/env twill, twill script.twill and twill run script.twill, with Swift-inspired guards, callbacks and cleanup. The Node ESM loader remains available through node --import @swiftuijs/twill/register. The shell SDK adds native argv execution, bounded output and process ownership without command compilation. It works independently with ordinary JS/TS; scoped streams, pipelines and shell templates remain deferred.

Related Packages

Version History

Version Released Vue License
0.3.1 ^3.3.0 MIT
0.3.0 ^3.3.0 MIT
0.2.0 ^3.3.0 MIT
0.1.2 ^3.3.0 MIT
0.1.1 ^3.3.0 MIT