Upgrading projects from classic scripts to modules

14

Stats

4,593 visits, 6,311 views

Tools

Translations

This tutorial hasn't been translated.

License

This tutorial is licensed under CC BY 4.0. Please refer to the license text if you wish to reuse, share or remix the content contained within this tutorial.

When Construct's JavaScript coding feature was launched in 2019, all the scripts in a project ran as classic mode scripts. This is how JavaScript has run in web pages since its introduction in the 1990s. However modern JavaScript now uses modules - a newer and better way to organise scripts and their dependencies.

Construct 3 r226 added support for using modules for your project's JavaScript code. In Construct 3 r251, support for the legacy classic mode was removed. This tutorial will guide you through what's changed and how to update your code, in case you have any old projects still using classic mode.

Classic mode vs. modules

In classic mode:

  • All your project's script files are loaded automatically on startup.
  • You can't control the order script files are loaded in.
  • Global variables and functions can be used everywhere, including in other scripts, and in scripts in event blocks.

In module mode:

  • Only the project main script is loaded automatically on startup.
  • Other script files aren't used unless you import them.
  • You can control the order script files are loaded by the order you import them.
  • Each module has its own scope. This means top-level variables and functions cannot be used outside the module, unless:
    • they are exported, and imported where they are used (recommended)
    • they are explicitly made global by adding them as properties on the global object (not recommended)

Using modules helps you organise your code better. For example, making all your variables and functions global is generally regarded as poor design, is prone to problems like naming clashes, and makes re-using code more difficult. Modules allow you to write clean, self-contained pieces of code that have clearly defined dependencies, and export a clearly defined interface for other code to use. Individual modules can then easily be re-used in different places.

You can learn more about how modules work in JavaScript at the MDN guide on JavaScript modules.

Changing modes

In Project Properties inside the Advanced section is a Scripts type setting. You can use this to change between Classic and Module modes. You should switch your project to use Modules mode. This affects how script files in your project are loaded, and you may have to make changes, as explained in the following steps.

Changing modes only affects script files. If you only use scripts in event blocks and don't have any script files at all, you don't need to do anything. Construct will automatically switch your project to use modules and everything will continue working as it did before.

Step 1: assign a main script

Once you've switched to modules mode, the first thing you need to do is tell Construct which is your main script. This is the only script that will be loaded on startup. All other scripts then need to be imported by this script.

Find the script you want to use as your main script in the Project Bar. Usually this is the script which has a call to runOnStartup. Select the script in the Project Bar, and some properties appear in the Properties Bar for it. Change the Purpose property to Main script. The main script also appears bold in the Project Bar.

Step 2: add exports

Next, for every other script file your project uses, look through it and identify which functions, classes and objects need to be made available to other script files. These need to be exported using the export keyword.

For example consider a JavaScript file utilities.js that defines a function add:

// utilities.js
function add(a, b)
{
	return a + b;
}

This function can be exported by adding the export keyword:

// utilities.js
export function add(a, b)
{
	return a + b;
}

The module now exports a function named add. You can add lots of exports, as well as a default export.

You can learn more about the export syntax at the MDN guide on the export statement.

Step 3: add imports

Now starting with the main script, add imports to load other script files, and make the things they export available. Continuing the previous example, the main script may have formerly relied on calling a global function add, since it was defined at the top level:

// main.js
console.log(add(2, 3));

Now you can tell the main script to load utilities.js and use the things it exports with the import statement:

// main.js
import * as Utils from "./utilities.js";

console.log(Utils.add(2, 3));

Note that:

  • The * means "import everything from this script"
  • The script's exports are given the name Utils, so the exported method add is accessed with Utils.add().
  • The script URL has to start with ./

You may wish to think about how to arrange all your scripts and their imports and exports so everything is organised and grouped in to relevant modules.

You can learn more about the import syntax at the MDN guide on the import statement. There are lots of ways to import scripts, including only importing certain things, renaming imports, importing just the default export, and so on.

Step 4: update scripts in events

If you don't use any scripts in event blocks, you can skip this step.

Suppose you have a script block in an event sheet that also called add, using it as a global function. Once you've changed your script files to modules, this method is no longer available globally - it has to be imported. However you cannot use import in script blocks. (Internally each script block is its own function, and you cannot import inside a function.)

