Created
September 24, 2024 11:18
-
-
Save jwulf/6e7b093b5b7b3e12c7b76f55b9e4be84 to your computer and use it in GitHub Desktop.
A difference in behavior between Node 22's strip types and TypeScript transpilation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This code behaves differently when run with: | |
* | |
* node --experimental-strip-types test.ts | |
* | |
* tsc --lib es2017,dom test.ts && node test.js | |
* | |
* This is an edge-case where mixing dynamic programming and static typing reveals a difference in behavior. | |
* | |
*/ | |
export class LosslessDto { | |
constructor(obj?: any) { | |
if (obj) { | |
for (const [key, value] of Object.entries(obj)) { | |
(this as any)[key] = value | |
} | |
} | |
} | |
} | |
class V extends LosslessDto { | |
orderId!: string | |
customerId!: string | |
paymentStatus!: "unpaid" | "paid" | |
constructor(data: V) { | |
super(data) | |
} | |
} | |
const v = new V({ | |
orderId: '123', | |
customerId: '456', | |
paymentStatus: 'paid', | |
}) | |
/** | |
* When run with Node 22 strip types, all properties of v are undefined. | |
* | |
* When transpiled, they are defined. | |
*/ | |
console.log('v', v); | |
/** | |
* The cause of this is the property definitions of the class V. These are provided in TypeScript to create strong typing for the constructor parameter. | |
* Particularly, you can specify whether they are optional or required with the ? and ! operators. | |
* | |
* However, when these operators are stripped the property names are left, and they act as property *initializers* in JS, and they are | |
* applied *after* the call to `super` in the constructor. | |
* | |
* This means that the properties are initialized to `undefined` *after* the super constructor dynamically assigns them with the provided values. | |
* | |
* In the transpiled code, since nothing is assigned to these properties, they are stripped out of the class definition, and the super constructor | |
* is able to set them without them being overwritten. | |
*/ | |
And here is the transpiled code:
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var LosslessDto = /** @class */ (function () {
function LosslessDto(obj) {
if (obj) {
for (var _i = 0, _a = Object.entries(obj); _i < _a.length; _i++) {
var _b = _a[_i], key = _b[0], value = _b[1];
this[key] = value;
}
}
}
return LosslessDto;
}());
var V = /** @class */ (function (_super) {
__extends(V, _super);
function V(data) {
return _super.call(this, data) || this;
}
return V;
}(LosslessDto));
var v = new V({
orderId: '123',
customerId: '456',
paymentStatus: 'paid',
});
console.log('v', v);
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here it is with stripped types: