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

Use external module in internal modules #557

Closed
antoinebj opened this issue Aug 28, 2014 · 16 comments
Closed

Use external module in internal modules #557

antoinebj opened this issue Aug 28, 2014 · 16 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@antoinebj
Copy link

Hi,

I don't have a strong background in JavaScript, I have mainly been coding in C#.
I'm starting a client-side application using TypeScript and Angular.js and wish to use the underscore.string library, with its TS definition available on NuGet.

Here is an excerpt of that definition:

declare module 'underscore.string' {
    var underscoreString: UnderscoreStringStatic;
    export = underscoreString;
}

Here are my observations and assumptions:

  1. The definition contains declare module 'underscore.string'. The quoted name automatically makes it an external module.

  2. Had it been declare module underscoreString, it would still have been an external module because of its use of export =.

  3. If I write import _ = require('underscore.string') in the same file as an internal module (but not within the module), that internal module has to become external. Besides an error appears: Cannot compile external modules unless the '--module' flag is provided. (Which, in a VS project, means having to check either AMD or CommonJS in the project properties)

  4. If I try import _ = require('underscore.string') within an internal module, that internal module also has to become external, otherwise the following error appears: Import declarations in an internal module cannot reference an external module.

  5. The language specification for v1.0 mentions an ImportDeclaration in section 10.3:

import Identifier = EntityName;

However this only applies to internal modules, which 'underscore.string' is not if my first assumptions are correct.

  1. If I change my internal modules to be external, they won't be open-ended anymore and I will need to name a different module for each of my files.

  2. I do not have difficulty using Angular.js because it was declared as an internal module.

Now, my main question is:
How can I use what appears to be an external module ('underscore.string') in my open-ended internal modules?

