Migrating to CanJS 3
This guide walks you through the step-by-step process to upgrade a 2.x app to CanJS 3.
CanJS 3 introduces an even more modular project structure and several new features, while having a minimal number of deprecations and removals from the 2.3 API.
Use this guide alone or with the Using Codemods guide, which shows you how to use the can-migrate CLI code refactoring tool to automate parts of the migration process.
This guide goes over:
- Why you should upgrade
- Pre-migration preparation you can do in your current 2.x project to more easily move to 3.x in the future.
- The minimal migration path, which includes the fewest changes required to upgrade from 2.x to 3.x.
- The modernized migration path, which includes upgrading your code to match more modern conventions (such as using the new npm packages).
- The latest & greatest migration path, which uses all of the modern libraries we are most excited about (such as can-define).
- How to avoid future deprecations & removals in releases after 3.x
Why Upgrade
- Security: XSS vulnerability fixes in v3.3.1 and v3.14.0.
- can-stache-bindings has new intuitive syntaxes for event, one-way bindings, and two-way bindings:
on:event="key()"
for event binding.prop:from="key"
for one-way parent-to-child binding.prop:to="key"
for one-way child-to-parent binding.prop:bind="key"
for two-way binding.
- can-define/map/map is faster, removes the need for writing
.attr()
, and allows nice syntax conventions like getters and will give warnings when setting properties that aren't defined. - Support for real-time with can-connect’s real-time module.
- Much improved API docs.
- More guides/recipes.
- {{debugger}} stache helper will break at the given point in the template so you can inspect the current scope in the browser’s console.
- Named inline partials (define reusable partials inside your templates).
- Support for slots & templates via
<can-slot>
and<can-template>
. - can-stache-converters for two-way binding with form elements.
- Control whitespace in stache templates.
- New validation packages: can-validate and can-validate-validatejs.
- Support for FeathersJS with can-connect-feathers.
- NDJSON support: can-connect-ndjson and can-ndjson-stream.
- No dependency on jQuery/Mootools/Dojo/etc. Integration with jQuery is still as easy as ever.
- Hundreds of other bug fixes and new features.
Pre-migration preparation
Before upgrading your project from 2.x to 3.x, make sure your project builds successfully and all the tests pass.
Additionally, you can take the following steps in your CanJS 2.x app to prepare it for migrating to CanJS 3.
Use the module folders
You can start importing CanJS code in a modular way before moving to CanJS 3.
For example, you might be using can-component like this:
import can from 'can';
can.Component.extend({ /* ... */ });
Update your code to instead look like this:
import Component from 'can/component/component';
Component.extend({ /* ... */ });
Use the same pattern for the other modules you are using. Be careful when declaring names for imported modules that share a similar name to native objects like Map.
Instead of:
import Map from 'can/map/map'; // this local declaration of Map will collide with ECMAScript2015 [Map](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map)
Write:
import CanMap from 'can/map/map';
Here’s a list of all the can.
properties in CanJS 2.3 that can be replaced with modular paths:
can.autorender
—can/view/autorender/autorender
can.batch
—can/event/batch/batch
can.bindings
—can/view/bindings/bindings
can.Component
—can/component/component
can.compute
—can/compute/compute
can.Construct
—can/construct/construct
can.Control
—can/control/control
can.Deferred
—can/util/deferred
can.define
—can/map/define
can.deparam
—can/util/string/deparam/deparam
can.ejs
—can/view/ejs/ejs
can.event
—can/event/event
can.fixture
—can/util/fixture
can.isFunction
—can/util/js/is-function/is-function
can.LazyMap
—can/map/lazy/lazy
can.List
—can/list/list
can.Map
—can/map/map
can.Model
—can/model/model
can.Model.Cached
—can/model/cached/cached
can.mustache
—can/view/mustache/mustache
can.Object
—can/util/object/object
can.route
—can/route/route
can.stache
—can/view/stache/stache
can.util
—can/util/util
can.view.callbacks
—can/view/callbacks/callbacks
Replace uses of can.$
can.$
allows you to access the underlying DOM library bundled with CanJS; for example, jQuery in can.jquery.js
.
You might be using it in your code to easily reference the library:
import can from 'can';
const body = can.$('body');
Update your code to explicitly require the library on which you depend. For example:
import $ from 'jquery';
const body = $('body');
Set leakScope
in components
CanJS 2.2 introduced leakScope: false as a property on a can-component. This prevents values in parent templates from leaking into your component’s template. In CanJS 3, leakScope is now false
by default.
If you have a template like:
<some-component></some-component>
That you render with a map containing a page
property like so:
render(new Map({
page: "home"
}));
This page
property is not available within some-component
’s own template.
If the component’s template looks like:
<h1>Hello {{page}}</h1>
It can only lookup the page
property on some-component
’s own ViewModel. To restore the behavior in 2.x, simply set leakScope to be true
on the component:
Component.extend({
tag: "some-component",
ViewModel: { /* ... */ },
leakScope: true
});
Minimal migration path
If you are already using can
through npm, simply run the following command to install the latest version:
npm install can@3 --save
This will update your package.json
to look something like this:
{
// ...
"dependencies": {
"can": "^5.33.3"
}
}
The ^
ensures you get minor and patch releases as those are released.
At a minimum, to upgrade your code for CanJS 3, you must make all of the following changes to your code:
Use can/legacy
In your code where you normally would import can
, instead import can/legacy
:
import can from 'can/legacy';
This will give you a can
object with most of the same APIs as in 2.3, with a few exceptions:
- can.mustache is not included with
can/legacy
, but it can still be installed as a separate package. - The former
can.view
functionality no longer exists; see below for more details.
Asynchronous inserted
& removed
events
In your can-components, the [can-util/dom/events/inserted/inserted inserted] and [can-util/dom/events/removed/removed] events were previously fired synchronously as the element was inserted or removed from the DOM. To improve performance, these events are now fired asynchronously.
There is now a beforeremove event that fires synchronously in case you need to perform memory cleanup. For example, you might need to access the parent’s viewModel:
Component.extend({
tag: "my-panel",
events: {
"{element} beforeremove": function(){
canViewModel(this.element.parentNode).removePanel(this.viewModel);
}
}
}
Replace uses of can.view
The can.view
methods have been removed in CanJS 3. The most common use was to create a template renderer from a script element within the page.
Instead of:
let render = can.view('some-id');
Just use the DOM APIs and pass the string directly into can-stache:
let templateString = document.getElementById('some-id').innerHTML;
let render = stache(templateString);
If you were using can.view
to load a template from a URL like so:
let render = can.view('./template.stache');
We encourage you to use StealJS with steal-stache:
import render from "./template.stache";
Alternatively, use the Fetch API:
fetch("./todos.stache")
.then((resp) => stache(resp.text()))
.then((render) => /* use render */);
If you’re using another module loader (such as Browserify or webpack), check out Setting Up CanJS for instructions on how to load templates.
can.view.preload
If you were using can.view.preload
then use registerPartial instead.
stache.registerPartial("some-id", renderer);
Replace uses of can.Construct.proxy
The can.Construct.proxy
method has been removed in favor of Function.prototype.bind.
Instead of:
this.proxy(randFunc)
You should now do this:
randFunc.bind(this)
Replace uses of can.batch
can.batch
has been moved to the can-event
module.
Instead of:
can.batch.method();
You should now do this:
import canBatch from 'can-event/batch/batch';
canBatch.method();
The trigger
method has also been renamed to dispatch
.
can.batch.trigger(myObj, 'myEvent');
Becomes:
canBatch.dispatch(myObj, 'myEvent');
Replace uses of can.isFunction
can.isFunction
has been moved to the can-util
module.
Instead of:
can.isFunction(func);
You should now do this:
import isFunction from 'can/util/js/is-function/is-function';
isFunction(func);
can.event
Some methods have been renamed in can.event
.
can.event.addEvent.call(el, 'click', function() {});
can.event.removeEvent.call(el, 'click', function() {});
Becomes:
can.event.addEventListener.call(el, 'click', function() {});
can.event.removeEventListener.call(el, 'click', function() {});
can.extend
This method has been split into two: a shallow and deep merge. Previously, passing true
as the first parameter would do a deep merge. Now, you explicitly invoke the deep merge or shallow merge function.
can.extend({}, { answer: 42 }); // shallow
can.extend(true, {}, { answer: 42 }); // deep
Becomes:
import assign from 'can-util/js/assign/assign';
import deepAssign from 'can-util/js/deepAssign/deepAssign';
assign({}, { answer: 42 }); // shallow
deepAssign({}, { answer: 42 }); // deep
can.addClass
This method now requires the DOM element to be the context of function.
Replace this:
can.addClass(el, 'myClass');
With this:
import className from 'can-util/dom/class-name/class-name';
className.add.call(el, 'myClass');
can.append
This method now require the DOM element to be the context of function.
Replace this:
can.append(el, '<p></p>');
With this:
import mutate from 'can-util/dom/mutate/mutate';
mutate.append.call(el, '<p></p>');
can.data
This method now requires the DOM element to be the context of function. It also has a separate method for getting and setting data.
Replace this:
can.data(el, 'something', 'secret'); // set
can.data(el, 'something'); // get
With this:
import domData from 'can-util/dom/data/data';
domData.set.call(el, 'something', 'secret');
domData.get.call(el, 'something');
String methods
All string methods are grouped together now, so you only have to import the string utilities once.
can.camelize('first-name');
can.hyphenate('firstName');
Becomes:
import string from 'can-util/js/string/string';
string.camelize('first-name');
string.hyphenate('firstName');
Use native Promises
Native Promises are used instead of jQuery promises which means you need to:
- Include a Promise polyfill if targeting browsers that do not have native support. Steal includes a polyfill for you.
- Use
.catch()
instead of.fail()
. - Use
.then()
instead of.done()
.
Note that only a single value is returned from a native Promise.
Passing the this
value to a helper
can-stache now passes a can-compute to helpers.
If you were passing the this
value to a helper like so:
{{helper .}}
You can fix this either by having your helpers handle computes, or by using call expressions instead:
{{helper(.)}}
No more global scope names
Most recently-built applications do not depend on adding to the global namespace, but in case you have code that does:
Construct.extend("foo.bar", /* ... */)
Which sets window.foo.bar
, this argument is no longer accepted by can-construct. If you really need to set a global, you can do so yourself using the return value of extend.
Instead, the first argument to extend is the name of the constructor function. This is nice for development as you’ll get named objects in your dev tools.
Using steal-stache
for templates
If you use StealJS, you’ll need to install steal-stache to load your templates:
npm install steal-stache@3 --save
If you’re using StealJS 0.16, you don’t need to do anything else to make your templates load correctly.
If you’re using StealJS 1, you also need to add steal-stache
to the plugins
configuration in your package.json
:
{
// ...
"steal": {
// ...
"plugins": ["steal-stache"]
}
}
Replace template
with view
in components
The template
property name has been deprecated in favor of view
.
Instead of:
import template from "./todo.stache";
Component.extend({
tag: "some-component",
template: template
});
You should write:
import view from "./todo.stache";
Component.extend({
tag: "some-component",
view: view
});
Modernized migration path
CanJS 3 is divided into separate npm packages. This allows us to more quickly update parts of CanJS without affecting other functionality.
In addition to the above, take advantage of the individual packages by installing and using them directly to set your project up for easier upgrades in the future.
For example, you might be using can-component like either:
import can from 'can';
can.Component.extend({ /* ... */ });
or
import Component from 'can/component/component';
Component.extend({ /* ... */ });
Regardless of which you are using, update your code to instead look like:
import Component from 'can-component';
Component.extend({ /* ... */ });
Use the same pattern for the other can
modules you are using. In general, you should not be using the can.
properties any more, but rather importing (through your module loader / bundler) only the packages and modules that you are using.
Here’s a list of all the paths in CanJS 2.3 that now have separate modules in CanJS 3:
can/component/component
— can-componentcan/compute/compute
— can-computecan/construct/construct
— can-constructcan/construct/super
— can-construct-supercan/control/control
— can-controlcan/event/event
— [can-event]can/list/list
— can-listcan/map/backup
— [can-map-backup]can/map/define
— can-map-definecan/map/map
— can-mapcan/model/model
— can-modelcan/route/pushstate/pushstate
— can-route-pushstatecan/route/route
— can-routecan/util/fixture
— can-fixturecan/util/string/deparam/deparam
— can-deparamcan/util/util
— [can-util]can/view/autorender/autorender
— can-view-autorendercan/view/callbacks/callbacks
— can-view-callbackscan/view/ejs/ejs
— [can-ejs]can/view/href/href
— [can-view-href]can/view/import/import
— can-view-importcan/view/live/live
— can-view-livecan/view/mustache/mustache
— can-mustachecan/view/node_lists/node_lists
— can-view-nodelistcan/view/parser/parser
— can-view-parsercan/view/scope/scope
— can-view-scopecan/view/stache/stache
— can-stachecan/view/target/target
— can-view-target
Latest & greatest migration path
In addition to the steps taken in the two sections above, make the following changes to your application if you really want to stay ahead of the curve.
Move from can-map
to can-define
If you’ve used can-map-define in the past, then using can-define should be familiar to you. Using can-define/map/map is the easiest migration path and is what we show in all of the examples in CanJS 3’s docs.
A typical map looks like:
import Map from 'can-map';
import 'can-map-define';
const CarOwner = Map.extend({
define: {
cars: {
Type: Car.List
},
favorite: {
Type: Car
},
color: {
type: "string"
},
age: {
value: 18
}
}
});
Which can be replaced by flattening it into a can-define/map/map like so:
import DefineMap from 'can-define/map/map';
const CarOwner = DefineMap.extend({
cars: Car.List,
favorite: Car,
color: "string",
age: {
type: "number",
value: 18
}
});
Using can-define allows you to use maps without the .attr() method that’s needed in can-map and can-list. To use this with DefineMap, just use the .
(dot) operator instead:
const carOwner = new CarOwner();
// This is observable!
carOwner.favorite = new Car({ make: "Toyota" });
Note: With can-map
you are able to assign initial values to a property while defining a Map
like so:
import CanMap from 'can-map';
const Person = CanMap.extend({
name: "Justin"
});
This shorthand in can-define/map/map
defines the type, not the initial value.
Here’s the example above updated for can-define/map/map
:
import DefineMap from 'can-define/map/map';
const Person = DefineMap.extend({
name: {value: "Justin"}
});
Remove use of change
events
When you upgrade to use can-define, you’ll no longer receive change
events on maps. If you had any code that binded to a map’s change
event, you’ll want to instead bind to the properties that you are interested in.
For example:
route.bind("change", function(){
// The route changed
});
Can be modified to instead use a compute that calls serialize
on the route’s map:
let routeMap = compute(function(){
return route.map.serialize();
});
routeMap.bind("change", function(){
// A property on the route’s map changed.
});
As you might notice, [can-event.on on()] is preferable to bind()
, although bind()
still works the same.
Use can-connect
directly
When using the easy migration path, you were secretly using [can-connect/can/model/model], a constructor that is mostly backwards-compatible with can-model.
Most new projects should use can-connect directly. can-connect/can/super-map/super-map is the easiest way to create models with can-connect
’s features. Using can-connect
directly allows you to use can-define/map/maps as your models like so:
import DefineMap from 'can-define/map/map';
import DefineList from 'can-define/list/list';
import superMap from 'can-connect/can/super-map/super-map';
const Message = DefineMap.extend({
id: "*"
});
Message.List = DefineList.extend({
"#": Message
});
const messageConnection = superMap({
url: 'https://chat.donejs.com/api/messages',
idProp: 'id',
Map: Message,
List: Message.List,
name: 'message'
});
Avoid future deprecations & removals
Wrap elements in jQuery objects
If you are using [can-jquery/legacy] to automatically get jQuery-wrapped elements in can-control event handlers, you’ll want to remove the usage of [can-jquery/legacy] as it doesn’t play well with can-components that do not expect elements to be jQuery-wrapped.
Instead, use [can-jquery] directly and handle the wrapping yourself. For example:
import Component from 'can-component';
import $ from 'can-jquery';
Component.extend({
tag: "some-component",
events: {
inserted: function(){
this.element = $(this.element);
},
"li click": function(li){
let $li = $(li);
}
}
});
[can-jquery] will continue to be supported indefinitely but [can-jquery/legacy] will be dropped in a future major version.
Use the new binding syntax
CanJS 2.3 introduced new binding syntaxes. They’re available in CanJS 3 with can-stache-bindings. Although the old binding syntaxes still work, they will likely be dropped in 4.0. View can-stache-bindings’s documentation to learn how to use the new binding syntax.
An example is changing a can-value
binding from:
<input type="text" can-value="{someProp}" />
To:
<input type="text" value:bind="someProp" />
CanJS 3 also introduces new stache converters, which are special two-way helpers that update an element when an observable value changes and update the observable value when the form element’s value changes (from user input).
An example is the string-to-any converter, which converts a primitive value to a string to set a <select>
’s value, and then converts the <select>
value when a user selects an <option>
back to the primitive value to update the scope value:
<select value:bind="string-to-any(~favePlayer)">
<option value="23">Michael Jordan</option>
<option value="32">Magic Johnson</option>
</select>