Class members marked as private in TypeScript remain private only until you compile the code to JavaScript.
During compilation, TypeScript simply removes the private modifier, making these members accessible at JavaScript runtime.
This tool changes this behavior by converting private member names to unique symbols even before TypeScript begins compilation.
Access to such members is only possible through their corresponding symbols, which remain available only to the declaring class.
This ensures true runtime privacy in JavaScript.
Before transformation:
class UserService {
private users: User[] = [];
private nextId: number = 1;
private logOperation(message: string): void {
console.log(message);
}
addUser(user: User) {
this.users.push(user);
this.logOperation('Added user');
}
}After transformation:
const ᛰUserServiceᛰusersᛰ = Symbol('ᛰUserServiceᛰusersᛰ');
const ᛰUserServiceᛰnextIdᛰ = Symbol('ᛰUserServiceᛰnextIdᛰ');
const ᛰUserServiceᛰlogOperationᛰ = Symbol('ᛰUserServiceᛰlogOperationᛰ');
class UserService {
private [ᛰUserServiceᛰusersᛰ]: User[] = [];
private [ᛰUserServiceᛰnextIdᛰ]: number = 1;
[ᛰUserServiceᛰlogOperationᛰ](message: string): void {
console.log(message);
}
addUser(user: User) {
this[ᛰUserServiceᛰusersᛰ].push(user);
this[ᛰUserServiceᛰlogOperationᛰ]('Added user');
}
}npm i -D make-typescript-privacy-better
yarn add -D make-typescript-privacy-better
pnpm add -D make-typescript-privacy-betterThe package provides webpack integration (example below shows configuration for example.ts file located in the src/tests directory):
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: './src/tests/example.ts',
target: 'node',
module: {
rules: [
{
test: /\.ts$/,
use: ['ts-loader', 'make-typescript-privacy-better/webpack'], // use before ts-loader
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.ts', '.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
};- Symbol transformation of private class members:
- Properties
- Methods
- Getters and setters
- Static members
- Constructor declarations
- Automatic detection and replacement of private member usage with generated symbols
- Generated symbol names don't collide with user-defined symbols (thanks to the unique
ᛰcharacter that cannot be typed on a keyboard) - Full TypeScript compatibility
- Bundler integration:
- Webpack
- Rollup
- Vite
- esbuild
- tsc
- Ability to override
tsconfig.jsonfor transformation
- MakeTypescriptPrivacyBetter — Main class responsible for TypeScript code transformation
- Bundle loaders — Loaders that integrate transformation with bundlers (currently only Webpack)
- Integration tests — Usage examples and transformation functionality tests
- build.mjs — Build script for the plugin, loaders, and entire package bundling. Uses
esbuild.
make-typescript-privacy-better/
├── scripts/
├── build.mjs # Package build script. Add build scripts for additional loaders here
├── src/
│ ├── plugin/ # Main TypeScript code transformation logic
│ │ ├── make-typescript-privacy-better.ts # Main TypeScript transformation class
│ ├── tests/ # Usage examples and integration test files
│ ├── webpack/ # Webpack integration
│ │ ├── dist/ # Output directory for webpack integration tests
│ │ ├── loader.ts # Main Webpack loader file
│ │ ├── options.ts # Webpack configuration options
│ │ ├── webpack.config.ts # Webpack configuration for integration tests
├── dist/ # Output directory
├── ... # (other standard files from node-based projects)
- Loader — Intercepts
.tsfiles and passes them for transformation. - Transformation —
MakeTypescriptPrivacyBetterbegins transforming the.tsfile:- Finds all private class members, excluding constructors and skipping
static block declarations. - Creates a unique symbol based on class name and member name. For anonymous classes,
AnonymousClassis used as the class name. - Replaces all private member occurrences with generated symbols -
this.memberbecomesthis[ᛰClassᛰmemberᛰ]. - Replaces the private member declaration (from
private member: Type;toprivate [ᛰClassᛰmemberᛰ]: Type;). - Analyzes the constructor:
- Finds all private member declarations in constructor parameters (
constructor(private member: Type) {}). - Creates a symbol based on class name and member name.
- Replaces all private member occurrences with generated symbols -
this.memberbecomesthis[ᛰClassᛰmemberᛰ]. - Removes the
privatemodifier from constructor parameter declarations. - Adds a private member declaration with the generated symbol (
private [ᛰClassᛰmemberᛰ]: Type;) to the class. - Adds an initialization line in the constructor body (
this[ᛰClassᛰmemberᛰ] = member;).
- Finds all private member declarations in constructor parameters (
- Repeats these steps for all classes in the file.
- Finds all private class members, excluding constructors and skipping
- Returns the transformed code to the Loader, which continues processing the file.