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

Namespaces #2923

Merged
merged 8 commits into from
Apr 29, 2015
Merged

Namespaces #2923

merged 8 commits into from
Apr 29, 2015

Conversation

ahejlsberg
Copy link
Member

To better align with ES6 modules as suggested in #2159, this PR introduces a new namespace keyword as the preferred way to declare what was formerly known as "internal modules". Our terminology changes as follows:

  • Internal module becomes Namespace.
  • External module becomes Module.

For backwards compatibility, namespaces can still be declared in the old way (using the module keyword) and no existing code is broken by this change. We may consider introducing a compiler option that disallows using the module keyword to declare namespaces.

This PR focuses on the external effects of the change and doesn't change any internal type or flag names in the compiler.

@DanielRosenwasser
Copy link
Member

What about compiler tests relating to namespaces?

@mhegazy
Copy link
Contributor

mhegazy commented Apr 28, 2015

👍

@mhegazy
Copy link
Contributor

mhegazy commented Apr 28, 2015

There are some classification/quick info impact that we need to handle in VS, sublime.. etc.. but i will file issues for these separately.

ahejlsberg and others added 3 commits April 28, 2015 16:49
Conflicts:
	src/compiler/diagnosticInformationMap.generated.ts
	src/compiler/diagnosticMessages.json
	src/compiler/program.ts
	tests/baselines/reference/constDeclarations-access5.errors.txt
	tests/baselines/reference/es6-amd.errors.txt
	tests/baselines/reference/es6-declaration-amd.errors.txt
	tests/baselines/reference/es6-sourcemap-amd.errors.txt
	tests/baselines/reference/es6-umd.errors.txt
	tests/baselines/reference/es6-umd2.errors.txt
	tests/baselines/reference/es6ImportDefaultBindingFollowedWithNamedImport.errors.txt
	tests/baselines/reference/es6ImportNameSpaceImport.errors.txt
	tests/baselines/reference/es6ImportNamedImport.errors.txt
	tests/baselines/reference/es6ImportNamedImportInExportAssignment.errors.txt
	tests/baselines/reference/es6ModuleWithModuleGenTargetAmd.errors.txt
	tests/baselines/reference/es6ModuleWithModuleGenTargetCommonjs.errors.txt
ahejlsberg added a commit that referenced this pull request Apr 29, 2015
@ahejlsberg ahejlsberg merged commit 4016206 into master Apr 29, 2015
@ahejlsberg ahejlsberg deleted the namespaces branch April 29, 2015 00:23
@ahejlsberg ahejlsberg mentioned this pull request Apr 29, 2015
@basarat
Copy link
Contributor

basarat commented Apr 29, 2015

Thank you! 🚀 🌹

@mikehaas763
Copy link

Are there any discussions happening for something like using My.Namespace? I'm not all that sure how this would work but I would actually use internal mod... sorry namespaces if I didn't need that nasty /// <reference at the top.

@basarat
Copy link
Contributor

basarat commented Apr 29, 2015

'm not all that sure how this would work but I would actually use internal mod... sorry namespaces if I didn't need that nasty /// <reference at the top.

Don't use references, use tsconfig.json. Don't use namespaces, use modules : https://github.com/TypeStrong/atom-typescript/blob/master/docs/out.md

@yahiko00
Copy link

Good news. I really like the direction TypeScript is taking.

@NoelAbrahams
Copy link

@ahejlsberg, I too have the same question as @mikehaas763

Are there any discussions happening for something like using My.Namespace

If this PR goes in without using then it won't be possible to introduce it at a later stage, because that would be a breaking change.

@fletchsod-developer
Copy link

I agree with @NoelAbrahams

@mikehaas763
Copy link

@basarat Yep, using modules is one option. Though, I personally want another option. I think it's a great idea being that one of the goals of TypeScript is not only to be javascript but to evolve it.

On a technical level, I'm not sure how using directives would actually work. Would it just be a replacement for the existing reference comments. Essentially just being an indicator as to how the loading order should happen?

@mhegazy
Copy link
Contributor

mhegazy commented Apr 29, 2015

@ahejlsberg proposed a import * from "mod" syntax to do that a while back. and in this case it would be import * from namespace. the problem here is it makes it harder to do a syntactic transformation on a single file, as the compiler needs to have the full program information to know how things should be qualified. it is something that we are looking into, i would recommend filing a new issue for this instead of having the discussion on this PR.

The PR is just renaming internal modules to namespaces to avoid any confusion with ES6 modules. this is something we got feedback for regularly from customers.

@mikehaas763
Copy link

As you can see above I created a new issue to track this at #2956.

