Modules
Ghost has one import statement, and two things can appear on the right of it: a file on disk, or a module behind a scheme — a standard library module under ghost:, or a module an embedding host registered under a scheme of its own.
import "helpers" // a .gs file sitting next to this one
import "ghost:math" // the standard library's math module
import "lumen:canvas" // a module Lumen registered under its own scheme
Ghost tells the two apart by the string alone: anything matching scheme: — two or more letters followed by a colon — is a scheme import, and everything else is a file path. (Two letters, so a Windows drive letter is never mistaken for a scheme.)
Importing files
Every Ghost file is its own module with its own scope. Paths are written without the .gs extension and are resolved relative to the file doing the importing — there is no project root, no search path, and no package registry.
Importing a file into another does not merge its scope. Two modules can define the same top-level variable without colliding.
Importing a whole file
A bare import runs the module's top-level code once and binds the module itself, under the last segment of its path, as a map of its top-level names:
import "beverages"
import "modules/helpers"
beverages.brew()
helpers.greet("world")
as chooses the name instead:
import "modules/helpers" as h
h.greet("world")
Nothing stops you from ignoring that binding and importing purely for the side effect of running the file.
Binding names
Every top-level variable in a module is exportable. Name the ones you want:
import Request, Response from "network"
This imports and binds Request and Response from the network module, making them available in your file.
Asking for a name a module does not export is an error, and Ghost suggests the nearest name it does export.
Aliases
Import a name under a different one with as:
import str as isString from "helpers"
Importing everything
* binds every top-level name the module has:
import * from "helpers"
Importing the standard library
The standard library lives behind the ghost: scheme, and — console and type aside — a script has to import what it uses.
import "ghost:math" // the whole module, bound to `math`
import "ghost:math" as m // aliased
import { pi, sqrt } from "ghost:math" // named imports
import pi, sqrt from "ghost:math" // the same thing without the braces
import * from "ghost:math" // every method, property, and class
The bare form binds the module itself, so dot access works as it always has: math.pi, math.sqrt(2).
The from forms pull individual methods, properties, and classes out by name. A property is evaluated once, at the import — import { pi } from "ghost:math" binds a plain number, the same value math.pi would read.
import { sqrt, pi } from "ghost:math"
console.log(sqrt(16)) // >> 4
console.log(pi) // >> 3.141592653589793
The modules under ghost: are math, date, random, os, file, path, json, http, and ghost.
"ghost:mathh" — gets the same nearest-match suggestion every other unresolved name in Ghost gets.Importing from a host
ghost: is not special-cased. It is simply the one scheme Ghost itself pre-registers, and a Go program embedding Ghost can claim a scheme of its own. Lumen, the game engine, registers its modules under lumen::
import "lumen:keyboard"
import { Image } from "lumen:image"
Named imports, aliases, and * all work identically whatever the scheme. A class a module exports binds the class value itself, and new works on it exactly as it does on a class declared in Ghost.
Combining the two forms
The module and a name out of it can be imported in one statement: the module name first, the braced list after it.
import image, { Spritesheet } from "lumen:image"
image.something() // the whole module, as `import "lumen:image" as image`
new Spritesheet("sheet.png") // the named export
The braces matter here. import a, b from "path" — no brace after the comma — keeps its own, unrelated meaning of two named imports. The combined form works for file imports too, and import name, { * } from "path" combines the module with every export.
Shared imports
Ghost keeps track of every module it imports. A module is read, parsed, and evaluated once, no matter how many files import it; every later import of the same path binds what the first one produced.
Cyclic imports
Because Ghost memoizes imports, a cycle short-circuits rather than recursing forever:
// main.gs
import "a"
// a.gs
console.log("start a")
import "b"
console.log("end a")
// b.gs
console.log("start b")
import "c"
console.log("end b")
// c.gs
console.log("start c")
import "a"
console.log("end c")
start a
start b
start c
end c
end b
end a
A cycle that cannot be resolved this way is reported as an import error naming the cycle, rather than looping. Either way, cyclic imports are worth treating as a smell.
A worked example
main.gs
modules/
functions.gs
variables.gs
main.gs
import * from "modules/functions"
import * from "modules/variables"
console.log("Program loaded.")
add(one, two)
greet(hello)
functions.gs
function add(a, b) {
console.log(a + b)
}
function greet(message) {
console.log(message)
}
variables.gs
one = 1
two = 2
hello = "Hello, world!"
Modules are nothing more than other Ghost files, and can live anywhere in your program. Putting them in a modules directory is a convention, not a requirement.