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

Are namespaces legacy? #30994

Closed
ChuckJonas opened this issue Apr 17, 2019 · 36 comments
Closed

Are namespaces legacy? #30994

ChuckJonas opened this issue Apr 17, 2019 · 36 comments
Labels
Discussion Issues which may not have code impact

Comments

@ChuckJonas
Copy link

ChuckJonas commented Apr 17, 2019

Sorry... I know it specifically says not to ask questions here... but I don't want the opinion of random developers on stack exchange... I want to hear from the typescript team directly.

Are namespaces (aka internal modules) legacy and going away?

There is a discussion about Babel7 support for namespaces . One of the arguments for not supporting them is that they are going away in the future anyways. However, I can't seem to find any mention to that in official documentation.

If that is the case, then it really should be mentioned somewhere... Lots of people are relying on them for libraries (because they provide some very convenient features; especially around code generators).

I personally have at least 10 production projects that would need to be heavily refactored if they went away.

FYI @DanielRosenwasser (because you were quoted as saying they were legacy).


Response from TypeScript Team (@RyanCavanaugh)

Namespaces are not being removed from TypeScript! See comments downthread.

@Wolvereness
Copy link

@DanielRosenwasser Was quoted saying:

... namespaces are not a huge loss ...

Which from my interpretation meant that it's not a priority for a system to support namespaces, but nothing to the effect of implicating their deprecation or legacy status.

@Timer was quoted saying they were legacy, which could only be substantiated using third party opinion blog posts.

@ChuckJonas
Copy link
Author

ChuckJonas commented Apr 17, 2019

@Wolvereness sorry yes, didn't mean to put words in @DanielRosenwasser mouth. His quote was used to argue that they are legacy...

Here's the full post for clarity:

TypeScript PM here. Broadly speaking, I feel that

namespaces are not a huge loss; the React community is firmly on-board with ES modules anyway, and that's the direction the TypeScript world is taking.
const enums are often misused. Sure, they can be great for the internals of a library to get some speed improvements, but they generally shouldn't be used outside of that scope.
Merging behavior is marginally useful now that TypeScript 3.1 has the ability to tack properties onto functions. The fact that enums merge is also something I've never seen used out in the wild.
Anyway, @RyanCavanaugh might have some thoughts here too.

@RyanCavanaugh RyanCavanaugh added the Discussion Issues which may not have code impact label Apr 17, 2019
@MartinJohns
Copy link
Contributor

The TypeScript goal 11 states:

Do not cause substantial breaking changes from TypeScript 1.0.

So I would not expect them to go away.

@RyanCavanaugh
Copy link
Member

I guess you're referring to babel/babel#8244 (comment) ? This person isn't a TypeScript team member and I don't know where they got that idea.

We've never removed any syntax from the language since 1.0 and don't intend to do so in the future.

@DanielRosenwasser
Copy link
Member

Yes, I think @Wolvereness captured my intent accurately - in other words, Babel probably shouldn't drastically re-architect itself to support namespaces.

@DanielRosenwasser DanielRosenwasser changed the title Are namespaces legacy Are namespaces legacy? Apr 17, 2019
@Jonatthu
Copy link

Jonatthu commented May 9, 2019

@RyanCavanaugh dotansimha/graphql-code-generator#643 (comment)

Is this really happening on Typescript?
Is Typescript team deprecating namespaces?
I think it is really useful for this use case and others that creates multiple types related to a dynamic query such as Graphql gen types.

import { Action } from '@ngrx/store';

export namespace PermissionsActions
{

	export const LOAD_DETAILS: string = '[Permissions] Load Details';
	export const LOAD_DETAILS_FAIL: string = '[Permissions] Load Details Fail';
	export const LOAD_DETAILS_SUCCESS: string = '[Permissions] Load Details Success';

	export class LoadDetails implements Action
	{
		/**
		 * The type of the action.
		 */
		public readonly type: string = LOAD_DETAILS;

		constructor(public permissionKey: string) { }
	}

	export class LoadDetailsFail implements Action
	{
		/**
		 * The type of the action.
		 */
		public readonly type: string = LOAD_DETAILS_FAIL;

		constructor() { }
	}

	export class LoadDetailsSuccess implements Action
	{
		/**
		 * The type of the action.
		 */
		public readonly type: string = LOAD_DETAILS_SUCCESS;

		constructor(public payload: any) { }
	}

	/**
	 * Permissions Actions Type Union
	 */
	export type Actions =
		| LoadDetails
		| LoadDetailsFail
		| LoadDetailsSuccess
		;

}

Why should we lose this ability?

