Working with Modules
Let’s dive deeper into how to use the module system properly in ScaffScript.
Exporting Modules
Section titled “Exporting Modules”Module in ScaffScript is a .ss file that only used for exporting variables, functions, etc. These are called exports. Here’s the common exports you can use in ScaffScript:
| Export | Description |
|---|---|
export var | Export a variable. Compiles to local variable (var) in GML. |
export let | Export an instance variable. Compiles to instance variable in GML. |
export const | Export a constant. Compiles to global constant (#macro) in GML. |
export function | Export a function. Compiles to global function in GML, like when you’re creating a function in a script asset in the GameMaker IDE. |
export enum | Export an enum. Compiles to enum in GML. |
export class | Export a class. Compiles to struct constructor in GML. |
import vs include
Section titled “import vs include”ScaffScript provides two ways to import modules: import and include. They both serve the same purpose of importing modules, but they have different syntax and behavior. Here’s the difference between them:
import | include | |
|---|---|---|
| Syntax | import { ExportsName } from "path/to/file" | include { ExportsName } from "path/to/file" | include { "filename" } from "path/to/dir" |
| Content Directives | ✅ | ❌ |
| Inline Content | Need @content directive | Direct inlining |
| Use Case | General purpose | Inlining module content |
| Exports Alias | ✅ | ❌ |
.gml Inlining | ❌ | ✅ |
Example
Section titled “Example”Let’s use my-file module as an example. It exports a variable and a function.
export var x_point = 10;
export function hello() { show_debug_message("Hello, World!");}Using Exports
Section titled “Using Exports”import { x_point, hello } from "./my-file"
@content hellohello();
@content x_pointshow_debug_message($"x_point = @:x_point");include { hello } from "./my-file"hello();
include { x_point } from "./my-file"show_debug_message($"x_point = @:x_point"); // can't use content directives!Compiled Result
Section titled “Compiled Result” import { x_point, hello } from "./my-file"
@content hello function hello() { show_debug_message("Hello, World!"); }hello();
@content x_point show_debug_message($"x_point = x_point"); var x_point = 10; show_debug_message($"x_point = 10"); include { hello } from "./my-file" function hello() { show_debug_message("Hello, World!"); }hello();
include { x_point } from "./my-file" var x_point = 10;show_debug_message($"x_point = @:x_point");Final Result
Section titled “Final Result”function hello() { show_debug_message("Hello, World!");}hello();
var x_point = 10;show_debug_message($"x_point = 10");function hello() { show_debug_message("Hello, World!");}hello();
var x_point = 10;show_debug_message($"x_point = @:x_point");export * from
Section titled “export * from”export * from is called barrel export. It’s useful for re-exporting modules from a deep path with a more convenient path.
Example
Section titled “Example”Let’s say you have this structure:
Directorysrc/
Directorylib/
Directorymath/
Directorysimple/
- vector2.ss
- vector3.ss
Directorycomplex/
- matrix2-2.ss
- matrix3-3.ss
- index.ss
Directoryutils/
- my-utils.ss
- other-utils.ss
- yet-another-utils.ss
- index.ss
- index.ss
Without the re-export, you would have to import the modules from the deep path, like this:
import { read_multi_file } from "./lib/utils/my-utils"
import { Vector2, Vector3 } from "./lib/math/simple/vector2"
import * from "./lib/math/complex/matrix3-3"It’s tedious and error-prone, especially when you’re working with a large project. This is where export * from shines. You can re-export all exports from a module in lib/index.ss, like this:
export * from "./simple/vector2"export * from "./simple/vector3"
export * from "./complex/matrix2-2"export * from "./complex/matrix3-3"// ./utils doesn't have index.ss, so we import the exports from the deep pathexport * from "./utils/my-utils"export * from "./utils/other-utils"export * from "./utils/yet-another-utils"
// ./math has index.ss, and we already re-export all exports from it in math/index.ssexport * from "./math"Now you can import the exports from lib/index.ss like this:
import { read_multi_file } from "./lib"
import { Vector2, Vector3 } from "./lib"
import * from "./lib"Path Aliases
Section titled “Path Aliases”You have a deeply nested module, but you don’t want either to use the full path, or re-export it? Path aliases are here to help you!
Example
Section titled “Example”Let’s use the same structure as the export * from example above:
Directorysrc/
Directorylib/
Directorymath/
Directorysimple/
- vector2.ss
- vector3.ss
Directorycomplex/
- matrix2-2.ss
- matrix3-3.ss
- index.ss
Directoryutils/
- my-utils.ss
- other-utils.ss
- yet-another-utils.ss
- index.ss
- index.ss
You can add path aliases in scaff.config.* to make it easier to import the modules. Here’s an example:
module.exports = { clearOutputDir: false, noIntegration: true, path: { "@vec2": "./lib/math/simple/vector2", "~vec3": "~/lib/math/simple/vector3", "Math": "./lib/math", "@utils-1": "./lib/utils/my-utils", "Utils-other": "./lib/utils/other-utils", "Lib": "./lib", }, production: false, tabType: "1t", targetPlatform: "all", useGmAssetPath: true // other options...};export default { clearOutputDir: false, noIntegration: true, path: { "@vec2": "./lib/math/simple/vector2", "~vec3": "~/lib/math/simple/vector3", "Math": "./lib/math", "@utils-1": "./lib/utils/my-utils", "Utils-other": "./lib/utils/other-utils", "Lib": "./lib", }, production: false, tabType: "1t", targetPlatform: "all", useGmAssetPath: true // other options...} satisfies Partial<ScaffConfig>;And then, you can use the aliases in your modules:
import { Vector2 } from "@vec2"import { Vector3 } from "~vec3"
import * from "Math"
import { read_multi_file: read_multi_file_from_utils_1 } from "@utils-1"import { read_multi_file: read_multi_file_from_utils_other } from "Utils-other"Relative Paths
Section titled “Relative Paths”A path that starts with ./ or without any prefix, will be resolved relative to the importing file’s directory.
Example
Section titled “Example” // --- snip --- path: { "@vec2": "./lib/math/simple/vector2", "Math": "./lib/math", "@utils": "lib/utils" }, // --- snip ---import { Vector2 } from "@vec2"import { Vector3 } from "Math"Explanation
Section titled “Explanation”- Current directory is
./src. - The
@vec2path alias is resolved to"./src/lib/math/simple/vector2".- So, it’ll import
Vector2from"./src/lib/math/simple/vector2".
- So, it’ll import
- The
Mathpath alias is resolved to"./src/lib/math".- So, it’ll import
Vector3from"./src/lib/math".
- So, it’ll import
import { Vector2 } from "@vec2"import { Vector3 } from "Math"Explanation
Section titled “Explanation”- Current directory is
./src/my-folder. - The
@vec2path alias is resolved to"./src/my-folder/lib/math/simple/vector2".- So, it’ll import
Vector2from"./src/my-folder/lib/math/simple/vector2". - It’ll throw an error, because the module (
"./src/my-folder/lib/math/simple/vector2") doesn’t exist.
- So, it’ll import
- The
Mathpath alias is resolved to"./src/my-folder/lib/math".- So, it’ll import
Vector3from"./src/my-folder/lib/math". - It’ll throw an error, because the module (
"./src/my-folder/lib/math") doesn’t exist.
- So, it’ll import
Absolute Paths
Section titled “Absolute Paths”Opposite to relative paths, absolute paths are resolved from the root of the project. It uses tilde (~) as the prefix. The root itself is based on the source option in scaff.config.*.
Example
Section titled “Example” // --- snip --- path: { "~vec2": "~/lib/math/simple/vector2", "Math": "~/lib/math", "~utils": "~/lib/utils" }, source: "./src", // --- snip ---import { Vector2 } from "~vec2"import { Vector3 } from "Math"Explanation
Section titled “Explanation”- Absolute path doesn’t care about the importing file’s directory.
- The
~vec2path alias is resolved to"/src/lib/math/simple/vector2".- So, it’ll import
Vector2from"/src/lib/math/simple/vector2".
- So, it’ll import
- The
Mathpath alias is resolved to"/src/lib/math".- So, it’ll import
Vector3from"/src/lib/math".
- So, it’ll import
import { Vector2 } from "~vec2"import { Vector3 } from "Math"Explanation
Section titled “Explanation”- Current directory is
./src/my-folder, but absolute path doesn’t care about the current directory. - The
~vec2path alias is resolved to"./src/lib/math/simple/vector2".- So, it’ll import
Vector2from"./src/lib/math/simple/vector2". - It’ll work, because the module (
"./src/lib/math/simple/vector2") exists.
- So, it’ll import
- The
Mathpath alias is resolved to"./src/lib/math".- So, it’ll import
Vector3from"./src/lib/math". - It’ll work, because the module (
"./src/lib/math") exists.
- So, it’ll import
Wildcard Paths
Section titled “Wildcard Paths”ScaffScript supports wildcard paths in path aliases. It uses * as the wildcard character. Here are the rules:
*must be the last character of both the path alias name and the resolved path alias.*matches any number of characters and path segments, including none.- You can use
*in both relative and absolute paths.
Example
Section titled “Example” // --- snip --- path: { "@math/*": "./lib/math/*", "~utils/*": "~/lib/utils/*" }, source: "./src", // --- snip ---import { Vector2 } from "@math/simple/vector2"import { Matrix33 } from "@math/complex/matrix3-3"import { read_file } from "~utils/my-utils"Explanation
Section titled “Explanation”- Current directory is
./src. - The
@math/simple/vector2path alias is resolved to"./src/lib/math/simple/vector2".- So, it’ll import
Vector2from"./src/lib/math/simple/vector2". - It’ll work, because the module (
"./src/lib/math/simple/vector2") exists.
- So, it’ll import
- The
@math/complex/matrix3-3path alias is resolved to"./src/lib/math/complex/matrix3-3".- So, it’ll import
Matrix33from"./src/lib/math/complex/matrix3-3". - It’ll work, because the module (
"./src/lib/math/complex/matrix3-3") exists.
- So, it’ll import
- The
~utils/my-utilspath alias is resolved to"/src/lib/utils/my-utils".- So, it’ll import
read_filefrom"/src/lib/utils/my-utils". - It’ll work, because the module (
"/src/lib/utils/my-utils") exists.
- So, it’ll import
import { Vector2 } from "@math/simple/vector2"import { Matrix33 } from "@math/complex/matrix3-3"import { read_file } from "~utils/my-utils"Explanation
Section titled “Explanation”- Current directory is
./src/my-folder. - The
@math/simple/vector2path alias is resolved to"./src/my-folder/lib/math/simple/vector2".- So, it’ll import
Vector2from"./src/my-folder/lib/math/simple/vector2". - It’ll throw an error, because the module (
"./src/my-folder/lib/math/simple/vector2") doesn’t exist.
- So, it’ll import
- The
@math/complex/matrix3-3path alias is resolved to"./src/my-folder/lib/math/complex/matrix3-3".- So, it’ll import
Matrix33from"./src/my-folder/lib/math/complex/matrix3-3". - It’ll throw an error, because the module (
"./src/my-folder/lib/math/complex/matrix3-3") doesn’t exist.
- So, it’ll import
- The
~utils/my-utilspath alias is resolved to"/src/lib/utils/my-utils". Absolute paths are not affected by the current directory.- So, it’ll import
read_filefrom"/src/lib/utils/my-utils". - It’ll work, because the module (
"/src/lib/utils/my-utils") exists.
- So, it’ll import
Best Practices
Section titled “Best Practices”- Use
@prefix,~prefix, or uppercase for path alias names to avoid conflicts with normal paths. - Use absolute paths over relative paths, as its persistent behavior makes it more predictable.
- Use
*wildcard character to make your path aliases more flexible. So, you won’t waste your time remapping your path aliases when your project grows.
Using Content Directives in import
Section titled “Using Content Directives in import”After importing a module with import, you can use the content directives to inline the module’s content. Here are the commonly used content directives:
| Directive | Usage |
|---|---|
@content | Inlines the full compiled GML declaration of the module. |
@valueof | Inlines the right hand side or the body of the module. |
@: | Shorthand for @valueof. |
Example
Section titled “Example”export var x_point = 10;
export function hello() { show_debug_message("Hello, World!");}import { x_point, hello } from "./my-file"
@content hellohello();
var my_method = function() { show_debug_message("This is my method!");
@content x_point var my_x = @:x_point; show_debug_message($"my_x * x_point = {my_x * x_point}");}Result
Section titled “Result” import { x_point, hello } from "./my-file"
@content hello function hello() { show_debug_message("Hello, World!"); }hello();
var my_method = function() { show_debug_message("This is my method!");
@content x_point var my_x = @:x_point; var x_point = 10; var my_x = 10; show_debug_message($"my_x * x_point = {my_x * x_point}");}Common Gotchas
Section titled “Common Gotchas”importalone doesn’t emit anything.- If you set a path alias target to
"./some/path", the resolved paths are relative to the importing file, not from the root of the project. - Forgetting that
includeinlines the full declaration, not just the value. - Using
export * fromrecklessly leads to naming conflicts.