E. Friedberg
E. Friedberg

Reputation: 171

How do I export Typescript interfaces from a node module?

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.

Upvotes: 17

Views: 26575

Answers (3)

Klas Mellbourn
Klas Mellbourn

Reputation: 44377

Add this to your tsconfig.json file:

"allowJs": false,
"declaration": true,

Add this to your package.json file:

"types": "dist/index.d.ts",

Also, don't forget to put your d.ts files in a separate folder that you publish in your package.json, or they will be missed. For example, putting them in a typings folder beside the src folder and add this to package.json

  "files": [
    "/dist",
    "/typings"
  ],

For further details: https://medium.com/cameron-nokes/the-30-second-guide-to-publishing-a-typescript-package-to-npm-89d93ff7bccd

Upvotes: 1

Nathan Friend
Nathan Friend

Reputation: 12804

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

Upvotes: 10

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249506

You should use the --declaration option to generate d.ts files for your module. The definition file will contain exported interfaces and you can use them in your application.

You should include the definition file with your module in a location typescript looks for definitions in:

Similarly a non-relative import will follow the Node.js resolution logic, first looking up a file, then looking up an applicable folder. So import { b } from "moduleB" in source file /root/src/moduleA.ts would result in the following lookups:

/root/src/node_modules/moduleB.ts
/root/src/node_modules/moduleB.tsx
/root/src/node_modules/moduleB.d.ts
/root/src/node_modules/moduleB/package.json (if it specifies a "types" property)
/root/src/node_modules/moduleB/index.ts
/root/src/node_modules/moduleB/index.tsx
/root/src/node_modules/moduleB/index.d.ts 

Upvotes: 1

Related Questions