@rbuckton
Copy link
Member

👍

@fletchsod-developer
Copy link

Does namespace do both import and export, as in 1? Other languages like C# don't do import or export, if I understand this correctly.

@pocesar
Copy link

pocesar commented May 18, 2015

does the namespace create a IIFE that can keep some code private? and can it be nested? I usually use 2 levels of internal modules in Angular to help debugging and measuring memory snapshots. ((Async.Controller.Main): 18882,2 is much easier to spot than (anonymous): 18882,2)

@mhegazy
Copy link
Contributor

mhegazy commented May 18, 2015

@pocesar yes and yes.

namespace M { 
    export namespace N { 
        export var publicVar = 0;
    }
    var privateVar = 0;
}

generates:

var M;
(function (M) {
    var N;
    (function (N) {
        N.publicVar = 0;
    })(N = M.N || (M.N = {}));
    var privateVar = 0;
})(M || (M = {}));

@pocesar
Copy link

pocesar commented May 18, 2015

awesome!

@ddotlic
Copy link

ddotlic commented May 22, 2015

Forgive my ignorance, but i don't think this PR handles (IMHO) a typical use case for simple TypeScript projects.

Imagine the following: I work in an enterprise X working on an ASP.NET MVC Web site which already uses N JS libraries which are all clean in the sense that they do not pollute global namespace.

We now want to add some functionality on our own, but don't want to do it in JS because TS is so much better.

We use one-class-per-file approach and let TypeScript compiler produce a final JS file using latest version (1.5-beta) and the following tsconfig.json

{
    "compilerOptions": {
        "target" : "ES5",
        "noImplicitAny": true,
        "out": "ns.js"
    }
}

Assume the whole project is just two files, like this:

// in file a.ts
class A { }

// in file b.ts
class B extends A {}

The final result is satisfactory, everything works fine if we include the ns.js into our web page(s), but the classes A and B pollute global scope.

As good JS citizens we do not want this.

So we turn to modules, ahem, namespaces.

It is important to note that we do not want to put ///<reference ...> comments all over the place since this is a nightmare to manage (unless you have to), the tsconfig.json approach works just fine for us.

So, namespaces then, which are synonymous with internal modules but IMHO a lot better name for what we're trying to achieve. Unfortunately the namespace keyword isn't in 1.5-beta so I'll use module keyword as an example, but you'll understand what I mean. Here's how the files could look:

// a.ts

module NS {
    export class A {
    }
}

// b.ts

module NS {
    class B extends A {
    }
}

This works. The classes A and B do not pollute global namespace, but there are two issues:

  1. Each class is wrapped separately in an IIFE, it would have been preferrable to have only one
  2. If I omit export from the class A, the B does not compile

While the issue 1 is annoying, it's not a deal breaker. The issue 2 however is very irritating because I thought that the purpose of internal modules/namespaces is exactly to avoid all kinds of ceremony, including exporting classes - a bit like in C# where if I put the two classes in the same namespace (of the same assembly) I do not need to mark the class with anything (it's by default internal) so it's "visible" to other classes in the same namespace. What I'm trying to do here is purely to isolate an entirety of my final result from the global scope, why would I export anything (besides to satisfy the compiler, which is silly).

So to conclude:

  1. Would it be possible to - uniquely when using namespace keyword and precisely for the use case like I present above - not require us to export classes we want to use internally?
  2. Would it be possible to merge IIFEs, again only for this particular use case?

Thanks for listening and for the great, great product that TypeScript is.

Also, if there's some way I've not seen that the above can be done, someone please enlighten me. Thanks!

@danquirk
Copy link
Member

@ddotlic these are common problems/requests with internal modules/namespaces.

Would it be possible to - uniquely when using namespace keyword and precisely for the use case like I present above - not require us to export classes we want to use internally?

The problem is that export has an important meaning in the emit. The emit is actually necessary to make this content visible to the other definitions of the same module. Unfortunately this has the side effect of making it visible to all sorts of other places too though. Which leads to your second proposal as a solution...

Would it be possible to merge IIFEs, again only for this particular use case?

since now you actually wouldn't need to export from an emit perspective because both modules would actually be in the same IIFE. But now you risk merging a bunch of IIFEs that can clobber things from one another without the TypeScript compiler giving you any useful warnings.

