Imports and Exports

Lux modules are private by default. Top-level declarations create bindings that are visible to all parts of the same module, but other modules only see names that are explicitly exported.

export is a mapping from an internal binding to a public API name. It is not a general "make this file public" switch.

Import Forms

Named imports bind selected public names from another module into the current part. Import aliases rename only the local binding.

Named import and alias
import { player_inventory as inventory, grant } from "inventory"

grant(ply, inventory.defaultItem)

Namespace imports keep the imported module table under one local binding.

Namespace import
import * as inventory from "inventory"

inventory.grant(ply, inventory.defaultItem)

Side-effect imports load the module without binding its exports. Use these for registration modules, not for normal API sharing.

Side-effect import
import "hud/register"

Macro imports are compile-time imports. They do not become runtime locals.

Macro import
import macro { hookPaint } from "@lux/gmod/macros"

Top-level imports are part-local. If two part files need the same external module, each part imports it explicitly.

Path Resolution

Relative-looking paths without a package prefix resolve inside the current Lux project. Package paths use package ids such as @lux/std.

Project module and package module
import { grant } from "inventory"
import { arr } from "@lux/std"

Do not import another part file from the same module. All part files in one module already share the same logical module scope.

Imported Bindings Are Immutable

An imported name is a local binding owned by the importing part. Reassigning it would make later use-sites ambiguous, so Lux rejects it.

Imported binding cannot be assigned
import { grant } from "inventory"

grant = nil

If a mutable wrapper is needed, create a new local variable.

Exports Are Public Name Mappings

The simplest export maps a binding to the same public name.

Export same public API name
local player_inventory = {}

export { player_inventory }

Other modules import the public name, not the source filename or internal file layout.

Import the public name
import { player_inventory } from "inventory"

The Lua-style alias form maps public_name = local_binding.

Lua-style export alias
local player_inventory = {}

export { p_inv = player_inventory }

After this export, external users can only import p_inv; the original internal name remains private.

Importing an exported alias
import { p_inv as inventory } from "inventory"

Lux also accepts the JS-style export alias for readability in mixed teams.

as-style export alias
local player_inventory = {}

export { player_inventory as p_inv }

The spec always defines the result as exported_name -> binding_id; both alias spells produce the same export entry.

Exported Declarations

You can export while declaring a module-scope binding.

Exported declaration
export fn grant(ply, itemId) {
  inventory.grant(ply, itemId)
}

Method path declarations are not module-scope bindings and cannot be exported directly.

Cannot export a method path declaration
export fn PANEL:Paint(w, h) {
  paintPanel(self, w, h)
}

Use a private method path for GMod hooks and export a separate named helper when other modules need an API.

Realm Exports

An export may have an explicit realm. The export realm controls which generated artifact exposes the public name.

Realm export
server fn grantServer(ply, itemId) {
  grantItem(ply, itemId)
}

export server { grant = grantServer }

Exported declarations can combine visibility and realm in one declaration.

Realm exported declaration
export client fn openInventoryPanel(ply) {
  vgui.Create("LuxInventoryPanel"):OpenFor(ply)
}

The supported declaration order is:

export [realm] fn name...
[realm] fn name...

client export fn ... is intentionally not part of the style.

Exports Can Narrow, Not Expand

An export cannot make a binding available in a realm where the binding itself is not available.

server-only cannot export as shared
server local player_inventory = {}

export shared { p_inv = player_inventory }

A shared binding may be exported as shared, client-only, or server-only. This is narrowing the public API surface, not changing the binding's safety.

Shared binding can be narrowed
fn normalizeId(id) tostring(id)

export server { normalizeId }

Formally, exported_realms must be a subset of binding_available_realms.

Export Restrictions

MVP 0.1 keeps export rules strict:

  • Only module-scope bindings may be exported.
  • Imported bindings from another module cannot be re-exported directly.
  • The public exported name participates in duplicate export checks.
  • Exporting with an alias does not expose the original internal name.
  • Export realms cannot expand the binding's available realms.
Cannot re-export an imported binding
import { grant } from "inventory"

export { grant }

Wrap intentionally forwarded APIs so diagnostics and source maps point at your module.

Forward API through a wrapper
import { grant as grantInventory } from "inventory"

export fn grant(ply, itemId) {
  grantInventory(ply, itemId)
}

public all

public all is an escape hatch for modules that intentionally expose every module-scope binding in the current effective realm.

public all
public all

fn add(a, b) a + b
fn sub(a, b) a - b

Prefer explicit export { ... } in application code. public all is best for small compatibility modules, generated modules, or packages whose entire file is already a public API surface.