V2 ESM Script Developer Experience

Hi everyone,

with the introduction of ESM scripts and the recommendation of using those in new projects. I have a few questions regarding extending default playcanvas behaviour.

  1. Lets start with the tweening library:

It seems like the preferred way of using it now is to import tweenEntity and supply it with the entity I want to tween and tween itself. (I didn’t look into tweening a custom object yet, not sure how to do that)

import { tweenEntity, SineOut } from './tween.mjs'

tweenEntity(entity, entity.getLocalPosition()).to({x: 10}, 1.0, SineOut);

There is still the option to augment Entity and AppBase with the tween function by calling addTweenExtensions and use it the old way.

import { addTweenExtensions, SineOut } from './tween.mjs'
import * as pc from 'playcanvas'

// Adds .tween() to Entity and Application
addTweenExtensions(pc);

entity.tween(entity.getLocalPosition()).to({x: 10}, 1.0, SineOut);

Altough this works, the editor will be unhappy and will throw an error, because the tween function isn’t defined at compile time.

  1. pc.extend

I have a custom SceneManager which handles scene switching/loading. I took inspiration from the old TweenManager setup and set it up the same way.
What is the recommended way to update it to the new system? I guess do it like the new tween system?

  1. Extending prototypes

I also have a few extensions on different playcanvas classes. For example helper functions on the GraphNode:

(function () {
    // Search the graph node and all of its ascendants for the first node that has a script component with the specified type.
    pc.GraphNode.prototype.findScriptInParent = function(type){
        var entity = this.findOneInParent(function (node) {
            return node.script && node.script[type];
        });
        return entity && entity.script[type];
    };
})();

This will continue to work but will also create errors in the editor as those functions aren’t defined at compile time.
I guess the correct way would be to define custom util/helper esm and use those instead of augmenting prototypes?

I’d love to hear suggestions from all of you on how to best approach this.

Hey @LucaHeft ! Great questions - and nice to see you using ESM!

Tweening

You’re right that the preferred approach now is to use tweenEntity directly instead of augmenting prototypes:

import { tweenEntity, SineOut } from './tween.mjs';

tweenEntity(entity, entity.getLocalPosition()).to({ x: 10 }, 1.0, SineOut);

This is the most editor and tooling friendly option, especially with type checking and IntelliSense.

Augmenting Entity/AppBase

While addTweenExtensions(pc) still works, as you noted, it muddies the types and causes issues in the editor since the extensions aren’t declared in the PlayCanvas types. I’d recommend avoiding prototype augmentation, especially if you’re using ESM and want to keep your tooling happy.

pc.extend and SceneManager-style modules

Yes, updating your custom SceneManager to follow the same pattern as TweenManager’s new ESM approach is ideal. So rather than modifying global state or prototypes, expose clean factory functions or classes and inject dependencies where needed.

Extending Prototypes (e.g., GraphNode)

Instead of this:

pc.GraphNode.prototype.findScriptInParent = function(type) { ... }

Prefer this:

// graph-utils.js
export function findScriptInParent(node, type) { ... }

This avoids mutating engine classes, improves type safety, and avoids issues in the PlayCanvas Editor and ESLint.

Disable linting

It’s worth noting that those editor errors you see are there to help, so you don’t access things that don’t exist, but you can sidestep this using the //@ts-expect-error which will disable the editor error highlight

// @ts-expect-error I'm intentionally modifying the GraphNode here! 
pc.GraphNode.prototype.findScriptInParent = function(){ ... }

Hope that all helps!

1 Like

Thank you for the in depth reply!

This confirms I’m moving in the right direction with my assumptions.

Although I must say this convolutes the code on the calling side quite a bit. But I guess that’s just how it works when using esm.
On the positive site this will give us Intellisense and type safety.

I think i just have to wrap my mind around it.

Regarding extending playcanvas types are you open to PRs?
I’ve got a few extensions/additions like smoothdamp which I use quite a lot and I think it could be valuable to have it integrated in the engine itself.

Yep, all PR’s welcome :pray: