Migrate YUI components

I work on an application that uses quite a lot of Javascript components based on the YUI framework, which has been obsolete since around 2014. We recently had the budget to start migrating them to plain Javascript.

How YUI widgets work

Our YUI components use the syntax of YUI3. Sometimes they also use widgets from YUI2 : these have to be rebuilt, or migrated to use another library.

A YUI 3 component is structured as follows:

The component is called this way:

Migration

Migrating the components implies a lot of steps. The first and easiest, is to replace the YUI methods inside with native or equivalent methods. As a side note, we’re using JQuery in the application : much easier than native JS for querying DOM, creating elements, making ajax calls, etc.

The Y.use('foo', function (X) {}) can be removed, and calls to X.foo.bar() will be replaced with calls to the migrated “foo” component. Warning: existing return inside the Y.use were just breaking out of the use, they now break out of the wrapping method. doc

Y.JSON becomes just JSON. doc

Y.log becomes console.log, or more often nothing. doc

Y.each(foo, function (bar) can be replaced with $.each(foo, function (index, bar), or just for (let bar in foo) in native JS ; take care, Y.each takes a second parameter for the execution context, but not $.each : calls to this inside the Y.each will have to be replaced. doc YUI ; doc Jquery

Y.UA can be switched to equivalents with UAParser ; that said, try not to use it at all if you can. doc

Y.Cookie can be remplaced by equivalents using js-cookie (managing cookies in JS is a pain) ; it so happens that get, set and remove methods exist in both librairies, so it’s often just replacing Y.Cookie with Cookies. doc

Y.Lang.isValue is actually pretty useful, and I chose to copy its source. doc ; source

Y.Lang.isArray is replaced with Array.isArray.

Y.QueryString can be replaced with URLSearchParams : const urlParams = new URLSearchParams(location.search);

AJAX calls

Y.io (doc) is replaced with $.ajax (doc) which works similarly, but the signature differences are pretty important.

If you need blocking calls, sync: true becomes async: false ; but if you can, making async calls with a callback is a better idea.

The object parameter { on: { success: function(id, xhr, args) {}, failure: function(id, xhr, args) {}, context: this } } “goes up a level”, context is removed, and failure is renamed error : { success: function(data, status, xhr) {}, error: function(xhr, status, error) {} }.

You can replace headers: { 'Content-Type': 'application/json' } with contentType: 'application/json' but I think both work.

You can also remplace the success/error methods with promises, for instance Y.io('foo', { success: function() {} }) becomes $.ajax('foo').done(function() { }).

In the method success/done :

  • the signature goes from id, xhr, args to data, status, xhr
  • the data object can be used directly, rather than parsing xhr: if it’s JSON, it’s already been parsed, you need to remove JSON.parse(xhr.responseText).

The method error/fail also changes signature (from id, xhr to xhr, status, errorThrown).

Node operations

The base YUI selector Y.one(foo) becomes $(foo)doc YUI ; doc Jquery

All variable usage will then have to be modified:

  • YUI returns null if no element is found, whereas JQuery returns an object with a zero length property: you’ll have to change your tests on empty values.
  • .appendChild becomes.append
  • .one becomes.find
  • Y.Node.create('foo') becomes $('<foo></foo>')
  • .set('text', x) becomes .text(x)
  • .set('innerHTML', x) becomes .html(x)

Take especially care if the DOM object is exposed by the component : other parts of your application will have to be changed, too.

Final template

Here is the template we’re now using to create JS components/widgets.