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.
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
typings
entry to package.json
pointing to the generated index.d.ts
fileTo use the library:
node_modules
folder like any other libraryimport * as mylib from 'mylib'
: it will automatically import all the exported definitions and the typingsThis 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>;
}
....