Hem has no configuration file of its own. The nearest tsconfig.json is the project file for the CLI, the compiler, the language server, and the Vite plugin.
Project discovery uses TypeScript’s own config resolution, so extends, files, include, exclude, and all standard compilerOptions behave as they do for tsc. Hem adds .hem as a recognized project extension.
The hem key
{
"hem": { "effectImportSource": "effect" }
}| Option | Type | Default | Meaning |
|---|---|---|---|
hem.effectImportSource |
Non-empty string | Omitted | Enables the Effect extension and names the module Hem probes and emits imports from |
With the option omitted, the Effect extension is inactive and emitted output contains no Effect imports.
The supported preview sets this option to "effect" and uses
exactly effect@4.0.0. Accepting a module name here is not a support
guarantee. See Effect setup for the installation and
version contract.
A hem value that is not an object, or an effectImportSource that is empty or not a string, makes the project configuration invalid.
What Hem reads from compilerOptions
Module resolution, libraries, JSX, paths, target, and project membership all come from the TypeScript configuration. Two of them have Hem-visible consequences.
Project membership comes from files and include. A .hem file outside that coverage is refused rather than checked.
JSX configuration is TypeScript’s. "jsx": "react-jsx" works against @types/react 19 as written. "jsx": "preserve" requires "jsxImportSource": "react" alongside it; bare "preserve" fails with TypeScript’s own TS7026, not a Hem diagnostic.
The strictness overlay
Hem’s private no-emit checker forces a fixed set of options regardless of what the project configures:
strictstrictNullChecksnoUncheckedIndexedAccessnoEmitallowImportingTsExtensions
The overlay applies only inside that checker. An existing application build does not have to adopt these options, and Hem code still receives its guarantees. noUncheckedIndexedAccess is what makes xs[i] a ?T.
Vite plugin options
import { defineConfig } from "vite";
import { hem } from "@hem-lang/vite-plugin";
export default defineConfig({
plugins: [hem({ effectImportSource: "effect" })],
});| Option | Type | Default | Meaning |
|---|---|---|---|
effectImportSource |
string |
Reads hem.effectImportSource from tsconfig |
Vite-scoped override of the Effect transport |
root |
string |
Vite’s resolved config.root |
Starting directory for tsconfig discovery |
Before Vite resolves its configuration, the provisional root is the process working directory.
The plugin compiles each .hem file to a .ts or .tsx sibling on disk and resolves .hem imports to those siblings. It owns no JSX transform and has no JSX option: Vite and the framework keep that responsibility.
Formatter options
@hem-lang/format accepts printWidth, defaulting to 100. This is an API-level option. No tsconfig key and no CLI flag exposes it.
The generated-file manifest
.hem/generated-files.json is written and maintained by Hem, not authored. It carries a fixed schema version, the generator name, ownership policy values, and one entry per source and sibling pair.
The manifest is deterministic and intended to be committed. hem generated verify checks it, hem generated activate canonicalizes it, and hem generated exclusions prints its siblings for other tools’ configuration.
File extensions and output
A .hem file emits .tsx when it contains JSX and .ts otherwise. Relative specifiers ending in .hem rewrite to .ts at emit.
A foo.hem sitting beside a handwritten foo.ts is a build error (HEM3002).
Every emitted file is a module. A source file with no imports or exports still emits a trailing export {}, so its top-level bindings never join the ambient global scope.
Normative source
Specification 0.2, §1 (compilation model), §11 (JSX), §13 (modules and interop), §14.1 (extension activation).