Module System
Prism provides a modern module system inspired by ES modules, allowing you to organize code into reusable components. The module system supports imports, exports, and proper isolation between modules.
Basic Usage
Exporting from Modules
You can export values, functions, and variables from a module:
// math.prism
export const PI = 3.14159
export const E = 2.71828
export square = x => x * x
export cube = x => x * x * x
export function circleArea(radius) {
return PI * square(radius)
}
// Default export
export default {
PI,
E,
square,
cube,
circleArea
}
Importing into Modules
Import functionality from other modules using various patterns:
// Named imports
import {PI, square} from "./math.prism"
// Default import
import math from "./math.prism"
// Namespace import
import * as mathUtils from "./math.prism"
// Renamed imports
import {square as sq, cube as cb} from "./math.prism"
// Mixed imports
import defaultMath, {PI, E} from "./math.prism"
Export Patterns
Named Exports
Export individual values with names:
// Direct export
export const VERSION = "1.0.0"
export apiKey = "secret-key" ~> 0.9
// Export functions
export greet = name => "Hello, " + name
export function calculate(x, y) {
return x * y
}
// Export after declaration
userId = 12345
userName = "Alice"
export {userId, userName}
// Export with renaming
internalFunction = () => "internal"
export {internalFunction as publicAPI}
Default Exports
Each module can have one default export:
// Export default object
export default {
name: "MyModule",
version: "1.0.0",
initialize: () => console.log("Initialized")
}
// Export default function
export default function main() {
return "Main module function"
}
// Export default value
export default 42
Re-exports
Re-export from other modules:
// Re-export specific items
export {helper1, helper2} from "./helpers.prism"
// Re-export with renaming
export {oldName as newName} from "./legacy.prism"
// Re-export all named exports
export * from "./utilities.prism"
Module Resolution
Relative Paths
Modules are resolved relative to the importing file:
// Same directory
import {util} from "./utils.prism"
// Parent directory
import {shared} from "../shared.prism"
// Nested directories
import {component} from "./components/button.prism"
File Extensions
The .prism
extension is optional in imports:
// Both work the same
import {data} from "./data.prism"
import {data} from "./data"
Module Execution
Import Hoisting
Imports are hoisted and processed before any other code:
// This works even though import appears after usage
console.log("PI is", PI)
import {PI} from "./math.prism"
Module Isolation
Each module has its own scope:
// module-a.prism
secret = "module A secret"
export public = "module A public"
// module-b.prism
import {public} from "./module-a.prism"
console.log(public) // "module A public"
console.log(secret) // Error: undefined variable
Single Evaluation
Modules are evaluated only once, even if imported multiple times:
// counter.prism
console.log("Counter module loaded")
count = 0
export increment = () => ++count
export getCount = () => count
// main.prism
import {increment, getCount} from "./counter.prism"
import * as counter from "./counter.prism"
// "Counter module loaded" printed only once
increment()
console.log(getCount()) // 1
console.log(counter.getCount()) // 1 (same instance)
Confidence in Modules
Confidence values are preserved through imports and exports:
// sensor.prism
export temperature = 23.5 ~> 0.92
export humidity = 65 ~> 0.88
export async function readSensor() {
// Simulate sensor reading
value = await getSensorValue()
return value ~> 0.9
}
// main.prism
import {temperature, readSensor} from "./sensor.prism"
// Confidence is preserved
console.log(temperature) // 23.5 ~> 0.92
reading = await readSensor()
// Confidence flows through function calls
Async Modules
Modules can use async/await at the top level:
// data-loader.prism
const data = await fetchData("/api/config")
export config = data.config
export settings = data.settings
// main.prism
import {config, settings} from "./data-loader.prism"
// Module waits for async operations before exporting
Best Practices
1. Single Responsibility
Keep modules focused on a single concern:
// Good: math-utils.prism
export add = (a, b) => a + b
export multiply = (a, b) => a * b
export average = nums => nums.reduce(add, 0) / nums.length
// Avoid: utilities.prism with mixed concerns
2. Clear Exports
Be explicit about what you export:
// Good: Clear public API
internal = "private implementation"
helper = x => x * 2
export function publicFunction() {
return helper(internal)
}
// Only publicFunction is accessible
3. Consistent Naming
Use consistent naming for modules and exports:
// user-service.prism
export function getUser(id) { /* ... */ }
export function updateUser(id, data) { /* ... */ }
export function deleteUser(id) { /* ... */ }
// Import with clear context
import * as userService from "./user-service.prism"
const user = userService.getUser(123)
4. Avoid Circular Dependencies
Structure modules to avoid circular imports:
// Bad: Circular dependency
// a.prism: import {b} from "./b.prism"
// b.prism: import {a} from "./a.prism"
// Good: Extract shared code
// shared.prism: export common = "shared"
// a.prism: import {common} from "./shared.prism"
// b.prism: import {common} from "./shared.prism"
5. Index Modules
Create index modules for cleaner imports:
// utils/index.prism
export * from "./math.prism"
export * from "./string.prism"
export * from "./array.prism"
// Clean import
import {add, capitalize, unique} from "./utils"
Common Patterns
Factory Modules
Export factory functions:
// logger.prism
export function createLogger(name) {
return {
log: msg => console.log(`[${name}] ${msg}`),
error: msg => console.error(`[${name}] ERROR: ${msg}`),
warn: msg => console.warn(`[${name}] WARN: ${msg}`)
}
}
// usage.prism
import {createLogger} from "./logger.prism"
const logger = createLogger("MyApp")
logger.log("Application started")
Configuration Modules
Centralize configuration:
// config.prism
export default {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3,
features: {
analytics: true,
debug: false
}
}
// app.prism
import config from "./config.prism"
if (config.features.debug) {
console.log("Debug mode enabled")
}
Service Modules
Organize related functionality:
// api-service.prism
baseUrl = "https://api.example.com"
export async function get(endpoint) {
response = await fetch(baseUrl + endpoint)
return response.json()
}
export async function post(endpoint, data) {
response = await fetch(baseUrl + endpoint, {
method: "POST",
body: JSON.stringify(data)
})
return response.json()
}
// usage.prism
import * as api from "./api-service.prism"
const users = await api.get("/users")
const newUser = await api.post("/users", {name: "Bob"})
Limitations
Currently, the Prism module system has some limitations:
- No Dynamic Imports: All imports must be static
- Limited Circular Dependencies: Circular dependencies are partially supported:
- ✅ Functions can reference each other across circular imports
- ❌ Values cannot be used during module initialization in circular dependencies
- Example that works:
// a.prism
import {funcB} from "./b.prism"
export funcA = () => "A"
export callB = () => funcB()
// b.prism
import {funcA} from "./a.prism"
export funcB = () => "B"
export callA = () => funcA() - Example that doesn't work:
// a.prism
import {valueB} from "./b.prism"
export valueA = "A"
// b.prism
import {valueA} from "./a.prism"
export valueB = "B uses " + valueA // valueA is undefined here
- File System Based: Modules must exist as files (no virtual modules)
- No Import Maps: No support for import maps or module aliasing
These limitations may be addressed in future versions of Prism.