I can just import this as

import { PermissionsActions } from '...';
vs
import * as PermissionsActions from '...';

With namespaces I can find all references easily plus we can maintain the same naming convention when we import this kind of types on a ts file.

Really useful on Angular Applications using NGRX and now applications using Graphql Types Generation.

@FredyC

@RyanCavanaugh
Copy link
Member

@Jonatthu I'd refer you to my comment upthread. Again: We've never removed any syntax from the language since 1.0 and don't intend to do so in the future.

For anyone reading this thread: Please don't trust internet randos about TypeScript's feature plans! Our roadmap is public anyone saying crazy stuff like that should be able to point to a roadmap entry for it.

@weswigham
Copy link
Member

weswigham commented May 13, 2019

Namespaces are probably never going away and, simultaneously, you probably shouldn't use them, since better, more standard and modern patterns exist through the use of modules. If you have code that you feel needs to use a namespace, more power to you and go for it, but most of the time you don't need one, as we now have a different conceptual organizational model recommended for large-scale JS (modules). That's why @DanielRosenwasser said namespaces are not a huge loss - most new TS code should probably never need to use them, and should think hard about if they really need to.

Minimally, we'll never outright remove namespaces because they're incredibly useful for describing type hierarchies and the like of existing cjs libraries - it's just that modern esm-like code probably doesn't need them, since the module itself is a namespace (where nesting is achieved thru reexports).

@dragomirtitian
Copy link
Contributor

@weswigham Is namespace merging with class, function and enum a 'good' use of namespaces? (as described in the docs)

I ask because people seem afraid of namespaces these days for some reason and the merging functionality can't easily be achieved by other means. (Functions have nicer semantics these days for adding members, but aside from that)

@weswigham
Copy link
Member

weswigham commented May 13, 2019

Is namespace merging with class, function and enum a 'good' use of namespaces?

Sometimes - since we can't recognize ad-hoc attachments of certain kinds of static properties, it might be warranted - in many cases an ad-hoc property on a function or a class static will suffice (unless you need it to contain types), though. Although it's just as valid to question if you really needed the merge to begin with - if, for example, you want to associate a component and its argument type, isn't exporting both of them from the same module sufficient? Why also wrap them in a namespace? There's no point there.

It comes down to this:
If you're considering using namespaces for code organization: Don't. Modules have subsumed that role.
If you need functionality that only namespaces can provide: Do, but check that it's not equally expressive to express the concept without a namespace (eg, with a class static or function property, or reexported module). It is also bad style to mix namespaces and modules in the same project - it just feels off, since one of the major features of namespaces in the traditional sense is cross-file scope merging, which doesn't occur across modules (since, as I said, the module itself is actually a namespace).

@zijianhuang
Copy link

zijianhuang commented Jun 12, 2019

I had read the comments at babel/babel#8244 made by the Babel team as mentioned above.

Complex business fat clients like SPA may contain a lot types in different namespaces. ES module is not going to replace namespaces of TS, since module is 1 to 1 mapping with file. Manually avoiding circular references among files is a nightmare against application programming and overall productivity of app programmers. Namepaces is the best solution for code generation -- all generated by code generator stay in one file.

All types from such generated file are with the namespace prefix, so app programmers could easily tell which types are from generated lib.

I am building a complex business app of Angular SPA with tons of types mapped to the server types of .NET backend. Having namespaces is great for productivity.

And the good thing is, the Babel team may be changing their mind about supporting namespaces: babel/babel#9785
So much thanks to @Wolvereness.

Hopefully this will be available in next release.

@shrinktofit
Copy link

Why also wrap them in a namespace?

For our team, we can stop to use namespace once if public type T = and public interface I {} is allowed in class scope. Because sometimes we don't want to expose interfaces/types directly into module scope. Only choice is to use namespace merging:

class C { }
namespace C {  interface I { }  }

const i: C.I = /**/ ;

@nelson6e65
Copy link

@weswigham Is namespace merging with class, function and enum a 'good' use of namespaces? (as described in the docs)

