Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Export assignment and exporting types in TypeScript ambient module

I'm trying to add types for the config module specific to our app. The config module is dynamically generated from a JSON file so it's tricky to type. Since it's a node module, I'm using an ambient module for the typings.

// config.d.ts
declare module 'config' {
  interface AppConfig {
    name: string;
    app_specific_thing: string;
  }
  const config: AppConfig;
  export = config;
}

How do I also export AppConfig so I can use it as a type like so:

import * as config from 'config';

const appConfig: config.AppConfig;

Attempts

  • If I export AppConfig directly in the config module it errors with:

    TS2309: An export assignment cannot be used in a module with other exported elements.

  • If I move AppConfig to another file (e.g. ./app_config) to hold the exports and import them into config.d.ts it errors with:

    TS2439: Import or export declaration in an ambient module declaration cannot reference module through relative module name.

  • If I put the AppConfig export in the same file, but outside the config module, it errors with:

    TS2665: Invalid module name in augmentation. Module 'config' resolves to an untyped module at $PROJ/config/lib/config.js, which cannot be augmented.

This is similar to Typescript error "An export assignment cannot be used in a module with other exported elements." while extending typescript definitions with the requirement that I want to be able to import AppConfig as a type directly in other TS files.

like image 216
Joe Avatar asked Oct 15 '25 07:10

Joe


1 Answers

The answer requires a confusing Typescript concept:

Declaration merging - the compiler merges two separate declarations declared with the same name into a single definition. In this case, we create two declarations of config.

// config.d.ts
declare module 'config' {

  // This nested namespace 'config' will merge with the enclosing 
  // declared namespace 'config'.
  // https://www.typescriptlang.org/docs/handbook/declaration-merging.html
  namespace config {
    interface AppConfig {
      name: string;
      app_specific_thing: string;
      my_enum: FakeEnum;
    }

    interface MyInterface {}

    // See side note below
    type FakeEnum = 'A' | 'B' | 'C';
  }

  const config: AppConfig;
  export = config;
}

You can use the imports like so:

import * as config from 'config';
import { FakeEnum, MyInterface } from 'config';

As a side note, you cannot use enums with an ambient module (the declare module 'config') because enums compile to a JS object and you can't add new object to a module you don't control. You can work around the issue by faking an enum with a union type:

type FakeEnum = 'A' | 'B' | 'C';
like image 95
Joe Avatar answered Oct 18 '25 13:10

Joe



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!