The holidays are here and as a fairly junior web developer (but veteran native developer) I wanted to catch up with some of the things that I am currently not utilizing in my active PlayCanvas game.
I am currently not using TypeScript, and I am currently not using ESM. So, why not try both in a new project?
As mentioned I am new to web, and new to Node.js so please be gentle in letting me know if I am over complicating things.
I pulled down the playcanvas-editor-ts-template and got to work. I followed the setup instructions and tested the helloworld.ts sample. Works!
Next step was to change the Hello World sample to use ESM Scripts. It was fairly straight forward. However, I get red squiggly lines underneath some of the imports and it does not compile.
import { Script, Entity } from 'playcanvas';
export class HelloWorld extends Script {
static scriptName = 'helloWorld';
/** @attribute */
text = '';
/**
* @attribute
* @type {Entity}
*/
entityLink: Entity | null = null;
initialize() {
console.log('Hello ' + this.text);
if (this.entityLink) {
console.log('Linked entity:', this.entityLink.name);
}
}
}
After some Googling I realized the template was referencing an old version of playcanvas, 1.73-something, and ESM was added in a later 2.x version. I changed the dependency to a newer version and the red squigglies disappeared. Nice!
However, when running my tsc I got a bunch of errors. Googling the problem let me know that the template contained node dependencies to a too old version of typescript. I upgraded the typescript version and all errors are gone. The tsc output is yet again synced to my PlayCanvas project. Cool!
However, the script does not parse in PlayCanvas. After Googling I realized that to build ESM you need to explicitly specify so in the tsconfig file. I changed this in my tsconfig.
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
}
Nice! The output is now valid ESM. However, PlayCanvas assumes .mjs file endings instead of .js for ESM. While testing I renamed the .js manually in PlayCanvas, but I can’t keep doing that. I added a simple post-build step that renames the .js to .mjs before syncing. The sync only syncs .js files by default, so I added this to the sync command.
pcsync.js pushAll --yes --ext mjs
Now the playcanvas-sync syncs the files to PlayCanvas with the correct file endings. The files are also compiled from TS to ESM, and are parsed as such.
When testing the helloworld example above I did however notice that the typescript compiler strips unreferenced types in imports. This means that the Entity type (that is only referenced in the markup) is stripped in the runtime version.
import { Script } from 'playcanvas';
export class HelloWorld extends Script {
static scriptName = 'helloWorld';
/** @attribute */
text = '';
/**
* @attribute
* @type {Entity}
*/
entityLink = null;
initialize() {
console.log('Hello ' + this.text);
if (this.entityLink) {
console.log('Linked entity:', this.entityLink.name);
}
}
}
//# sourceMappingURL=helloworld.mjs.map
The result was that the helloWorld script was missing the entityLink attribute.
So, after some additional Googling I understood that typescript is meant to strip all unreferenced imports. Which is good, I guess, but not in this case. I noticed you can add the following in tsconfig.json.
"compilerOptions": {
"verbatimModuleSyntax": true
}
This way all imports are preserved even if unreferenced. I guess this is a drawback that you just have to live with. Maybe it’s possible to make the ts compiler strict enough to not allow unreferenced imports before compiling, that way I feel we’re a bit safer. Not sure this is the correct way to go about this.
That’s it! I can now write ESM Script in TypeScript and they work in the PlayCanvas editor. Nice!
I thought it would be a good idea to share my journey since there were a few hoops to jump through. Also, for the authors of the playcanvas-ts-template maybe it would be cool to have an ESM template, or simply upgrade the template to support both? Or two different templates?
Anyway, please let me know if I made it more difficult than it needed to be. Also, let me know if there are things that I have missed and I will run into in the future. Thanks!