internal
modifierOften there is a need to share information on types within a program or package that should not be
accessed from outside of the program or package. While the public
accessibility modifier allows
types to share information, is insufficient for this case as consumers of the package have access
to the information. While the private
accessibility modifier prevents consumers of the package
from accessing information from the type, it is insufficient for this case as types within the
package also cannot access the information. To satisfy this case, we propose the addition of the
internal
modifier to class members.
This proposal aims to describe the static semantics of the internal
modifier as it applies to members of a class (methods, accessors, and properties).
This proposal does not cover any other use of the internal
modifier on other declarations.
Within a _non-declaration_ file, a class member marked internal
is treated as if it had public
visibility for any property access:
source.ts:
class C {
internal x: number;
}
let c = new C();
c.x = 1; // ok
When consuming a class from a _declaration_ file, a class member marked internal
is treated as
if it had private
visibility for any property access:
declaration.d.ts
class C {
internal x: number;
}
source.ts
/// <reference path="declaration.d.ts" />
let c = new C();
c.x = 1; // error
When checking assignability of types within a _non-declaration_ file, a class member marked
internal
is treated as if it had public
visibility:
source.ts:
class C {
internal x: number;
}
interface X {
x: number;
}
let x: X = new C(); // ok
If one of the types is imported or referenced from a _declaration_ file, but the other is defined
inside of a _non-declaration_ file, a class member marked internal
is treated as if it had
private
visibility:
declaration.d.ts
class C {
internal x: number;
}
source.ts
/// <reference path="declaration.d.ts" />
interface X {
}
let x: X = new C(); // error
It is important to allow assignability between super- and subclasses from a declaration file
with overridden members marked internal
. When both types are imported or referenced from a
_declaration_ file, a class member marked internal
is treated as if it had protected
visibility:
declaration.d.ts
class C {
internal x(): void;
}
class D extends C {
internal x(): void;
}
source.ts
/// <reference path="declaration.d.ts" />
let c: C = new D(); // ok
However, this does not carry over to subclasses that are defined in a _non-declaration_ file:
declaration.d.ts
class C {
internal x(): void;
}
source.ts
/// <reference path="declaration.d.ts" />
class D extends C {
internal x(): void; // error
}
Would the following also error:
declaration.d.ts
class C {
internal x(): void {};
}
source.ts
/// <reference path="declaration.d.ts" />
class D extends C {
x(): void {}; // error?
}
How does this differ from the more common language semantic protected
?
The non-goal is not to address this in other declarations, but couldn't this start to becoming confusing for someone trying to utilise a package or library? For example, I would assume the following you would want to work this way:
declaration.d.ts
interface InterfaceC {
}
class C implements InterfaceC {
internal x(): void {};
}
source.ts
/// <reference path="declaration.d.ts" />
class D implements InterfaceC {
}
const c: C = new D(); // error, but why? I implemented the interface properly.
When targeting ES6, do you see any changes, considerations in the emit?
Do you have a more practical use case of where this pattern would resolve significant problems? I can understand how for a consumer it could clean up an API, but at the same time, it could be really confusing where you are "hiding" things that will always exist at runtime from compile time. It almost seems like a six of one, half a dozen of another.
An alternative to modifiers is:
package.d.ts
/// <reference path="declaration.d.ts" />
internal {
C.x;
}
An internal {}
block marks which parts of the public api are hidden.
+1 to something like this
+1 for this.
Internal modifier for classes and their properties/methods within a component/program would allow greater scope for Typescript identifier shortening.
+1 for this proposal.
I think I'd want to extend internal
to also work on classes.
It would work pretty much like the --stripInternal
compiler flag works now, except without the need to add JSDoc to the class.
Additionally, though this may need some exploring, it could be used to keep classes within a module. So for example:
//file1.ts
module A.B
{
internal class C { }
var c: C = new C(); //Works
}
//file2.ts
module A
{
var d: A.B.C = new A.B.C(); //Error because C is only available inside A.B module scope.
}
//file3.ts
module A.B
{
var e: A.B.C = new A.B.C(); //Works, back in scope
}
I'm polishing classes' members' openness of RxJS like https://github.com/ReactiveX/RxJS/pull/1244, then I wanted this feature to control openness in a complexed class dependencies.
+1 Having to write @internal all over the docs is currently my biggest pain point with TS. Which is another way of saying that things are pretty good - good job guys :)
+1
any progress on this?
+1, this would be awesome to have.
This would be really nice for Angular components where you need properties/methods to be public to be accessed by the template, but want them to be private to other users that are consuming your components. The current /** @internal */
feature is problematic because it causes types to fail to satisfy implemented interfaces when the d.ts
is compiled downstream.
+1 This would really help with testing; right now you have to make methods public that you really just want to be visible for testing but not part of the public API.
Agreed. This would make sense for services, where you can have internal methods that can only be accessed from other services if necessary, but not the API consumer.
+1 internal modifier would be a big Christmas gift.
We really need this feature too. I am a developer of a HTML5 engine called Egret. Our engine contains plenty of internal APIs which are not allowed to accessed by user, otherwise it may cause internal problems. Currently, we have to make them as public
and add some documents for it says ' do not use this method! '. It is really hard for us to design a safe and stable engine API because of the lacking of internal
. Hope that you could add this feature soon, thanks in advance :)
excuse me, i know what a module or a namespace is, but what is a package?
excuse me, i know what a module or a namespace is, but what is a package?
CommonJS Packages which npm
and other JavaScript package managers build upon.
collection of modules, assets etc
looks like something that only makes sense in NodeJS realm
a rigid definition would be a plus
looks like something that only makes sense in NodeJS realm
No, AMD modules are organised into packages, although the AMD loader doesn't read the package.json
. Bower deals with packages and specifically is targeted at a browser context. It is a pretty well understood concept of a set of modules and other resources (e.g. CSS) that serve a well defined purpose.
a rigid definition would be a plus
The Internet has (sometimes) thrived because of non-rigid definitions.
you might be right about the Internet in general, but this is a
which is a place where rigid definitions are more than welcome
+1 for this. I am currently writing a library that uses builders to construct instances of classes. I have the builder and its target in the same module. The target class should have an internal (or private) constructor because it should only be constructed by the builder. Currently it can't unless I use Reflect to construct the target class. Having a module internal modifier would fix this issue. Would really like to see this in TypeScript.
This is an incredibly useful proposal and, with great respect, I'm a bit surprised it doesn't seem to be getting any love from the dev team, given the number of comments and upvotes.
I need various properties exposed for manipulation by publicly-exported functions that take an instance of a given class and the perform manipulation on members that public to the package, but private to external consumers. The only way to do this currently involves hacks:
The example below is contrived; I'm actually using classes only as minimalistic containers for structural data, but providing a functional interface for manipulation thereof.
export interface MyType {}
export class MyTypeImpl implements MyType {
constructor(public _k: number) {};
}
// External consumer has no access to the
export function Foo(x: MyType): MyType;
export function Foo(x: MyTypeImpl): MyTypeImpl {
return new MyTypeImpl(x._k + 1);
}
Alternatively, I could do this:
export function Foo(x: MyType): MyType {
const y = <MyTypeImpl>x; // but now I'm compiling this pointless variable into my JS output
return new MyTypeImpl(y._k + 1);
}
Having to use an interface to hide internal members from consumers creates a whole bunch of pointless code throughout my codebase in order to see class internals.
bopping this issue to keep it on the radar, it's important...
Workaround for library authors is to use /** @internal */
on relevant class members in order to stop them from showing up in type definition files.
+1 for this proposal. It should simplify logic for patching object that normally use in JavaScript library.
For current TypeScript, I use the following code to add internal property to external object.
// This signaure should be defined by other module.
interface Bar {
publicProperty: string;
}
// Signature for using in this module
interface InternalBar extends Bar {
_internalProperty?: string;
}
// Public API
type FooSignature = (obj: Bar, times: number) => void;
// Real Function
const Foo:FooSignature = function (obj: InternalBar, times) {
obj._internalProperty = '1234';
obj.publicProperty = 'test';
times++;
};
export { Foo };
https://gist.github.com/Soul-Master/faf2e1d59c08bedf25d6fcf2722deff0
Is there any news on this? It would be a very much welcomed improvement.
Personally I'm more partial to friend
access as you don't have to build multiple times to make use of the feature. It sounds like making something internal
to a part of your project would require giving it its own build step.
If your needs are small, the following example might suffice. It exposes protected
properties and methods via a subclass. It has many drawbacks and limitations, but it also has the important pro of avoiding any casts.
Proper support for this feature (or something similar) can't come soon enough!
// For clarity, we define all the "internal" methods and properties here
abstract class InternalWidget {
protected abstract set internalProperty(value: number);
protected abstract internalOperation(): void;
}
class Widget extends InternalWidget {
protected set internalProperty(value: number) {
console.log("you have the privilege to set this");
}
protected internalOperation(): void {
console.log("you have the privilege to invoke this");
}
}
// The horrible hack and nasty boilerplate
abstract class InternalWidgetAccessor extends InternalWidget {
static setInternalProperty(widget: InternalWidgetAccessor, value: number): void {
widget.internalProperty = value;
}
static internalOperation(widget: InternalWidgetAccessor): void {
widget.internalOperation();
}
}
// Without
{
const widget = new Widget();
widget.internalProperty = 42; // compiler error
widget.internalOperation(); // compiler error
}
// With
{
const widget = new Widget();
InternalWidgetAccessor.setInternalProperty(widget, 42); // no error
InternalWidgetAccessor.internalOperation(widget); // no error
}
Hope this helps someone out.
I'm not sure if introducing such a complex construct is worth the effort given that one can already achieve the same result with existing syntax. The only trade off is to export factories instead of classes (shouldn't you already be doing this anyway?), and then you get a beautiful solution:
export interface ITheClass {
doStuff(): void;
}
export class TheClass implements ITheClass {
// === Public API ===
public doStuff() {
this.doInternalStuff();
}
// === Visible for testing ===
public doInternalStuff() {
}
}
Your main export file then looks like this:
// This file is only exporting the public API and a factory method.
// Not only is this solving the problem the `internal` keyword is trying to solve,
// it also ensures I'm hiding implementation and instead only exposing interfaces.
export ITheClass from '...';
export function makeTheClass(): ITheClass {
return new TheClass();
}
The test code can just new TheClass
and get access to the test-only methods, but a user of the package does't see the symbol 'TheClass' and hence can't directly instantiate it.
I'm interested if someone can come up with a use case where the internal
keyword would be preferable over the above pattern.
@hmil My main criticisms of your outlined approach are yet-more-boilerplate and inflexibility. In your chosen example, TheClass
conveniently has zero constructor arguments. The pattern you are advocating for becomes onerous in code with constructors that (necessarily) take many arguments (and not dictionaries). Additionally, access to the class type is sometimes a requirement. Examples include: use of a framework like Angular where the classes need to be registered for injection, performing instanceof
testing, and subclassing.
A factory function can always accept the same arguments as the constructor, which admittedly isn't going to be very DRY.
When you need access to the class itself (for instanceof
and such), you can still use the above pattern, just adapt the wording a little. Embed the factory function as a static function of the class and declare the constructor private
. You'll need an extra interface to work with this solution.
Here's what you'd get:
export interface ITheClass {
doStuff(): void;
}
export interface ITheClassTestingInterface extends ITheClass {
doInternalStuff(): void;
}
export class TheClass implements ITheClass {
public static create(...args): ITheClass {
return new TheClass(...args);
}
public static createForTesting(...args): ITheClassTestingInterface {
return new TheClass(...args);
}
private constructor(...args) { /* ... */ }
// === Public API ===
public doStuff() {
this.doInternalStuff();
}
// === Visible for testing ===
public doInternalStuff() {
}
}
This may seem verbose but I would argue that it is worth the extra effort. My point stands: The tools currently available are powerful enough that you can achieve the result but also restrictive enough to enforce good practice (hide your implementations!).
I fear that introducing the internal
modifier would just add useless confusion and introduce yet another way for careless developers to screw their packaging (think leaky encapsulation). The exact semantics of the keyword are hard to define (what is a JavaScript "package" anyway?) and maybe harder to understand. The question is: is the above _workaround_ such a hassle that we want a dedicated keyword just for this manageable use case?
@hmil Here is a _typical_ constructor from my code base (there are bigger ones than this):
class TheClass {
constructor(
private _project: Project,
private _contextualState: ContextualStateMutator,
private _flatNavigableTocItems: FlatNavigableTOCItems,
private _highlightedTokenPosition: HighlightedTokenPosition,
private _moveSelectedRegions: MoveSelectedRegions,
private _resizeSelectedRegions: ResizeSelectedRegions,
private _revealFlatNavigableTocItem: RevealFlatNavigableTOCItemActor,
private _revealTokenLocality: RevealTokenLocalityActor,
private _selectTokenLocality: SelectTokenLocality,
private _selectTokenSpan: SelectTokenSpan,
private _tokenLabelsMutator: TokenLabelsMutator,
private _visibleFlatNavigableTocItemMutator: VisibleFlatNavigableTOCItemMutator,
private _visibleTokenIndexMutator: VisibleTokenIndexMutator,
private _selectedRegionModels: SelectedRegions,
private _selectedTokenSpan: SelectedTokenSpan,
) { }
}
Implementing your suggested pattern on this class would be insanely verbose, and nicely demonstrates why a new keyword/language feature is so important.
I see how a factory method becomes a problem in your case :smile: I'm still not convinced there is no other way than the internal
keyword though. Consider the following:
The example you gave suggests you are using dependency injection to create TheClass
since that is a pretty large amount of arguments for a class you'd construct manually.
In general DI helps with separating implementations from abstractions (that's basically all it does) so it should actually make a case against the internal
keyword and not for it. In Java with Guice you would never ever invoke the constructor. Instead there would be something like this somewhere in your code:
bind(ITheClass.class).to(TheClass.class);
Of course we can't do exactly this in TypeScript because interfaces don't materialize in JavaScript. However the DI library you are using has to have a similar way to provide bindings. Which means you could keep the class constructor private
and suggest the usage of the interface type rather than the class type.
Although in such situation the user will have access to the "private" API on the class object and will have to remember to only use the interface types from your module. Is it something you are trying to avoid?
@hmil
DI isn't of direct concern to my problem at hand, although I am using it elsewhere for the UI.
I have a bunch of model classes that need to interact with each other and with an undo/redo mechanism, as well as with a persistence layer (dirty checking, generating diffs etc). Most importantly, this model is consumed by the rest of the application. Thus, I would like the "behind the scenes" interaction to be obviously marked as such, to be typesafe, and be not too verbose. I would also like the "behind the scenes" interaction to not be plastered all over the public interface of the model.
The consumer of the model (the rest of the app) needs to be using class types. Why?
instanceof
tests with calls to typeguard functions.Basically, I want something akin to Java's package access scope. I don't really care what "package access scope" means in the context of typescript, I'll make it work with whatever we (hopefully) end up with. If it was limited to the current compilation unit (file?) that'd be fine with me. The friend
keyword would also do the trick.
Its hard to express the difficultly of working around this language limitation if you've never naturally run against it. Yes, everything can be worked around if you're willing to triple the amount of code you will write _and maintain_, or if you're willing to have a few points of unsafe casting here or there. Personally, the package access scope feature from Java is sorely missed.
@mark-buer Point made. Thanks for taking the time to explain it in such details.
+1 for the Angular case. Component interfaces are terrible right now and very easy to misuse, especially in testing. An internal modifier would greatly improve things.
+1
I'd like to see this too, but have you guys considered the way Golang handles this? I find their approach quite elegant.
They let you have a directory called /internal
, which only allows access to internal modules in the folder that contains that internal folder (and access from within that internal folder)
Graphically it looks like this:
/project
/internal
/component
component.go // internal
/api
api.go // access to internal component
main.go // access to internal component
/another-project
main.go // no access to project's internal component
@matthewmueller Too easy to mess up. An explicit keyword is much better. You don't need to look where the file is located to understand it's functionality.
@matthewmueller That approach is limited to making an entire module internal or external. Usually I just want to mark a few members internal. For example, I have a tree-like structure that has node instances. The nodes need to be able to muck around with the tree in order to fulfill some of their operations. Part of the tree's API is internal (to allow the node's pseudo-private access) and part of the tree's API is public.
@rbuckton Is there any status on this? It's been 2.5 years since this was proposed. I'm working on Aurelia vNext right now, completely written in TS. We could really make use of this feature...
As we work on project references (#3469), I think we have even more reason to hold off on this. For example, the current proposal relies on a distinction between a declaration in a .ts
file and a .d.ts
file, but project references heavily leverage .d.ts
files.
But is this issue all about preventing definitions of one interface being exported?
We can add a export protected
statement that exports one type but disallow accessing its internals with user code (thought the internals are still used by type checking).
protected export interface Engine { a: number, b: string }
A protected export
ed interface
(or class
' type half) would only outside to see its name and generic parameters (a.k.a. "kind" in FP). It prevents...
{a: 1, b: 'a'}
is not Engine
).Going to throw my proverbial hat in the ring here, I love internal
in C# and would love to see it in TS. “Pure” encapsulation is nice, but fully decoupling the internals of a program/library is not always practical and then I end up just declaring stuff public
when I really would prefer not to.
I've found a really neat trick. Not sure if many people know about it or not:
export class Foo {
['foo'](arg: number) {}
}
The foo
method is hidden from IntelliSense, yet TypeScript still validates any callsites, so you can call foo
from the internals without sacrificing type-safety. 🎉
I would recommend prefixing these "hidden methods" with an underscore, so users aren't tempted to call them if they happen upon them while looking through the source code.
edit: It's worth noting that foo
_is_ suggested in auto-complete when bracket notation is used on the instance of Foo
.
@aleclarson I'm not sure I would recommend abusing what appears to be a bug in the tooling for this purpose...
@errorx666 I don't think it's a bug. Bracket notation with a string literal allows unsafe property access on objects, so this seems like a natural extension of that behavior. Obviously, we need confirmation from the TS team to know for sure.
It doesn’t allow unsafe access though - unless the object’s type includes an index signature. Otherwise you can only access properties that are part of the type, same as with dot notation.
@fatcerberus That's true if you're using noImplicitAny
(which you should be)
@errorx666 In the specification, there's no mention of using bracket notation in a method signature, so it seems like unintended behavior. But this comment suggests otherwise (sort of).
It’s valid JS syntax - effectively a computed property name but as a method rather than a plain property. If IntelliSense isn’t picking it up that sounds like a bug rather than a feature, but I could be wrong...
For a less buggy-looking solution, you can do this instead..
class Foo {
private foo() {}
}
const inst = new Foo()
inst['foo']()
..which is officially supported.
@DanielRosenwasser Bumping this again since TS is farther down the road after the original project ref work. This does keep coming up in both my open source work and "the day job". I just had a conversation today about how this would be nice to enable certain testing scenarios for our teams, who also don't want the APIs they need for testing exposed to consumers of their APIs, even within the same engineering org. The larger a project/team becomes, I think this sort of things becomes more and more important to have an official solution for, supported by compiler and tooling.
@EisenbergEffect agreed. I've taken to just including a second entry point (e.g. @some-org/somelib/testing
) that exposes the "internal" exports as a workaround for testing purposes, but something akin to .NET's internal
and the internalsVisibleTo
attribute would be far more ideal.
@EisenbergEffect @DanielSchaffer does a tool like API Extractor (which @octogonz and others at Microsoft develop) do what you need?
API Extractor is able to customize your .d.ts
files to do things like roll your files into one top-level .d.ts
file, strip away things marked as @internal
in your doc comments, and can be used to generate documentation pages.
Check out https://api-extractor.com/pages/setup/generating_rollups/#dts-rollup-with-trimming
It's usable for Aurelia, since we ship the APIs officially via npm. However, I'm not sure this works when the context is a larger set of engineers working together to build a unified product, but where the codebase is internally modularized and there are areas of ownership.
To be more concrete, imagine you've got 100 engineers, split into 10 squads of 10 engineers plus a PM each. Each one of those squads then has 3 or 4 sub-squads with 2-3 engineers each. Each sub-squad owns a set of APIs which are consumed by their peers, both inside their squad or outside of it, across the broader team. The sub-squad wants to be able to mark certain things as internal so that they can test them within their own domain, but don't have to worry about one of the other squads using them directly. The area of ownership often manifests as a simple folder within the project, facilitated by GitHub code owners policies. It's not necessarily a formal shipping division, such as an npm package.
Does that help clarify?
@EisenbergEffect I might be missing why the internal
modifier would facilitate building protected APIs any more than API Extractor in that organizational structure.
I might be missing why the
internal
modifier would facilitate building protected APIs any more than API Extractor in that organizational structure.
@DanielRosenwasser if I understood right, he's asking for fine-grained accessibility relationships between individual source files within a project folder. This is similar to C++ friend
classes. By comparison, API Extractor operates on NPM packages (under the idea that your code base is decomposed into lots of small library packages) -- this is more like C#'s internal
keyword that applies to entire .NET DLLs (but all the code within a single DLL has access to all the internals).
For what it's worth, @EisenbergEffect it seems to me that if a collection of files are cohesive enough to be "owned" by a team of people, it might make sense to separate that code into an NPM package. This approach has worked pretty well for our projects, which frequently have 100+ people working on them.
@octogonz In our case, for various reasons, we can't simply extract 100+ individual packages and shift to a monorepo. It's not that we haven't thought of that and don't want to do it. It's just not possible for this project at this time (and probably for a few years at least). So, I'm looking for ways to work within the current constraints.
All that aside, there is a desire to not have to bring additional tools into play to handle creating an accurate public API and get correct intellisense. So, even with something like API Extractor, it still seems like a work around for language features for which there is precedent elsewhere and which, if incorporated, would result in a better tooling experience.
FYI This isn't a sword I'm willing to die on. It's not a huge issue, more of a nice to have. But, I did want to see where we were at on this, since it seemed like it was being considered, but there was a desire to delay until after TS projects landed.
So, even with something like API Extractor, it still seems like a work around for language features for which there is precedent elsewhere and which, if incorporated, would result in a better tooling experience.
Agreed. It would be better for @internal
to be a first class feature of the TypeScript language. (The @alpha
/@beta
/@public
release designations perhaps are more in the province of an external tool.)
If for no other reason than the benefits for testing, I'd still say this is worth doing. On top of that, my project is also in a similar situation as @EisenbergEffect, where having the ability to control the accessibility on a per-file (or even a per-package, similar to Java) basis would considerably improve the typescript experience for my team.
In my opinion, the whole "internal" thing is related to the need to flatten declarations (#4433).
Both these features imply the fact that there will be an "inside" and an "outside" of the resulting library, usually because of some bundling step that will happen later, after the code is compiled, using tools such as webpack or rollup.
To achieve bundling, one usually has an entry module (index.ts
or similar) that re-exports symbols from other modules. The "external" API of the bundled library would then include everything exported, directly or indirectly, from the entry file. That external API is the one that I would expect to see flattened in a single .d.ts file, something that TypeScript is not capable of doing yet.
Exporting the flattened API in that way has two issues and internal
would help with both of them.
Keep properties of exported classes from appearing on the API.
To do that manually, one would have to define an interface (exported outside of the library) and a class (kept only inside). This is often too complex for just hiding a couple of methods because they are used for internal communication or for unit testing.
With internal
you would just mark methods/properties and the compiler would remove them from the flattened .d.ts. Of course, they are still available at runtime, but a conscious consumer of a TS library should not rely on non-API names. This is no different from how private
fields are still accessible at runtime but hidden in the definitions.
Prevent unintentional export of classes
The exported API is the result of a series of re-exports, possibly including full re-exports of intermediate index modules (sometimes called "barrels"). It is easy for a developer to forget about the whole API thing and unintentionally export a top-level name (class, but also function, etc.) that was meant to remain internal.
By marking classes internal
the developer would signal his intention to keep that name from appearing on the API. The compiler could then warn about unintentional exports for manual resolution by the developer.
I want to use this so that Cue.go
can be used by this Checkbox
parent class, but cannot be used when accessing Checkbox.OnChecked
externally. For now, I am considering this ugly code to be my solution:
type CheckedSignature = (isChecked: boolean) => void;
type InternalCheckedCue = Cue<CheckedSignature>;
type CheckedCue = Pick<InternalCheckedCue, "bind" | "unbind" | "unbindAll">;
class Checkbox {
/** Fires when the Checkbox is Checked */
public OnChecked: CheckedCue = new Cue<CheckedSignature>();
public SetChecked() {
(this.OnChecked as InternalCheckedCue).go(true);
}
}
How does one specify which classes/modules/files are able to see a given property?
With my idea over at https://github.com/microsoft/TypeScript/issues/35554, the usage is explicit about what code is allowed to access protected or private members.
I think that internal
like in C# (from what I've read, I haven't used C# before) is something that works for C# because the lib can be compiled into an "assembly" -- a grouping of the compiled code, like a DLL or packages in Java.
But in JavaScript/TypeScript, _there are no package boundaries, only module (file) boundaries_, and NPM package scope is out of question because there are other ways to consume code beside NPM packages, so I think it would be somewhat limiting for internal
to apply only within a file.
For example, what if I have code in 5 files that all need to access a private field in the class of a 6th file? It would not be ideal to have to merge the files all into one to achieve the sharing.
So for this to work well in JavaScript/TypeScript, I think we need something that is designed around sharing accessibility explicitly between certain other code in any files, considering that there are no such things as "package" boundaries in JS.
@lorenzodallavecchia You were trying to imagine how a "package" could be defined, above, but as you can tell, it is problematic. We can't prescribe everyone to use a certain form of bundling, or to consider a package to be based on an entry point, or to consider package boundaries to be NPM-style, or etc. End users of any library can import code in many different ways, and can import individual files arbitrarily, not necessarily from any bundle at all, and maybe not even from any sort of package as we know it (for example ES Modules allow us to import files from any URL that can be accessed through the internet and downloaded over HTTP).
I think it may be beneficial to think of methods that would be unique to JS/TS (similar yet distinct from features of other languages) for explicitly specifying shared accessibility with specific modules or things in modules, like #35554 starts to show.
What other syntaxes can we use? Can we do it with things other than class members?
Yes @trusktr you have a point in saying that there are possibly multiple concepts of "internal" and "external", With the example of webpack bundling I wanted to present one use case that I think is very common.
It is true that there may be other cases. One may want to declare "internal" scopes within his library just for the sake of encapsulation, without an actual runtime boundary. For example, I miss package-level protection (from Java and Closure) which can only be emulated with poor results using subdirectories in TS.
Even webpack itself can be configured in countless ways, for example splitting the codebase into multiple packages.
TypeScript should ideally have a way of describing the internal/external boundary in all these cases, and I think that is no easy task!
One possible idea is having a form of "scope" declaration that could be used for assigning module files to cross-cutting scopes inside the application. The internal
keyword would then refer to the internal side of those scopes. Think of the same as Java package
but without the restriction on the directory structure.
But still, I would like to see this feature interact seamlessly with the behavior of bundler, since they are a so common scenario.
And lastly, whatever the solution, I still think that this is somewhat related to API flattening. Simply put, one would want to flatten the API as seen from outside of the outermost boundaries of the library.
Help!
All my helper libraries are cluttered with public _internal_doSomething()
methods.
This is ugly.
However, less ugly than calling private methods with bracket notation as mentioned above (which is barbaric).
Given that JavaScript has recently added support private fields, I'm not sure if adding new TypeScript-specific access modifiers is a good call for future proofing the langauge.
I can see how adding new features along the lines of the current access modifiers, which might end up deprecated in favor of native private properties, might be a bad idea. But the need for some well-designed alternative to @internal
remains, even if it doesn't look like a new access modifier.
Here is the cleanest workaround I have devised so far:
export class Type {
protected __shared: number;
constructor() { }
}
export interface Type {
__shared: number;
}
import * as internal from "./Internal"
import Type from "./Type";
let instance = new Type();
(<internal.Type><any>instance).__shared = 1000;
The key is that we need shared properties/methods between classes that are hidden from public APIs
If anyone has an even cleaner typed workaround, let me know.
public
is exposed to users through IntelliSense, but protected
won't allow enough access, and use of <any>
or instance['__shared']
breaks typing.
@jgranick could this approach be modified a bit to use declaration merging? E.g. if Internal.ts
was Internal.d.ts
, then when the declaration file was used, you'd have access to the internal members without the need for type casting. Without it Internal.d.ts
, they're still inaccessible.
I have another thought here. From what I understand, an internal
modifier means that method is accessible until it is used by an outsider. But, what is an outsider? I assume we just mean after typescript has built a project. So, what if we had a simple @stripmethod
doc comment above methods? Then when typescript declarations are built we remove those methods from the declarations. I think both defining the internal methods and marking them as 'ignored' in declaration files feels like unnecessary complexity. If we simply strip the methods, we might even be able to handle this from outside typescripts compiler.
[edit]
Wait. Is this solved already? There appears to be a --stripInternal
compiler option https://stackoverflow.com/questions/32188679/how-does-a-typescript-api-hide-internal-members.
//Class will not be visible
/** @internal */
export class MyHelperClass {
}
export class MyPublicClass {
//Method will not be visible
/** @internal */
helperMethod() {}
}
// Binding will not be visible
/** @internal */
export const MyValue = 5;
tsc --stripInternal
@andykais If the project is distributed as d.ts then yes, this hides the properties but the properties are still visible in IntelliSense when working with .ts. If you use a common prefix such as "_" then it is sorted before your public members.
I propose that if an internal
keyword were adopted, it would behave like /** @internal */
when generating type declarations, public
when compiling but protected
in IntelliSense hints.
@jgranick By protected do you mean that it will not show up in intellisense?
My preference for internal
would be:
public
within the project where the code is defined.Another way I think of it is that it's a normal public property/method/etc in JavaScript. There's no runtime component to this. It's just that it's completely hidden by compiler/intellisense/tooling for any code outside of the project where it is defined.
@EisenbergEffect I would be happy with your proposal _if_ the concept of "inside" or "outside" the project is clear to the compiler. I was concerned this was a foreign concept?
If there is not a way to distinguish cleanly between "inside" or "outside" the project then my proposal might be a second best:
public
.protected
visibility)I think it's been mentioned elsewhere in this thread, but I would add to @EisenbergEffect's description:
This is both a way to provide a level of runtime enforcement for internal types/functions/variables, and provides a runtime benefit (smaller names) with no downside. It's also part of the reason that I think internal
should be standardized (so tools can use the additional information that it provides).
Just my +1 for internal. I was surprised to find out that Typescript does not support this :(
Another use case to consider, which is specifically mine ;), is having classes share a common abstract parent class. You may want to distribute/document their children, but neither the parent itself nor its methods/properties...
You may want to distribute/document their children, but neither the parent itself nor its methods/properties...
That's simlar to "package protected" as called in other languages. What we need in JS and TS is "module protected" and "module private". Seems that https://github.com/microsoft/TypeScript/issues/35554 would suit your needs more, and it is better than the internal
idea here because it limits scope of variables to certain modules, rather than making everything visible in the whole project (protected and private is still valuable within projects, and replacing protected
with internal
makes them public
within a project which is not ideal).
+1 for internal.
I would also like to see this for global functions, variables, etc which also have the export keyword, so that they can be accessed from other files in the same package, including tests (which requires an export declaration,) but 'hidden' externally. The internal keyword would communicate clearly to future maintainers the intent of the associated construct.
On our team, we use the jsDoc /** @internal */ comment quite a lot to designate internal functions, but there are some files that we exclude from the compilation used to generate the index.d.ts file, and in those files, we don't always consistently use the @internal comment (though we probably should.) If someone in the future then does something such that those files are included in the index.d.ts compilation, those functions would be exposed.
An internal keyword with the characteristics described would be especially useful to better communicate intent, and to enforce it as best as Typescript can.
Most helpful comment
@rbuckton Is there any status on this? It's been 2.5 years since this was proposed. I'm working on Aurelia vNext right now, completely written in TS. We could really make use of this feature...