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

Importing Typescript class from another npm library #2262

Closed
omer727 opened this issue Mar 8, 2015 · 27 comments
Closed

Importing Typescript class from another npm library #2262

omer727 opened this issue Mar 8, 2015 · 27 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@omer727
Copy link

omer727 commented Mar 8, 2015

Hi,
I'm working on node.js with npm. I've written a TS library and I want to use it in another project by doing npm install.
I'm not sure what will should be the contents in my npm package:
should it be only .js, only .ts, or .js with additional declaration files (d.ts)?

Furthermore if I'm using the third option, what exactly am I writing in the definition file? how exactly am I using it?I'm not sure what's the connection between d.ts and the actual javascript.
I see a lot of different answers but most them didn't help me.

Thanks,
Omer

@omer727 omer727 changed the title importing typescript class from another npm library Importing Typescript class from another npm library Mar 8, 2015
@mhegazy
Copy link
Contributor

mhegazy commented Mar 8, 2015

The .js is the executable, and the .d.ts is the type information that goes with it. in C terms, .js is your .dll , the .d.ts is your matching .h file.
you want to distribute matching .js and .d.ts.

on the consumption side, your user would do require("module") and that would load the JavaScript (based on node resolution logic, looking in node_modules and package.json and so forth).

import m = require("module");

