microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.3k stars 12.53k forks source link

export access modifiers #41316

Open RebeccaStevens opened 4 years ago

RebeccaStevens commented 4 years ago

Search Terms

export access modifiers public protected private

Related: #321

Suggestion

Allow adding public, protected and private access modifiers to export statements. This would limit where the data can be imported from.

Access Modifiers

private

Other file in the same directory scope have access to the export.

protected

Other file in the same directory scope or a nested scope have access to the export.

public (default)

Current behavior - Can be access from anywhere.

Use Cases

Most useful for large projects - allows modules to limit where things they expose are used.

Examples

Given the following director structure:

.
├── module
│   ├── submodule
│   │   └── index.ts
│   ├── file.ts
│   └── index.ts
└── index.ts
// module/file.ts
public export const foo = "hello";
protected export const bar = "world";
private export const baz = "!!!";
// module/submodule/index.ts
import { foo, bar, baz } from '../file';
                // ^^^ Cannot access baz as is private.
// module/index.ts
import { foo, bar, baz } from './file'; // All Ok.
// index.ts
import { foo, bar, baz } from './module/file';
           // ^^^ Cannot access bar as is protected.
                // ^^^ Cannot access baz as is private.

Note: If using import * as X from ..., X's type simply wouldn't include things it doesn't have access to.

Checklist

My suggestion meets these guidelines:

mwaddell commented 3 years ago

I like this approach! I think it would solve the most common use cases enumerated in #35554, #5228, #321, etc - that of needing a level between "public" and "private" (other than "protected" which is class-specific) to provide access to internals in a controlled way for the purpose of unit testing or to classes which do not extend the class itself.

Currently, there is no "protected" equivalent for modules, so people use kludges like prefixing an underscore to the name to indicate that it's "internal" and shouldn't be used outside of that file and its unit tests. Having an official way to do this which is supported in the language would be outstanding.

For example, the file Diagram.tsx:

export function Diagram(props: DiagramProps) {
    return <>
        <_Header>My Floorplan</_Header>
        {props.rooms.map((r) => <_Room path={r.path}>{r.name}</_Room>)}
        <_Legend />
    </>;
}

export function _Header(props: HeaderProps) { ... }

export function _Room(props: HeaderProps) { ... }

export function _Legend() { ... }

would become:

export function Diagram(props: DiagramProps) {
    return <>
        <Header>My Floorplan</Header>
        {props.rooms.map((r) => <Room path={r.path}>{r.name}</Room>)}
        <Legend />
    </>;
}

private export function Header(props: HeaderProps) { ... }

private function Room(props: HeaderProps) { ... }

private function Legend() { ... }

This would allow Diagram.test.tsx in the same directory access to the 3 private exports so it can fully mock them, unit test them, etc while preventing access outside of this directory.

mwaddell commented 3 years ago

Another approach to solve this issue would be Conditional Compilation - https://github.com/microsoft/TypeScript/issues/3538#issuecomment-893566580 - but that's been sitting open for 6 years without any real movement on it...

branko-d commented 3 years ago

May I propose some clearer (IMHO) keywords:

folder export const foo = "visible in the same folder";
tree export const bar = "visible in the same folder and its recursive children";
export const baz = "same as before";

We might also have something like:

export class MyClass {
    file public const foo = "visible in the same file";
}

The file public makes class members visible to the code outside of the class but in the same file. This would be great for observable-driven React components, as described in https://github.com/microsoft/TypeScript/issues/35554#issuecomment-979737464.

We might even have folder public and tree public.

All of these could be useful when we need to make the visibility of the member more limited compared to the visibility of the class.

litera commented 2 years ago

I also upvoted for this feature. I'm annoyed by the fact that libraries use folder index files and when you're importing types, it may be confusing which path to import them from. And other developers may import the same thing from a different path which leads to unclean code.

I would suggest another modifier: hidden or in the scope of your modifiers public hidden. This would allow the import of the type, but you would have to know that it exists. Because what I'm mostly annoyed at is that libraries for instance may have this structure:

├── components
│ ├── componentOne
│ │ ├── ComponentOne.tsx
│ │ └── ComponentOne.theme.ts
: :
│ └── componentN
│   ├── ComponentN.tsx
│   └── ComponentN.theme.ts
└── index.ts

All components could have public hidden exports of the components themselves and /components/index.ts would re-export them with normal public export. This would help code hinting/intellisense tools to filter out hidden import paths or at least put the hidden ones at the end of the possible selection to use as an import path. The latter is IMHO a better approach because there may be types that one would want to export as hidden, so it should not be used but for the very uncommon occasion it could. It would be possible to import it, but being a hidden export would automatically signal that this is some functionality that should used with caution.

A-Shleifman commented 2 years ago

There's another issue #41425 discussing the same problem.

ethanresnick commented 1 year ago

FWIW, it seems like Microsoft has also run into this problem, and has made their own solution using "packlets". I agree that it'd be nice to have something like this be more standard. However, I do wonder whether it's something that should be developed in concert with other players in the JS ecosystem, rather than just addressing it in TS only through custom syntax.

jjhiggz commented 1 month ago

Love this idea, might be cool if you could use a type modifier on this to so that way you could make a type signature public/protected and keep the code private.