Typescript: Support looking for modules under node_modules when importing

Created on 25 Jul 2014  ·  138Comments  ·  Source: microsoft/TypeScript

Update 5 November 2015

The functionality requested below is currently implemented in typescript since at least 1.8 with one main difference:

Instead of having typescript.main and typescript.definition properties, there is only one typings property which you can point to either a d.ts file or a normal .ts file.

If you're developing a module to just use locally, you can have the typings point to a .ts file, but if you plan to publish the module, it is recommended to have it point to a d.ts file. This is because you don't want your module consumers to recompile your module files, just consume its typings.

I have setup an example of using this here:
https://github.com/chanon/typescript_module_example

There is a documentation page here that has more information:
http://www.typescriptlang.org/docs/handbook/typings-for-npm-packages.html

Thank you TypeScript devs and all contributors.

Original issue / feature request follows


Motivation

In TypeScript it is a lot harder to re-use typescript modules compared to re-using npm modules in JavaScript.

It would be beneficial if the typescript compiler is smart enough to look in node_modules folders and package.json files.

The reason is so that npm module developers that use TypeScript might be able to start writing and distributing modules through npm itself. TypeScript would be able to piggyback on npm's infrastructure and wide support.

Example for node_modules

If we had:

./node_modules/concator/index.ts
./myApp.ts

And in index.ts we had:

export function concat(param1: string, param2:string): string {
      return param1 + ' ' + param2;
}

in myApp.ts:

import concator = require('concator');  // loads the module from node_modules
var result = concator.concat('I like', 'this.');
var wontWork = concator.concat('this will fail');  // compile error, concat needs 2 params

So basically, the compiler is smart enough to find the module in node_modules and it automatically uses the typescript version (index.ts).

Then when the code is compiled to JavaScript, it naturally uses the JavaScript version.

Importing Folders as Modules

A more basic case is supporting Node.js's popular rule of http://nodejs.org/api/modules.html#modules_folders_as_modules as suggested by @vvakame below and in the closed (semi-duplicate) #207 issue.

typescript.main in package.json

There could be an addition to package.json files to specify where the main .ts file of a TypeScript npm module is. This would be similar to the main key that specifies where the main JavaScript / .js file is but for TypeScript instead.

Eg package.json for an npm module named "myModule" located at node_modules/myModule/package.json

{
     "main": "./dist/index.js",
     "typescript": {
          "main": "./src/index.ts"
     }
}

In this example node_modules/myModule/src/index.ts would be the main TypeScript file and doing an import myModule = require("myModule"); would import the node_modules/myModule/src/index.ts file.

For a JavaScript coder writing var myModule = require("myModule"); in a JavaScript file, the require would load the node_modules/myModule/dist/index.js file as usual.

As can be seen in this example, the TypeScript src is in the node_modules/module-name/src folder and the compiled JS files would be in node_modules/module-name/dist.

Something like this could be a (semi) standard for TypeScript npm modules so that the TypeScript source is cleanly separated from the compiled JavaScript output.

A more basic case as suggested by @vvakame below is supporting the popular Node.js rule of http://nodejs.org/api/modules.html#modules_folders_as_module

typescript.definition in package.json

Another possible key for package.json for non-TypeScript (plain JavaScript) npm modules could be typescript.definition. This would point to a .d.ts file that defines the module for TypeScript users of the npm module.

So that an
import $ = require('jquery');
would automatically read a jquery.d.ts file defined in the typescript.definition key in jQuery's package.json and make $ the correct type.

Example format:

{
     "main": "./dist/index.js",
     "typescript": {
          "definition": "./index.d.ts"
     }
}

(This format is already used by tsd as @Bartvds explains below.)

Then us TypeScript coders would just have to try to get as many non-TypeScript npm module maintainers to merge our pull requests that have our .d.ts files and package.json typescript.definition keys.

If we succeed with that, then TypeScript coders' life would be bliss ... no more separate managing of DefinitelyTyped .d.ts files. Just npm install and you get your TypeScript definitions too! Automatically and up-to-date with the module version installed.

List of Benefits

What we get from all of this is

  • npm modules can be written in TypeScript.
  • Both TypeScript and JavaScript coders can use these modules
  • The module users who use TypeScript get the benefits of static typing without the module coder (or user) having to use the usual method of writing (or generating) separate .d.ts files (so when the sourcecode of the module is already in TypeScript we don't have to have another .d.ts file to maintain)
  • There would be a way to easily re-use and distribute TypeScript modules using npm which everyone is already familiar with
  • It could help promote TypeScript usage itself, as JavaScript coders who use the npm modules written in TypeScript might see how nice / better the TypeScript source is and try switching to it. Or they might want to contribute to the module and since the source is in TypeScript they would have to learn it which may lead to them liking it and deciding to use it in their own projects.
  • Basically it would help grow the TypeScript community through all the code sharing that could result. Right now everyone's TypeScript code is mostly their own / in their own silo. This is actually probably an extremely important thing as not having a proper/easy way to share/distribute/re-use modules is probably limiting the growth of the language. [1]
  • Re-use of modules in internal projects would be a lot less of a hassle and would reduce the need for all those relative paths to .d.ts files and relative paths in module requires, and general .d.ts stuff. (Right now when writing TypeScript code, I find I have to learn to be good with counting ../../ s)
  • This approach also supports Browserify/Webpack as Browserify/Webpack also look under node_modules, so this allows for easy reusable / npm distributed TypeScript modules for both the server and browser
  • Non-TypeScript npm modules can easily be used in TypeScript with type information through the typescript.definition key addition. This allows .d.ts definition files to be packaged together with the npm module so that updating an npm module will automatically update its .d.ts definition file. This removes the need to update .d.ts files manually.
  • Using definition files through the typescript.definition is simpler because it is simply an import moduleName = require("moduleName") statement with no need for a separate ///<reference ...
  • Using definition files through the typescript.definition should also allow the use of different versions of the module in the same code base without type names clashing.

Detailed Proposal

@Nemo157 has written a very detailed proposal for how all of this should work at:

Proposed TypeScript Require Resolution Semantics
https://gist.github.com/Nemo157/f20064a282ee620f3877

An addition in the proposal is the usage of /typings folders which can hold definition files that can be automatically managed by tools such as tsd for JavaScript npm modules that won't include definition files in their repositories.

Final Supporting Facts

Since TypeScript compiles to JavaScript which runs mainly in two places: node.js and in browsers, supporting node_modules is beneficial to both places (practically all of where TypeScript is used) because of npm and Browserify/Webpack.

ie. there is no reason to come up with a different scheme when node_modules is what all TypeScript users use already for maybe 75%-100% of all their JavaScript code. (Taking out 25% for maybe RequireJS users.)

Footnotes

[1] - BTW I see there is a NuGet package manager (?) from Microsoft that can distribute typescript modules (?), but coming from a node.js focused (non .NET focused) background, I don't see NuGet becoming widely used outside of Microsoft focused shops, especially as npm is _the_ standard for node.js and is a leading standard for client side JavaScript too. If I didn't use TypeScript I would have never heard of NuGet. The average node.js / client side JavaScript coder would probably prefer using npm, the same tool they use already rather than having to use something Microsoft specific such as NuGet. (I don't actually know a thing about NuGet so what I'm saying here might not actually matter.)

Committed Suggestion

Most helpful comment

In any case, the compiler currently claims that it can't find the module, when in actual fact it's just refusing to import the module without type definition. That's not really helpful, is it?

All 138 comments

[moved to typescript.main in package.json in the issue description above]

:+1:
I think http://nodejs.org/api/modules.html#modules_folders_as_modules is very popular rule of Node.js.

other example.

./test/index.ts

export function hello() { return "Hello, world"; }

./main.ts

import test = require("./test/");
console.log(test.hello()); // print "Hello, world"

[moved to typescript.definition in package.json in the issue description above]

One solution would be to solve it with better tooling. E.g. import foo = require('foo') gives you a hint to look for some local node_module+package.json (foo is a ts project) or DT definition (foo is a js project where library authors don't want to maintain the ts def).

FYI; I'm actually testing something similar to this in TSD; a way to expose & link definitions that are bundled in the npm (or bower) packages.

It's in my dev version for 0.6, and it works by adding a typescript element to the package.json (or bower.json), with a sub element definition (a sub element because maybe one day there'd be source too, or whatever).

{
    ...
    "main": "./index.js",
    "typescript": {
        "definition": "./foo.d.ts"
    }
    ...
},

Then you can run a command on TSD, currently tsd link and it will scan all package.json files in node_modules (or bower or whatever), find that property if defined and add a reference to it to the central tsd.d.ts bundle in your project.

Example: defined here and use here

@Bartvds That's pretty nice. Could be the first step for having .d.ts in npm packages. I like the package.json structure with "definition" in "typescript", it's a lot more organized.

If TypeScript compiler itself could read it automatically, would be very cool.

Tagging this for discussion -- smarter external module resolution is something we need to talk about to understand the various scenarios here. This was brought up previously and we made a few tweaks in 1.0.

Also discuss #207 - looking under 'index'

:+1:

@chanon tsMain is not necessary if we use declarations generation.

Yep, the issue is immensely important even for browsers - lots of projects use browserify/packify and therefore node-compatible directory layouts

:+1:

There's been some work already in this area back in the codeplex repo, one pull request by leebyron and one by kayahr.

I've been meaning to try and port one of them to the new repository, but it seems the related code has been re-arranged a lot.

I have written a proposal for how to add this to the TypeScript compiler. This also includes looking in the typings directory as that will be important when working with javascript modules that will not maintain their own typescript definitions. Unfortunately to make this all work transparently with tsd will still take a little work as the requirement of /// <referenced files being ambient external declarations makes things a little hairy. I will take a look at implementing this on a branch and providing some example modules using it.

Looks sensible. Will this cover modules with clashing type dependencies? I mean if an app imports A and B, and B is also dependent on A. It's easy to fall into duplicate type errors if there are two copies of the external declarations.

It shouldn't, they should resolve to different external module names so declare identically named independent types in different modules. The structural type checking should then allow the different modules to interact without issues.

That's one of the major issues with the current tsd definitions that this is trying to resolve, by defining external ambient modules there is no way for them to handle having multiple versions of a library with identically named, but slightly different types.

@joewood also there will be troubles between different versions of A

├── [email protected]
└── [email protected]
    └── [email protected]

there is no way for them to handle having multiple versions of a library with identically named, but slightly different types.

interested to hear solutions to the same. TypeScript has a global level variable scope for ambient declarations

Even if the versions match, at the moment I see issues with two different, but matching ambient declaration files causing duplicate symbol errors. The locally defined /// reference directive needs to somehow resolve back to a single file. Either that, or the type identity needs to include the path and structural typing will take care of version mismatches etc..

@joewood Yep, that's what I hope will be fixed in the majority of cases by changing require to load the correct definitions, avoiding /// reference wherever possible. The external modules will be identified by their absolute paths instead of having ambient declarations, so loading multiple of the same name from different paths and at different versions should be possible.

Yep, that's what I hope will be fixed in the majority of cases by changing require to load the correct definitions, avoiding /// reference wherever possible.

:+1:

:+1: Thanks @Nemo157 and everyone for the discussion.

I've updated the top issue text to merge the package.json additions and linked to @Nemo157's proposal.

:+1:

I've done an initial implementation of my proposal along with creating an example set of modules to show that all the different ways to make a module will work. Including using different versions of the same library in different sub-modules.

:+1:

Folders as modules with index.ts :+1:

:+1:

:+1:

:+1:

:+1:

:+1: :+1: :+1:

:+1: :+1: :+1: :+1:

Commenting to bump this back onto our radar.

I've had good success with @Bartvds's suggestion :

Then you can run a command on TSD, currently tsd link and it will scan all package.json files in node_modules (or bower or whatever), find that property if defined and add a reference to it to the central tsd.d.ts bundle in your project.

So definitely consider supporting typescript in package.json as that's the way community has gone.

We definitely need tsc to be aware of the node_modules folder if TypeScript is going to be widely used in the node.js community. I currently use symlinks to modules that are part my development project (using npm-workspace), and I have two options at the moment:

  • import directly from node_modules, which looks wrong: import Foo = require("node_modules/mymodule/Foo");
  • get tsc to generate the declaration files and then stitch them together using a manually maintained module declaration file that switches over the string module names

Could tsc check the external module name in import declarations and then during path resolution also apply node_modules in the same manner that the node.js runtime does? A compiler option could also turn this on or off.

:+1: :+1: :+1:

Could tsc check the external module name in import declarations and then during path resolution also apply node_modules in the same manner that the node.js runtime does

This is how it _should_ be. The current manner of just looking up the directory tree to find a file with the said name bares no resemblance to JS execution contexts.

This has been on my list for a while now. will try to get this in the next release.

We (Booktrack) use TypeScript for all our web development, and we face similar challenges to the node.js developers. I speak up because the voice of web devs doesn't seem as loud as that of the node devs.

Solutions (in order of worst-for-us to best-for-us):

  1. resolution of top-level external module names via hard-coded lookup into node_modules
  2. no control over resolution of top-level external module names
  3. a compiler flag to control the optional resolution of top-level external module names into the node_modules directory
  4. a compiler "module search path(s)" parameter to control the optional resolution of top-level external module names into the given path(s)
  5. a compiler "module resolver" parameter to provide the compiler with a JS plugin that will resolve all (not just top-level) external module names
  6. appropriate hooks into language services such that the external module name resolution is controllable

Option 1 is terrible, I'd rather option 2.

Option 5 and 6 would allow for exotic uses of import statements, such as those found when working with requirejs or webpack plugins. The basic idea: the compiler delegates all external module name lookups (not just top-level) to a third-party (a plugin or callback). The delegate, given the path to the module under compilation and the external module name, returns to the compiler the filesystem path OR the type information for the given module name.

I'd be happy if option 4 was implemented, delighted if option 6 was implemented and over-the-moon if both option 4 and option 6 were implemented.

How about a --basePath for your module search root, all module imports will be looked relative to that path. This only applies for AMD.

I think the node issue is much simpler, we just need to implent it:)

