typescripttypescript-compiler-api

Typescript Compiler API - Extract Exported Typescript Object


I am trying to use the Typescript Compiler API and local file system to retrieve the exported object of a typescript config file and use it in node.js.

Given a simplified example like the below:

// test.config.ts

type Config = {
  hello: string;
};

const config: Config = {
  hello: "world",
};

export default config;

In another file how would I use the Compiler API to extract the exported object into a variable so I can use it in js?

//another-file.js

const source = "./test.config.ts"

let exportedObject = /* some Compiler API function(s) to retrieve exported object from 'test.config.ts' */

console.log(exportedObject.hello)
// logs "world"

I've been able to load a program and source file - but I'm a bit lost on what do do next. Any documentation/resources would be greatly appreciated!

//another-file.js
const source = "./test.config.ts";

const program = ts.createProgram([source]);
const sourceFile = program.getSourceFile(source);

Solution

  • If you want to get the exports of the module, you can use the TypeChecker#getExportsOfModule method:

    const checker = program.getTypeChecker();
    
    const sourceFileSymbol = checker.getSymbolAtLocation(sourceFile)!;
    const exports = checker.getExportsOfModule(sourceFileSymbol);
    

    Then from these exports you can get/check if there's a default export, which will be a ts.Symbol:

    const defaultExportSymbol = exports.find(e => e.escapedName === "default")!;
    

    From that, you can get its type, which will allow you to find its properties:

    const defaultExportType = checker.getTypeOfSymbolAtLocation(
      defaultExportSymbol,
      defaultExportSymbol.declarations![0],
    );
    
    for (const prop of defaultExportType.getProperties()) {
      const propType = checker.getTypeOfSymbolAtLocation(prop, prop.declarations![0]);
      console.log(prop.name); // hello
      console.log(checker.typeToString(propType)); // string
    }
    

    If something is not working, try checking console.log(ts.getPreEmitDiagnostics(program)) to ensure there are no diagnostics in the program.

    Getting config

    To get config, you will need to get the aliased symbol of the default export:

    const configSymbol = checker.getAliasedSymbol(defaultExport);
    

    From that, you can get the variable declaration along with the initializer's object literal expression which when traversing the properties will give you the value:

    const configDecl = configSymbol.declarations![0] as ts.VariableDeclaration;
    const objLit = configDecl.initializer as ts.ObjectLiteralExpression;
    
    for (const prop of objLit.properties) {
      console.log(prop.name!.getText()); // hello
      console.log((prop as ts.PropertyAssignment).initializer.getText()); // "world"
    }
    

    Obviously I'm doing a ton of assertions in this code... the actual code should be written to be more flexible and handle more scenarios.