LordOfThePigs LordOfThePigs - 3 months ago 51
TypeScript Question

error TS2306: '...index.d.ts' is not a module

I'm trying to write a lib in Typescript that I want to use in various other typescript or JS projects. All of the those other projects run in browsers.

My Typescript lib project has multiple files, and each file is a React component with 1 class and 1-2 interfaces (Component + Props + State, for those that are familiar with React).

I want my component library to be importable as an AMD module by the other projects, and I want all of the compiled JS code to be bundled in a single file as part of the library's build process.

So far I've managed to set everything up so that my lib compiles. I've set up the compiler with the following options :

jsx: "react", declaration: true, module: "amd"


The generated code for the lib looks like:

define("epb-widget/WidgetItemNav", ["require", "exports", "react"], function (require, exports, React) {
"use strict";
exports.WidgetItemNav = ...;
});
define("epb-widget/WidgetItemInfo", ["require", "exports", "react"], function (require, exports, React) {
"use strict";
exports.WidgetItemInfo = ...;
});
define("epb-widget/WidgetItem", ["require", "exports", "react", "epb-widget/WidgetItemNav", "epb-widget/WidgetItemInfo"], function (require, exports, React, WidgetItemNav_1, WidgetItemInfo_1) {
"use strict";
exports.WidgetItem = ...;
});
define("epb-widget/WidgetOptions", ["require", "exports", "react"], function (require, exports, React) {
"use strict";
exports.WidgetOptions = ...;
});
define("epb-widget/Widget", ["require", "exports", "react", "epb-widget/WidgetItem", "epb-widget/WidgetOptions"], function (require, exports, React, WidgetItem_1, WidgetOptions_1) {
"use strict";
exports.Widget = ...;
});
define("epb-widget", ["require", "exports", "epb-widget/Widget", "epb-widget/WidgetItem", "epb-widget/WidgetItemInfo", "epb-widget/WidgetItemNav", "epb-widget/WidgetOptions"], function (require, exports, Widget_1, WidgetItem_2, WidgetItemInfo_2, WidgetItemNav_2, WidgetOptions_2) {
"use strict";
exports.Widget = Widget_1.Widget;
exports.WidgetItem = WidgetItem_2.WidgetItem;
exports.WidgetItemInfo = WidgetItemInfo_2.WidgetItemInfo;
exports.WidgetItemNav = WidgetItemNav_2.WidgetItemNav;
exports.WidgetOptions = WidgetOptions_2.WidgetOptions;
});


That's a lot of AMD modules for my taste, but I can live with it. The generated code looks like it would work well.

The generated
.d.ts
file looks like that:

declare module "epb-widget/WidgetItemNav" {
import * as React from "react";
export interface WidgetItemNavProps {
...
}
export class WidgetItemNav extends React.Component<WidgetItemNavProps, void> {
...
}
}
declare module "epb-widget/WidgetItemInfo" {
export interface WidgetItemInfoProps {
...
}
export const WidgetItemInfo: (props: WidgetItemInfoProps) => JSX.Element;
}
declare module "epb-widget/WidgetItem" {
export interface WidgetItemProps {
...
}
export const WidgetItem: (props: WidgetItemProps) => JSX.Element;
}
declare module "epb-widget/WidgetOptions" {
import * as React from "react";
export interface WidgetOptionsProps {
...
}
export interface WidgetOptionsState {
...
}
export class WidgetOptions extends React.Component<WidgetOptionsProps, WidgetOptionsState> {
...
}
}
declare module "epb-widget/Widget" {
import * as React from "react";
import { WidgetItemProps } from "epb-widget/WidgetItem";
export interface WidgetProps {
...
}
export interface WidgetState {
...
}
export class Widget extends React.Component<WidgetProps, WidgetState> {
...
}
}
declare module "epb-widget" {
export { Widget, WidgetProps } from "epb-widget/Widget";
export { WidgetItem, WidgetItemProps } from "epb-widget/WidgetItem";
export { WidgetItemInfo, WidgetItemInfoProps } from "epb-widget/WidgetItemInfo";
export { WidgetItemNav, WidgetItemNavProps } from "epb-widget/WidgetItemNav";
export { WidgetOptions, WidgetOptionsProps } from "epb-widget/WidgetOptions";
}


Again, that looks quite reasonable. And that was all output directly by the Typescript compiler.

Finally, this library is an npm module, and its
package.json
looks like this

{
"name": "epb-widget",
"version": "1.0.0",
"description": "...",
"main": "./dist/index.js",
"typings": "./dist/index",
"globalDependencies": {...},
"devDependencies": {...}
}


But here's the problem

When I try to use the library in one of my projects, it doesn't actually work.

I simply try import the classes like this:

import {Widget, WidgetProps, WidgetItem} from "epb-widget";


but the typescript compiler throws the following error

error TS2656: Exported external package typings file '.../node_modules/epb-widget/dist/index.d.ts' is not a module. Please contact the package author to update the package definition.


I honestly can't figure out what I should do here.
index.d.ts
was generated by
tsc
, and I'm not sure why that very same
tsc
isn't able to consume it in another project.

Answer

This is too long to go into our discussion above but here is my TL;DR to create a library:

  • create an index.ts that re-exports the definitions exposed in the library e.g.

    export * from './API'
    export * from './Decorators'
    ...
    
  • compile the library files with the --declaration flag to automatically generate the typings

  • add a typings entry to package.json pointing to the generated index.d.ts file

To use the library:

  • drop it in the node_modules folder like any other library
  • simply import * as mylib from 'mylib': it will automatically import all the exported definitions and the typings

This process works perfectly fine with commonjs and from what I understand, with systemjs too. I do not know about amd, and your generated typings look quite different from mine:

The generated index.d.ts typically looks like

export * from './API';
export * from './Decorators';
...

and the API.d.ts file will typically look like

import * as stream from 'stream';
import * as net from 'net';
import * as url from 'url';
export interface Factory {
    end(): Promise<void>;
}
export interface ChannelFactory extends Factory {
    getChannel(name: string, closeListener?: () => void): Promise<Channel>;
    existsChannel(name: string): Promise<boolean>;
}
....
Comments