Module System
ScaffScript’s module system lets you share code between .ss files using export, import, include, and export ... from. These are compile-time only, so they’re fully resolved and stripped before GML output is written.
export
Section titled “export”Marks a declaration as available for other files to consume.
Variable / Constant
Section titled “Variable / Constant”Syntax
Section titled “Syntax”export <var|let|const> <name> = <value>var compiles to var, const compiles to #macro, and let or no keyword compiles to instance variable.
Example
Section titled “Example”export var myVar = 10;export let myLet = 20;export const MY_CONST = "hello";export x = 1;Result
Section titled “Result”var myVar = 10; // `var` keyword is preservedmyLet = 20; // `let` keyword is stripped, compiles to instance variable#macro MY_CONST "hello" // `const` compiles to `#macro`, a global constantx = 1; // no keyword compiles to instance variableFunction
Section titled “Function”Syntax
Section titled “Syntax”export function <name>([<args>]) { ... }Example
Section titled “Example”export function print() { show_debug_message("Hello, World!");}
export function myFunction(arg1, arg2?) { show_debug_message(arg1);}Result
Section titled “Result”function print() { show_debug_message("Hello, World!");}
function myFunction(arg1, arg2 = undefined) { show_debug_message(arg1);}Function Expression
Section titled “Function Expression”Syntax
Section titled “Syntax”export const <name> = function([<args>]) { ... }Example
Section titled “Example”export const myFunc = function(arg1) { show_debug_message(arg1);}Result
Section titled “Result”myFunc = function(arg1) { show_debug_message(arg1);}Arrow Function
Section titled “Arrow Function”Syntax
Section titled “Syntax”export const <name> = (<args>) => { ... }It has the same behavior as a function expression.
Example
Section titled “Example”// multi-lineexport const myMethod = (arg1, arg2 = 0) => { show_debug_message(arg1);}
// single-lineexport const oneLiner = (x?) => show_debug_message(x);Result
Section titled “Result”myMethod = function(arg1, arg2 = 0) { show_debug_message(arg1);}
oneLiner = function(x = undefined) { return show_debug_message(x);}Syntax
Section titled “Syntax”export enum <name> { ... }export const enum <name> { ... }Currently, there’s no difference between those two.
Example
Section titled “Example”export enum MyEnum { A, B = 3, C}
export const enum MyConstEnum { X = 20, Y = 30}Result
Section titled “Result”enum MyEnum { A, // 0 B, // 3 C // 4}
enum MyConstEnum { X = 20, Y = 30}Syntax
Section titled “Syntax”export class <name> { constructor([<args>])}Compiles to a GML struct constructor. Inheritance is not supported yet.
Example
Section titled “Example”export class MyClass { constructor() // constructor is required
field1 = 1; field2 = 2;
method1() { show_debug_message("method1"); }
method2 = function() { show_debug_message("method2"); }}
export class MyClassConstructor { constructor(_name, _age = 0) // with parameters and default value
name = _name; age = _age;
print = function() { show_debug_message($"Name: {name}"); }
show_age(age?) { show_debug_message(age); }}Result
Section titled “Result”function MyClass() constructor { field1 = 1; field2 = 2;
method1 = function() { show_debug_message("method1"); }
method2 = function() { show_debug_message("method2"); }}
function MyClassConstructor(_name, _age = 0) constructor { name = _name; age = _age;
print = function() { show_debug_message($"Name: {name}"); }
show_age = function(age = undefined) { show_debug_message(age); }}Interface
Section titled “Interface”Defines a struct shape/template. Members support inline types, default values, and optional flag (?). Supports extends and primitive types (number, string, boolean).
Syntax
Section titled “Syntax”export interface <name> { <field>[?][: <type>] [= <default>]}export interface <name> extends <parent> { <field>[?][: <type>] [= <default>]}Example
Section titled “Example”export interface MyInterface { name, age, address, is_active?: boolean = true}
export interface MyExtInterface extends MyInterface { address: string = "somewhere", score: number, phone?}import { MyInterface, MyExtInterface } from "./my-interface"
var user_simple = @use MyInterface { name: "John", age: 30, address: "somewhere"}
var user_ext = @use MyExtInterface { name: "Jane", is_active: false}Result
Section titled “Result”var user_simple = { name: "John", age: 30, address: "somewhere", is_active: true};
var user_ext = { name: "Jane", address: "somewhere", is_active: false, score: 0, phone: undefined};Explanation
Section titled “Explanation”You might wond why the result is different from the original. Here’s why:
interfaceandtypewill be compiled asstructliteral.user_extdoesn’t haveageset. Theagefield is stripped in the output, because it’s not set, has no default value, and not an optional field.user_ext.addressis set to"somewhere", because the default value is overridden inMyExtInterface.user_simple.is_activeistrue, because default value ofis_activeistrueinMyInterface. It also will be applied touser_extif you don’t set it.user_ext.scoreis0, because no default value is set forscoreinMyExtInterface, but it has a data type ofnumber, so it’s compiled to0.user_ext.phoneisundefined, because it’s optional and no default value is set.
Similar to interfaces, no meaningful difference in runtime behavior. Supports intersection (&) with other type.
Syntax
Section titled “Syntax”export type <name> = { <field>[?][: <type>] [= <default>]}export type <name> = <parent> & { <field>[?][: <type>] [= <default>]}Example
Section titled “Example”export type MyType = { name: string, age: number, is_active?: boolean};
export type MyIntersectedType = MyType & { address: string = "somewhere", phone?};import { MyType, MyIntersectedType } from "./my-type"
var user_simple = @use MyType { name: "John", age: 30}
var user_ext = @use MyIntersectedType { name: "Jane", is_active: false, phone: other_var}Result
Section titled “Result”var user_simple = { name: "John", age: 30, is_active: undefined};
var user_ext = @use MyExtInterface { name: "Jane", age: 0, is_active: false, address: "somewhere", phone: other_var};import
Section titled “import”Imports the exported modules from another .ss file. The statement itself is stripped from output. Imported names are available via content directives (@content, @valueof, etc.) in the same file.
Syntax
Section titled “Syntax”import { ExportsName } from "path/to/file"import { Name1, Name2 } from "path/to/file"import { ExpName: ExpAlias } from "path/to/file" // ExpName -> Aliasimport * from "path/to/file" // import all exports from fileExample
Section titled “Example”import { ExportsName } from "path/to/file"import { Name1, Name2 } from "path/to/file2"import { Original: Alias } from "path/to//deep/file"import * from "path/to/file-3"
// after import, you can use the imported namesxx = @valueof Aliasshow_debug_message($"Name1 = @:Name1, Name2 = @typeof Name2, xx = {xx}");
@content ExportsNameinclude
Section titled “include”Inlines exported modules or raw .gml files directly at the point of the statement. Shorthand for import + @content for each module.
Inlining exports
Section titled “Inlining exports”Syntax
Section titled “Syntax”include { ModuleName } from "path/to/file"include { Name1, Name2 } from "path/to/file"include * from "path/to/file" // * = include all exportsExample
Section titled “Example”export let xx = 10;export const MY_CONST = 20;
export function my_function() { show_debug_message("Hello, World!");}include { xx } from "./export-modules"show_debug_message("`xx` has been included");
include { MY_CONST, my_function } from "./export-modules"xx = 10;show_debug_message("`xx` has been included");
#macro MY_CONST 20my_function = function() { show_debug_message("Hello, World!");}Explanation
Section titled “Explanation”includeinlines the module at the point of the statement. So, the order of the code is preserved.- Unlike
importwhich only imports the module,includeinlines the module at the point of the statement. So, you can not:- Use the module using content directives (
@content,@valueof, etc.) - Alias the module with
:
- Use the module using content directives (
Inlining raw GML files
Section titled “Inlining raw GML files”Syntax
Section titled “Syntax”include { "filename.gml" } from "path/to/dir"include { "file1.gml", "file2" } from "path/to/dir"Example
Section titled “Example”show_debug_message("Hello, World!");function other_raw() { show_debug_message("This one is from other-raw.gml!");}include { "raw-gml.gml" } from "./"
show_debug_message("This is in between");
include { "other-raw" } from "./dir"Result
Section titled “Result”show_debug_message("Hello, World!");
show_debug_message("This is in between");
function other_raw() { show_debug_message("This one is from other-raw.gml!");}Explanation
Section titled “Explanation”- The raw content of the
.gmlfile is injected at that position. The target can be a file tracked by ScaffScript (from the scan) or any file path on disk. - You must use curly braces and double quotes for including raw GML files. Otherwise, it will be treated as a module, which will cause an error.
- The
.gmlextension in the{ <filename> }statement is optional. If not provided, ScaffScript will add it for you.
export ... from
Section titled “export ... from”Re-exports modules from another file, making them available to files that import from the current one.
Syntax
Section titled “Syntax”export { ModuleName } from "path/to/file"export { ModuleName, OtherModule } from "path/to/file"export * from "path/to/file"Example
Section titled “Example”export var x = 10;
export interface Shape { x: number, y: number}
export function hello() { show_debug_message("Hello, World!");}export * from "./mod/my-file" // re-exports all modules from "mod/my-file.ss"Now you can import x, Shape, and hello from exporter, instead of mod/my-file:
import { x, hello } from "./exporter"
@content hello
var my_x = @:x;Result
Section titled “Result”function hello() { show_debug_message("Hello, World!");}
var my_x = 10;index.ss Files
Section titled “index.ss Files”index.ss acts as a barrel file for its directory. It’s always processed last among files at the same depth, so it can safely re-export everything from sibling files.
export * from "./some_file"export * from "./another_file"// import the exported modules from the directory, not the index file directly// with this, you can import all modules from "./some_file" and "./another_file" at once import { something } from "./my_dir/index" import { something } from "./my_dir"Best Practices
Section titled “Best Practices”Extends and Intersects One-by-one
Section titled “Extends and Intersects One-by-one”When using extends in an interface, or & in a type, you should extends/intersects one-by-one. For example:
export interface MyInterface extends A, B, C { // ...}
export type MyType = A & B & C & { // ...};export interface B extends A { // ...}
export interface C extends B { // ...}
export interface MyInterface extends C { // ...}
export type B = A & { // ...}
export type C = B & { // ...}
export type MyType = C & { // ...}