How to Upgrade a DoneJS Plugin to CanJS 3

Chasen Le Hara by Chasen Le Hara

How to Upgrade a DoneJS Plugin to CanJS 3

Chasen Le Hara This article will show you how to upgrade an existing DoneJS plugin to CanJS 3.

posted in Open Source ,Development ,CanJS on December 21, 2016 by Chasen Le Hara

CanJS 3 has been released! It’s a major new version with some breaking changes, but our migration guide has everything you need to upgrade your app or plugin today.

The migration guide breaks the upgrade process up into five steps:

  1. Pre-migration preparation you can do in your current 2.x project to more easily move to 3.x in the future.
  2. The minimal migration path, which includes the fewest changes required to upgrade from 2.x to 3.x.
  3. The modernized migration path, which includes upgrading your code to match more modern conventions (such as using the new npm packages).
  4. The latest & greatest migration path, which uses all of the modern libraries we are most excited about (such as can-define).
  5. How to avoid future deprecations and removals in releases after 3.x

In a previous blog post, we made a reusable DoneJS plugin. It already follows the best practices from CanJS 2.3 and doesn’t use any features that might be deprecated or removed in the future, so in this post, we’re going to follow the middle three upgrade steps to make our plugin ready for the future.

Minimal migration path

First, we’re going to run the following command to install the latest version of CanJS and save it to our package.json:

npm install can --save

This will change our package.json to use something like ^3.3.0 instead of ^2.3.0.

Next, we’ll install steal-stache so our templates can be imported.

npm install steal-stache --save

This will add steal-stache to our package.json.

Now, when we run npm test, we’ll see all five tests passing. Success!

Those two steps really are the only things we need to do for our plugin. Depending on which CanJS 2.3 features your plugin uses, you might need to follow some of the other steps in the migration guide.

Modernized migration path

You can set your project up for easier upgrades in the future by using the independent CanJS modules (instead of the main can package).

First, we’ll need to search for any uses of the can package. We can do this pretty easily with grep:

grep --exclude-dir={dist,node_modules} -R "can/" .

This returns the following:

./src/donejs-number-input.html:        main="can/view/autorender/">
./src/donejs-number-input.js:import Component from 'can/component/';
./src/donejs-number-input.js:import Map from 'can/map/';
./src/donejs-number-input.js:import 'can/map/define/';

The migration guide lists all of the 2.3 module paths that can be replaced with new modules.

Before After
can/component/ can-component
can/map/ can-map
can/map/define/ can-map-define
can/view/autorender/ can-view-autorender

This tells us which modules we need to install:

npm install can-component can-map can-map-define can-view-autorender --save

Now we can replace the first three lines in src/donejs-number-input.js:

import Component from 'can/component/';
import Map from 'can/map/';
import 'can/map/define/';

With:

import Component from 'can-component';
import Map from 'can-map';
import 'can-map-define';

Similarly, in donejs-number-input.html, we’ll replace:

<script src="../node_modules/steal/steal.js"
        main="can/view/autorender/"></script>

With:

<script src="../node_modules/steal/steal.js"
        main="can-view-autorender"></script>

Now we can uninstall the main can package:

npm uninstall can --save

Now, when we run npm test, we’ll see all five tests passing. Success!

Again, the migration guide has additional steps you can follow, depending on which CanJS 2.3 features your plugin uses.

Latest & greatest migration path

Our upgrade looks great so far, but we can make our plugin take advantage of all of CanJS 3’s awesomeness by moving from the legacy can-map module to can-define.

First, let’s install can-define:

npm install can-define --save

Next, we’re going to start modifying the src/donejs-number-input.js file.

Let’s start by replacing these lines:

import Map from 'can-map';
import 'can-map-define';

With:

import DefineMap from 'can-define/map/';

You’ll notice that we went from two lines down to one; can-define/map/ has the define functionality we want built in.

Next, let’s update our view model to use the new module, move the properties out of the define property, and drop our use of attr. Replace:

export const ViewModel = Map.extend({
  define: {
    value: {
      value: 0,
      type: 'number',
      set(value) {
        if(value > this.attr('max')) {
          return this.attr('max');
        }
        
        if(value < this.attr('min')) {
          return this.attr('min');
        }
        
        return value;
      }
    },
    max: {
      value: Number.MAX_VALUE,
      type: 'number'
    },
    min: {
		value: 0,
		type: 'number'
	}
  },
  
  increment() {
    this.attr('value', this.attr('value') + 1);
  },
	
  decrement() {
    this.attr('value', this.attr('value') - 1);
  }
});

With:

export const ViewModel = DefineMap.extend({
  value: {
    value: 0,
    type: 'number',
    set(value) {
      if(value > this.max) {
        return this.max;
      }

      if(value < this.min) {
        return this.min;
      }

      return value;
    }
  },
  max: {
    value: Number.MAX_VALUE,
    type: 'number'
  },
  min: {
    value: 0,
    type: 'number'
  },

  increment() {
    this.value = this.value + 1;
  },

  decrement() {
    this.value = this.value - 1;
  }
});

There might be other uses of `.attr()` in our code, so let’s use grep again to find them:

grep --exclude-dir={dist,node_modules} -R ".attr(" .

This returns the following:

./src/donejs-number-input_test.js:  QUnit.equal(vm.attr('value'), 0, 'Default value is 0');
./src/donejs-number-input_test.js:  QUnit.equal(vm.attr('max'), Number.MAX_VALUE, 'Max value is number max value');
./src/donejs-number-input_test.js:	QUnit.equal(vm.attr('min'), 0, 'Max value is number max value');
./src/donejs-number-input_test.js:  QUnit.equal(vm.attr('value'), 1, 'Value incremented');
./src/donejs-number-input_test.js:  QUnit.equal(vm.attr('value'), 1, 'Value updated');

We’ll need to make these replacements:

Before After
vm.attr('max') vm.max
vm.attr('min') vm.min
vm.attr('value') vm.value

After those replacements have been made, we can uninstall the `can-map` and `can-map-define` packages:

npm uninstall can-map can-map-define --save

Now, when we run npm test, we’ll see all five tests passing. Success!

Depending on which CanJS features you’re using, there are more steps you can take to take advantage of everything CanJS 3 has to offer.

Migrate today

The migration guide is a great resource for moving your DoneJS plugin to CanJS 3 today. In it, you’ll find all of the information that was used in this blog post, and more!

You can also see the commits for each step of upgrading our example plugin:

  1. Minimal migration path
  2. Modernized migration path
  3. Latest & greatest migration path

If you have any questions about migrating, please post in our forums or Gitter chat!

Create better web applications. We’ll help. Let’s work together.