What is @microsoft/dynamicproto-js?
@microsoft/dynamicproto-js is a JavaScript library that allows developers to dynamically define and extend prototype methods for JavaScript classes. This can be particularly useful for creating flexible and maintainable code, especially in scenarios where you need to add or override methods at runtime.
What are @microsoft/dynamicproto-js's main functionalities?
Dynamic Method Definition
This feature allows you to dynamically define methods on a class prototype. In this example, `myMethod` is added to the prototype of `MyClass` at runtime.
const dynamicProto = require('@microsoft/dynamicproto-js');
class MyClass {
constructor() {
dynamicProto(MyClass, this, function (_self) {
_self.myMethod = function () {
console.log('Dynamic method called');
};
});
}
}
const instance = new MyClass();
instance.myMethod(); // Output: Dynamic method called
Method Overriding
This feature allows you to override existing methods in a class. In this example, `myMethod` in `DerivedClass` overrides the method in `BaseClass` and also calls the base method.
const dynamicProto = require('@microsoft/dynamicproto-js');
class BaseClass {
myMethod() {
console.log('Base method called');
}
}
class DerivedClass extends BaseClass {
constructor() {
super();
dynamicProto(DerivedClass, this, function (_self, base) {
_self.myMethod = function () {
console.log('Derived method called');
base.myMethod.call(_self);
};
});
}
}
const instance = new DerivedClass();
instance.myMethod(); // Output: Derived method called
// Base method called
Other packages similar to @microsoft/dynamicproto-js
lodash
Lodash is a modern JavaScript utility library delivering modularity, performance, and extras. While it is not specifically designed for dynamic prototype manipulation, it provides a wide range of utility functions that can help in manipulating objects and their properties.
underscore
Underscore is a JavaScript library that provides a whole mess of useful functional programming helpers without extending any built-in objects. Similar to Lodash, it offers utility functions that can be used to manipulate objects and their prototypes.
mixin-deep
Mixin-deep is a library for deep object merging. It allows you to merge properties of multiple objects into a single object, which can be useful for dynamically adding methods or properties to an object's prototype.
Dynamic Proto JavaScript
Generates dynamic prototype methods for JavaScript objects (classes) by supporting method definition within their "class" constructor (like an instance version), this removes the need to expose internal properties on the instance (this) and the usage of ClassName.prototype.funcName()
both of which result in better code minfication (smaller output) and therefore improved load times for your users.
The dynamically generated prototype methods support class inheritance of any type, which means you can extend from base classes that use instance or prototype defined methods, you also don't need to add the normal boiler plate code to handle detecting, saving and calling any previous instance methods that you are overriding as support for this is provided automatically.
So whether creating a new class or extending some other class/code, your resulting code, can be successfully extended via TypeScript or JavaScript.
Removing / Hiding internal properties from instance
By defining the properties / methods within the constructors closure, each instance can contain or define internal state in the form of properties which it does not have to expose publically as each defined "public" instance method has direct access to this define state within the context/scope of the closure method.
While this does require some additional CPU and memory at the point of creating each instance object this is designed to be as minimal as possible and should be outwayed by the following advantages :-
- Avoids polluting the instance (this) namespace with internal values that can cause issues with inheritence for base/super classes or even derived classes that extend your class.
- Smaller code as the internal properties and methods when defined within the instance can be minified.
- As the resulting generated code can be better minified this should result in a smaller minified result and therefore better load times for your users.
When to use
While this helper was originally created to support better minification for generated code via TypeScript, it is not limited to only being used from within TypeScript.
And as with including any additional code into your project there is a trade off that you need to make before using this helper which is the size of the additional code of this utility vs the minification gains that may be obtained. As in most cases of creating JavaScript code for better minfication if your code doesn't expose or provide a lot of public methods or only uses un-minifiable "names" less than 2 times then the potential gains may not be worth the additional bytes.
And yes at the end of the day, if you are creating JS classes directly in Javascript you should be able to create a simplier one-off solution that would result in smaller output as this project needs to be generic to be able to support all use-cases.
Basic Usage
import dynamicProto from "@microsoft/dynamicproto-js";
class ExampleClass extends BaseClass {
constructor() {
dynamicProto(ExampleClass, this, (_self, base) => {
_self.newFunc = () => {
if (_self.someProperty) {
...
}
}
_self.myFunction = () => {
if (_self.someProperty) {
base.myFunction();
}
...
}
_self.initialize = () => {
...
}
_self.initialize();
});
}
}
Build & Test this repo
-
Install all dependencies
npm install
npm install -g @microsoft/rush
-
Navigate to the root folder and update rush dependencies
rush update
-
Build, lint, create docs and run tests
rush build
npm run test
If you are changing package versions or adding/removing any package dependencies, run
rush update --purge --recheck --full
before building. Please check-in any files that change under common\ folder.
Performance
The minified version of this adds a negligible amount of code and loadtime to your source code and by using this library your generated code can be better minified as it removes most references of Classname.prototype.XXX methods from the generated code.
Summary:
- ~2 KB minified (uncompressed)
Browser Support
| | | | | |
---|
Latest ✔ | Latest ✔ | 8+ Full ✔ | Latest ✔ | Latest ✔ | Latest ✔ |
Contributing
Read our contributing guide to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to Application Insights.
ES3/IE8 Compatibility
As an library there are numerous users which cannot control the browsers that their customers use. As such we need to ensure that this library continues to "work" and does not break the JS execution when loaded by an older browser. While it would be ideal to just not support IE8 and older generation (ES3) browsers there are numerous large customers/users that continue to require pages to "work" and as noted they may or cannot control which browser that their end users choose to use.
As part of enabling ES3/IE8 support we have set the tsconfig.json
to ES3 and uglify
settings in rollup.config.js
transformations to support ie8. This provides a first level of support which blocks anyone from adding unsupported ES3 features to the code and enables the generated javascript to be validily parsed in an ES3+ environment.
Ensuring that the generated code is compatible with ES3 is only the first step, JS parsers will still parse the code when an unsupport core function is used, it will just fail or throw an exception at runtime. Therefore, we also need to require/use polyfil implementations or helper functions to handle those scenarios.
ES3/IE8 Features, Solutions, Workarounds and Polyfil style helper functions
This table does not attempt to include ALL of the ES3 unsuported features, just the currently known functions that where being used at the time or writing. You are welcome to contribute to provide additional helpers, workarounds or documentation of values that should not be used.
Feature | Description | Usage |
---|
Object.keys() | Not provided by ES3 and not used | N/A |
ES5+ getters/setters
Object.defineProperty(...) | Not provided by ES3 and not used | N/A |
Object.create(protoObj, [descriptorSet]?) | Not provided by ES3 and not used | N/A |
Object.defineProperties() | Not provided by ES3 and not used | N/A |
Object.getOwnPropertyNames(obj) | Not provided by ES3 and not used | N/A |
Object.getPrototypeOf(obj) | Not provided by ES3 and not used | _getObjProto(target:any) |
Object.getOwnPropertyDescriptor(obj) | Not provided by ES3 and not used | N/A |
Object.preventExtensions(obj) | Not provided by ES3 and not used | N/A |
Object.isExtensible(obj) | Not provided by ES3 and not used | N/A |
Object.seal(obj) | Not provided by ES3 and not used | N/A |
Object.isSealed(obj) | Not provided by ES3 and not used | N/A |
Object.freeze(obj) | Not provided by ES3 and not used | N/A |
Object.isFrozen(obj) | Not provided by ES3 and not used | N/A |