If I can't, then my corollary question is:
What is the most convenient way to structure the application without having to declare a new external module for each file? Especially since I would only name those modules after the sole class they contain (I follow C# convention of 1 class = 1 file).

Ideally I would deal with modules as I deal with .NET namespaces, but the way it looks now, I would have to deal with them as if every file were a .NET assembly.

Thanks in advance for any clarification

@koloboid
Copy link

look at #17 - does is about the same?

@antoinebj
Copy link
Author

Well this is more of a question than a feature request or a bug report. I'm simply trying to understand and discuss it.

Regarding #17 , that still pertains to external modules. I would like to avoid having to make my internal modules external. Hence the question of whether that's feasible in that context, and if not, what the best workaround not involving a future release would be.

@RyanCavanaugh
Copy link
Member

This .d.ts file was written in the good "internal/external" pattern whereby you can consume it simply by referencing the .d.ts file and adding this line of code somewhere in your file:

declare var _: UnderscoreStringStatic;

Closing, but feel free to post follow-up questions.

@ylemoigne
Copy link

Even if it's old, the title is exactly what i'm looking for. I look at #17 and i'm not sure it's the same issue. And as there is no clear answer, I'll try to explain the question clearly.

I want have an app.tsx which looks like this :

/// <reference path="../../../typings/tsd/react/react.d.ts" />

import * as React from "react";

class App extends React.Component<React.Props<App>, AppState> {
...
}

class Foo extends React.Component<React.Props<Foo>, any> {
...
}

I want to organise the code, mainly by spliting the file and get Foo in the Other module, so following ts handbook, I create a new file Foo.tsx :

/// <reference path="../../../typings/tsd/react/react.d.ts" />

import * as React from "react";

module Other {
  export class Foo extends React.Component<React.Props<Foo>, any> {
    ...
  }
}

Now, the app.tsx looks like :

/// <reference path="../../../typings/tsd/react/react.d.ts" />
/// <reference path="Foo.tsx" />

import * as React from "react";

class App extends React.Component<React.Props<App>, AppState> {
  ...
  var t:Other.Foo; // <= not recognized
  ....
}

As I understand things, Other.Foo don't work because in Foo.tsx, there is an import of an external module.

So going back to the title of the question : How can I use external module in internal modules ?

@weswigham
Copy link
Member

Change module Other to export module Other in Foo.tsx, and add import {Other} from "./Foo" in app.tsx.

@ylemoigne
Copy link

Great ! thanks @weswigham .

@ylemoigne
Copy link

Ho no... Sorry, it still not good.

It works for one file only. As soon as I have a second file and I want it to contribute to the same module, I can't use import twice with same identifier in the app.tsx

@weswigham
Copy link
Member

Namespace merging is not a feature you can use with external modules. Instead of using namespaces to create a group of methods, use a file instead. For example, assume I have a foo.ts and a bar.ts:

export function foo() {}
export function bar() {}

If I want to access both of those through one object, I can create a third file, api.ts:

export * from "./foo"
export * from "./bar"

And now I can import * as api from "./api" to get a single object with all the exported members of both foo and bar on them.

Namespaces I find aren't actually terribly useful with external modules, except for singletons and file-internal organization.

@davidstellini
Copy link

@weswigham Sure, and then you pollute the global scope by adding a gazilion different exports to it.
Internal modules allow me to split up my code and organize it nicely, but I can't use, or reference, an external module from my internal module?

@weswigham
Copy link
Member

How do external modules "pollute" anything? You can't even easily declare globals when using external modules, because everything gets module-scoped.

@davidstellini
Copy link

I thought you meant:
export function foo() {}
not

module bar {
     export function foo(){}
}

In the first case, isn't foo living in the window object? In either case; what this boils down to is something very simple: If you want to use separate your code using internal modules, then you effectively can't use third party libraries. Take a look at: http://stackoverflow.com/questions/35582275/using-a-node-library-within-a-typescript-internal-module

Referencing a gazilion different files via a relative path is ugly.

@weswigham
Copy link
Member

The first case does not put anything on the window object - it generates a module closure (depending on the module format) and adds that function to the exports of that closure.

That stack overflow post is shallow in its analysis (and is also an unanswered question) - any namespace structure can be replicated with external modules - look at tslint as a great example both of how to migrate from namespaces to external modules (see the code before v3, then after v3) and of how you can use external modules to layer APIs in this way to imitate namespace structure.

While you can reference everything directly, as done in that SO post, that can be indicative of poor API design, as that's akin to reaching into the "internal" API of every module you use, rather than organizing things into groups (API layers), then importing from that group (API). When done correctly, you should be able to pull in most of what a given file needs via a single import. Angular is actually structured very well internally, in this regard.

@davidstellini
Copy link

Thank you for your suggestions, but you are basically saying:

  1. typescript namespaces (internal modules) are useless. If you are using internal modules you should change your structure to external use modules.
  2. having many imports in the same file is indicative of poor API design.

I think you are right 95% of the time. In my specific use case, I have typescript files (Models) that are going to be generated by an external tool. Rather than using imports for cross-dependencies between model files, a "namespace Models" before every class file would have been perfect and reduced code-generation complexity.

Converting to external modules would not solve the problem, because external modules do not support namespace merging.

So I cannot, say, have something like:

import myListLib = require("ListLib");
namespace Models {
   class UserModel extends Model {
      addresses : myListLib.List<AddressModel>;
   }
}

namespace Models {
    class AddressModel extends Model {}
}

I have to now import "Address" manually in UserModel. Is there any alternative?

@weswigham
Copy link
Member

UserModel.ts:

import * as Models from "./index";
export class UserModel extends Models.Model {
    addresses : myListLib.List<Models.AddressModel>;
}

AddressModel.ts:

import * as Models from "./index";
export class AddressModel extends Models.Model {}

Model.ts:

export class Model {}

index.ts:

export * from "./Model";
export * from "./AddressModel";
export * from "./UserModel";

You can also destructure only the members you need out of the import statement, which, IMO, usually makes it more clear what other bits the component depends upon.

Using them from another part of your program (assuming these are all in a models folder):

import * as Models from "./models"; // finds ./models/index.ts under node module resolution
let model = new Models.UserModel();

Let me also clear something up for you which you example make it seem like you may be confusing: import statements for module systems and merged namespaces across files are fundamentally incompatible. namespaces work around the concept that each namespace is just an object with an initializing closure - merged namespaces just reinitialize the object with another closure to add more members. This all has to be done within the same scope so that global namespace object is available. The module systems you use with import statements have different scope rules - each file has its own scope, meaning namespaces can't possibly merge across module boundaries, since they do not share a scope (and so end up initializing different objects). They are two, different, mutually exclusive ways of structuring a program. If you need require calls in a program structured with namespaces, write them as const lib: LibType = require("lib"); // This means the compiler doesn't find the types for a library for you, you have to add them yourself, as adding import or export statements make your project into modules, which then grants each module its own scope, making it pretty much impossible to use namespaces as you would apparently like to.

@RyanCavanaugh
Copy link
Member

@davidstellini
Copy link

That makes sense. I think the confusion stems from comparing namespaces to C# namespaces (the 'as usual' makes me think I'm not the only one). Intuitively, if you were a C# developer, your brain says, this is the equivalent of using an external DLL/Library (external module) in C#, and the compiler says you cannot use namespaces (internal module) in the same file - why is this limitation in place?!. That said, it is purely my confusion and I should be comparing with Javascript not C#. Thanks for clearing this up!

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

6 participants