How do I export Typescript interfaces from a node module?

E. Friedberg picture E. Friedberg · Feb 14, 2018 · Viewed 11.9k times · Source

Figured it out!

Initially, I was trying to import my module like this:

const qml = require('quill-marking-logic')
const { checkSentenceCombining, checkSentenceFragment, checkDiagnosticQuestion, checkFillInTheBlankQuestion, ConceptResult } = qml

because I got a TS2307: Cannot find module 'quill-marking-logic' error when I tried to use

import { checkSentenceCombining, checkSentenceFragment, checkDiagnosticQuestion, checkFillInTheBlankQuestion, ConceptResult } from 'quill-marking-logic'

This was because I was using "module": "es6" in my importing app's tsconfig, which by default sets the moduleResolution option to Classic. By explicitly setting it to node, I was able to use the import syntax and get my interfaces!

Original post

I've built a node module using Typescript that I am using as a dependency in another app. I have a couple of interfaces in the module that I am trying to export from the its entry point so that I can use them in my other app, but they are erased after compilation. I understand that this is part of Typescript's design, because the interfaces are used for runtime analysis, but I'm wondering if there's a way to get around it so I don't have to define them again in my other app and have to maintain the same code in two places. I'm using rollup as my bundler.

This is what the .d.ts version of my entry point looks like:

export { checkSentenceCombining } from './libs/graders/sentence_combining';
export { checkDiagnosticQuestion } from './libs/graders/diagnostic_question';
export { checkSentenceFragment } from './libs/graders/sentence_fragment';
export { checkFillInTheBlankQuestion } from './libs/graders/fill_in_the_blank';
export { Response, PartialResponse, ConceptResult, FocusPoint, IncorrectSequence, FeedbackObject, GradingObject, WordCountChange } from './interfaces/index';

That last line of exports is where the interfaces should be coming through.

Here is my tsconfig:

{
    "compilerOptions": {
        "target": "es5",
        "module": "CommonJS",
        "moduleResolution": "node",
        "allowSyntheticDefaultImports": true,
        "sourceMap": false,
        "noImplicitAny": false,
        "lib": [
            "dom",
            "es7"
        ],
        "typeRoots": [
            "node_modules/@types/"
        ],
        "declaration": true
    }
}

Here is my tsconfig for the app I'm trying to import this in to:

{
    "compilerOptions": {
        "outDir": "./dist/",        // path to output directory
        "sourceMap": true,          // allow sourcemap support
        "strictNullChecks": true,   // enable strict null checks as a best practice
        "module": "es6",            // specifiy module code generation
        "jsx": "react",             // use typescript to transpile jsx to js
        "target": "es6",            // specify ECMAScript target version
        "allowJs": true,            // allow a partial TypeScript and JavaScript codebase
        "lib": ["ES2017", "DOM"],            //
        "allowSyntheticDefaultImports": true // Allow import React from 'react'
    }
}

And I'm pointing to the generated .d.ts file in the "typings" key in my package.json.

Answer

Nathan Friend picture Nathan Friend · Feb 14, 2018

Yes, this is possible. To do this, export the interfaces you'd like to make available to consumers of your module in your module's entry point file:

// in entry.ts
import { MyInterface1 } from '/path/to/interface/one';
import { MyInterface2 } from '/path/to/interface/two';

export { MyInterface1, MyInterface2 };

Then, in your code that uses this module, you can do:

import { MyInterface1, MyInterface2 } from 'my-module`;

In order for this to work, you'll need to make sure the declaration compiler option is set to true in your module - this causes the compiler to output a .d.ts file that contains your module's typing information.

The last piece of the puzzle is to include a types property in your module's "package.json" that points to this .d.ts file:

{
    "name": "my-module",
    "main": "./entry.js",
    "types": "./my-module.d.ts"
}

For more information about preparing a module for publishing: https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html