We were actually literally just talking about a potential internal keyword for this scenario as the compiler itself does suffer from this problem as well (see #892 which is probably a better place to continue the conversation). However, our current thinking is that this shouldn't be necessary in the future as JS and TS both move more towards ES6 style modules rather than internal modules/namespaces.

@ddotlic
Copy link

ddotlic commented May 22, 2015

@danquirk Thanks for your comment.

I still don't understand why would emit be necessary for other modules - I don't want modules, I only use them because I need to. IOW, I don't really care what the mechanism is, I just want my scenario (avoiding pollution of globals) supported. If that's too much to ask (for any reason, complexity or otherwise) than too bad for me.

But it seems to me that with the namespace keyword you now have a chance to do this properly, seeing how it could have nothing to do with modules which are something else entirely.

Alternatively, even better would be to actually not do anything of the sorts. No module everywhere, no namespace - just classes. This is in fact what I do today and it works great except for the global namespace pollution. Then a postprocessing step would be needed...

I know I can wrap the final result of the compilation (since in my case it's just a single JS file) myself - I already have a quite elegant gulp build script - but this will screw up the source maps because everything will be shifted by a few lines.

Would it be too much to ask for typescript compiler to do this - a single wrapper "namespace" around the whole thing (through a flag which would only be available when using 'out' aka single JS file output) because then TS compiler can immediately emit correct source maps?

Do you see any other way my - and for many others I guess - scenario could be supported with minimal ceremony?

Thanks again for TypeScript, I don't think I would be doing any JS development without it.

@NoelAbrahams
Copy link

@danquirk,

However, our current thinking is that this shouldn't be necessary in the future as JS and TS both move more towards ES6 style modules rather than internal modules/namespaces.

Namespaces are useful for lots of ambient-only or interface definitions, e.g.

// interfaces.d.ts

declare namespace my.interfaces {

      interface Foo { ... }

      namespace bar {
           interface Bar  { ... }     
      }
}

// implementation.ts

class Bar implements my.interfaces.bar.Bar {
}

How do you see ES6 modules replacing the use of namespaces here?

@mihailik
Copy link
Contributor

The choice of the word is quite harmful. TypeScript "namespaces" are not namespaces at all, they are in fact "modules".

  1. Putting class A into "namespace" B does not affect the name resolution for the rest of the code. So it's not creating a namespace.
  2. Code inside "namespace" B is imperative rather than declarative. A function/class/state has to be exported from the "namespace" explicitly to be accessible. That having a behaviour and necessity to export anything explicitly precisely matches the meaning of "module".

@matthiasg
Copy link

@ddotlic with you on that, but could somebody please explain to me why we couldnt use the normal ES6 compliant import syntax to organize our code instead of namespaces or modules ? import syntax is supported and maps nicely to the nodejs commonjs way of thinking which is quite successful for quite large applications already.

not sure what we really gain from this other than that the resulting code is now even structurally further removed from plain js.

@mihailik
Copy link
Contributor

@matthiasg what makes you think you couldn't use the normal ES6 syntax?

Did you try it on the latest tsc from the master branch?

@matthiasg
Copy link

@mihailik my point was that we can use the normal import syntax. i am just wondering why namespaces are in TS to begin with, what the value is (note i have developed very large apps in c# and namespaces there are essential the way it is, just never saw any value in e.g node.js).

bg: i have a team of devs and am currently deciding between bablejs.io for ES6/7 or typescript. typescript looks promising (all of ES6/some of ES7 + types). But when actually writing code day in day out it is quite cumbersome to constantly check documentation and test against es6/7 to find out whether a feature is part of the ES6/7 line of thinking with broader support to be expected or something that TS added. The documentation doesnt delineate.

e.g namespaces for me at least dont seem to offer any added value on top of import (any examples are welcome of course). thats why i was asking.

@ddotlic
Copy link

ddotlic commented Jul 17, 2015

@matthiasg The namespaces are an 'alias' for internal modules, which are TypeScript specific.

If you look at this thread carefully, you'll see that @danquirk already said... hell, I'll just quote

However, our current thinking is that this shouldn't be necessary in the future as JS and TS both move more towards ES6 style modules rather than internal modules/namespaces.

So if you're not using namespaces AKA internal modules, TS and ES6 should already align perfectly. Just feel free to ignore the whole thing 😄

I mistakenly thought that namespaces could solve a related issue of mine, but they don't and it's not a big deal - I will probably never write a single line of code of TS with namespace keyword in it, but it does not prevent me from using and enjoying TS. Cheers!

@matthiasg
Copy link

@ddotlic ok .. i get that .. the issue for us here is more that the neither the current documentation nor the upcoming documentation (as far as its finished) really mentions anywhere or even hints at the fact that the path forward should avoid a certain construct such as namespace due to the fact that it might not even be necessary. i am not even sure the TS team itself sees it that way. therefore i would presume lots of code being written using that.

i had hoped there was an actual argument namespaces vs import or not :)

cheers

@ddotlic
Copy link

ddotlic commented Jul 17, 2015

@matthiasg Nope, no argument there, they serve a different purpose.

In fact, somewhat related to my comments, I really don't see much use for internal modules/namespaces. For those that are in the same boat as me, the namespaces don't help. For others, full blown modules are 'clearly' the way to go.

I suspect this whole thing will actually be obsoleted at some point in the future.

@mihailik
Copy link
Contributor

Namespaces are there to organize parts of a large codebase. The main use case is compiling with '--out' parameter, bundling all the JS output into a single file.

The alternative, using modules to avoid name collisions have two downsides:

  • Manual management of your modules' directional dependency graph.
  • Module loading infrastructure needs to be present.

In the end it's up to you to weigh costs/benefits.

For my use case the idea of calling 'require' or 'import' and going through a magical package loader logic for merely calling a utility function defined in a different file is an overkill.

@ddotlic
Copy link

ddotlic commented Jul 17, 2015

For my use case the idea of calling 'require' or 'import' and going through a magical package loader logic for merely calling a utility function defined in a different file is an overkill.

@mihailik Indeed! For me, adding even namespace plus export is overkill for small projects using --out (and even ///<reference... which is unfortunately occasionally necessary).

I really don't see why adding all that noise that comes with ES6 modules is so great.

@NoelAbrahams
Copy link

There are a few misconceptions quoted above. Internal modules are actually more scalable and better suited for very large scale applications. I hope TypeScript will continue to support and develop this idea.

@matthiasg, you may want to take a look at the related (rather long) discussion on the old codeplex site.

@matthiasg
Copy link

@NoelAbrahams .. Thanks for the input .. I will definitely look into that discussion ... I just wish TS would make it easier to delineate between standard features and additional other features (which might be awesome if course and genuinely helpful but I would like to opt in and not take such steps likely )..

@drudru
Copy link

drudru commented Jul 17, 2015

@ahejlsberg - after trying to re-use code from a client side project (script includes) to a server side (node) project with external modules... I am really looking forward to these changes. cheers.

👍

@danquirk
Copy link
Member

@matthiasg namespace is simply a codification of the pattern:

(function myLib() {
   // keep my stuff out of the global namespace
});
  • concat your outputs (rather than AMD/CommonJS/ES6 modules) which is/was quite common in existing JS. You're making the same choice in TypeScript as you would with JavaScript in that sense. TypeScript just has a keyword to allow you to distinguish functions that do things from functions that merely act as named containers/scopes.

@supermoos
Copy link

Is it possible to define two classes in seperate files using the same namespace without one overriding the other?

file structure example:

  • main.ts
  • services (folder)
    • JSONLoader.ts
    • JSONParser.ts

JSONParser.ts:

namespace services {
    export class JSONParser {
        constructor(json:JSON) {
            console.log(json);
        }
    }
}

JSONLoader.ts:

namespace services {
    export class JSONLoader {
        constructor(url:string) {
            console.log(url);
        }
    }
}

main.ts

import JSONLoader = services.JSONLoader;
import JSONParser = services.JSONParser;
class Main {
    constructor() {
        var dataLoader:JSONLoader = new JSONLoader('someurl');
        var jsonParser:JSONParser = new JSONParser(<JSON>'[]');
    }
}

(() => {
    window['main'] = new Main();
})();

I get output like this:

var JSONLoader = services.JSONLoader;
var JSONParser = services.JSONParser;
var Main = (function () {
    function Main() {
        var dataLoader = new JSONLoader('someurl');
        var jsonParser = new JSONParser('[]');
    }
    return Main;
})();
(function () {
    window['main'] = new Main();
})();
var services;
(function (services) {
    var JSONLoader = (function () {
        function JSONLoader(url) {
                console.log(url);
        }
        return JSONLoader;
    })();
    services.JSONLoader = JSONLoader;
})(services || (services = {}));
var services;
(function (services) {
    var JSONParser = (function () {
        function JSONParser(json) {
            console.log(json);
        }
        return JSONParser;
    })();
    services.JSONParser = JSONParser;
})(services || (services = {}));

So it's overriding the namespace on the second import?

@mhegazy
Copy link
Contributor

mhegazy commented Oct 5, 2015

@lasseyls your code shows a single object (namespace) called services, that has two properties JSONParser and JSONLoader.

i am not sure i understand what you mean about "overriding the namespace in the import"..

Also, i would say file an issue for this, or use SO/gittter chat room instead of posting on a closed PR.

@matthiasg
Copy link

funny .. thats what i wanted in #3908 .. and back then i was referred to just use linting :) ... happy about this change

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.