Extension methods (C#) are missed in TypeScript. 😅

@zijianhuang
Copy link

@nelson6e65 , Extension methods are supported in JavaScript thus in TypeScript as well.

@nelson6e65
Copy link

@nelson6e65 , Extension methods are supported in JavaScript thus in TypeScript as well.

Really? How? I had to write a wrapper generic class for specific enums to achieve this, but not as extension methods.

@nelson6e65
Copy link

nelson6e65 commented Jan 3, 2020

Kind of:

// status.ts
export enum Status {
  None,
  Good,
  Other
}

// status extension.ts
export class StatusExtension {
  public static message(this status: Status) {
    return status === Status.Good ? 'All is good' : 'Not that good'
  }
}
import { Status } from './status';
import './status-extension.ts';

const sta = Status.Good;

console.log('Status message:', sta.message());

...similar to C# extension methods.

With the namespace-enum merging you have to call something like Status.message(sta) instead of sta.message()

But currently I have to create a wrapper class:

class StatusWrapper {
  constructor(public readonly value: Status) {}

  message() {
    return this.value=== Status.Good ? 'All is good' : 'Not that good'
  }
}

//

const sta2 = new StatusWrapper(Status.Bad);

console.log(sta2.message());

@zijianhuang
Copy link

@nelson6365, prototype, though people should use prototype with cautions. A TS example is here: https://putridparrot.com/blog/extension-methods-in-typescript/

@JirkaDellOro
Copy link

I've experienced many times that people like myself struggle a lot with ts-namespaces and es-modules, especially when it comes down to try to work with both in the same project. I did some "structured" fiddling and found that on the Javascript-side after compilation, things appear to be quite simple, but can't be done in a standard way with typescript before compilation.

I wrote about it here and I'd be happy to see some comments on that before I start creating a plugin or extension, that tweaks the compiled output. I expect to be missing some important parts...

https://jirkadelloro.github.io/TS-Namespaces-ES-Modules/

@cdpark0530
Copy link

cdpark0530 commented May 23, 2022

If I put another benefit of using namespace like @shrinktofit said:

// ListView.tsx
function ListView(props: ListView.Props) {
  return (
    <>
      {
        props.items.map((item) => <div key={item}>{item}</div>)
      }
    </>
  );
}

namespace ListView {
  export interface Props extends React.HTMLAttributes<HTMLElement> {
    items: string[];
  }
}

export default ListView;
// IndexedListView.tsx
import ListView from "./ListView";

function IndexedListView(props: IndexedListView.Props) {
  const { indexStyle, ...rest } = props;

  console.log(indexStyle);

  return (
    <ListView {...rest} />
  );
}

namespace IndexedListView {
  export interface Props extends ListView.Props {
    indexStyle: string;
  }
}

export default IndexedListView;
  1. It doesn't only decrease redundancy in naming like ListViewProps and IndexedListViewProps, but also improve refactoring names.
  2. Each component will be hierarchically organized, and your code will be intuitive, which other developers can easily understand.

But unfortunately, there are some discourages structuring your project in this way:

  1. You can't neatly apply this pattern with HoC.
  2. When those nested types conflict, TS error messages won't be kind enough to show the namespaces of those types.

@zijianhuang
Copy link

If your SPA on Angular needs to talk to multiple backends from different vendors, you will see greater benefits of using namespaces than using modules, especially you have the client API codes generated for each backend, namespaces gives you clearer readability and higher abstraction, since namespaces could be more semantically abstract than modules.

@ecalcutin
Copy link

Namespaces are probably never going away and, simultaneously, you probably shouldn't use them, since better, more standard and modern patterns exist through the use of modules. If you have code that you feel needs to use a namespace, more power to you and go for it, but most of the time you don't need one, as we now have a different conceptual organizational model recommended for large-scale JS (modules). That's why @DanielRosenwasser said namespaces are not a huge loss - most new TS code should probably never need to use them, and should think hard about if they really need to.

Minimally, we'll never outright remove namespaces because they're incredibly useful for describing type hierarchies and the like of existing cjs libraries - it's just that modern esm-like code probably doesn't need them, since the module itself is a namespace (where nesting is achieved thru reexports).

I have let's say the following interfaces:

// user.ts
export interface Status {
  isActive: boolean;
}

// account.ts
export interface Status {
   lastLogin: Date
}

And I don't want to have name collision while trying to import/auto-import Status interface.

With namespace like:

namespace User {
  export interface Status { ... }
}

namespace Account {
  export namespace Status { ... }
}

I am sure I won't import wrong Status interface since I have to import the right namespace (The Status interface is hidden):

import User from 'path-to-user-namespace';
let status = User.Status;

So If I use namespace only for grouping and encapsulating types/interfaces/namespaces - is it okay?

@weswigham
Copy link
Member

weswigham commented Aug 22, 2022

...no, not really, because you can do the same thing with actual modules. Eg,

import * as User from "./user";
import * as Account from "./account";
export { User, Account };

@ecalcutin
Copy link

...no, not really, because you can do the same thing with actual modules. Eg,

import * as User from "./user";
import * as Account from "./account";
export { User, Account };

Exactly, I am aware of that. The only downside of that - Status interface is still accessible to be imported (not hidden).

@evanmcd
Copy link

evanmcd commented Aug 24, 2022

I’m trying to understand why namespaces shouldn’t be used to improve encapsulation, such as the following case in which I have some complex functionality in a server-side app that I want to spread across multiple files, and want to make public only a small subset of the members that should be available to other members of the namespace.

(and forgive me if the code is not 100% correct - I’ve never actually used namespaces, I’m just looking into whether I should or not).

// calculator.ts
namespace Pricing {
	export class Calculator {
		export function methodIWantPublic {...}
		function calculatorMethodAvailableOnlyInNamespace {
			const result = helperMethodAvailableOnlyInNamespace();
		}
	}
}

// pricing-utils.ts
namespace Pricing {
	class Utils {
		function helperMethodAvailableOnlyInNamespace {...}
		// lots of other methods I don't want to be used outside of Pricing
	}
}

// consumer-code.ts
class SomeNonPricingSpecificClass {
	const result = Pricing.methodIWantPublic();
}

Is there a way to do this on the server with ES6 Modules? What’s the benefit to that approach vs this one?

@DanielRosenwasser
Copy link
Member

If you are using ES6 modules, you just don't export the things you don't want to share. It works the same way if I'm understanding the question correctly.

@emcdaniel-sungage
Copy link

@DanielRosenwasser thanks for the reply. But how would that work with local modules (not on NPM) that have multiple files? How would I keep the consumer code from being able to access a method in a helper (for example) that another file in the module needed access to?

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Aug 24, 2022

You could create a folder that provides utilities for a module. The module can pick-and-choose what it wants to export. If something imports from foo-utils that isn't foo.ts itself, then it gives that same sort of "code smell".

src/
├── foo-utils/
│   ├── util1ForFoo.ts
│   ├── util2ForFoo.ts
│   └── util3ForFoo.ts
└── foo.ts

There are variations of this, but that's about it.

If you have multiple projects, you can control the exposed API shape with --stripInternal or API Extractor. If you're using npm packages with Node 12 and later, you can even control which files are reachable from within a package using the exports field - which is actual runtime controlled encapsulation.

There's a bunch of options, and at the end of the day your namespaces didn't give any more encapsulation than modules without those options. Someone can always reach into a namespace and grab an exported function - it's just that maybe it seemed suspicious to do so.

If at the end of the day you still want to use namespaces, that's fine. TypeScript won't be removing them any time soon, though just be aware that you're swimming against the current these days. The broader community is not leveraging them anymore, and not all tooling may choose to put in the work to support them.

@zijianhuang
Copy link

@be aware that you're swimming against the current these days. The broader community is not leveraging them anymore, and not all tooling may choose to put in the work to support them.

Be aware which current you are in. A few years ago, The Babal's team had made up their mind not to support namespace, however, eventually they had changed their mind and supported namespace at babel/babel#9785 .

Yes, it is technically possible that you can survive with modules only and without namespace even in developing complex business applications with multiple layers, tiers and brokers, just as you can survive with structural programming without OOP. However, it is more viable to have namespaces, kind of managed complexity, for managing unmanaged complexity. With namespaces, some dev tools could be easier to develop, for example, service to client code generators, and the codes generated are overall much better. So application developers could live better.

@ecalcutin
Copy link

@be aware that you're swimming against the current these days. The broader community is not leveraging them anymore, and not all tooling may choose to put in the work to support them.

Be aware which current you are in. A few years ago, The Babal's team had made up their mind not to support namespace, however, eventually they had changed their mind and supported namespace at babel/babel#9785 .

Yes, it is technically possible that you can survive with modules only and without namespace even in developing complex business applications with multiple layers, tiers and brokers, just as you can survive with structural programming without OOP. However, it is more viable to have namespaces, kind of managed complexity, for managing unmanaged complexity. With namespaces, some dev tools could be easier to develop, for example, service to client code generators, and the codes generated are overall much better. So application developers could live better.

Could you give any thoughs about that #30994 (comment) ?

@emcdaniel-sungage
Copy link

Good points @zijianhuang.

at the end of the day your namespaces didn't give any more encapsulation than modules without those options.

@DanielRosenwasser My understanding is that, in my namespace example above, the Pricing.Calculator.calculatorMethodAvailableOnlyInNamespace method would not be available to SomeNonPricingSpecificClass (which is not in the pricing namespace) while in your modules example anything in foo-utils that was needed in foo.ts would have to be exported and thus available to any other code in the system. Is that not the case?

@nvlled
Copy link

nvlled commented Oct 2, 2022

I'm in agreement that namespaces have valid use cases. In addition to the points given above (converting namespaces to modules could break encapsulation), my reason is that I would like to keep related code properly encapsulated together as much as possible, and would rather avoid having them split and cluttered across many files.

@ghost
Copy link

ghost commented Nov 10, 2022

I use namespaces all the time to nest interfaces within classes and I don't see modules improving the ergonomics of that at all. True declaration merging is not possible with modules as far as I can see.

The nice thing about the code using namespaces below is that, in a large project, the types Yada and Yada.Stuff are going to be referred to by the same exact name everywhere unless someone explicitly goes out of their way to rename Yada when they import it. Obviously this increases uniformity in our code, which benefits comprehension, code search and refactoring with find/replace.

export class Yada {
  doStuff(stuff: Yada.Stuff) {
    console.log(`Doing stuff to ${stuff.thing}...`);
  }
}

export namespace Yada {
  export interface Stuff {
    thing: string;
  }
}

To use this in any file we naturally do import { Yada } from "./Yada"; and everything is great.

I tried testing this out with modules over at playcode.io and codesandbox.io with this code:

// File: Foo.ts

export default class Foo {
  doStuff(stuff: Stuff) {
    console.log(`Doing stuff to ${stuff.thing}...`);
  }
}

export interface Stuff {
  thing: string;
}

And in another file:

// File: script.ts, index.tsx, etc:

import * as Foo from "./Foo";

// NOTE: I kinda hate the "import * as Foo" style here because there is no autocomplete for the import name.

function doesNotWork() {
  const foo = new Foo(); // Foo is not a class.
  foo.doStuff({ thing: "the thing" });
}

function worksButUgly() {
  const foo = new Foo.default(); // This is garbage.
  foo.doStuff({ thing: "the thing" });
}

Module augmentation is not a replacement and re-exporting things to create the nesting structure in a completely separate file which has to import and re-export things is also complete garbage IMO.

Maybe modules are better when you're building a library that you'll publish on NPM but for large internal projects where most of the code lives in the project, I'm going to continue to use namespaces for nesting types.

@JirkaDellOro
Copy link

I've experienced many times that people like myself struggle a lot with ts-namespaces and es-modules, especially when it comes down to try to work with both in the same project. I did some "structured" fiddling and found that on the Javascript-side after compilation, things appear to be quite simple, but can't be done in a standard way with typescript before compilation.

I wrote about it here and I'd be happy to see some comments on that before I start creating a plugin or extension, that tweaks the compiled output. I expect to be missing some important parts...

https://jirkadelloro.github.io/TS-Namespaces-ES-Modules/

A student of mine struggled again using namespaces and modules together. I don't see the point why this can't be done out of the box. Referring to my article, it would be nice to hear from the TypeScript-Team to understand what I'm missing.

@maxpatiiuk
Copy link

Note, module declarations, and module expressions may come to ECMAScript in a few years - those should be able to further replace some use cases for namespaces mentioned earlier in this thread.

@kizerkizer
Copy link

kizerkizer commented Dec 9, 2024

It's silly that namespaces in TS are (as I've found out) evidently "frowned upon". It's like one of those countless no no's that dogmatically permeate software development. Namespaces are ultimately just for names (shockingly). They are a natural organizational facility for identifiers. That's why so many languages have them.

They don't have to compete with ES modules, they can compliment them. Though ES imports offer aliasing the core issue is that ES modules being designed for JS, a dynamically typed language, are about exporting/importing values. With values you can implement namespacing with regular objects, just setting properties to values and so on. But you cannot handle a type this way because it is not a value nor existent at runtime. In this case we can use TypeScript namespaces.

Don't listen to the haters TypeScript! Namespaces forever!

@nelson6e65
Copy link

I use namespaces to extend some interfaces. For example, add functions to normalize and transform data.

As we know, interfaces are just types: they will not generate runnable code on runtime. But, if you add a namespace with the same name, in the same file, you can export functions that you can call as a static method of an interface.

export interface IUserData {
  // ...
}

export namespace IUserData {
  export const defaulted = (data: Partial<IUserData> = {}): IUserData => ...
}
import { IUserData } from '@/data/user.data.ts';

// ...

IUserData.defaulted(reponse.data);

This is useful when you have a lot of models and want to use some in the same file. If you export the defaulted function directly as module, you will have to add alias to each import (or name them like defaultedUserData, defaultedDocumentData, etc) loosing consistency.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Discussion Issues which may not have code impact
Projects
None yet
Development

No branches or pull requests