babel-plugin-transform-private-to-weakmap
Version:
Transforms class properties prefixed with an underscore to weakmaps.
194 lines (154 loc) • 4.85 kB
Markdown
## 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:
```json
{
"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
```js
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:
```js
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
```js
class Point {
_x=0; _y=1;
constructor(x=null, y=null) {
if(x !== null) this.x = x;
if(y !== null) this.y = y;
}
}
```
##### Output
```js
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
```js
class Test {
_name = 'Test';
test() {
console.log(this.name);
}
}
```
##### Output
```js
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.