How does a Typescript API hide internal members?
Asked Answered
C

3

23

Building a library in TS to be called by TS and JS code and wondering how people hide internal methods and fields. Java has package visibility to handle this. Lacking that in TS, I see two options:

  1. Define interfaces to only expose certain members. But this involves duplicating the definitions and extra code to cast arguments from interface to implementation types.

  2. Put an @private doc comment on the internal members. Possibly exclude such from generated docs.

Note: the private qualifier is NOT a solution, as it only allows code from the same class access. I am talking about the need to access fields from other classes in the same library, but preventing access from clients of the library. That is what Java's package access does.

Coerce answered 24/8, 2015 at 17:57 Comment(5)
There is a private modifier that can be used on ES6/TypeScript classes. At compile time, TypeScript gives you an error when trying to access private members, but at runtime they are still accessible. (See typescriptlang.org/Handbook#classes, especially the 'Understanding private' section)Semifluid
Private is not a solution. I've amended the question to explain why.Coerce
OK, so you don't really want private, but more of an internal/package access. As JavaScript wasn't designed that way, what you want to do would require some third-party JavaScript library to enforce access to members at runtime. TypeScript, and any of its typing constraints, do not exist at runtime.Semifluid
I solved this simply by moving the public portion of my API to the root module, and moving all internal members to a sub-module - then configure a separate build for the declaration (.d.ts) file, in which I include only the root module. This way, anything residing in a sub-module ends up being effectively internal. (vice-versa, if your sub-modules are public, move your internal members to a specific sub-module and exclude that from your declaration-build.)Neilson
There's a proposal for an internal modifier - something I'd love to see in the future: github.com/microsoft/TypeScript/issues/5228Aspic
D
31

Since TypeScript 3.1, you can use the stripInternal compiler option. When generating declaration files, this stops declarations being generated for code that has the @internal JSDoc annotation.

This compiler option can be enabled in the tsconfig.json file:

{
    "compilerOptions" : {
        ...
        "stripInternal": true
    }
}

For example, the following declarations will be suppressed:

//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;

However, this will have no effect on code that sees the *.ts source files, such as your own code.

If you distribute your package as a mix of *.js and *.d.ts files, like most NPM packages, this will effectively hide the member from code outside your package. But if you distribute the code as *.ts files, it won’t help you.

Deeannadeeanne answered 2/11, 2018 at 18:45 Comment(3)
--stripInternal is now undocumentedRomanticize
stripInternal is a compiler option that you set in your tsconfig file and it is not undocumented typescriptlang.org/tsconfig#stripInternalBrundisium
@JohnLeidegren It was redocumented actually.Deeannadeeanne
H
3
  • You can mark fields and functions as private using the private modifier keyword.
  • You can use a naming convention, like start all privates with an underscore.
  • If the classes should not be exposed you can also use naming convention (prefix with "Internal" or something. You can also put in the (class) documentation that the class is not intended to be used anywhere outside the framework.

Note that in the end everything in JavaScript is quite public because of the prototyped based language. There are also no actual namespaces. So maybe you should not worry too much. Good documentation always helps.


As a sidenote it might be interesting to know that there are great other transpilers like TypeScript. Haxe for example solves this problem a bit more elegant because of their more OOP oriented language design, where you can grant and request access using metadata on top of the access modifiers. You might find this more convenient coming from Java. Still, in the end everything will be public because of JavaScript.

Herzel answered 24/8, 2015 at 18:6 Comment(0)
S
1

Starting with 1.3, protected is available as an access specifier. That's one of your concerns (and mine!) addressed.

As for limiting the visibility of other members to certain libraries, at least where the TypeScript compiler is concerned, you could create different versions of the declaration files.

Let's say you compile a file using the --declaration switch, producing a .d.ts that file looks like this:

class Something {
    // ...
    public internal_use_only: someType;
    // ...
}

To produce a more restrictive declaration file, simply copy it to another file and delete the line containing internal_use_only.

If you want this automated within a compile/test cycle, you can have a [ task runner | project build utility ] script that invokes the compiler with the --declaration switch and then a text processing utility like grep (found in Linux or Cygwin, which provides Windows with some Un*x commands and shell environment). A grep command invocation to eliminate only the line in question would look like this:

cat something.d.ts | grep -v \\binternal_use_only\\b > moreRestrictiveSomething.d.ts

There are similar commands for other shells, like findstr in Windows/CMD, which also works with Regular Expressions.

Sermonize answered 25/8, 2015 at 2:6 Comment(1)
That's an interesting idea - use the .d.ts file like an interface definition. Generate it based on absence of @private doc comment. It would be more complicated than a grep because internal members are multiline expressions including comments, but doable with a parser.Coerce

© 2022 - 2024 — McMap. All rights reserved.