To load the types you need an explicit reference to the .d.ts, this is a bug that should be fixed in future releases (issue #247), to do that in your consumer add:

/// <reference path="node_module/module/module.d.ts" />
import m = require("module");

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Mar 8, 2015
@omer727
Copy link
Author

omer727 commented Mar 8, 2015

I tried to write like you suggested:
I wrote the following ts class

    class Person {
        name:string;
        toString():string{
            return "the person name is "+name;
        }
    }

than to use it, I tried to compile it using the command
tsc --module "commonJs" --target "ES5" Person.ts --declaration
I took the results (js,and d.ts files) and put them in a different project under node_modules\demo
and wrote the following test:

/// <reference path="node_modules/demo/person.d.ts" />
import Person = require('person');
var p:Person;

console.log(p.toString());

my node_modules\demo\person.d.ts looks like this

declare class Person {
    public name: string;
    public toString(): string;
}

but when I compile using this command:
tsc --module "commonJs" --target "ES5" consumer\test.ts
I'm getting the following output:

C:\tdemo\consumer\test.ts(2,1): error TS2071: Unable to resolve external module ''person''.
C:\tdemo\consumer\test.ts(2,1): error TS2072: Module cannot be aliased to a non-module type.

capture

What am I missing?

@ahejlsberg
Copy link
Member

You need to export the class:

class Person {
    name:string;
    toString():string{
        return "the person name is "+name;
    }
}
export = Person;

A file with no imports or exports is not considered a module (but rather it is considered a "global" file that will be included using <script/> tag or some other means).

@omer727
Copy link
Author

omer727 commented Mar 9, 2015

I tried that but I still have problems with line:2 import Person = require('person');
when I compile the code using the command: tsc --module "commonJs" --target "ES5" test.ts
I get the following output:

C:\tdemo\consumer\test.ts(2,1): error TS2071: Unable to resolve external module ''person''.
C:\tdemo\consumer\test.ts(2,1): error TS2072: Module cannot be aliased to a non-module type.

I didn't find any reference which explains it clearly.
It will be helpful to get a simple example.

@mhegazy
Copy link
Contributor

mhegazy commented Mar 9, 2015

Here is what i have.

file: demo/person.ts

class Person {
    name:string;
    toString():string{
        return "the person name is " + this.name;
    }
}
export = Person;

file: test.ts

/// <reference path="demo/person.d.ts" />

import person = require("./demo/person")
console.log(new person().toString());

call the compiler on test.ts:

node c:\ts\built\local\tsc.js --m CommonJS test.ts

run it:

node test.js

this the result I am getting:

the person name is undefined

@omer727
Copy link
Author

omer727 commented Mar 10, 2015

Ok. It works. Thanks a lot!
I guess i need to explicitly require the full path of the .d.ts and js files.

Let me ask you another question.
If I want that person will be in module demo.

demo\person.ts

module demo{
 class Person {
    name:string;
    toString():string{
        return "the person name is " + this.name;
    }
 }
export = Person;
}

so when I'll import the file I'll get the module, and I'll aggregate several classes under one module:
test.ts

/// <reference path="demo/person.d.ts" />
import demo = require("./demo/person")
console.log(new demo.person().toString());

how to do it exactly?
I'm not sure about the details
(The documentation doesn't give enough examples, this is something that require improvement)

@danquirk
Copy link
Member

It sounds like you want to export Person rather than export=. Export= will make the module behave like the target identifier. So in this case you could say new demo() and you now have a Person. If instead you do not use export= and add an export modifier to class Person you'll access it through new demo.Person() like you have tried to do here.

@mhegazy
Copy link
Contributor

mhegazy commented Mar 10, 2015

your module demo is an "internal" module. so that is not going to work with node. This is a tricky issue that ppl have commonly ran into. i would look at the module wiki page first.

@atrauzzi
Copy link

This is a crazy amount of work involved that typescript offers no help with to get a basic unit of code sharing/reuse.

@danquirk
Copy link
Member

If you have some specific suggestions please do file issues. Modules in JS have been difficult for some time now and they're unfortunately complicated in TS as well. That should be getting better with ES6 modules. In an ideal world you might expect things like an npm JS package to include a .d.ts (we've had suggestions of this sort) and less confusion over what is/isn't an internal/external module and what that really means (we have a suggestion to use namespace instead of internal module to help reduce confusion).

@atrauzzi
Copy link

@danquirk - Definitely, I've just opened another ticket with some thoughts. And I think I've probably got another volley of observations as I try to articulate just what makes all of this so difficult.

@bgrieder
Copy link

There does not seem to be much tooling (external to the ts compiler) available either. A similar question I asked n StackOverflow is not attracting many answers... (http://stackoverflow.com/questions/31654376/automatically-generating-ambient-module-declarations)

This issue should trigger more attention. The ability to easily write reusable (external) modules - usable both from typescript and javascript - is key to large application and APIs development and maintenance.

@mhegazy
Copy link
Contributor

mhegazy commented Jul 28, 2015

@bgrieder i am working on this right now. can you elaborate on your scenario..

For input:

`api/Token.ts``

interface Token { 
    code: string
}
export {Token}

index.ts

export * from './api/Token'

would
(1) emit all in a single file:

out.d.ts

declare module "api/Token" {
    interface Token { 
        code: string
    }
    export {Token}
}

declare module "index" {
    export * from "api/Token";
}

(2) knowing that the entry module is "index.ts", follow the exported surface area of that and just emit its shape as such:
index.d.ts

// came from api\token.ts, then exported through `export *`
export interface Token { 
     code: string
}

(3) bundle that and rename it maybe.. would you want it to bundle, like:
out.d.ts

declare module "myPackage" {
    export interface Token { 
        code: string
    }
}

@mhegazy
Copy link
Contributor

mhegazy commented Jul 28, 2015

also as a note, your post on stackoverflow assumes that a default export can be exported. that is not true based on the ES6 spec.

so
api\token.ts

interface Token {

}
export default Token; // this is the default export, can only be imported using import d from "api\Token";

index.ts

export * from `.\api\token`; // token exports only a default export, * will bring you nothing.

so if you want to export token, you will either have to 1. mark interface Token as exported, or 2. import the default and reexport it.

export interface Token { // add an export specifier. this way it is exported as part of the module and as a default
}
export default Token;

or

index.ts

import Token from ".\api\token";
export {Token};

@jasonswearingen
Copy link

I'd like to chime in with my currently (as of 1.5) unsupported scenario:

I have a typescript library I'd like to publish to NPM, let's call it "CoreLib". And a program I'd like to reference this library. lets call it "MyApp"
CoreLib's structure is like this:

corelib/index.ts
corelib/dev/maths.ts
corelib/dev/jshelper.ts

The corelib/index.ts file exports the modules found under corelib/dev, something like:

//corelib/index.ts, this NPM module's entrypoint
export import maths = require("./dev/maths");
export import jshelper = require("./dev/jshelper");

I'm using CommonJs, and the above is written using the 1.4 module syntax, (in case there is something in 1.5 that makes this slicker, I haven't looked into that)

So I'd like to publish CoreLib to NPM, and then have the MyApp application depend on it.

unfortunatly, the following code does not work:

//myapp/index.ts
import corelib = require("corelib");   
var x = corelib.maths.add(1,3);
//typescript doesn't understand the typing definitions, 
//even though all the typescript files are there, under ```myapp/node_modules/corelib```

I hope this makes sense. Please let me know if you need clarification.

@jasonswearingen
Copy link

currently my workaround is that both corelib and myapp are in seperate folders under an umbrella typescript project. This means I can't publish to NPM, as I need to "use" the library in the following fashion

//myapp/index.ts
import corelib = require("../corelib/index");   
var x = corelib.maths.add(1,3);
//typescript understands the definition if I reference it relative, and the root most folder is inside the same typescript project.  (if i try accessing a folder below the project's root, typescript doesn't like it)

@mhegazy
Copy link
Contributor

mhegazy commented Jul 28, 2015

@jasonswearingen i think the change in #2338, @basarat has already submitted a PR (#3147) for the change, and @vladima is doing the infrastructure/tooling work needed and should be done soon.

@bgrieder
Copy link

@mhegazy Glad to hear you are working on it !

On the fact that default exports cannot be re-exported: I missed that entirely from the specs. The goal would be for index.ts to re-export everything that needs to be made "visible" to the library consumers. The scenario would then be for index.ts to contain (added User to illustrate)

import Token from ".\api\token";
import User from ".\api\User";
export {Token, User};

My preferred end-result would be your scenario 3) i.e.

declare module "myPackage" {
    export interface Token { 
        code: string
    }
    export interface User { 
        username: string
    }
}

The reasons are that this is consistent with the content of index.ts and is also the most consistent with what is mostly found for librairies on DefinitelyTyped: a single .d.ts file that contains all the definitions.

@bgrieder
Copy link

I guess the single d.ts file would also be a simpler fit for the case where User has a property token: Token

@jasonswearingen
Copy link

@mhegazy awesome, thanks for the links and status update.

@jjrv
Copy link

jjrv commented Aug 17, 2015

I've come up with one solution that already seems to work today. It relies on a small tool called autodts which is based on previous work from tsd and dts-generator. There's an example package which is published on npm and an example project that uses the package.

Typings are packaged to two .d.ts files when publishing a package, and reference paths are resolved when installing packages so that dependencies across nested node_modules dirs all get resolved correctly.

There's a blog post with more detailed information.

@bgrieder
Copy link

Since work is performed on this issue, shouldn't this issue be re-opened or its content moved to a new issue ?

@mhegazy
Copy link
Contributor

mhegazy commented Sep 15, 2015

@bgrieder the work referenced in #2338 has been completed and is in master as well as in TS 1.6-beta release. See PRs: #3147, #4154, and #4352.

With TS1.6, you do can import node typing just like you import the js files, which eliminates the need to bundle your .d.ts into a single file. can you give it a try and let us know if the issue has been truly resolved or not.

@bgrieder
Copy link

@mhegazy The answer is Yes (99%) ! Details posted in this issue: #247

EDIT
100% with the suggestion from @sccolbert of moving ambient module declaration files references to tsconfig.json and getting rid of the <reference ...> instructions

@matthiasg
Copy link

matthiasg commented Apr 18, 2016

@mhegazy is there any example project setup demonstrating how to use an external typescript module ? Optimally a project with a proper test setup to be more realistic (btw i mean a TS module referencing another TS module, both of which using some node standard modules like streams).

I am basically stuck with using require to import my own modules since all generated d.ts files include reference statements to e.g node.d.ts which i use in the referencing module too. Causing a duplicate identifier error.

Example:

c:/Development/iris-event-subscriber/node_modules/iris-event-publisher/typings/node/node.d.ts(343,9): error TS2300: Duplicate identifier 'RangeError'. 

Both modules are referencing node.d.ts and others of course in the ts files.

PS: the tsconfig.json in both projects excludes the typings folders its really just the /// reference statements, removing them and using standard require then works without errors but leaves me without types of course.

@mhegazy
Copy link
Contributor

mhegazy commented Apr 18, 2016

@matthiasg this is the problem that #7156 attempts to solve. node.d.ts should be included only once though it is referenced by both modules.

For now the recommendation is to not add /// to your dependencies, as that force your user to include them. see https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Typings%20for%20NPM%20Packages.md

that means that your sources should not include the /// <reference .. > either.

#7156 is scheduled for the next release, TS 2.0 and is available in master already, so if you want to try it, you can use typescript@next, and then switch your /// <reference path="typings\node\node.d.ts" /> to add a "types": ["node"] in your tsconfig,json, and add /types/node/index.d.ts

@matthiasg
Copy link

matthiasg commented Apr 19, 2016

Good to hear that work is being done there.

PS: If i do not add the /// statements though VSCode and Sublime dont work correctly and do not offer intellisense.
PS: There seems to be a jsconfig file i can use too .. i will look into that
PPS: After restarting a few times it seems to work

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

9 participants