# TypeScript and modules

> What Elide's TypeScript type stripping accepts and rejects, JSX, type checking with tsc and @elide-dev/types, and how ES modules, CommonJS, JSON and exports resolve.

Elide runs `.ts`, `.mts`, `.cts` and `.tsx` files by removing their types; it does not type-check them or read
`tsconfig.json`.

## TypeScript syntax

| Syntax | Status |
| --- | --- |
| Type annotations, interfaces, `type`, generics, `as`, `satisfies`, `!` | Removed |
| `import type`, `import { type X }` | Removed |
| `enum`, `const enum` | Compiled to objects |
| `namespace` | Compiled |
| Constructor parameter properties (`constructor(public x: number)`) | Compiled |
| `abstract`, `declare` fields, `override`, `accessor` | Supported |
| `import fs = require("node:fs")` | Supported |
| Decorators on classes and methods | Standard decorators: each receives `(value, context)` |
| `experimentalDecorators`-style decorators | Not supported; they receive standard arguments |
| Parameter decorators (`constructor(@inject x)`) | `SyntaxError: … expected BindingIdentifier or BindingPattern` |
| `<string>value` type assertions | `Precompiler failed with 1 diagnostic(s): … [ERROR] Unexpected token`; use `value as string` |
| `export = value` | `SyntaxError: … Expected an operand but found export` |
| `tsconfig.json` `paths` | Ignored: `TypeError: Module not found: '@lib/math'` |

Imports may name the `.ts` file, a `.js` extension, or no extension; all three resolve to `math.ts`:

```ts
import { add } from "./src/lib/math.ts";
import { add as add2 } from "./src/lib/math.js";
import { add as add3 } from "./src/lib/math";
console.log(add(1, 2), add2(2, 2), add3(3, 3));
```

```text
3 4 6
```

## JSX

`.tsx` files use the automatic JSX runtime and import `react/jsx-runtime`. Install React yourself:

```tsx
// app.tsx
import { renderToString } from "react-dom/server";

function Hello({ name }: { name: string }) {
  return <p>hello {name}</p>;
}

console.log(renderToString(<Hello name="elide" />));
```

```bash
elide install --ecosystems=npm   # react, react-dom in package.json
elide app.tsx
```

```text
<p>hello <!-- -->elide</p>
```

| Input | Result |
| --- | --- |
| `/** @jsxImportSource preact */` at the top of a `.tsx` file | Imports `preact/jsx-runtime` |
| `jsxImportSource` in `tsconfig.json` | Ignored; React is used |
| `/** @jsx h */` | Ignored; React elements are created |
| JSX in a `.jsx` file | `SyntaxError: … Expected an operand but found <`; rename it to `.tsx` |

## Check types

Install TypeScript and run its compiler through Elide:

```json
{
  "scripts": { "typecheck": "tsc --noEmit" },
  "devDependencies": { "typescript": "^5.9.0", "@types/node": "^24.0.0" }
}
```

```json
{
  "compilerOptions": {
    "module": "nodenext",
    "target": "esnext",
    "strict": true,
    "noEmit": true,
    "allowImportingTsExtensions": true,
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true,
    "types": ["node"]
  }
}
```

```bash
elide install --ecosystems=npm
elide run typecheck              # or: elide run tsc
```

```text
bad-types.ts(1,7): error TS2322: Type 'string' is not assignable to type 'number'.
```

`tsc` exits `2` on type errors. Pass compiler options after `--`: `elide run tsc -- --version`. `erasableSyntaxOnly`
makes `tsc` reject enums, namespaces and parameter properties; drop it if you use them.

## Elide types

`@elide-dev/types` declares Elide's own globals and modules and pulls in `@types/node`. Add it as a dev
dependency and name it in `types`:

```json
{
  "type": "module",
  "scripts": { "typecheck": "tsc --noEmit" },
  "devDependencies": { "typescript": "^5.9.0", "@elide-dev/types": "nightly" }
}
```

```json
{
  "compilerOptions": {
    "module": "nodenext",
    "target": "esnext",
    "strict": true,
    "noEmit": true,
    "allowImportingTsExtensions": true,
    "types": ["@elide-dev/types"]
  }
}
```

```ts
// main.ts
import { Database } from "elide:postgres";
import { readFileSync } from "node:fs";

const bytes: number = readFileSync("package.json").length;
console.log(`Elide ${Elide.version}: ${bytes} bytes`);
export const open = (url: string) => Database.connect(url);
```

```bash
elide install --ecosystems=npm
elide run typecheck
elide main.ts
```

```text
Elide 1.6.0: 149 bytes
```

| API | Declared |
| --- | --- |
| `Elide.version`, `debug`, `release`, `buildMode` | Yes |
| `Elide.targetArchitecture` | Typed as `boolean`; the value is a string |
| `Elide.serve()` | No: `Property 'serve' does not exist` |
| `elide:postgres`, `elide:mysql` | Yes |
| `elide:sqlite`, `elide:cel`, `elide:telemetry` | No |
| `import … from "./script.py"` | Yes, as `any` |
| `node:*` modules | Yes, through `@types/node` |

The `nightly` tag tracks the newest declarations; `latest` is older.

## ES modules and CommonJS

| File | Module system |
| --- | --- |
| `.mjs`, `.mts` | ES module |
| `.cjs`, `.cts` | CommonJS |
| `.ts`, `.tsx` | ES module |
| `.js` | ES module when the nearest `package.json` has `"type": "module"`, else CommonJS |

A `.js` file with `import` statements and no `"type": "module"` fails with `SyntaxError: … Expected an operand but found
import`; add the field or rename the file to `.mjs`.

| Feature | Behavior |
| --- | --- |
| `import lib, { a } from "./lib.cjs"` | `module.exports` is the default export, its properties are named exports |
| `await import("./lib.cjs")` | Works |
| `require("./lib.mjs")` | `Error: require() of ES Module … not supported.` (`ERR_REQUIRE_ESM`); use `await import()` |
| `createRequire(import.meta.url)` | Works |
| `import.meta.url`, `.filename`, `.dirname` | Work |
| `import.meta.main`, `import.meta.resolve` | `undefined` |
| `__dirname` in CommonJS | Works |
| `__filename` in the entry CommonJS file | `"unknown"`; use `module.filename`. Required files get the real path. |
| `__dirname`, `require` in ES modules | Defined, unlike Node; do not rely on them |

## JSON

All of these load `data.json`:

```js
import data from "./data.json" with { type: "json" };
import same from "./data.json";
const viaRequire = require("./data.json"); // CommonJS
```

## Package `exports` and `imports`

| `package.json` field | `import` | `require()` |
| --- | --- | --- |
| `exports` subpaths (`"./feature": …`) | Works | Works |
| `exports` for the package root, with `main` | Works | Uses `main` |
| `exports` for the package root, no `main` | Works | `Error: Cannot find module 'pkg'` |
| `import` and `default` conditions | Work | — |
| A `require` condition listed before `default` | Selected instead of `default` | — |
| Unexported subpath | `Package subpath is not defined by "exports" field` (no `code`) | — |
| `imports` (`"#internal"`) | `TypeError: Packages imports do not define the specifier: '#internal'` | `Error: Cannot find module '#internal'` |

## Samples

- [TypeScript without a build](https://elide.help/docs/code-samples#typescript-without-a-build)

## See also

- [JavaScript and TypeScript](https://elide.help/docs/web)
- [Elide for Node users](https://elide.help/docs/elide-for-node)
- [NPM Installer](https://elide.help/docs/toolchain/web/npm)