To solve this, you can add another script with the purpose Imports for events. Add a new script file named importsForEvents.js. Select it in the Project Bar, and in the Properties Bar, set its Purpose property to Imports for events. Now anything you import in this file can be accessed by all scripts in events.

For example you can also import utilities.js here the same way the main script does:

// importsForEvents.js
import * as Utils from "./utilities.js";

Now your script blocks in event sheets can also use Utils.add().

You can also define functions and variables in this file which will be accessible to all scripts in events (but not other script files).

Useful patterns

Utilities/helper functions

It's often useful to have a series of helper or utility functions that are used across a range of scripts. The prior example demonstrates importing a utilities script. You can import scripts in multiple places, e.g. using import * as Utils from "./utilities.js"; in several scripts, and re-using the same set of helper functions everywhere. The module will only be loaded once even if it's imported multiple times, ensuring everything works efficiently.

A module for globals

Remember that modules have their own scope. If you declare a top-level variable, it's not global - it's only available in that module. So, similar to a utilities script, you may want to make a module to represent all global variables your code uses, and import that in to every script.

However module exports are read-only. This means if you export a variable like this:

export let myGlobalVariable = 0;

...then myGlobalVariable cannot be changed by any scripts importing it, as if it were actually declared with const. So this approach only works for constants.

Instead you can export globals as an object with properties instead, like so:

// globals.js
const Globals = {
	myGlobalVariable: 0,
	myOtherGlobal: 1,

	// Example global function
	someGlobalFunction()
	{
		/* ... */
	}
};

export default Globals;

Now scripts can access global state like so:

// main.js
import Globals from "./globals.js";

// Change a global variable
Globals.myGlobalVariable = 1;

Note this example uses a default export. The import Globals from ... statement only imports the default export, which in this case is the object with all our global variables.

This approach is better than using global variables, since it keeps everything better organised, avoids clashing with hundreds of names in the browser's own global namespace, and makes it easier to debug since you can easily view just your own global variables rather than everything.

Access global scope

The prior example of a global module is the recommended way to manage global state in your game. However if you really need to break out of the module's own scope and access the full global scope, you can do it by accessing properties on globalThis, e.g.:

globalThis.myGlobalScopeVariable = 0;
globalThis.myGlobalScopeFunction = function ()
{
	/* ... */
}

These can then be accessed everywhere so long as you consistently use them as properties of globalThis.

Import the main script

In your Imports for events script, you can also import the main script, like so:

// importsForEvents.js
import * as Main from "./main.js";

Now your scripts in events can call exported functions in the main script, e.g. Main.myFunction(). Remember to add the export keyword to those functions in the main script, since otherwise they won't be imported.

Conclusion

Support for classic mode scripts has now been removed, so any old projects will need updating. Using modules is a modernised and better-organised way to manage scripting in Construct. You may have to make some changes to your code, but the result should be cleaner code that is easier to manage and re-use in other projects.

  • 9 Comments

  • Order by
Want to leave a comment? Login or Register an account!
  • Great reference and tutorial for modules, thank you!

  • Very Nice Thanks !

  • It is really amazing what is happening with C3, with the option to use modules, everything will be well organized.

  • Do we have a definitive date when this is going to be depricated? I upgraded from C2 to C3. Fixed everything that wasn't supported. And the only dependency I have left is the old magicam plugin, which someone kindly upgraded from C2 to C3 in the forums. However, the game won't start unless I switch to "classic" script mode. So basically, I will need a couple of months before I can rework the whole camera solution in my game. Or if someone more knowledgeable with plugin development is willing to help me upgrade the plugin to use the new module setup? (I am willing to pay for this upgrade)

      • [-] [+]
      • 1
      • Ashley's avatar
      • Ashley
      • Construct Team Founder
      • 1 points
      • (1 child)

      Classic scripts have been deprecated since November 2020. Support for them was already removed in r242, but this has not yet come to a stable release.

      • oh gosh, this is quite terrifying news Ashley. I will start investigating my next step on Monday. Hopefully I can rewrite the camera routines and rid my dependency of magicam. (this will break all the cut-scenes in the game however) o_O I hope I can fix this quickly enough.

  • i dont really use scripts in my project, do i still need to do this ?

  • Load more comments (1 replies)