0
votes

I'd like to write a project in Typescript. The project builds on top of the Typescript compiler, so I'm using typescript as a library as well, which (AFAIK) is a CommonJS library. While the project is intended to be ran on Node (not in the browser), I'd like to structure this project to use ES Modules as much as possible. In other words, I set "type": "module" in the package.json.

Example project

I have created a minimally reproducible example here: https://github.com/evertheylen/test-typescript-import. You can run a file with tsc && node build/test-typescript-compile-[...].js. There's two approaches:

1. Standard Typescript import

See the file test-typescript-compile-with-import.ts:

import * as ts from "typescript";
// This will print: `The ts object has keys: ['default']`
console.log("The ts object has keys:", Object.keys(ts));
// This will of course fail
console.log(ts.createProgram(['foobar.ts'], {noEmitOnError: true}).emit());

Here, the Typescript static compilation checks work (i.e. ts.createProgram is recognized with the proper types). However, at runtime the ts object only has a single default key (i.e. in order to actually run createProgram I'd have to write ts.default.createProgram which Typescript does not allow).

2. Use require

See the file test-typescript-compile-with-require.ts (also see the createRequire docs):

import { createRequire } from 'module';
const require = createRequire(import.meta.url);

const ts = require("typescript");
// This will print: `The ts object has keys: <lots of them>`
console.log("The ts object has keys:", Object.keys(ts));
// This will succeed!
console.log(ts.createProgram(['foobar.ts'], {noEmitOnError: true}).emit());
// The problem is that the 'ts' object is of type any, so (for example)
// the following typo won't be caught at compile time:
console.log(ts.createProogram(['foobar.ts'], {noEmitOnError: true}).emit());

This works, but Typescript does not recognize it and the ts object is of type any, which is not what I want (as is clear from the example).

Possible solutions

  • Typescript is not going to become available as ES modules for a while, see this issue.
  • Use approach (1) but set ts = ts.default in some way (maybe with a @ts-ignore). I couldn't get this to work, Node will complain about this. It also feels very hacky to import a CommonJS library as if it were an ES module.
  • Use approach (2) but somehow convince Typescript that this ts object follows the same type as if I imported typescript. I read into various usages of declare and .d.ts files but couldn't come up with anything.
1

1 Answers

0
votes

I feel a bit stupid now, I did a search for "createRequire typescript", and encountered this commit in the ts-node project: https://github.com/TypeStrong/ts-node/commit/a7aa0af9aefae1a7d801bbfe969148866c852a5c. It showed the use of typeof when using require.

The solution in my case boils down to:

import { createRequire } from 'module';
const require = createRequire(import.meta.url);

import * as _ts from "typescript";
const ts: typeof _ts = require("typescript");

Edit: while this did solve the typechecks for typos like createProogram, it does not actually fully solve it. In particular, you can not type something like options: ts.CompilerOptions as ts is not seen as a namespace (error TS2503: Cannot find namespace 'ts'.).