ToddThomson / tsproject

Typescript minifier and modular typescript bundle optimizer for gulp (Ts Vinyl Adapter).

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Generate "declare module" definition file

heruan opened this issue · comments

Is tsproject able to bundle TS definitions in a single .d.ts file in the following form?

declare module 'bundle-name' {
  // project definititions
}

It would be useful to bundle plugins and provide a single module-definition file.

TsProject produces a single definition file for bundles. Definition files are built using the Typescript compiler API from the generated bundle source code, so are really dependent on the bundle source files.
I tend to construct bundles using sets of ES6 classes and so the definition files look like:

export declare class TodoItem {
    title: string;
    completed: boolean;
    constructor(title: string, completed: boolean);
}
... additional exports

Please let me know if this answers your question or not.

No response from @heruan so I am closing this issue.

@heruan I am reopening this issue. I now understand what you are asking for. I will try to get this into the 1.2.x NPM release.

See issue #71

Thank you @ToddThomson! I was busy and couldn't give feedback, but that's what I meant 👍
I'll gladly test when the feature will be available on master.

My requirements for TsProject bundles are this:

  1. Nodejs single file application/module which exports a minimal API. Usually this is just a single API method.
  2. Browser client-side single file component/application module
  3. Nodejs or Browser single file library module.

For all 3 of these uses I want to have a clean bundle definition file with only the exports that define the public API. I also want to be able to minify ALL internal identifiers.

I believe this can be achieved by convention and a bundle configuration property. For a commonjs node module I can make use of a Typescript namespace:

export namespace TsProject {
    export function src( configFilePath: string, settings?: any ) {
    }
}

// Nodejs module exports
module.exports = TsProject;

With the bundle configuration specifying the exportNamespace as TsProject the d.ts definition file will be constructed properly and all other exports will be considered internal giving the TsProject minifier a context to further minimize internal identifiers.

If the bundle package is set to "library", TsProject will now wrap the the non-external module references in:

export namespace "bundle name" {
}

this will result in a d.ts file like

// import statements to external referenced modules 
export declare namespace "bundle name" {
    // exported types
}

The "bundle name" is configured with "packageNamespace" in the bundle configuration.

If the bundle package is set to "component", TsProject will only export the API in the namespace that matches the bundleNamespace configuration option. For TsProject itself ( a node application/component ), bundleNamespace is set to "TsProject" and package is set to "component". This results in the d.ts definition file:

import * as stream from "stream";
export declare namespace TsProject {
    function src(configFilePath: string, settings?: any): stream.Readable;
}

@heruan This feature is now provided in the NPM release 1.2.0-rc.5.

Thank you very much @ToddThomson! I'll try it out ASAP.

@heruan You're welcome. Please use the latest rc.7 for testing.

Feature(s) provided in TsProject 1.2.

That's great, thank you! Is it also possibile to have TsProject generate just the bundles .d.ts files, without transpiling to JavaScript? I.e. having just the .d.ts files (one for each bundle) in the outputStream.

Also, I cannot get declare module in the bundle definition, only declare namespace. Can I configure TsProject to output declare module instead?

@heruan When I added this feature I originally provided "declare module". However, when I reviewed the feature with how Typescript 1.8.x defines a "module" it was apparent that "declare namespace" was the correct usage.

@heruan I have complete control over the output stream and what file streams are output. To have only the bundle .d.ts definition file output I would need to add a bundle configuration option. Perhaps you could add an issue for this and I will add it to the 1.3 milestone.

@ToddThomson I didn't know about declare namespace, but to work with ES6 modules a declare module is needed (also, many modules have names with dash chars). Maybe you could add a new option in tsconfig.json to output module declarations like this?

{
    "bundles": {
        "my-module": {
            "files": [ "..." ],
            "config": {
                "package": "module",
                "declaration": true
            }
        }
    }
}

and use the bundles key as module name:

declare module "my-module" {
// ...
}

@heruan I'm going to reopen this issue for tracking purposes.

I will review the use of declare "namespace" vs "module" again. However, I am almost certain that declare module in Typescript is now only used for pre Typescript 1.5.

The namespace "name" comes from ts code, so you should be able to use whatever you like.

@heruan OK, for ES6 modules, declare module should be used. Namespaces still must be supported. I will support both based on the code context.

The TsProject packageNamespace bundle configuration option will be renamed to packageModuleName. TsProject will determine if the module name refers to a Typescript namespace ( "Internal" module ) or an ES6 module and output accordingly.

That would be great! Why not use the keys of the bundles object in tsconfig.json as module names?

@heruan I'll have some time early next week to resolve this issue. Whatever is simplest and works with all package types will be what I go with. Thanks again for your input.

@heruan I've researched this issue and it is not straightforward in that there are a few TsProject concepts/features that are related. I want to find the simplest solution.
It terms of this issue, using TypeScript 1.8.x:

  1. TypeScript can generate an ambientDeclaration of the form declare module "mod-name" { }. Albeit with a bit of trickery.
  2. TypeScript does support the the generation of declare namespace NamespaceIdentifier { }
    2b) Typescript allows declare module NamespaceIdentifier {} but it is only for backwards compatibility with older TypeScript versions. TsProject will use the namespaceKeyword.

So, what I need to get from you is code ( a complete minimal TypeScript Project) that I can see that shows your need to have TsProject add an ambientDeclaration to the bundle d.ts file.

My thoughts right now are that I can produce a bundle d.ts file directly with TypeScript 1.8 using:

MyApp.ts - doesn't have to exist as a physical file for TsProject. TsProject could inject the simple code SourceFile.

export {} // A module named "MyApp" ( or whatever the physical filename is. if injected then use bundle name )

in App.ts

namespace MyApp {
  // bundle API goes here...
  export function src(): string {
      return "src";
  }
}

declare module "MyApp" {
    export = MyApp;
}

MyAppConsumer.ts

/// <reference path="app.d.ts" />

var bundle: string = MyApp.src();

The MyApp.d.ts produced is:

declare namespace MyApp {
    function src(): string;
}
declare module "MyApp" {
    export = MyApp;
}

@heruan I'd like to release v1.2.2 this week if I can get this issue closed. Do you have some time early this week?

Thank you @ToddThomson for your intereset. Here's a sample repository: https://github.com/heruan/tsproject-bundles
gulp build will produce this dist/src/bundles/foobar.d.ts:

export declare namespace foobar {
    class Foo {
    }
    class Bar {
    }
}

where I would expect this:

declare module "foobar" {
    class Foo {
    }
    class Bar {
    }
}

@heruan Thank-you. Can you update your test project to include a "consumer" for your bundled library? I would like to see how the consumer would use the foobar.d.ts in the way that you have requested.

@heruan There is so much conflicting information on how to structure a Typescript Declaration Module properly! From what I've read through concerning using Ambient Module Declarations ( what you are asking for - declare module "moduleName" {} ) within a d.ts is not the "current" / "proper" approach.
What I really need to see from you is how you want to consume a bundle created with TsProject and why this consumer requires an ambient module declaration within the bundle d.ts file.

Thank you @ToddThomson! The output you show here, i.e.

declare namespace MyApp {
    function src(): string;
}
declare module "MyApp" {
    export = MyApp;
}

could work for me, how can I get that?