diff --git a/src/framework/components/script/component.js b/src/framework/components/script/component.js index 057d50a079c..b9b809dba0c 100644 --- a/src/framework/components/script/component.js +++ b/src/framework/components/script/component.js @@ -712,7 +712,16 @@ class ScriptComponent extends Component { if (typeof scriptType === 'string') { scriptType = this.system.app.scripts.get(scriptType); } else if (scriptType) { - scriptName = scriptType.__name ??= toLowerCamelCase(getScriptName(scriptType)); + + const inferredScriptName = getScriptName(scriptType); + const lowerInferredScriptName = toLowerCamelCase(inferredScriptName); + + if (!(scriptType.prototype instanceof ScriptType) && !scriptType.scriptName) { + Debug.warnOnce(`The Script class "${inferredScriptName}" must have a static "scriptName" property: \`${inferredScriptName}.scriptName = "${lowerInferredScriptName}";\`. This will be an error in future versions of PlayCanvas.`); + } + + scriptType.__name ??= scriptType.scriptName ?? lowerInferredScriptName; + scriptName = scriptType.__name; } if (scriptType) { diff --git a/src/framework/handlers/script.js b/src/framework/handlers/script.js index fdbfe388b5e..3a5a57f2ba5 100644 --- a/src/framework/handlers/script.js +++ b/src/framework/handlers/script.js @@ -1,4 +1,5 @@ import { platform } from '../../core/platform.js'; +import { Debug } from '../../core/debug.js'; import { script } from '../script.js'; import { ScriptTypes } from '../script/script-types.js'; import { registerScript } from '../script/script-create.js'; @@ -137,7 +138,13 @@ class ScriptHandler extends ResourceHandler { if (extendsScriptType) { - const scriptName = toLowerCamelCase(scriptClass.name); + const lowerCamelCaseName = toLowerCamelCase(scriptClass.name); + + if (!scriptClass.scriptName) { + Debug.warnOnce(`The Script class "${scriptClass.name}" must have a static "scriptName" property: \`${scriptClass.name}.scriptName = "${lowerCamelCaseName}";\`. This will be an error in future versions of PlayCanvas.`); + } + + const scriptName = scriptClass.scriptName ?? lowerCamelCaseName; // Register the script name registerScript(scriptClass, scriptName); diff --git a/src/framework/script/script.js b/src/framework/script/script.js index 10920792f9e..fef323759d9 100644 --- a/src/framework/script/script.js +++ b/src/framework/script/script.js @@ -347,6 +347,7 @@ const funcNameRegex = /^\s*function(?:\s|\s*\/\*.*\*\/\s*)+([^(\s\/]*)\s*/; */ export function getScriptName(constructorFn) { if (typeof constructorFn !== 'function') return undefined; + if (constructorFn.scriptName) return constructorFn.scriptName; if ('name' in Function.prototype) return constructorFn.name; if (constructorFn === Function || constructorFn === Function.prototype.constructor) return 'Function'; const match = (`${constructorFn}`).match(funcNameRegex); diff --git a/test/framework/components/script/component.test.mjs b/test/framework/components/script/component.test.mjs index 6ee2384d46d..a2b69a3e70a 100644 --- a/test/framework/components/script/component.test.mjs +++ b/test/framework/components/script/component.test.mjs @@ -1,8 +1,10 @@ import { expect } from 'chai'; +import { Debug } from '../../../../src/core/debug.js'; import { Asset } from '../../../../src/framework/asset/asset.js'; import { Entity } from '../../../../src/framework/entity.js'; import { createScript } from '../../../../src/framework/script/script-create.js'; +import { Script } from '../../../../src/framework/script/script.js'; import { createApp } from '../../../app.mjs'; import { jsdomSetup, jsdomTeardown } from '../../../jsdom.mjs'; @@ -2925,4 +2927,56 @@ describe('ScriptComponent', function () { expect(e.script.has(null)).to.equal(false); }); + it('warns when an ESM Script class does not have a static "scriptName" property', function () { + class TestScript extends Script {} + const a = new Entity(); + a.addComponent('script', { enabled: true }); + a.script.create(TestScript); + + expect(Debug._loggedMessages.has( + 'The Script class "TestScript" must have a static "scriptName" property: `TestScript.scriptName = "testScript";`. This will be an error in future versions of PlayCanvas.' + )).to.equal(true); + }); + + it('correctly registers an ESM script with its scriptName', function () { + class TestScript extends Script { + static scriptName = 'myTestScript'; + } + const a = new Entity(); + a.addComponent('script', { enabled: true }); + a.script.create(TestScript); + + expect(a.script.has('testScript')).to.equal(false); + expect(a.script.has('myTestScript')).to.equal(true); + }); + + it('falls back to camelCase script name if scriptName is not defined', function () { + class TestScript extends Script {} + const a = new Entity(); + a.addComponent('script', { enabled: true }); + a.script.create(TestScript); + + expect(a.script.has('testScript')).to.equal(true); + expect(a.script.has('myTestScript')).to.equal(false); + }); + + it('does not warn when a ScriptType is used', function () { + Debug._loggedMessages.clear(); + createScript('nullScript'); + const e = new Entity(); + e.addComponent('script', { + enabled: true, + order: ['nullScript'], + scripts: { + nullScript: { + enabled: true + } + } + }); + app.root.addChild(e); + + expect(Debug._loggedMessages.size).to.equal(0); + expect(e.script.has('nullScript')).to.equal(true); + }); + });