Repository navigation
Suggestion: package declaration file for commonjs packages #3089
Description
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
packageNamecompiler option, used to specify the name of the node.js package. - Add a
packageMaincompiler option, used to specify the path to the main typescript module for the package. - Add a
packageDeclarationcompiler option, used to specify the output path of the declaration file for the package. - When the above three options are supplied, along with
declarationand eithermoduleor atargetofES6, 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.jsonfile, 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.
Activity
lazdmx commentedon May 9, 2015
👍
rbuckton commentedon May 9, 2015
I've updated the branch in a way that differs slightly from the above proposal, in that
packageDeclarationis independent fromdeclarationand can be used separately or together.yortus commentedon 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.tsreferencesnode.d.tsbecause its API surfaces varioushttptypes.knex.d.tsreferences bothbluebird.d.tsandnode.d.tsbecause its API surfaces both bluebird promises and node events.rbuckton commentedon May 10, 2015
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 commentedon May 11, 2015
So if I understood correctly, a scenario might go something like this:
my-modulehas types fromnode.d.tsin its public APImy-modulealso usesbluebird.d.tsand a few other third party libs as internal implementation details.my-module.d.tsto contain///<reference path="../node/node.d.ts />to be compatible with DefinitelyTyped/tsd.///<ref...comments preserved. Certainly not thebluebird.d.tsref.tsconfig.jsonincludes all my source files and third party typings except not thenode.d.tstyping./typings/my-module/externals.d.tscontaining///<reference path="../node/node.d.ts />, and I add thisexternals.d.tsto the file list.--packageDeclarationet al.my-module.d.tswith a reference tonode.d.tsbut no other references.The thing I'm not sure about is the concept of the 'file list'. Is that from
tsconfig.jsonas in the example above, or is it the transitive closure of everything reachable frompackageMain? If the latter, thennode.d.tswill presumably end up in the file list and therefore not in the.d.tsfile.rbuckton commentedon May 11, 2015
Troy Gerwien (@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 commentedon May 11, 2015
Ron Buckton (@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 commentedon 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 commentedon Jun 3, 2015
Using the global
declare module "foo"will result in pain in the long runmhegazy commentedon Dec 9, 2015
this should be handled today using
--outFileby #5090yortus commentedon Dec 9, 2015
Mohamed Hegazy (@mhegazy) #5090 doesn't work for commonjs and this proposal is for node (commonjs) so I think it's not covered by #5090?
mhegazy commentedon Dec 9, 2015
that is correct. thanks Troy Gerwien (@yortus).
This is now not needed after #2338
yortus commentedon Dec 9, 2015
Mohamed Hegazy (@mhegazy) #2338 is about module resolution, this issue is about emitting a
.d.tsfile. Not sure how related?mhegazy commentedon Jan 4, 2016
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.