Thursday, March 14, 2019

Release 0.2.9: ES6 Support


This release mainly introduces ES6 support as well as improvements to the serialization helpers. A new flavor is created for Node.js users.

Release 0.2.9: ES6 Support

Release content

ES6 support

While working on a side project which is based on Node.js, I realized that the library did not support ES6 classes. Not only the gpf.define API was not able to extend any of them (even if it does not really make sense) but it was also impossible to add attributes to a class that was not previously created with gpf.define (which is more problematic).

After doing a quick test, a solution was drafted to detect and handle these classes the right way. It is all explained in the article How I learned from a crazy idea.

The $singleton and $abstract syntaxes were adapted accordingly.

It is clearly not recommended to extend ES6 class using gpf.define.

In order to integrate attributes properly, a quick look in the coming ES6 features pointed out the fact that decorators are used to qualify class members. Hence, an attribute decorator was created.

Last but not least, since decorators are not yet supported without transpiling, the library allows preprocessing of resources so that decorators can be substituted with manual call of the decorator.

This was also the opportunity to refactor in depth the validation of the require configuration options.

Improved serialization

The side project is extensively using serialization attributes. Quickly, the need for code simplification became obvious.

First, it does not make sense to repeat the property name when it can be easily deduced from the member the attribute is assigned to.

When set on a 'private' member, the result property name won't include the underscore.

Then, these attributes are used in a context where serialization is used to implement an ODATA service. Consequently, they are used to describe how the data should be sent back the client but also how it is received.

For instance, an entity unique identifier must be transmitted to the client but it will never be modified by the client.

With the introduction of the readOnly property, it is possible to make this distinction and have asymmetric serialization.

But, as for names, it does not make sense to repeat something that is already built in the class. Indeed, with the use Object.defineProperty - or using ES6 class getter / setter - it is possible to define the (get, set) couple and, when not setter exists, configure read-only members.

That's why, when the hosts supports it, the serialization code will leverage Object.getOwnPropertyDescriptor recursively on the class hierarchy to determine if the member is read-only.

Improved compatibility

Browser's base64 helpers (atob and btoa) were added to the compatibility layer.

The Function.prototype.compatibleName method has been removed since it induced an extension of the Function prototype. Usually, libraries should avoid doing that because it is against best practices.

Because of the mocking implementation, the gpf.http.request was limited in terms of which http methods could be used. Some hosts do not support custom verbs (and this is documented in the compatibility page) but browsers & node supports almost any verb. The code was modified to allow the use of custom verbs.

Surprisingly, the String method .substr is documented as "to be avoided when possible". Since it was massively used in the sources, an ESLint custom rule was developed and the code reworked.

New flavor node

A Node.js flavor was created and is used as the default library being loaded when using require("gpf-js")

It implements all features including the compatibility's atob and btoa.

Lessons learned

asymmetric Serialization

The user story asymmetric serialization required several updates since it was pretty difficult to find the right balance between simplicity of use and flexibility. In the end, this feature is really powerful when applied with a converter function. Indeed, this is the place where one can control if the value will be serialized or not.

Refactoring of classes

Integrating ES6 classes was only the visible part of the challenging iceberg. Actually, the library was suffering from a structural defect regarding how classes were handled.

Initially, each class was associated to a class definition created only when using gpf.define. This object holds important information such as the list of attributes.

When subclassing, the parent class definition was looked up by searching the one that matches the condition instanceBuilder.prototype instanceof entityDefinition.getInstanceBuilder() (see full code)

As a result, you could have classes in the hierarchy that were invisible because not created with the library.

To solve this issue, a new code was put in place to import any class as well as its hierarchy up to the root class (i.e. Object). It also means that base classes are now associated with a class definition during the startup of the library.

This also implies that the library may have to deal with anonymous functions when importing a class.

It is still not possible to use gpf.define without a class name but, internally, the library can import any class.

Refactoring of tests

Introduction of ES6 in the library had a significant impact on how the tests are executed.

Indeed, it is mandatory to check if the host is really supporting the ES6 class syntax before trying to create one.

So a new algorithm was built to:

  • detect features (with the possibility to override them, like for nodewscript), result is transmitted in a global

object available during the tests

  • include test files dynamically

Improved flavor mechanism

Writing the Node.js flavor was harder than expected. The main struggle came from the inclusion of base64 helpers without getting the whole compatibility layer. Furthermore, without the compatibility layer, the compatibleName function member was no more available. This broke the code at many places. That's why it was decided to replace it with an internal helper to extract the function name where needed (it points to the name property by default).

Also, a flavor debugging page was created to ensure that any update on the flavor algorithm would fit the expectations.

New eslint rules

As mentioned before, a custom eslint rule was created to forbid the use of .substr: no-substr.

The same way, another rule was created to ensure that when a module has no function, a default one is being created: no-empty-modules.

One weakness of plato is the evaluation of a module with no function

As the linters are applied every time a module is modified, more custom rules will be created to solve common problems (such as dependencies update).

Release notes

Today, there are more than 14 releases for the library. It takes some time to access the release notes since one has to go to the release information in GitHub in order to find them.

It was decided to change the readme to embed a direct link to each note.

However, regarding the last version, its notes are usually written after the release was created. A page was built to redirect the reader when the notes are out.

Next release

The next release content is about performances. It's been a while I wanted to manipulate the release code to inline functions as much as possible and substitute loops for performances.

Still, I need to work on the side project because it really requires all my attention.