-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generate a single declaration file for external modules #2568
Comments
Also related to #17 |
Tagging @csnover because of https://github.com/SitePen/dts-generator |
I would like to have a |
My experiences with Typescript 1.5 have been positive. The only thing that keeps giving me grief is the internal "module" ( which is only a namespace ). I really don't like fighting back and forth between internal and external/real/es6 modules and what I am allowed to do with both ( mostly the grief that comes from defining external module bundles ). I really only want to use the ES6 module syntax going forward. |
@mhegazy I want to re-phrase your statement. If all code initially uses internal modules, then it does not matter if declarations are spread over several files. Files can be copy-pasted and used as is. I see no problem in this scenario. But, if your lib uses external libraries, i.e. import/require, then manual labour is required, because developer needs declare module "external-thingy" {
...
} and this currently is not done automatically. |
If modules are all internal, then multiple files declaration can be concat-ed with gulp / grunt into single file without stepping on each other's toes. Such is simple nature of internal modules. Say. There was a news that number of node (npm) modules is already comparable if not surpassing a number of maven (!) modules. Hooray! It is time to fix external modules' declarations! |
I've put together an npm module ( TsProject ) that uses the tsconfig.json file to define bundles. It generates a typescript source file based on the dependencies in a source file as specified by a bundle. The generated typescript source file is output along with the compiled javascript and definition file. |
I'm not too sure it's really "a single .d.ts file" that we'd need. Having one .d.ts file per .emitted .js file is a very good fit for the CommonJS way of working.
Note that with external modules, the name of an external module is typically not present in the file itself. It is determined by its filename. So, I'd vote for keeping the currently generated .d.ts files as-is, and not e.g. put 'declare module "my-module" {}' inside of them, not even in an 'overall bundle file'. Tools like dts-bundle and dts-generator seem to prefer to write out one file, but I believe this is mainly because TypeScript doesn't allow specifying relative paths for the import()'s in .d.ts files. I think that if TSC would allow those paths even in .d.ts files, dts-bundle or dts-generator would no longer be necessary. Still, a solution needs to be found for any other 'really external' modules (i.e. different npm packages, not just different .ts files inside the package) that are referenced, as this would basically mean that the /// <reference ...> line of the package would need to be 'local' to that package's .d.ts file. Otherwise these really external modules will start conflicting with similar modules in other packages. TSC disallows having an external module be declared multiple times, which you can only get to work if all references are pointing to the same definition file. So, let's not try to 'throw away' that information by bundling, but support the case of leaving the files where they are, and use the 'real' filesystem to resolve the references. Note: my use-case is mainly 'plain' CommonJS, using TSC's --outDir option, and using browserify for the browser. I guess that the single-.d.ts-file case would be handy for TSC's --out option. |
Use case against clobbering declarations into a single file. Step one. I ship a node lib with one declarations file. Due to having declarations, users of the lib (developers!) now can see type info and comments, when these were properly transferred into declarations. |
Worth sharing here : #2829 all it needs is the path to your |
Not true. Look at : https://github.com/TypeStrong/atom-typescript-examples/blob/master/node/lib/main.ts You can import any file you have exported despite using a single Again the details of the proposal : #2829 |
@basarat I know about that possibility, yes, and in fact, that's what we use today (using output of dts-bundle at work, and recently started using dts-generator at home). The problem I see with it though, is that it will break as soon as more than one dependency level is introduced. Consider this dependency tree:
In the case of generating .d.ts files with This is not a problem as long as myutils is only used internally (and thus its types are never exposed through mylib/myotherlib), but that will often not be the case. |
@poelstra awesome. Perhaps you can hash out what a Then I can have something to work from and understand your proposal better (perhaps then I can even implement it). |
[edit] TL;DR re-ordered summary of the below:
[/edit] @basarat do you ever sleep? You seem to be online 24/7 😄 Strange as it may sound, my proposal would be to largely keep the .d.ts files as they currently are generated by tsc: i.e. without Changes to the compiler would be that it:
Point 3a. is probably the most problematic one, as people have come to expect these to be project-wide, and it's even actively promoted for some cases, IIRC to have a more C#-like experience. Point 3b. might be a nice alternative: the compiler would then search for (DefinitelyTyped) definitions in e.g. a Example: /// src/bar.ts
export interface BarType {
bar: number;
}
/// src/foo.ts
import bar = require("./bar");
export function something(): bar.BarType {
return { bar: 42 };
}
/// src/tsconfig.json
{
"version": "1.4.1",
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": true,
"noImplicitAny": true,
"removeComments": false,
"noLib": false,
"outDir": "../dist"
},
"filesGlob": [
"./**/*.ts",
"!./node_modules/**/*.ts"
],
"files": [
"./bar.ts",
"./foo.ts"
]
} Existing typings generated by Typescript: /// dist/bar.d.ts
export interface BarType {
bar: number;
}
/// dist/foo.d.ts
import bar = require("./bar");
export declare function something(): bar.BarType; (In atom-typescript, opening I like these generated typings very much, as they are a direct reflection of the sources. Now if only we could actually use them :) Let's call this package Another example would be if
The contents of these files are exactly like given above (i.e. no Note that in practice, most packages include some kind of Also note that it still isn't required to exactly replicate a package's file-structure. As long as a package e.g. exports all/most of its (public) types through its 'index' (which most packages do), having everything in one .d.ts file works perfectly fine. So it really should be 'just' stripping the In both scenarios (TS and non-TS package), there is no |
@poelstra your proposal may emotionally seem strange, because it is eery close to the simplest, yet good, solution. I love what you propose, and only want to simplify it a little further.
This is it. Just two points, and nothing else needed! We should not change anything with @poelstra TS 1.5 does generate for me .d.ts with relative imports. I humbly assume that, if compiler makes it, compiler must consume it. If this is not so, we have a bug for @mhegazy @mhegazy can you comment yourself on this proposal and pass it to other gurus and makers of a wonderful TS.
|
Personal preferences, five cents. I want to get rid of tds.d.ts. I want to simplify things. |
@3nsoft Nice writeup, mine indeed sounds way more complicated than what is actually proposed :) I've editted my earlier post with a TL;DR for future readers... Note that I like your idea of using Re: changing |
This is the current behavior now, each .ts file will generate a single .js file and a single .d.ts files. the compiler never emits module declarations in this form. these are only hand-authored.
This is tracked by #2338, and @vladima is working on now. so should be available for you to try soon. @poelstra and @3nsoft , i think your proposals are valid, and needed and long over due. I believe #2338 captures this proposal, in addition to AMD module loading cleanup as well. As a library author i would like to have control over the shape of my externally visible .d.ts file. We have the same issue for the TypeScript package. we do some tricks to hide some declarations that we do not want to be public but we would like to share between different modules. Bundling gets specifically interesting with ES6 modules, where you can do a lot of compositions with // main.ts
export * from "core\core";
export * from "utilities";
// core\core.ts
export * from ".\part1";
export * from ".\part2";
export * from ".\part3";
export { helper } from ".\part4\helper;
// utilities.ts
export { getName } from "tools\helpers";
// core\part2.ts
export function build() {
} you really do not want your users to look at this .d.ts and keep jumping between exports to know that your module has a "build" function. I think for these scenarios you want the compiler to digest your project, and spit out a single module .d.ts that tells users what is the shape of the module and hides all the unnecessary implementation details: // main.d.ts
declare function build(): void;
declare function getName(): void;
declare module helper {} |
@poelstra when I say that |
Reading this thread and the discussions related to issues referenced by this one I'm still not any wiser what the final solution will be. Any suggestions? :) |
The issue has gone a bit long. I will log a new issue with some more details once i have them :) the basic idea is a command line switch, that tells the compiler to bundle the .d.ts from different modules into a single .d.ts file |
👍 |
Is this issue pending? Is it going to be dropped? Has it been implemented but not documented? |
@mhegazy, how can declaration output files be bundled? I followed up on #5090 and there it's mentioned that you need to set
|
why do you want to bundle your declarations if you are using node? the .js files are not bundled either. |
@mhegazy if TS itself cannot bundle JS files, that does not mean that no any other tool. |
@mhegazy I am getting lost. Maybe you can help me. I have a TypeScript application with the following configuration ( {
"compilerOptions": {
"declaration": true,
"lib": ["DOM", "ES6"],
"module": "CommonJS",
"moduleResolution": "node",
"noEmitOnError": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"outDir": "dist/commonjs",
"removeComments": true,
"rootDir": "src/ts",
"sourceMap": false,
"target": "ES5"
},
"exclude": [
"dist/commonjs",
"node_modules"
]
} When I run Now I want to distribute my code as npm package and I would like to support types for people using my code but I don't know where I should point the The documentation says: "Set the So what can I do now to publish my npm package along with it's definitions? |
@bennyn You can publish it as is. (all .js and .d.ts files) |
For short, Just like the For example: (package.json) {
"main": "lib/index.js",
"types": "lib/index.d.ts"
} |
@orefalo if file d.ts place is near the main js the types section is unneded. TS compiler try to load declaration with the JS file loaded, so no action needed. |
@ikatyang I have done what you have suggested. In my library I pointed package.json "types": "./dist/commonjs/index.d.ts" ./dist/commonjs/index.d.ts import WireAPIClient from './core/WireAPIClient';
export = WireAPIClient; Afterwards I tried to use my library in another TypeScript project: import {WireAPIClient} from '@wireapp/api-client'; This caused the following error:
So I changed my import statement: import WireAPIClient = require('@wireapp/api-client'); Which then led to the following errors:
|
@bennyn I have no idea what's going on to your compiled dts file, since I cloned your PR and execute (AuthAPI.d.ts) +/// <reference path="LoginData.d.ts" />
+/// <reference path="AccessTokenData.d.ts" /> It compiled fine for me (with triple-slash directives exist), and thus the following import works fine. import WireAPIClient = require('@wireapp/api-client'); NOTE: There are several unlinked definitions in your code (does not use |
@ikatyang Thank you so much for your support! I forgot to regenerate the Before (no interface export + triple-slash)LoginData.ts interface LoginData {
email: string;
password: number | string;
} AuthAPI.ts /// <reference path="LoginData.d.ts" />
import {AxiosPromise, AxiosRequestConfig, AxiosResponse} from 'axios';
export default class AuthAPI {
constructor(private client: HttpClient) {}
static get URL() {
return {
COOKIES: '/cookies',
INVITATIONS: '/invitations'
};
}
public postCookiesRemove(login: LoginData, labels?: string[]): AxiosPromise {
const config: AxiosRequestConfig = {
data: {
labels: labels,
password: login.password.toString(),
},
method: 'post',
url: `${AuthAPI.URL.COOKIES}/remove`,
};
return this.client.sendRequest(config);
}
// More code follows here...
} After (interface export + interface import)LoginData.ts interface LoginData {
email: string;
password: number | string;
}
export default LoginData; AuthAPI.ts import LoginData from './LoginData';
import {AxiosPromise, AxiosRequestConfig, AxiosResponse} from 'axios';
export default class AuthAPI {
constructor(private client: HttpClient) {}
static get URL() {
return {
COOKIES: '/cookies',
INVITATIONS: '/invitations'
};
}
public postCookiesRemove(login: LoginData, labels?: string[]): AxiosPromise {
const config: AxiosRequestConfig = {
data: {
labels: labels,
password: login.password.toString(),
},
method: 'post',
url: `${AuthAPI.URL.COOKIES}/remove`,
};
return this.client.sendRequest(config);
}
// More code follows here...
} OutcomeWith import Context from "@wireapp/api-client/dist/commonjs/core/Context";
import LoginData from "@wireapp/api-client/dist/commonjs/auth/LoginData";
import WireAPIClient = require('@wireapp/api-client');
const client: WireAPIClient = new WireAPIClient({
rest: 'https://prod-nginz-https.wire.com',
ws: 'wss://prod-nginz-ssl.wire.com'
});
const credentials: LoginData = {
email: 'name@mail.com',
password: 'secret',
persist: false
};
client.login(credentials).then((context: Context) => {
console.log(`Got self user with ID "${context.userID}".`);
}); This is exactly what I wanted to do. Thank you very much! 🍻 |
If compiling external modules, with --declaration, each module gets its own declaration file. this is not useful for library authors, who probably want to hand off a single .d.ts file for all their modules. the only way to do this now, is to either hand edit the file, or use a tool to concatenate the output.
This becomes even more desirable with ES6 module export/import where a module can reexport exports of another module.
See discussion in #2516 (comment) for more details.
The text was updated successfully, but these errors were encountered: