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

export access modifiers #41316

Open
5 tasks done
RebeccaStevens opened this issue Oct 29, 2020 · 6 comments
Open
5 tasks done

export access modifiers #41316

RebeccaStevens opened this issue Oct 29, 2020 · 6 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@RebeccaStevens
Copy link

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:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Nov 10, 2020
@mwaddell
Copy link

mwaddell commented Aug 5, 2021

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
Copy link

mwaddell commented Aug 5, 2021

Another approach to solve this issue would be Conditional Compilation - #3538 (comment) - but that's been sitting open for 6 years without any real movement on it...

@branko-d
Copy link

branko-d commented Nov 26, 2021

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 #35554 (comment).

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
Copy link

litera commented Oct 28, 2022

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
Copy link

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

@ethanresnick
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

7 participants