Security News
Node.js EOL Versions CVE Dubbed the "Worst CVE of the Year" by Security Experts
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
aurelia-class-enhancements
Advanced tools
Enhance your Aurelia's classes with high order functionality
Enhance your Aurelia's classes with high order functionality
If you are wondering why I built this, go to the Motivation section.
Here's a really basic example of what you can achieve using this library:
// myComponent.js
import enhance from 'aurelia-class-enhancements';
import LogStatus from './logStatus';
@enhance(LogStatus)
class MyComponent {}
export { MyComponent };
// logStatus.js
class LogStatus {
attached() {
console.log('attached :D!');
}
detached() {
console.log('detached D:!');
}
}
When your component gets attached to the DOM (or gets detached), via the enhancement class LogStatus
, it will log a message on the console; all of this without having to write the functionality on your component class and without needing class inheritance.
As you saw on the example above, the way it works is pretty straightforward:
enhance
decorator.When instantiating an enhancement, the library sends to a reference of the target class instance to the enhancement constructor, so you can access methods and properties on your custom methods.
Let's say you have a component that renders a form where the user saves some important information, and if the user were to leave the route without saving the information, you would want to use a prompt to ask for confirmation.
A basic approach would be to have a flag indicating if the changes are saved and then use the canDeactivate
lifecycle method to decide whether to show the prompt or not:
class MyForm {
isSaved = false;
...
canDeactivate() {
// If everything is saved, go away.
if (isSaved) {
return true;
}
// Let's make the prompt async.
return new Promise((resolve) => {
// ask for the user confirmation.
const answer = confirm('Confirm that you want to leave without saving');
// resolve the promise with the answer.
resolve(answer);
});
}
}
export { MyForm };
It works, is simple and you didn't have to involve any external logic. But now, what if you have to implement this same logic on four more forms? That's when an enhancement is useful.
Let's write an enhancement that uses the reference for the ViewModel in order to verify if the user needs to be prompted.
class FormConfirmation {
constructor(viewModel) {
this._viewModel = viewModel;
}
canDeactivate() {
if (this._viewModel.isSaved) {
return true;
}
return new Promise((resolve) => {
const answer = confirm('Confirm that you want to leave without saving');
resolve(answer);
});
}
}
It's the same functionality, but it now checks the isSaved
property form the ViewModel.
In this case it was really easy because the property is a
boolean
and theif
checks with falsy, but we could've check if the property was defined and use a different default, like "if the ViewModel doesn't have the property, the user can always leave without confirmation".
Ok, let's add it to the form:
import enhance from 'aurelia-class-enhancements';
import { FormConfirmation } from '...';
@enhance(FormConfirmation)
class MyForm {
isSaved = false;
...
}
export { MyForm };
And that's all, you can now add it to the other four forms using the enhancement.
Just like on any other class you use in the Aurelia context, you can use the @inject
decorator on an enhancement in order to inject dependencies.
We'll use the enhancement from the first example and trigger an event before the log messages.
import { inject } from 'aurelia-framework';
import { EventAggregator } from 'aurelia-event-aggregator';
@inject(EventAggregator);
class LogStatus {
constructor(viewModel, ea) {
this._viewModel = viewModel;
this._ea = ea;
}
attached() {
this._ea.publish('something:attached', this._viewModel);
console.log('attached :D!');
}
detached() {
this._ea.publish('something:detached', this._viewModel);
console.log('detached D:!');
}
}
Since the library was made to enhance any kind of class, that means that you could also enhance an enhancement class.
Let's take the example about dependency injection and move the events part to another enhancement:
import { inject } from 'aurelia-framework';
import { EventAggregator } from 'aurelia-event-aggregator';
@inject(EventAggregator);
class PublishStatus {
constructor(viewModel, ea) {
this._viewModel = viewModel;
this._ea = ea;
}
attached() {
this._ea.publish('something:attached', this._viewModel);
}
detached() {
this._ea.publish('something:detached', this._viewModel);
}
}
export { PublishStatus };
Now we can create an enhanced LogStatus
with PublishStatus
:
import enhance from 'aurelia-class-enhancements';
import { PublishStatus } from '...';
@enhance(PublishStatus)
class LogStatus {
attached() {
console.log('attached :D!');
}
detached() {
console.log('detached D:!');
}
}
That was just to prove the point that you can enhance an enhancement, but there are two other, and simpler, ways in which you can achieve the same result:
Decorators are just functions, in this case, a function that returns a function:
enhance(...Enhancements)(TargetClass): Proxy<TargetClass>
So, instead of enhancing LogStatus
with PublishStatus
, we can create a new enhancement with both of them:
import enhance from 'aurelia-class-enhancements';
import { LogStatus } from '...';
import { PublishStatus } from '...';
export const PublishAndLogStatus = enhance(PublishStatus)(LogStatus);
The enhance
decorator supports multiple enhancements as parameters, so we could just send LogStatus
and then PublishStatus
to the class with want to enhance and the result would be the same:
import enhance from 'aurelia-class-enhancements';
import { LogStatus } from '...';
import { PublishStatus } from '...';
@enhance(LogStatus, PublishStatus)
class MyComponent {}
Let's say we have this enhancement:
class FormConfirmation {
constructor(viewModel) {
this._viewModel = viewModel;
}
canDeactivate() {
if (this._viewModel.isSaved) {
return true;
}
return new Promise((resolve) => {
const answer = confirm('Confirm that you want to leave without saving');
resolve(answer);
});
}
}
But on the ViewModel, you want to add some other functionality that also needs to run on the canDeactivate
, but only if the enhanced method returned false
.
Modifying the signature of the enhanced method to add an extra parameter wasn't an option, as it could end up messing up methods with optional parameters.
The easiest way to solve this was adding a lifecycle method the library will call, if defined, with whatever was returned from the enhanced method.
The name of the method is dynamically generated based on the name of the original method: enhanced[OriginalMethodName]Return
(even if the method name starts with lowercase, the library will make the first letter uppercase).
This is its signature:
enhancedCanDeactivateReturn(value, enhancementInstance): void
All files are written using commonjs, as I targeted the oldest Node LTS, and it doesn't support modules (without a flag) yet, but you can still use it with ESM.
When the package gets published, an ESM version is generated on the path /esm
. If you are using the latest version of Node, or a module bundler (like projext :D), instead of requiring from the package's root path, you should do it from the /esm
sub path:
// commonjs
const enhance = require('aurelia-class-enhancements');
// ESM
import enhance from 'aurelia-class-enhancements/esm';
Since the next LTS to become "the oldest" is 12, which still uses the flag, I still have no plans on going with ESM by default.
Task | Description |
---|---|
test | Run the project unit tests. |
lint | Lint the modified files. |
lint:all | Lint the entire project code. |
docs | Generate the project documentation. |
todo | List all the pending to-do's. |
I use husky
to automatically install the repository hooks so...
I use conventional commits with commitizen
in order to support semantic releases. The one that sets it up is actually husky, it installs a script that runs commitizen on the git commit
command.
The hook for this is on ./utils/hooks/prepare-commit-msg
and the configuration for comitizen is on the config.commitizen
property of the package.json
.
I use semantic-release
and a GitHub action to automatically release on NPM everything that gets merged to main.
The configuration for semantic-release
is on ./releaserc
and the workflow for the release is on ./.github/workflow/release.yml
.
I use Jest to test the project.
The configuration file is on ./.jestrc.js
, the tests are on ./tests
and the script that runs it is on ./utils/scripts/test
.
I use ESlint with my own custom configuration to validate all the JS code. The configuration file for the project code is on ./.eslintrc
and the one for the tests is on ./tests/.eslintrc
. There's also an ./.eslintignore
to exclude some files on the process. The script that runs it is on ./utils/scripts/lint
.
I use JSDoc to generate an HTML documentation site for the project.
The configuration file is on ./.jsdoc.js
and the script that runs it is on ./utils/scripts/docs
.
I use @todo
comments to write all the pending improvements and fixes, and Leasot to generate a report. The script that runs it is on ./utils/scripts/todo
.
I put this at the end because no one usually reads it :P.
The example about prompting the user when there's a form unsaved was a real case requirement for me, and while discussing which would be the best approach to implement it, there were three conclusions:
So, I wanted something similar to React's HOCs, but for classes, and a little bit closer to a HOF (function), since there's no JSX on Aurelia.
Lately, I've been playing around with Proxies on some of my other libraries, and they are really powerful; so I thought I could use a proxy on top of a class and the only complicated part would be to solve the dependency injection, as I wanted the enhancements to be able to access other services.
I got a prototype working and that's when I realized that Aurelia makes "heavy use" of decorators, so instead of having a function to enhance the class before exporting it (like the React approach), I could "decorate the class":
@enhance(MyEnhancement)
class MyComponent {}
Yay :D!
FAQs
Enhance your Aurelia's classes with high order functionality
We found that aurelia-class-enhancements demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
Security News
cURL and Go security teams are publicly rejecting CVSS as flawed for assessing vulnerabilities and are calling for more accurate, context-aware approaches.
Security News
Bun 1.2 enhances its JavaScript runtime with 90% Node.js compatibility, built-in S3 and Postgres support, HTML Imports, and faster, cloud-first performance.