babel-plugin-transform-private-to-weakmap
v1.0.3
Published
Transforms class properties prefixed with an underscore to weakmaps.
Downloads
12
Maintainers
Readme
Introduction
The goal of this plugin is to add more 'real' privacy to properties in classes.
Sometimes just using underscores isn't enough because using Object.keys()
will
still return them and break some automation logic where private properties end
up being leaked.
This plugin makes use of closure and weakmaps to completely remove any desired properties from a class. The WeakMap holds all the values for all the instances of a class, where the instances are the key and an object containing all the properties is the value.
This is not perfect, but it added just the privacy I needed to my classes while allowing me to continue to write code the same way without bloating it with symbols or weird references.
Acknowledgment
Know that you can still view the private properties by accessing the static
property Classname.private
of your classes, but that means there is less of a chance
of them being leaked than the original method (using underscore prefix) and you would
require the instance itself as a key to access the values anyways.
It also means it's easy to debug the values (just log them in the terminal/console).
Requirements
This plugins works by transforming class properties. That means you need to have the class properties proposal plugin installed, which requires babel 7+.
Packages
| Package | Version | Reason of requirement | | :-- | :---: | :-- | | @babel/core | ^7.1.6 | The minimum required to run babel | | @babel/preset-env | ^7.1.6 | Optional, recommended. Includes most of the nice ESNext features | | @babel/plugin-proposal-class-properties | ^7.1.0 | Adds the class properties syntax support |
npm install --save-dev @babel/core @babel/preset-env @babel/plugin-proposal-class-properties
Installation
This plugin can be installed directly from NPM:
npm install --save-dev babel-plugin-transform-private-to-weakmap
Once it's installed, you can add it to your .babelrc
file:
{
"presets": [ "@babel/preset-env" ],
"plugins": [
"@babel/plugin-proposal-class-properties",
"babel-plugin-transform-private-to-weakmap"
]
}
How to use
Any class properties which are defined with a starting underscore will be transformed. Once weakmap per class is generated.
NOTE: The private properties only require an underscore when they are defined, they do not take any underscore when they are referenced in the code.
Example
class Person {
_firstname;
_lastname;
age;
constructor(first, last, age) {
this.firstname = first;
this.lastname = last;
this.age = age;
}
get name() { return this.firstname + ' ' + this.lastname; }
present() {
console.log(`Hi! I am ${this.name} and I am ${this.age} years old.`);
}
}
let bob = new Person('Bob', 'Holton', 26);
bob.present();
console.log(bob);
The above outputs:
Hi! I am Bob Holton and I am 26 years old.
Person { age: 26 }
Compiled
Here is how the above looks after the transformation:
class Person {
age;
constructor(first, last, age) {
Person.private.set(this, {});
Person.private.get(this).firstname = first;
Person.private.get(this).lastname = last;
this.age = age;
}
get name() {
return Person.private.get(this).firstname
+ ' '
+ Person.private.get(this).lastname;
}
present() {
console.log(`Hi! I am ${this.name} and I am ${this.age} years old.`);
}
}
Person.private = new WeakMap();
let bob = new Person('Bob', 'Holton', 26);
bob.present();
console.log(bob);
Keep in mind
Initializing private properties
You are allowed to initialize your private properties as expected:
Source
class Point {
_x=0; _y=1;
constructor(x=null, y=null) {
if(x !== null) this.x = x;
if(y !== null) this.y = y;
}
}
Output
class Point {
constructor(x=null, y=null) {
Point.private.set(this, {
x: 0,
y: 1
});
if(x !== null) Point.private.get(this).x = x;
if(y !== null) Point.private.get(this).y = y;
}
}
Point.private = new WeakMap();
Classes without a constructor
If your class does not have a constructor, one will automatically be added:
Source
class Test {
_name = 'Test';
test() {
console.log(this.name);
}
}
Output
class Test {
constructor() {
Test.private.set(this, { name: 'Test' });
}
test() {
console.log(Test.private.get(this).name);
}
}
Extending classes without constructor
If your class does not have a constructor and it extends another class, then this transform plugin will not be able to automatically call super() with the proper arguments. In that case, please take the time to add a proper constructor to your classes. This is a known limitation of this plugin.