Skip to content
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

Suggestion: package declaration file for commonjs packages #3089

Closed
rbuckton opened this issue May 8, 2015 · 14 comments
Closed

Suggestion: package declaration file for commonjs packages #3089

rbuckton opened this issue May 8, 2015 · 14 comments
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript

Comments

@rbuckton
Copy link
Member

rbuckton commented May 8, 2015

Proposal

To better support the development and consumption of node.js modules in TypeScript, I propose that we add the ability to emit a single declaration file for a node.js package. To that end, I suggest we add the following features:

  • Add a packageName compiler option, used to specify the name of the node.js package.
  • Add a packageMain compiler option, used to specify the path to the main typescript module for the package.
  • Add a packageDeclaration compiler option, used to specify the output path of the declaration file for the package.
  • When the above three options are supplied, along with declaration and either module or a target of ES6, we should emit a single declaration file for the program inputs.

The output for a packageDeclaration would have the following form:

  • Exports for the main module will be defined inside of an ambient external module that has the name provided via packageName.
  • Exports for relative modules will be defined inside of an ambient external module that has a name derived from the package name, and the path of the module relative to the nearest package.json file, common directory path, or the current directory.

Example

Given the following sources:

a.ts

import { C } from "./lib/b"; 
export function func(): C {
    return new C();
}

lib\b.ts

export class C {
}

index.ts

export * from "./a";

And the following command line:

tsc index.ts a.ts lib/b.ts --module commonjs --declaration --packageName sample --packageMain index.ts --packageDeclaration sample.d.ts

You would get the following package declaration output file:

sample.d.ts

declare module "sample" {
    export * from "sample/a";
}
declare module "sample/a" {
    import { C } from "sample/lib/b";
    function func(): C;
}
declare module "sample/lib/b" {
    class C {
    }
}

Out of scope

It would be nice to also be able to emit only the exports visible from the main module, and reduce the overall output side of the declaration file, but not all node.js packages are designed to work that way. Instead, in the future we could investigate an option to choose whether to emit a single ambient module declaration. The proposed approach can be used whether you intend to have a single output module or the ability to reach nested submodules, while the more restrictive approach would alienate some package authors.

It is also out of scope to infer package information from the package.json file, although that may be considered in the future. That might look something like having a package compiler option that can read the package.json file to infer the packageName from the "name" field of the package, and the packageDeclaration from the "typings" (proposed) field of the package. However, its more complicated to derive packageMain as the "main" field of the package will be pointing to the output file and not the original typescript file. We might have to propose a "devMain", or some
other similarly named field, to satisfy this requirement.

As proposed, using packageDeclaration will circumvent regular declaration file generation. We could opt to support both, by writing individual declaration files for each non-declaration input as well as the package-level declaration, though I am not certain whether that would be necessary.

A sample implementation for this proposal can be found in the packageDeclaration2 branch.

@danquirk danquirk added the Suggestion An idea for TypeScript label May 8, 2015
@lazdmx
Copy link

lazdmx commented May 9, 2015

👍

@rbuckton
Copy link
Member Author

rbuckton commented May 9, 2015

I've updated the branch in a way that differs slightly from the above proposal, in that packageDeclaration is independent from declaration and can be used separately or together.

@yortus
Copy link
Contributor

yortus commented May 10, 2015

Just curious how this would deal with Node.js modules whose public APIs include types from other Node.js modules. E.g.:

  • express.d.ts references node.d.ts because its API surfaces various http types.
  • knex.d.ts references both bluebird.d.ts and node.d.ts because its API surfaces both bluebird promises and node events.

@rbuckton
Copy link
Member Author

That's an area I am still investigating. Right now it depends on how it is referenced in the project. /// <reference/> tags will be preserved, as they are currently. If the declarations are included in the file list they will not be included in the output declaration.

I imagine something like tsd would be useful here.

@yortus
Copy link
Contributor

yortus commented May 11, 2015

So if I understood correctly, a scenario might go something like this:

  • Suppose my-module has types from node.d.ts in its public API
  • my-module also uses bluebird.d.ts and a few other third party libs as internal implementation details.
  • I'd therefore like my-module.d.ts to contain ///<reference path="../node/node.d.ts /> to be compatible with DefinitelyTyped/tsd.
  • But I don't wan't any other ///<ref... comments preserved. Certainly not the bluebird.d.ts ref.
  • So my file list in tsconfig.json includes all my source files and third party typings except not the node.d.ts typing.
  • I add something like /typings/my-module/externals.d.ts containing ///<reference path="../node/node.d.ts />, and I add this externals.d.ts to the file list.
  • I compile with --packageDeclaration et al.
  • I end up with my-module.d.ts with a reference to node.d.ts but no other references.

The thing I'm not sure about is the concept of the 'file list'. Is that from tsconfig.json as in the example above, or is it the transitive closure of everything reachable from packageMain? If the latter, then node.d.ts will presumably end up in the file list and therefore not in the .d.ts file.

@rbuckton
Copy link
Member Author

@yortus I don't have a recommendation or best practice yet. I'll have to think on this some this week. The problem with reference tags is that the paths end up fixed into the declaration file. If you ship a declaration file with these reference tags, you must be 100% sure that those paths will be the same ones that the consumer of the declaration file will use. For common declarations like node.d.ts, there's no guarantee that the consumer will want to use the same path, much less the same version of the declaration.

By file list, I meant the files either supplied to tsc on the command line or through tsconfig.json.

@yortus
Copy link
Contributor

yortus commented May 11, 2015

@rbuckton OK cool, looking forward. Yeah the relative reference paths thing is tricky. Definitely Typed has a consistent convention, but its not the only convention out there.

@Ciantic
Copy link

Ciantic commented Jun 2, 2015

I stumbled on this today.

Edit Removing my proposal, there is proper work going on with this in here: #2839 see this branch: https://github.com/Microsoft/TypeScript/compare/moduleResolution

@basarat
Copy link
Contributor

basarat commented Jun 3, 2015

Using the global declare module "foo" will result in pain in the long run

@mhegazy mhegazy added the Fixed A PR has been merged for this issue label Dec 9, 2015
@mhegazy
Copy link
Contributor

mhegazy commented Dec 9, 2015

this should be handled today using --outFile by #5090

@mhegazy mhegazy closed this as completed Dec 9, 2015
@yortus
Copy link
Contributor

yortus commented Dec 9, 2015

@mhegazy #5090 doesn't work for commonjs and this proposal is for node (commonjs) so I think it's not covered by #5090?

@mhegazy
Copy link
Contributor

mhegazy commented Dec 9, 2015

that is correct. thanks @yortus.

This is now not needed after #2338

@mhegazy mhegazy added Declined The issue was declined as something which matches the TypeScript vision and removed Fixed A PR has been merged for this issue labels Dec 9, 2015
@yortus
Copy link
Contributor

yortus commented Dec 9, 2015

@mhegazy #2338 is about module resolution, this issue is about emitting a .d.ts file. Not sure how related?

@mhegazy
Copy link
Contributor

mhegazy commented Jan 4, 2016

@mhegazy #2338 is about module resolution, this issue is about emitting a .d.ts file. Not sure how related?

before module resolution logic picked node packages, there was no way to define a node package except in an ambient declaration, this should not be needed now.

bundling module declarations in one file is supported for System and AMD, but not node, as node packages do not need to be bundled. if this is something you need please elaborate on the details of the scenario.

@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

7 participants