What is the process for transforming tsconfig.json into a configuration object using the TypeScript API?

Does a TypeScript API exist for parsing tsconfig.json and jsconfig.json files into complete TypeScript configuration objects, with all properties filled in either from the file itself or default values?

import ts from "typescript";

const tsconfig = await ts.parseConfigFile('./tsconfig.json');

tsconfig.compilerOptions.noEmit;
// false
// (or whatever it is set to in *.json)

Is there such kind of function in typescript npm package? It seems hard to find in the documentation or IntelliSense suggestions.

I have come across a similar question, but it remains unanswered and the referenced solution is quite old. Surely, there must have been updates to the API since then.


While I could write helpers to extract only the necessary properties from the config file, more complex scenarios like using Project References or extending parent config objects pose a challenge. Additionally, parsing files with comments and trailing commas adds complexity to the task. Rewriting something that likely already exists feels unnecessary.

  • there are more exotic use cases (such as using Project References and/or extending parent config object), in which such a solution would not work;
  • the aforementioned *.json objects might have comments and trailing commas, so it is not trivial to even parse the file in the first place;
  • it would be weird to rewrite something that is known to be written already.

Answer №1

Edit

Explained in a comment below by @Hiroki Osame, the use of ts.parseJsonConfigFileContent in this answer allowed for automatic handling of the extends without manual intervention.

Another insightful contribution can be found in @Simon Buchan's response on this page.

Short Answer

This function reads compiler options from a tsconfig file and manages tsconfig extends keyword inheritance seamlessly.

function getCompilerOptionsJSONFollowExtends(filename: string): {[key: string]: any} {
  let compopts = {};
  const config = ts.readConfigFile(filename, ts.sys.readFile).config;
  if (config.extends) {
    const rqrpath = require.resolve(config.extends);
    compopts = getCompilerOptionsJSONFollowExtends(rqrpath);
  }
  return {
    ...compopts,
    ...config.compilerOptions,
  };
}

The resulting JSON can be converted to type ts.CompilerOptions using:

const jsonCompopts = getCompilerOptionsJSONFollowExtends('tsconfig.json')
const tmp = ts.convertCompilerOptionsFromJson(jsonCompopts,'')
if (tmp.errors.length>0) throw new Error('...')
const tsCompopts: ts.CompilerOptions = tmp.options

TL;DR

Key functions include:

ts.readConfigFile
ts.parseConfigFileTextToJson
ts.convertCompilerOptionsFromJson
ts.parseJsonConfigFileContent
ts.parseJsonSourceFileConfigFileContent

Only the first three functions are discussed here:

ts.readConfigFile

console.log(
  JSON.stringify(
    ts.readConfigFile('./tsconfig.base.json', ts.sys.readFile),
    null,
    2
  )
);

The content of tsconfig.base.json:

{
  "extends": "@tsconfig/node14/tsconfig.json",
  "compilerOptions": {
    "declaration": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "lib": ["es2020"]
  }
}

ts.parseConfigFileTextToJson

const parsed2 = ts.parseConfigFileTextToJson(
  '',
  `
  {
    "extends": "@tsconfig/node14/tsconfig.json",
    "compilerOptions": {
      "declaration": true,
      "skipLibCheck": true,
      "sourceMap": true,
      "lib": ["es2020"]
    }
  }
  `);
console.log(JSON.stringify(parsed2, null, 2));

ts.convertCompilerOptionsFromJson

const parsed1 = ts.convertCompilerOptionsFromJson(
  {
    lib: ['es2020'],
    module: 'commonjs',
    target: 'es2020',
  },
  ''
);
console.log(JSON.stringify(parsed1, null, 2));

Discussion / Extends

When dealing with the extends keyword:

  • ts.readConfigFile
  • ts.convertCompilerOptionsFromJson

are useful. However, automatic extension following requires more complex logic.

How to Follow Extends Using API

A sample implementation:

function getCompileOptionsJSONFollowExtends(filename: string): {[key: string]: any} {
  let compopts: ts.CompilerOptions = {};
  const config = ts.readConfigFile(filename, ts.sys.readFile).config;
  if (config.extends) {
    const rqrpath = require.resolve(config.extends);
    compopts = getCompileOptionsJSONFollowExtends(rqrpath);
  }
  compopts = {
    ...compopts,
    ...config.compilerOptions,
  };
  return compopts;
}

Test run:

const jsonCompopts = getCompileOptionsJSONFollowExtends('./tsconfig.base.json')
console.log(JSON.stringify(jsonCompopts,null,2))
const tsCompopts = ts.convertCompilerOptionsFromJson(jsonCompopts,'')
console.log(JSON.stringify(tsCompopts,null,2))
console.log('');

Answer №2

It may seem a bit confusing, but the recommended approach is to utilize

ts.getParsedCommandLineOfConfigFile()
:

> ts.getParsedCommandLineOfConfigFile('tsconfig.json', {}, ts.sys)
{
  options: {
    moduleResolution: 2,
    module: 99,
    target: 6,
    lib: [ 'lib.es2019.d.ts' ],
    types: [ 'node' ],
    strict: true,
    sourceMap: true,
    esModuleInterop: true,
    importsNotUsedAsValues: 2,
    importHelpers: true,
    incremental: true,
    composite: true,
    skipLibCheck: true,
    noEmit: true,
    configFilePath: 'C:/code/.../tsconfig.json'
  },
  watchOptions: undefined,
  fileNames: [
    'C:/code/.../src/index.tsx',
...

The third argument is actually a ts.ParseConfigFileHost, so it's advisable to manually implement that (using methods from ts.sys)

If you've already parsed the configuration and it's embedded in a larger file, you can also use

ts.parseJsonFileContent(tsconfigContent, ts.sys, baseDir, {}, errorMessageFileName)
.

Answer №3

It seems like there might be a misunderstanding in your issue, but wouldn't it be sufficient to simply load the contents of the tsconfig.json file and then parse it using JSON.parse?

const fs = require('fs');

const txt = fs.readFileSync('./tsconfig.json');
try {
   const obj = JSON.parse(txt);
   console.log(obj);
} catch (e) {
   console.error(e);
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How can Observables be designed to exhibit both synchronous and asynchronous behavior?

From: Understanding the Contrasts Between Promises and Observables In contrast, a Promise consistently operates asynchronously, while an Observable can function in synchronous or asynchronous manners. This presents the opportunity to manipulate code in ...

Make Ionic 2 Navbar exclusively utilize setRoot() instead of pop()

When navigating to a different page, the ion-navbar component automatically includes a back button that uses the pop() method to return to the previous page. Is there a way to modify this behavior so that it utilizes the setRoot() method instead of pop(), ...

QueryBuilder in TypeORM can be utilized to select solely a particular key-value pair from a column with the JSONB

Consider a basic queryBuilder implementation: const workout = await this.workouts .createQueryBuilder('workout') .select(['workout.uuid', `workout.name ->> 'it'` as name]) .where(&apos ...

What is the reason TypeScript does not display an error when assigning a primitive string to an object String?

From my understanding in TypeScript, string is considered as a primitive type while String is an object. Let's take a look at the code snippet below: let s: string = new String("foo"); // ERROR let S: String = "foo"; // OK It's interesting to ...

Angular Material's dialog modal swiftly closes without delay

Could you please explain why the modal opens and then closes instantly when I click on the Create Project button? https://example.com/edit/angular-code I am trying to display a component within the modal using Angular Material. portafolio.component.ts ...

Having difficulty running lint on Vue 3 TypeScript application, but building process is successful

We are encountering an issue at the moment. We can successfully build our app, but we are facing challenges with linting using the vue tools (vue-cli-service ...). The hot-reloading feature works initially, but upon saving a file, we receive an error mess ...

Combining properties from one array with another in typescript: A step-by-step guide

My goal is to iterate through an array and add specific properties to another array. Here is the initial array: const data = [ { "id":"001", "name":"John Doe", "city":"New York&quo ...

What is the best way to convert Observable<Observable<{...}>[ ]> to Observable<{...}[ ]>?

I am currently working on merging two observable objects into a single observable to access data from both. These observables represent documents from separate collections in a NoSQL database, specifically a Cloud Firestore database. Both collections have ...

Error in Typescript: _this2.onClick is not a callable function

For react-native development, I rely on typescript. In my project, List and ListItem components come from NativeBase, with ListItem resembling a TouchableOpacity. ... public onClick = () => this.props.navigation.navigate("NextScreen"); public _renderRo ...

After the build/export process, the NextJS routing fails to function properly when I manually insert the URL

While testing my application on localhost using npm run dev, everything works normally. I can access routes like localhost:3000/settings/account and they render correctly. However, after running npm run build and npm run export, testing with serve -s out a ...

Building Custom Request Types for a Personalized Express Router in TypeScript (TS2769)

I've been facing challenges integrating custom Request types with TypeScript. Within my application, I have both public and private routes. The public routes utilize the Request type from Express. On the other hand, the private routes make use of a ...

Placeholder for Images in Next.js with Typescript

Recently, I've been exploring Next.js with TypeScript and trying to utilize the image component with the placeholder prop. Unfortunately, I keep encountering an error in my implementation. https://i.sstatic.net/rJpbB.png Take a look at my code below ...

What is the process for recording information using a static method in TypeScript within a class?

For my school project, I'm struggling to retrieve the names from a class using a method. One class creates monsters and another extends it. abstract class genMonster { constructor( public id: string, public name: string, public weaknesse ...

The enigma of the mysterious karma provider error

As a newcomer to unit testing in JavaScript, AngularJS, and Karma, I have successfully written passing tests for controllers. However, when trying to test services, I encountered an error: Unknown provider <- nProvider <- User. The User service is th ...

Understanding the specific types of subclasses derived from an abstract generic class in Typescript

There is a Base generic class: abstract class BaseClass<T> { abstract itemArray: Array<T>; static getName(): string { throw new Error(`BaseClass - 'getName' was not overridden!`); } internalLogic() {} } and its inherito ...

What are the steps to code this in Angular14 syntax?

Here's the code snippet: constructor(obj?: any) { this.id = obj && obj.id || null; this.title = obj && obj.title || null; this.description = obj && obj.description || null; this.thumbnailUrl = obj && obj.thumbnailUrl || null; this. ...

Mapping an array of objects using dynamically generated column names

If I have an array of objects containing country, state, city data, how can I utilize the .map method to retrieve unique countries, states, or cities based on specific criteria? How would I create a method that accepts a column name and maps it to return ...

Inspecting tRPC routing for examination purposes

Introduction Within my API, it is essential to authenticate the caller following input validation. The authorization for certain endpoints relies on details provided in the input parameters, such as the specific server-side resource being accessed and the ...

What is the recommended way to use the async pipe to consume a single Observable property in a view from an Angular2

Suppose I have a component with the following property: import { Component, OnInit } from 'angular2/core'; import { CarService } from 'someservice'; @Component({ selector: 'car-detail', templateUrl: './app/cars/ ...

Exploring deep nested components and elements in Angular for a targeted specific functionality

Is it possible to apply the ng deep css class to only one specific checkbox in my component, rather than all checkboxes? I want to customize just one checkbox and leave the others unchanged. How can this be achieved? Thank you. I need the CSS modificatio ...