Hem compiles functional application code to TypeScript that you can inspect, review, and import from existing TypeScript modules.
When Hem fits
Hem is useful when a module benefits from exhaustive variants, expression-oriented branching, immutable transformation stages, or explicit carrier propagation. These constructs remove repeated source scaffolding while exposing ordinary TypeScript functions and data to callers. The optional Effect extension also gives application operations a compact syntax for typed failures, services, concurrency, and cleanup.
Adoption adds a compiler step, a language server, and generated-file integration. Start with a bounded module whose tests and TypeScript consumers can show whether the new source is easier to maintain. Compare both the authored code and the work needed to build, edit, test, and debug it. The first-module tutorial demonstrates the basic workflow; the migration guide describes contract and behavior checks for an existing module.
Strict TypeScript settings, lint rules, and ordinary libraries may meet a project’s needs without another source language. Hem is a better fit when its expression syntax and checked composition rules provide enough benefit to justify maintaining that additional toolchain. Syntax preferences alone do not establish that tradeoff.
Ordinary values and explicit libraries
Variants become discriminated unions with a _tag field and constructor functions. The optional type ?T uses JavaScript’s null and undefined values without a wrapper.
Core syntax does not require an implicit Hem runtime. Explicit library imports still run in your application: @hem-lang/std provides boxed Option and Result values and their operations. Those boxes are intentional. Use ?T for platform-shaped absence, or a boxed carrier when you need nested absence, combinators, or typed failure data. See standard carriers.
The optional Effect extension emits imports from the configured Effect transport. Effect supplies execution, interruption, and resource management; Hem supplies syntax and compiler checks.
TypeScript types with additional checks
Hem uses the TypeScript checker and your project’s module resolution, libraries, paths, and JSX configuration. Its private checker settings enable strict checking without changing the settings for handwritten TypeScript.
Hem adds rules such as boolean-only conditions, exhaustive matching, explicit mutation, and checked foreign boundaries. These rules prevent specific mistakes; they do not verify the implementations behind imported declarations or make JavaScript universally sound. See safety.
Adopting one module at a time
Emitted modules expose ordinary TypeScript functions and data. You can use TypeScript libraries without duplicate bindings or a separate foreign function interface.
Your build must compile Hem before consuming its output. With Vite, the Hem plugin handles compilation and source imports. Other tools can consume the emitted modules using their normal resolution rules. See TypeScript interoperability.
The output also gives you a way to leave Hem: retain the generated TypeScript as source, adjust the build and imports, and keep any libraries the code uses.
Sibling output
foo.hem emits foo.ts or foo.tsx beside the source. Keeping those files together preserves relative layout in mixed Hem and TypeScript projects.
Generated files carry a banner and are listed in .hem/generated-files.json. Use that manifest to distinguish generated siblings from handwritten TypeScript. See generated-file integration.
Readable output can include temporary variables and cleanup scaffolding. Those preserve source evaluation order and resource behavior; readable does not mean identical to a hand-written translation.
Source diagnostics
Hem owns its output representation and printer so it can record source-to-output positions while printing. The checker uses those positions to map TypeScript diagnostics back to Hem source.
The printer also supplies separate version-3 runtime source maps. Generated siblings carry an inline map to the authored .hem file, including the banner’s line offset. Node’s --enable-source-maps and the Vite plugin can report authored runtime locations; rewritten scaffolding points to its nearest authored construct. Diagnostic/editor maps keep their own span and provenance contract.
Production source maps must survive the downstream build. Vite composes them when build.sourcemap is enabled; plain tsc needs an explicit composition step. See runtime debugging for the supported paths and source-content controls.
Related
- How Hem fits your toolchain explains build-time, editor, and runtime dependencies.
- Removed constructs lists excluded syntax and its replacements.