Note that we actually got an implementation (and tests) from @Nemo157 here long ago https://github.com/Microsoft/TypeScript/issues/247#issuecomment-57422329

Agree with @mark-buer and @mhegazy, option 4 a search path should be a simple fix. A more elaborate solution in the language service (option 6) is definitely required longer term.

_Worth adding_: this alone won't fix the TypeScript reuse issue in npm because of the duplicate symbol issue for common type references #1125. We really need to avoid /// by using .tsconfig files and get this fixed properly by bundling packages into a single external typed module as suggested in #17

Another vote for getting the node_modules folder recognised by the typescript compiler.

For CommonJS modules, typescript should ideally be following the same filename resolution that require.resolve does (as outlined by the psuedocode on this page: http://nodejs.org/api/modules.html#modules_all_together.

this functionality is critical to allow integration of the typescript compiler into other node packagers, such as browserify. @Nemo157 's patch does a fine job, but it doesn't allow for easy testing with existing packagers as most have moved on to later versions of typescript and are no longer compatible with his code

Where does the node_modules directory have to be relative to the sources?

Atom-TypeScript now supports typescript.definition out of the box. Details : https://github.com/Microsoft/TypeScript/issues/2829

You can also create such packages easily using atom-typescript :rose: again see #2829

Limitation

It will work _flawlessly_ if you share a package that doesn't depend on any external libraries. If it does then we need a module conflict resolution algorithm.

I am open to suggestions for this case, but my plan is:

  • we do some smart stuff in reading such a d.ts not giving TypeScript the external reference comments (e.g. node.d.ts here https://github.com/TypeStrong/atom-typescript-examples/blob/master/node/node_modules/example-typescript-b/definition/sample-b.d.ts) And instead pointing to our own .d.ts if we have it in our Typings.

Is this going to stay on track for 2.0? It seems like kind of an important feature for Node.js adoption, which right now is painful, even with DefinitelyTyped and the likes.

It seems like kind of an important feature for Node.js adoption, which right now is painful, even with DefinitelyTyped and the likes.

@LPGhatguy Please see https://github.com/Microsoft/TypeScript/issues/2338. @vladima is working on it but don't think its been assigned any milestone yet :rose:

@LPGhatguy we are trying to get this in the next release. hopefully soon.

@mhegazy You mean 1.6? That would be awesome!

Is that related to #2338?

yes for TypeScript 1.6 (at least this is what we are trying to do), an yes for #2338; there are a couple of other changes and issues around this around including #3147 and #4154

Then, why does it tagged as TypeScript 2.0 as a milestone?

thanks @heycalmdown, removed the milestone. we do not usually tag milestones to suggestions. hopefully we have an update within a week or so on this issue. stay tuned.

I would like to chime in asking for ES6 module support to follow this as well (which I assume it would?)

Typings for

import mylib = require('mylib');
mylib.foo(mylib.bar);

should behave the same as

import { foo, bar } from 'mylib';
foo(bar);

I would like to chime in asking for ES6 module support to follow this as well (which I assume it would?

@DavidSouther That would _automatically_ be the case. The lookup between the two is consistent :rose:

this is handled by #4154.

If I'm using nightly, starting tomorrow I should expect importing node modules to "just work"?

Question:
I think it is a common convention to have files named
"index" in commonJS and
"main" in AMD
for short paths.
In the question on top the author imports
/node_modules/concator/index.ts as
import concator = require('concator');

Please forgive me - I am currently figuring out if also the index resolution is supported now and how about index.ts or main.ts in AMD requires` resolution?
I am also pinging @basarat to know if it is / will be supported by https://github.com/TypeStrong/atom-typescript because now it does not allow me to require any index.ts as mentioned above.

@sebilasse, if you are using typescript@next today, this should work with --module commonjs

import concator = require('concator'); // resolves to node_modules/concator/index.ts

There are not changes to how AMD resolution from previous releases.

@DavidSouther it should be in typescript@next today. give it a try and let us know how it fares.

@mhegazy @basarat :+1: ok. But how do you think about AMD resolution for main.ts or index.ts - shouldn't it be consistent in the future?

@sebilasse, for AMD i think we need to do something as noted by @vladima in #2338 (section for: RequireJS/ES6 module loader).

Very nice!! \o/

@mhegazy Still not working quite as I expect.

I put together https://github.com/DavidSouther/typescript-example-using-node with my "ideal" use case. tslib is a simple library that exports a single function. tsclient depends on tslib, as well as readline from the Node packages. The setup script is a convenience to install, link, and execute. I've included a run, and annotated the unexpected portions with inline comments.

% ./setup
...
> [email protected] build ~/ts-node/tslib
> tsc --version ; tsc -p src/

message TS6029: Version 1.7.0-dev.20150831
...
> [email protected] build /Users/southerd/devel/tmp/ts-node/tsclient
> tsc --version ; tsc -p src/

message TS6029: Version 1.7.0-dev.20150831
# Expect this to find tslib, but fail type checking.
# See tsclient/app.ts for details
src/app.ts(4,21): error TS2307: Cannot find module 'tslib'.

+ node ./dist/app.js # This works as expected!
What would you ask? What is the meaning of life?
42
+ set +x

I am also not getting language support for tslib imports in tsclient (VSCode Version 0.7.0 (0.7.0)), though I am not entirely sure which TSC it's using, or how to change that.

@DavidSouther TypeScript compiler check 'typings' field in package.json to find '.d.ts' files. With this change I'm getting src/app.ts(13,7): error TS2322: Type 'string' is not assignable to type 'number'. which looks like a legitimate error

Ah, I must have missed that piece of docs. I'm still having the second issue - how do I change the VSCode tsc version?

you can provide custom TypeScript SDK location to VSCode via "typescript.tsdk" setting - it should point to the folder containing tsserver.js and standard '.d.ts' files. In case if TypeScript is installed as npm package it will be something like

// Place your settings in this file to overwrite the default settings
{   
    "typescript.tsdk": "C:\\Sources\\bugs\\node\\typescript-example-using-node\\tslib\\node_modules\\typescript\\lib"       
}

Did anyone get a sample working ? I tried the whole afternoon to play with this, but I always end up with

error TS2307: Cannot find module

At the moment the code is in node_modules/my-module/. And I defined an index.ts that takes care of exporting everything, and I referenced it in the package.json under typescript.main. In the consumer project I tried import {AClass} from 'my-module'; and import my-module = require('my-module'); They both lead to the same error with typescript 1.7.0-dev.20150901.

When resolving a module with non-relative name, with --module commonjs, the compiler will look for a .d.ts that matches the name in node_modules\name\index.d.ts or look in package.json for a property typings, and load the .d.ts it points to.

from your description, looks like you are setting it to index.ts, you do not really want to compile your dependencies, you just want to consume their typings, so you should set it to index.d.ts instead.

I've updated steps in node module resolution algorithm to reflect the implementation: https://github.com/Microsoft/TypeScript/issues/2338

@DavidSouther I have the exact same problem apparently. When I compile with a dependency in node_modules it can't resolve the module and outputs an error, but then, it still generates the js, and if I run it, it runs. Maybe it's due to my setup I don't know. My d.ts is mostly empty, I have an index.ts that takes care of importing and reexporting all the classes of my modules defined in many .ts files. And then my d.ts file just references this index.ts and exports everything from index.ts like this :

/// <reference path="index.ts" />

declare module 'my_module' {
    export * from 'index';
}

Also, it somehow still compiles the ts from node_modules, therefore I should add a clean task to delete it after compilation. Is there a description of the feature somewhere that sums up the process ?

edit: I got it to work, but it's super hacky and still outputs errors. I created a d.ts with dts-generator, and then I imported my classes like this :

import MyClass from '../../node_modules/my_module/dist/MyClass';

If I use import MyClass from 'my_module/MyClass' I get it to compile without error, but at runtime I get the error cannot find module 'my_module/MyClass'. With the above solution it points directly to the compiled .js and at runtime it works somehow, even though it outputs the error cannot find module at compile time.

Still having issues importing from submodules (eg npm install angular2, import { Inject, Binding } from 'angular2/di'. Will work on getting a contained test case up tonight.

Don't think that angular already bundles its typings in the way expected by TypeScript compiler.

That's likely true; I'll work with them and see where it ends up.

this feature is working well on typescript 1.1 , for example https://github.com/Nemo157/typescript_w_node_modules_example

but fail in typescript 1.5.3 , anything changed ?

------ update ---------

I know it will release in 1.6 , thanks

I can only get this "half-working" in 1.6.2

First, I package a library with a my-lib.d.ts in the dist directory, and points to this file in the package.json file typings attribute (e.g. "typings" : "dist/my-lib.d.ts" )

Then I import this library in a Test.ts TypeScript file using

import { MyObject } from "my-lib"

MyObject is properly imported and the js code is emitted on transpilation.
Visual Studio Code even provides completion on MyObject

However I get compiler warnings that:
Test.ts(10,60): error TS2306: File '[]/node_modules/my-lib/dist/my-lib.d.ts' is not a module.

Visual Studio Code will actually show the import as a hard error

Any imported modules from a node package need to be "external modules" and not "ambient module declarations". i.e. no declare module "foo" {.. } declarations, but rather, top level import or export declarations in the file.

So if your package "my-lib" is written in typescript, just build it with --declarations and set the typings to the .d.ts file of your main module. if not, and your typings are something you hand authored, or got from definitely typed, then you will need to either change it to an external module, or wait until #4665 is resolved (hopefully soon).

The reason of this restriction is this can cause global scope pollution and conflicts later on for your package users. there is a long discussion about this in #4665.

Here is some documentation for future searches: https://github.com/Microsoft/TypeScript/wiki/Typings-for-npm-packages

@mhegazy Thank you for your quick reply which really helped in my quest to automate the build of commonjs librairies, written in Typescript, which can be consumed by both Javascript and TypeScript code.
import/export syntax and resolution has been moving so fast recently that what I guess is missing right now is a definitive guide on how to build one.

I got it working... with one caveat (and hence another question)

Here is a simplified view of the setup.

The library consists of three objects A1, A2 and B in 2 typescript files A.ts and B.ts; something like

sources

A.ts

class A1{}
class A2{}
export { A1, A2 }

B.ts

class B{}
export { B }

What we want to expose is collected in an index.ts :

export * from './A'
export * from './B'

build
The build is conducted with the help of grunt and the --module commonjs and --declaration flags are set on tsc1.6.2
The end result of the build is a tree that looks like

    package.json
    dist/
        js/
             A.js
             B.js
             index.js
        typings/
             A.d.ts
             B.d.ts
             index.d.ts

package.json contains these two entries:

"main": "dist/js/index.js",
"typings": "dist/typings/index.d.ts"

Using this library in TypeScript 1.6.2 with a simple import {A1, A2, B} from "mylib" works absolutely fine. Multiple level dependencies (i.e. librairies importing each other) works fine too.

Where problems arise is when the library depends on another library which is _not_ a TypeScript library.
Say the library depends on Node.js.
In one of the source files there will be a reference to the typings of NodeJS e.g.
///<reference path="../typings/node/node.d.ts"/>

Upon transpilation that <reference > instruction is going to end up in the corresponding declaration file; the problem is that the path to node.d.ts is likely to be wrong or inexistent.
Is there a recommended way to go about this ?

_Note_: this problem is alleviated by the use on an index.ts file to expose the interesting parts of the library, since index.ts has no reason to contain a <reference > instruction and with 1.6.2, the compiler does not seem to care that A.d.ts has invalid path in the reference instruction

@bgrieder We handle this in Phosphor using tsconfig.json:
https://github.com/phosphorjs/phosphor-widget/blob/master/src/tsconfig.json

We simply add any external typings we need to the compiled files. This does mean that if any of those external types are part of our public interface, the consumers of the code will also need to provide those external typings as part of their build. _This is okay_. If we were to bundle those definitions, and some other lib used by the consumer _also_ bundled those same definitions, there would be duplicate symbol conflicts.

@sccolbert Yes !
I was worried that removing <reference ...> instructions would break auto-completion on all IDEs, but hey, no: at least Visual Studio Code 0.8.0 seems to be smart enough to pick those definitions from the tsconfig.json file ! (after a restart)

Done with reference. Excellent !

@sccolbert

What if you are using a plain Node.js project within it and *.d.ts for it, how does the tsconfig solution help you?

@heycalmdown you just add the d.ts to the files field on the tsconfig, here's an example which does that:

https://github.com/phosphorjs/phosphor-widget/blob/master/test/src/tsconfig.json#L11
https://github.com/phosphorjs/phosphor-widget/blob/master/test/src/index.ts#L10

Note that we have to use require here instead of import only because of how the d.ts file for expect.js is written internally.

Okay, I got it. Your repositories look like a kind of a good example.

Has this actually been implemented in TypeScript 1.6.2?

If it has, can somebody tell me what I'm doing wrong here?:
https://github.com/chanon/typescript_module_example

You probably want the es6 import syntax.

On Wed, Nov 4, 2015, 6:10 AM chanon [email protected] wrote:

Has this actually been implemented in TypeScript 1.6.2?

If it is, can somebody tell me what I'm doing wrong here?:
https://github.com/chanon/typescript_module_example


Reply to this email directly or view it on GitHub
https://github.com/Microsoft/TypeScript/issues/247#issuecomment-153688004
.

I've just updated it to use the es6 import syntax and I still get the same errors (cannot find module).

@chanon the package.json for node_modules packages need to have a typings key which points to a d.ts file, _not_ a typescript key pointing .ts files like you have now.

We use node modules resolution in PhosphorJS exclusively (on TS 1.6.2) and it works fine. Here's an example: https://github.com/phosphorjs/phosphor-widget/blob/f908341cb1d46ada8ad8149e695a75e7ea2fde57/package.json#L6

Thank you @sccolbert, however my initial proposal (at the top of this issue) was to allow for a typescript.main property in package.json which would point to the main TypeScript entry point for node module packages written in TypeScript.

This would allow for importing TypeScript modules in TypeScript code without the need for d.ts typing files (there would be no need to even generate them).

@chanon I was just pointing out what you need to do to get your code running.

@sccolbert I see thanks.

Can someone who knows tell me if I should create another issue requesting this specific feature?

Main related issues to module resolution logic (other than this one) seem to be #2338 which is closed and #5039 which seems to be about supporting SystemJS style path resolution which looks very complex.

However just simple CommonJS style importing like initially requested in this issue seems to have been forgotten? At least the part about typescript.main and folders as modules?

I don't understand the need of having (and desire to have) d.ts files if both the module and the module consumer are already written in TypeScript.

It's actually not much different from importing a TypeScript file relatively ex.

Instead of having to do:
import * as lib from '../relative/path/to/typescriptFile.ts'

Or:
import * as lib from '../../node_modules/myModule/index.ts'

Just let TSC be able to find the typescript file to import using normal node_modules path resolution. And at least allow for importing folders as modules (with an index.ts) so that in the second example I could do:

import * as lib from 'myModule'

Or is it because of "you do not really want to compile your dependencies, you just want to consume their typings"?

@chanon the behavior you outline is what is in master now. can you give typescript@next a try?

the original implementation of #2338 added some extra checks to ensure it is a .d.ts, and that is mainly for the reason you mentioned. you do not want your package users to compile your "sources" on every compiler invocation, and get different output based on their compiler options, you only want to share your typings.

However if you are building both packages you might want to do that while iterating on them. The restriction was removed in #5278.

Please take a look at the wiki page for sharing typings through npm package for more information: https://github.com/Microsoft/TypeScript/wiki/Typings-for-npm-packages

Thank you for your clarification @mhegazy! I'll try it out.

@mhegazy Just tried with typescript@next, works as expected!

This is great!! Thank you!!!

And thank you everyone for participating in this and related issues :+1:

I've added an update to the issue text above to explain the way this feature has actually been implemented.

At risk of stepping into a minefield, how is this expected to work with plain-old npm modules that _don't_ have a typings property in package.json? Trying to use either tape or get-parameter-names with typescript@next causes it to blow up in my face as it is unable to find those packages due to the fact they don't have that property.

Sure, they are not typescript modules, but I can't seem to find a work around and that locks out a significant portion of the npm ecosystem.

@danpantry Usually you would use a normal .d.ts file written specifically for that module. The DefinitelyTyped project has .d.ts files for lots of common modules. There is a tsd utility that can help install and manage .d.ts files from DefinitelyTyped for you.

You can use normal modules without typing by just using the normal commonjs require syntax.

eg
var moduleName = require("moduleName")

@chanon feels like a bit of a hack to use the 'classic' require syntax when using the ES6 syntax with TypeScript so I can use JavaScript dependencies, but thank you. I have heard of the tsd utility but I am unsure of how that would factor in to using ES6 imports.. unless I have to start using /// <reference path=... for these sorts of modules?

Yes that is one way. Another way is to us tsconfig.json files and put your root tsd.d.ts file as the first file.

You may also be interested in reading this, covering tsd and typings https://angularclass.com/the-state-of-typescript-packages/

@joewood thanks for the info, last time I looked into typescript was in the days of individual .d.ts files. excellent article

@chanon @danpantry It's somewhat unfortunate that import foo = require('foo') (with foo under node_modules) does not work when foo's package.json does not have a typings.

@wmono well, since maybe TypeScript 0.80 or maybe earlier import foo = require('foo') syntax was only for importing modules with typings. If it didn't have typings you'd use var foo = require('foo') instead. So I'd say its just the way it is.

@chanon Pardon me; user error. It looked like webpack was struggling with the "bare" require but the problem lay elsewhere.

Sorry can someone clarify why
var foo = require('foo');
Works for a standard node_module without typings but
import foo from 'foo';
doesn't? Was that just arbitrarily defined? I don't see why the latter _shouldn't_ work - I don't care if the external JS library has typings or not.

Sorry can someone clarify why
var foo = require('foo');
Works for a standard node_module without typings but
import foo from 'foo';
doesn't? Was that just arbitrarily defined? I don't see why the latter shouldn't work - I don't care if the external JS library has typings or not.

@harangue because var require was one of the competing module syntaxes (this one being commonjs and others in the list include amd) that was supported _outside of the type checking system_. If one wants to use the type system one would do import require. With ES6 taking the :hammer: and saying that one syntax (:ring:) to rule them all ... it makes sense to have that module syntax supported by the type checking system out of the box :rose:

@basarat Thanks for the clarification. The icons were helpful as well. ;) I'm still not sure though why the ES6 syntax mandates type checking. What's to stop the compiler from saying - "oh, I couldn't find any type data for that module but I'll import it anyway". What side effects would this (rather intuitive, IMO) behavior incur?

This is confusing for those (like myself) coming from ES6 to TypeScript. I'd expect the ES6 module import syntax to behave the same way in TypeScript, but alas it doesn't work at all for most npm packages...

but alas it doesn't work at all for most npm packages...

@teohhanhui I'm confused. Can you give a concrete example of what you are trying to achieve and what code you tried to achieve that. This is just so that I may be able to help you :rose:

import koa from 'koa';

gives Cannot find module 'koa'. (2307) when target is es6

koa is (of course) in node_modules

However, koa does not publish Typescript typings with their project. This is only for packages which publish a .d.ts with their project, and list it in their package.json. Because koa does not, you will want to additionally install those type definitions from Definitely Typed.

That seems like a poor decision as one would expect the import statement to just work like it does in plain ES6 modules. (Yes I know, most of the npmpackages are CommonJS modules but supporting them here would still be the sensible thing to do.)

I'm new to TypeScript so I probably have the wrong understanding, but aren't type definitions optional? Why can't I import the package without the type definition? I don't want to be forced to look for / create type definitions.

Switching to the legacy TS module import syntax is not an option because it's disallowed when using the es6 target.

I'm new to TypeScript so I probably have the wrong understanding, but aren't type definitions optional?

That is incorrect. Type definitions _in your own code_ can be implicit, which means the Typescript compiler will "figure it out". You are never going to pass a third-party library through the Typescript compiler, so the compiler never knows what those types are. If you plan on using third-party code, you really should learn how to use Definitely Typed and the tsd tool.

What about this answer by @basarat?

http://stackoverflow.com/a/27434010/1529493

Why can't non-TypeScript modules without type definitions be imported as such?

They can: const koa = require(‘koa’)

On 21 Jan 2016, at 14:47, Teoh Han Hui [email protected] wrote:

Why can't modules without type definitions be imported as such?


Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/247#issuecomment-173574234.

@bgrieder I'm referring to the ES6 import syntax.

(that is is you are using commonjs), if not, use the declare var of Basarat answer

On 21 Jan 2016, at 14:48, Bruno Grieder bruno.[email protected] wrote:

They can: const koa = require(‘koa’)

On 21 Jan 2016, at 14:47, Teoh Han Hui <[email protected] notifications@github.com> wrote:

Why can't modules without type definitions be imported as such?


Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/247#issuecomment-173574234.

I think the point being made is that one of TypeScript's goals is that valid JS should be valid TS. For the ES6 module syntax, TS should compile this with or without the presence of type declarations. Without type declarations an import statement should just resolve to any.

I totally agree with that.

@Joe Wood 👍

On 21 Jan 2016, at 14:52, Joe Wood [email protected] wrote:

I think the point being made is that one of TypeScript's goals is that valid JS should be valid TS. For the ES6 module syntax, TS should compile this with or without the presence of type declarations. Without type declarations an import statement should just resolve to any.

I totally agree with that.


Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/247#issuecomment-173575301.

FWIW this behaviour (expecting all 3rd party libraries to be typed ahead of time) is what is causing me to use Flow over TypeScript. While TypeScript has IDE support, expecting everything to be typed ahead of time isn't really reasonable, especially when there are plenty of libraries out there that:

  • Aren't on DefinitelyTyped
  • Don't have a ./typings folder
  • I don't have the time to type it up myself

I'd rather not have to fight something that's meant to be aiding my development.

@danpantry, why not just declare your lib entry point as any, and the compiler will be content with that:

declare var $: any;

That's a workaround. But I think it would be a good idea for TypeScript to respect the ES6 module code and fallback gracefully. I can't see a good reason why an import statement can't fallback to any rather than reporting an error if the top level module is untyped.

I'd prefer the compiler to fail early and loudly (the current behavior), rather than allow runtime issues to creep in unnoticed. I'd prefer to explicitly have a declare var foo: any line to indicate "this is unsafe, and I know what I'm doing".

Why not just allow the ES6 import syntax when no typings are available an issue a simple compiler warning that the import has resolved to any, instead of an « emit preventing » error ?

On 21 Jan 2016, at 18:42, S. Chris Colbert [email protected] wrote:

I'd prefer the compiler to fail early and loudly (the current behavior), rather to allow runtime issues to creep in unnoticed. I'd prefer to explicitly have a declare var foo: any line to indicate "this is unsafe, and I know what I'm doing".


Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/247#issuecomment-173649845.

In any case, the compiler currently claims that it can't find the module, when in actual fact it's just refusing to import the module without type definition. That's not really helpful, is it?

^^^ This a thousand times. There was no indication that I was doing something wrong when I first ran into this issue. It took some long hard Googling to finally figure out what was up (incidentally I think this issue is one of the only findable places that documents it). It's so unnecessary. And with Angular 2 bringing TypeScript more into the mainstream I can only imagine how many Stack Overflow questions will come from this behavior.

Very much agree with the poor error handling description and just make it import as any!

@sccolbert I agree there should be a notification but I'm not sure it should be a failure - a warning instead, perhap

I agree with @mhegazy and @sccolbert.

I'd much prefer our production build scripts (i.e. CI server) to complain loudly if something is amiss.

Hi guys,
This behavior just drives me crazy. Definition files are not up to date or not registered into package.json.
In the end TypeScript produces JavaScript, so, until we all go to this language pls be gentle to the rest of the world that provides libraries for the mother tongue in any other transpiled languages, like yours.
I really want to know if TypeScript wants to stay (I would say "to integrate" as it looks is not there, yet) in the JavaScript ecosystem or wants to live a life apart, as the second option will make me to go to something else.
To achive that a switch in the .tsconfig file will do, for that we can say that we want strict imports or not.
As for CI loud complaint there are test frameworks for that in JavaScript. Anyway, you'll not get type checking at runtime, the poor import checking is the less important issue to have.
All the best.

@devel-pa, the errors are meant to tell you that the compiler has no idea what your import is. any subsequent use of the import can not be checked.

Switching off the errors does not solve any issues. it just pushes these "unknowns" silently throughout your system. without type information, the compiler can not warn you on what is safe and what is not. and that is the whole point of TypeScript :)

As for generated JS. none of the TS type errors stop your output from being generated. if you do not care about the type errors, just ignore them all. the compiler still generates your matching .js files.

The solution for missing definitions is to declare them. you do not have to have the full shape of the module declared, but just the name. This allows the compiler to know that there is a module "myLibrary", it can warn you for typos in module names, and more importantly, no other modules would go unchecked.

declare module "myLibrary" {
    var a: any;
    export = a;
}

as outlined in #6615, TypeScript should support a shorter form of this soon.

I think the issue is pain this causes for onboarding projects. I think this problem is analogous to implicit any. Right now these imports are essentially implicit void. Sure, the errors can be ignored, but that causes a lot of noise and goes against the philosophy of gradual typing and valid JS is valid TS.

I can understand the confusion here for people new to TS. If they've actively used ES6 module syntax in JS, why doesn't it just work in TS? What's special about import from over var x = require - which _is_ treated as an implicit any. Originally import was a TS specific keyword, so it implied that the developer wanted the module to be treated as a typed module. Now that it's not exclusive to TS I don't think that assumption can be made.

implicit-any errors are on variable declarations without a type. but using an undeclared variable is still an error today. For jquery, you still need declare var $; somewhere for the compiler to know that you really meant to have a global variable called $ and not just miss-typed it. it is more or less the same for modules, the compiler needs to know that there is a module called "myLibrary" and that is not a miss-typed name. so just add a declaration for it.

on any rate, #6615 should support adding declare module "*"; to match all modules in your system, though i think if you do that, you are not getting any help from your tooling, and you are setting yourself up for a painful transition later on when you decide to remove it.

@mhegazy I understand your reasoning but I really have a problem with this rationale

Basically, you are telling us to design a "placeholder" module definition, and the problem will go away.
This risk is that after a while, on a reasonably sized project, these "placeholder" definitions may just creep up and will be buried in some typings directory. Having no more warnings, everybody will just forget about them and when an issue arises, going through every module definition to see which is a placeholder, is just going to be a pain.

The situation is even worse with typed modules (the ones with the typings entry in package.json) that we would assume to be properly typed could actually be using those placeholders internally.

I understand these placeholders cannot be prevented, but I would much prefer that, at least, they are not encouraged.
What should be advocated is that by default, the import resolves to any and that the compiler issues a warning on every compilation. So at least, looking a the compiler/CI logs, we know something is potentially fishy and needs to be improved.

(as a side not, as stated on the typescriptlang.org home page, Typescript is a 'superset' of Javascript, and IMHO any valid ES6 syntax should just be swallowed as is)

Workarounds and hiding the garbage under the carpet are for me the worst that can happen (beside ES6 super).
I am working with JS libs all the time, and usually with last versions as part of my job and extra coding at home. 90% are not typed or have old defs, 9% are not very well typed (as the compiler doesn't know to make one def for all the files). Neither mine have very good defs, for the same previous reason and for the reason that my target is JS, I don't think I have to care about the original language.
Also, I've seen the reason that there are 'unknown' libs. No, not at all, if the developers doesn't know and understand what libs are used, why bother with them, I know why I'm using stuff and why I'm adding to the stack. Warning and tests (in case it exists) are enough for that.
Pls, JavaScript first, this is the target and the ecosystem. TS is just an accessory for better coding and type check at compile time, but only for what we are building. The rest is none of typescripts business.
I want to mention that I was against nmps peerDependencies too, I am the one who chose, not the software. Imperative programming, baby.

what about importing modules from JSPM directory instead of node_modules?

edit: i've found an existing ticket -> https://github.com/Microsoft/TypeScript/issues/6012

I just ran into an issue with Node module resolution, when using SystemJS to load a directory:

Apparently, while Node (and therefore TypeScript) understands that the path is actually a directory and therefore loads index.ts instead, SystemJS does no such thing, meaning that valid TS code will actually fail to load in the browser.

This is also true of node modules that have a index.ts/js entry point or even use the package.json main property. This is slightly easier to work with, because it is easy (although repetitive) to add a package configuration to SystemJS. This is not so easy when working with arbitrary directories.

What is the appropriate solution for this?

JSPM automatic module resolution is not currently supported. this is tracked by https://github.com/Microsoft/TypeScript/issues/6012.

The recommendation is to use path mapping module resolution support, see https://github.com/Microsoft/TypeScript-Handbook/blob/release-2.0/pages/Module%20Resolution.md#path-mapping

The above instructions are a start, but some people may need to do something additional... You may have to add
<Folder Include="node_modules" />
to .njsproj

More info

I was building with gulp, and had TypeScriptCompileBlocked in .nsproj. To fix issues in debugging breakpoints in VS 15 Preview 5 (I was getting "frame not in module" error) and issues I was having with intellisense I had to add to .njsproj . That directory was already there when I imported the project, but was set to git-ignore. That is my theory on why Visual Studio ignored it (perhaps it should have an automatic exception for node_modules?)

Beyond both debugging and intellisense working, I also stopped seeing intellisense errors beyond the same exact errors I saw from gulp while it built, which is expected.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

wmaurer picture wmaurer  ·  3Comments

seanzer picture seanzer  ·  3Comments

uber5001 picture uber5001  ·  3Comments

blendsdk picture blendsdk  ·  3Comments

jbondc picture jbondc  ·  3Comments