Migration Guide
ESM Scripts replace the legacy Scripting system as the recommended way to develop PlayCanvas applications. Whilst legacy scripts will continue to be work in existing projects amd will be supported for the foreseeable future, we recommend using the newer ESM format for your projects.
Gradual Migration
Using ESM Scripts within your project is entirely optional and allows you to gradually migrate your projects over to the newer ESM based format in your own time, without affecting existing projects.
Projects can contain both ESM Scripts and legacy scripts
You do not need to update all your scripts together. We recommend gradually migrating scripts and iteratively testing
Codemod
In order to migrate legacy PlayCanvas Scripts, we've provided a codemod that will automatically update your code to the newer ESM Format.
You can find the codemods in our github repository and you can run the codemod using the following command:
npx codemod playcanvas-esm-scripts
Known differences
In general ESM Scripts provide a more expressive and flexible way of creating projects. Whilst we have attempted to keep the migration process as seamless as possible, there are some note-able differences that you should bare in mind.
Module Scope
ESM Scripts have module scope, classic scripts have global scope. This means modules cannot implicitly access variables defined in other files. Often this is used as a way to define a global settings or configuration. The config has a higher loading order than the script, and so the SPEED
is accessible globally.
See code example
// config.js
var SPEED = 10;
// script.js
// ❌ This will not work. `SPEED` is scoped to config.js
console.log(SPEED)
This is a hidden dependency which breaks if the loading order changes. Instead, use import/export
syntax to explicitly define the dependency.
// config.mjs
export const SPEED = 10
// script.mjs
import { SPEED } from './config.mjs';
// ✅ Works!
console.log(SPEED);
You can learn more about the other difference between ES Modules and standard scripts here
Loading Order
ESM Script do not have a loading order.
The loading order of scripts was introduced as a way to organize dependencies between scripts and guarantee certain code would execute before others. With ES modules, these relationships can be explicitly defined through import/export
syntax. As such ESM Scripts do not have an loading order and they should not be relied upon to load in a certain way. Instead we encourage you to use the import/export
to set up dependencies.
The new Script
class
With ESM Scripts the new Script
base class replaces the existing ScriptType
class as the default base class. The Script
class represents the minimal set of features necessary, but omits a couple of features present in the original ScriptType
class.
It's worth noting that although Script
is now the default base class, it's still possible to use ScriptType
as the base class (internally ScriptType
extends Script
), however we do not recommend doing this for ESM Scripts, due to some of the reasons listed below.
Attribute Events
ESM Script do not fire Attribute Events.
The Script
class does not support attributes events in the format of attr:[name]
. The reason behind removing this is that internally the engine would override class members, and in practice this would create difficult to debug scenarios as it's not fully compatible with ES6 class syntax.
Instead you can define your own events around class attribute members using something like the following;
See code example
const watch = (target, prop) => {
const privateProp = `#{prop}`;
target[privateProp] = target[prop];
Object.defineProperty(target, prop, {
set(value) {
if (target[privateProp] !== value) {
target.fire(`changed:${prop}`, value);
target[privateProp] = value;
}
},
get() {
return this[privateProp];
}
});
}
import { Script } from 'playcanvas'
export class Rotate extends Script {
/** attribute */
speed = 10;
initialize() {
watch(this, 'speed');
this.on('changed:speed', console.log)
}
}
This also means you can have events for any class members too, not only script attributes.
Attribute Copying
ESM Script Attributes are not copied, they are passed by reference.
Attributes are no longer copied, they are passed by reference. The reasons this was changed was also due a bug in ScriptType
that was incompatible with ES6 classes.
Instead, if you do need to copy values, we recommend you do it manually and explicitly via getters and setters. Whilst this is more verbose, it's clear and explicit
See code example
import { Script, Vec3 } from 'playcanvas';
class Rotate extends Script {
_speed = new Vec3();
set speed(value) {
this._speed.copy(value)
}
get speed() {
return this_.speed'
}
}