Auto generate index.d.ts, type definitions, from a typescript module
Asked Answered
R

2

20

If I have a TypeScript module saved as my-function.ts as follows :

export function myFunction (param: number): number { return param }

This will be compiled to JavaScript in whichever way and loose its type definitions. I am then able to create a index.d.ts file which declare this module's definitions, but this seems a bit tedious to redefine/redeclare the definitions.

Are there ways to generate the type definitions automatically from the my-function.ts file to a index.d.ts file?

Rockhampton answered 5/6, 2017 at 8:16 Comment(0)
T
19

If you compile with the --declaration flag, TypeScript will automatically generate .d.ts files for you.

This mode will require that you certain types are visible so that they can be described in your .d.ts files.

Tractarianism answered 5/6, 2017 at 8:18 Comment(3)
Is there an easy way to combine them? If that's matters, I'm talking about UMD modules.Ramonitaramos
You might be talking about declaration bundling (i.e. collapsing your declarations down to one top-level index.d.ts file), but TypeScript currently doesn't support that.Tractarianism
How do people get this in definitely typed?Linda
R
16

Here's how I managed to solve it:

Creating the infra

  1. Create an new Node package with typescript for the infra.
  2. Inside the new package, make sure to configure a tsconfig.json with declaration:true Doing so will cause typescript to generate definition files which can be consumed by the users of this infra.

My tsconfig.json:

  {
    "compilerOptions": {
       "target": "es5",
       "module": "commonjs",
       "declaration": true,
       "outDir": "./tsOutputs"
     },
    "include": [
      "lib/**/*.ts",
      "index.ts"
    ],
    "exclude": [
      "test/**/*.ts"
    ]
  }
  1. Create a an "index.ts" file which will export the public API of the infra.

Note: In order to be able to cast & create instances of objects, you need to have two different exports per each entity. Once as type and another as const.

Here's my index.ts:

import {HttpClient as HC} from "./lib/http/http-client";

import {HttpRequest as HReq, HttpResponse as HRes} from "./lib/http/contracts";

export namespace MyJsInfra {

    export type HttpClient = HC;

    export namespace Entities {
        export type HttpRequest = HReq;
        export const HttpRequest = HReq;

        export type HttpResponse = HRes;
        export const HttpResponse = HRes;
    }
}

You can read more info on the reasoning behind this dual declaration in here: https://github.com/Microsoft/TypeScript/issues/10058#issuecomment-236458961

  1. After all of the following, when we'll run build we should have the corresponding "*.d.ts" files per each type. Now we have to handle the package.json of the infra, in order to pack all the items.

  2. Inside the package.json make sure to set the types, main to point to the generated index.d.ts & index.js files. In addition, you have to make sure that the "*.d.ts" files are being packaged as part of infra. In my case, I've specified the following pattern in the files property: "tsOutputs/**/*.d.ts"

Here's my package.json:

    {
      "name": "my-js-infra",
      "version": "1.0.0",
      "description": "Infrastructure code.",
      "scripts": {
        "build":"./node_modules/.bin/tsc -p .",
        "prepublish":"npm run build",
      },
      
     "homepage": "https://github.com/Nadav/My.JS.Infra#readme",
      "devDependencies": {
       ...
        "typescript": "^2.4.2",
       ...
      },
      "dependencies": {
         ...
        "needle": "^1.4.2",
         ...
      },
      "files": [
        "tsOutputs/**/*.js",
        "tsOutputs/**/*.d.ts",
        "tsOutputs/index.d.ts"
      ],
      "types":"tsOutputs/index.d.ts",
      "main":"tsOutputs/index.js"
    }

All done. Now you can publish your common code.


Consuming the code

  1. Install the infra. In our case the user have to use: npm install my-js-infra --save
  2. Modify the tsconfig.json of the consuming application to load the modules using the Node module resolution. You do so by setting moduleResolution:true inside the file.

Here's my tsconfig.json:

    {
        "compilerOptions": {
            "target": "es5",
            "lib": ["es5", "es6"],
            "module": "umd",
            "sourceMap": true,
            "watch": false,
            "outDir": "./tsOutputs",
            "moduleResolution":"node" /* This must be specified in order for typescript to find the my-js-infra. Another option is to use "paths" and "baseUrl". Something like:
                                            ...
                                            "baseUrl": ".", // This must be specified if "paths" is used.
                                            "paths":{
                                                "my-js-infra":["node_modules/my-js-infra/tsOutputs/index.d.ts"]
                                            }
                                            ...
                                        */
        }
    }

You can read more on module resolution in Typescript in here: https://www.typescriptlang.org/docs/handbook/module-resolution.html

  1. Start using the code. For example:
import {MyJsInfra } from "my-js-infra";

public doMagic(cmd, callback) {
    try {
        var request: MyJsInfra.Entities.HttpRequest = {
            verb: "GET",
            url: "http://www.google.com",
        };

        var client = new MyJsInfra.HttpClient();
        client.doRequest(request, (err, data) => {
            if (err)
                return callback(err, null)
            return callback(null, data);
        })
    } catch (err) {
        callback(err);
    }
}
Ramonitaramos answered 16/1, 2018 at 14:9 Comment(3)
My index.ts has module.exports = {...}. Every other .ts file in module gets corresponding .d.ts file created properly expect index.d.ts, which just says export {};. Any suggestions?!Sharpeyed
What is "infra"?Randeerandel
@Randeerandel "infrastructure", I assumeSeema

© 2022 - 2024 — McMap. All rights reserved.