Multi-File Imports
Open the editor to try multi-file projects live.
Split simulation logic across files with named ES import syntax. The bundler resolves dependencies at compile time.
Creating Files
Use + in the file explorer. One file is the entry (main.js by default — shown with an entry badge). New files are modules (isEntry: false). There is no UI to change which file is entry; keep your return in main.js.
Critical Limitation
Module files do not receive time, dt, state, w, or h. They run once at compile time. Export pure helpers and pass frame data from the entry:
// physics.js
function step(body, dt, g) {
body.vy += g * dt;
body.x += body.vx * dt;
body.y += body.vy * dt;
}
module.exports = { step };
// main.js
import { step } from './physics';
step(state.ball, dt, 200);Import Syntax
import { gravity, drag } from './physics';
import { Particle } from './particle';| Feature | Supported | Example |
|---|---|---|
| Named imports | Yes | import { foo, bar } from './mod' |
| Default imports | No | import foo from './mod' |
| Namespace imports | No | import * as foo from './mod' |
| Dynamic imports | No | import('mod') |
export keyword in modules | No | Use module.exports |
Relative ./ | Yes | './physics', './physics.js' |
Parent ../ | No | Flat project namespace only |
| Multiline import statements | No | Keep imports on one line |
Builtin ai module
ai is the one built-in module — no file needs to exist. It supports default, namespace, and named imports: import ai from 'ai', import * as ai from 'ai', or import { seek, flock } from 'ai'. It is not a global, so each file that uses it must import it. See the AI cookbook.
How Modules Work
Non-entry files compile like CommonJS:
new Function('exports', 'module', /* optional __modules */, code)They run once per compile; module.exports is cached. They are not re-executed every frame.
// physics.js
function gravity(body, dt, g) {
body.vy += g * dt;
}
function drag(body, dt, coeff) {
body.vx *= (1 - coeff * dt);
body.vy *= (1 - coeff * dt);
}
module.exports = { gravity, drag };module and exports are injected — do not use ES export in module files.
Example: Particle Spawner
main.js (entry):
import { createParticle, updateParticles } from './physics';
if (!state.init) {
state.particles = [];
state.init = true;
}
if (state.clickX !== undefined) {
for (let i = 0; i < 8; i++) {
state.particles.push(createParticle(state.clickX, state.clickY));
}
state.clickX = undefined;
state.clickY = undefined;
}
state.particles = updateParticles(state.particles, dt, w, h);
return {
particles: state.particles.map((p) => ({
x: p.x, y: p.y, r: 5, color: p.color,
})),
text: { count: state.particles.length },
};physics.js:
import { COLORS } from './constants';
function createParticle(x, y) {
return {
x, y,
vx: (Math.random() - 0.5) * 250,
vy: -150 - Math.random() * 100,
life: 1,
decay: 0.2 + Math.random() * 0.5,
color: COLORS[Math.floor(Math.random() * COLORS.length)],
};
}
function updateParticles(particles, dt, w, h) {
return particles.filter((p) => {
p.vy += 200 * dt;
p.x += p.vx * dt;
p.y += p.vy * dt;
p.life -= p.decay * dt;
if (p.y > h - 20) { p.y = h - 20; p.vy *= -0.5; p.vx *= 0.8; }
if (p.x > w - 10 || p.x < 10) p.vx *= -1;
return p.life > 0;
});
}
module.exports = { createParticle, updateParticles };constants.js:
const COLORS = ['#8B5CF6', '#F59E0B', '#EF4444', '#22C55E', '#3B82F6'];
module.exports = { COLORS };The bundler topo-sorts the graph and compiles modules before rewriting entry imports.
Circular Dependencies
Cycles throw at compile time. Move shared pieces into a third leaf module.
Import Resolution
| Specifier | Resolution |
|---|---|
'./utils' | File named utils or utils.js |
'./utils.js' | File named utils.js |
'../utils' | Not supported |
Specifier must match the explorer file name (with or without .js).
Troubleshooting
| Error | Cause |
|---|---|
| Module not found | Specifier ≠ file name |
| Circular import detected | Cycle in the graph |
| Error in module 'X' | Syntax/runtime error while compiling that file |
dt is not defined in a module | Pass dt as an argument from the entry |