Typescript Namespace reference not working

Viewed 863

I am trying to experiment with typescript namespaces. It works perfectly when i dont import other modules but when I import other modules in my case sort the exported function get cant be used in other file even though they are in same namespace.

import { sort } from "../sort"

namespace Api {
    export async function get(url:string){
        const res = await fetch("api/"+url)
        return await res.json()
    }
    export class Users {
        private uid: string
        constructor(uid: string){
            this.uid = uid
        }
        public async getUserProfile(){
            return await get(`/u/${this.uid}/profile`)
        }
        public async getUserSongs(){
            return await get(`/u/${this.uid}/musics`)
        }
    }
}

/// <reference path="api.ts" />

namespace Api {
    export class Track{
        private track_id: string
        constructor(track_id){
            this.track_id = track_id
        }
        public async hasLiked(){
            return await get("/track/"+this.track_id+"/hasLiked")
        }
    }
}

It says could not find name get

But when i dont import sort it works perfectly

namespace Api {
    export async function get(url:string){
        const res = await fetch("api/"+url)
        return await res.json()
    }
    export class Users {
        private uid: string
        constructor(uid: string){
            this.uid = uid
        }
        public async getUserProfile(){
            return await get(`/u/${this.uid}/profile`)
        }
        public async getUserSongs(){
            return await get(`/u/${this.uid}/musics`)
        }
    }
}

1 Answers

When you add import statement to the file it turns that .ts file into a module, and "moves" all declarations in the file into the scope of that module.

So namespace Api is not seen outside of its module definition and removes it from global namespace. There are a few ways to deal with it:

  1. You can turn you main Api file into definition file by changing its extension to d.ts and wrap namespace Api into declare global {}. By that you don't even need triple slash import, it will still be available in global scope. Your new api.d.ts should look like this:

import { sort } from "./sort";

declare global {
  namespace Api {
    export async function get(url: string) {
      const res = await fetch("api/" + url);
      return await res.json();
    }
    export class Users {
      private uid: string;
      constructor(uid: string) {
        this.uid = uid;
      }
      public async getUserProfile() {
        return await get(`/u/${this.uid}/profile`);
      }
      public async getUserSongs() {
        return await get(`/u/${this.uid}/musics`);
      }
    }
  }
}

  1. Ommiting top-level import and using dynamic import like this:

namespace Api {
  export async function get(url: string) {
    const sort = await import("./sort");
    const res = await fetch("api/" + url);
    return await res.json();
  }
  export class Users {
    private uid: string;
    constructor(uid: string) {
      this.uid = uid;
    }
    public async getUserProfile() {
      return await get(`/u/${this.uid}/profile`);
    }
    public async getUserSongs() {
      return await get(`/u/${this.uid}/musics`);
    }
  }
}

  1. Simply turning namespace Api with Track into d.ts resolves the issue

Working with declaration merging and modules in typescript is pretty complex and i think there are still many workarounds and pitfalls about it so my answer can definitely be extended.

Related