1

Today I was writing a type declaration file for a JavaScript file but despite my hours of trying I couldn't make it work inside a node application. Then I tried switching to es6 module syntax and surprisingly it worked.

Then I discovered that I can also make it work with commonjs module syntax if I add ".default" before accessing any properties of that module. For example I've to use person.default.name instead of person.name to get the intellisence.

I wrote a small module to demonstrate the problem. I've created an object with identical typings of my actual module.

index.js

const names = {
  firstname: "Alex",
  middlename: "Blex",
  lastname: "Clex",
};

const state = {
  isAlive() {
    return true;
  },
  isWalking() {
    return false;
  },
};

function talk(speech) {
  console.log(speech);
}

const person = {
  names,
  state,
  talk
};

module.exports = person;

index.d.ts

declare type predicate = (v: unknown) => boolean;
declare function talk(speech: string): void;

declare const person: {
  names: {
    [key: string]: string;
  };
  state: {
    [key: string]: predicate;
  };
  talk: typeof talk;
};

export default person;

test1.js

const person = require("./index");

const isAlive = person.default.state.isAlive();
//---------------------^^^^^^^----------------
// The above line throws an error as expected.
// I've to use "person.default.whatever" to get intellisence
// In the editor it shows "typeof isAlive = boolean".

const isReallyAlive = person.state.isAlive();
// EditorError: Property 'state' does not exist on type
// 'typeof import(./index.js)'.

// In the editor it shows typeof "isReallyAlive = any"
// But infact isReallyAlive is boolean.

test2.js

Using es6 module syntax it works perfectly.

Using es6 module syntax

I'm fairly new to Typescript so kindly give me some hint where I'm making the mistake. Thanks in advance, I highly appreciate your time on StackOverflow <3.

h-sifat
  • 1,455
  • 3
  • 19
  • What about just using `person.state`? `default` is normally only used for ESM not CommonJS – evolutionxbox Sep 24 '21 at 15:04
  • With commonjs module the editor says `person.state` is not defined but if I console.log `person.state` it indeed prints the state object. – h-sifat Sep 24 '21 at 15:15
  • I'm assuming the real file is much more complicated than that, because otherwise it would be really simple to just convert it to ts lol – Elias Sep 24 '21 at 15:42
  • If I do `const person = require("./index");` person is `any` for me. Do you have any special tsconfig? I used a fresh project with `npx tsc --init`. – Elias Sep 24 '21 at 15:50
  • @Elias I don't have any special flags enabled in my tsconfig and before posting the question here I've tried using typescript. But even if I use typescript I'm concerned that those who will use my library in their Node application will not get intellisence as most of them will be using commonjs module syntax. – h-sifat Sep 24 '21 at 15:57
  • I think you would actually need to do `import person = require("./index");` inside typescript, and not const. Also, the default level is correct. – Elias Sep 24 '21 at 15:59
  • No typescript should generate the correct output for commonjs requires. – Elias Sep 24 '21 at 16:00
  • I just tried replacing `const` with `import` and I got greeted by Nodejs with `SyntaxError: Cannot use import statement outside a module` message. – h-sifat Sep 24 '21 at 16:02
  • 1
    Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/237465/discussion-between-elias-and-h-sifat). – Elias Sep 24 '21 at 16:03

1 Answers1

1

So as explained by this post https://stackoverflow.com/a/40295288/10315665 the export default option only creates a default key inside of the exports object. That's why you can still have normal exports besides it.

Many people consider module.exports = ... to be equivalent to export default ... and exports.foo ... to be equivalent to export const foo = .... That's not quite true though, or at least not how Babel does it.

So your definition file is wrong. It should be:

declare type predicate = (v: unknown) => boolean;

export declare const names: {
    [key: string]: string;
};

export declare const state: {
    [key: string]: predicate;
};

export declare function talk(speech: string): void;

And if you respect that, you can actually utilize typescript and it's awesome type checking by simply writing typescript in the first place!:

export const names = {
  firstname: "Alex",
  middlename: "Blex",
  lastname: "Clex",
};

export const state = {
  isAlive() {
    return true;
  },
  isWalking() {
    return false;
  },
};

export function talk(speech: string) {
  console.log(speech);
}

Just remember to enable "declaration": true in the tsconfig to also generate declaration files .

Elias
  • 3,592
  • 2
  • 19
  • 42
  • I just sort of "re-learnt" this, so yay for me :D – Elias Sep 24 '21 at 16:39
  • Sorry for late reply. I tried your solution with both nvim and vscode but sadly it didn't work. I still have to use `.default`. But I don't wanna waste your time anymore for this question. Lemme switch back to typescript. Thank you so much for giving me your valuable time <3 . – h-sifat Sep 24 '21 at 21:27
  • @h-sifat If you use the definition file I've provided it **should** work. Have you tried running `npx tsc --noemit` instead of just checking the editor? That should give you the real deal. Also, try the `> Typescript: Restart TS Server` command in vs code to clear any caching issues. – Elias Sep 25 '21 at 09:49
  • 1
    And you are not wasting my time, if I didn't feel like answering questions I would simply not open this website. – Elias Sep 25 '21 at 09:50
  • if you are trying to import this from typescript now, you would have to do `import * as person from "./person";` – Elias Sep 25 '21 at 10:03
  • Or try providing a key `default` in the `exports` (in JS) object and add an `export default` statement inside the d.ts file. I've not tested this but in theory... – Elias Sep 25 '21 at 10:05
  • Elias both of our declaration file works with typescript and es6 modules. But commonjs is the troublemaker here. I've tried writing my module with typescript and generate declaration file by the compiler but I still have to use `.default` with commonjs module syntax. Why life has to be so complicated :'( ?! – h-sifat Sep 25 '21 at 11:39
  • @h-sifat can you like make a repo that I can download and test against it? And most of the time these things are difficult because you are either doing something very niche, or your doing it wrong, lol :D – Elias Sep 25 '21 at 18:51
  • Currently I'm learning typescript. I'll need some time to finish this course. I'm hoping that after taking this course I'll be able to fix the problem myself otherwise I'll give you a knock :) . – h-sifat Sep 26 '21 at 09:08