# Project structure & imports

> How a game project is laid out, which imports are allowed, and how TypeScript is built.

A game project is an ordinary folder of JavaScript or TypeScript files, but
two rules make it different from a typical Node or web project. Knowing them
up front saves you from most build errors.

## What goes into a release

A project has a `game.json` at its root. Its `entry` field names the first
module, where your game starts.

When `disko` packages your game, it starts at the entry and follows every
import. Whatever it reaches goes into the release: modules and imported
assets alike. Anything it doesn't reach is left out, even if it sits in the
same folder.

```mermaid alt="game.json names src/main.ts as the entry. main.ts imports scoring.ts and art/puck.png, so all three are packaged. notes.txt and old.ts are not imported by anything, so they are left out."
flowchart LR
  manifest["game.json"] -- "entry" --> main["src/main.ts"]
  main --> scoring["src/scoring.ts"]
  main --> puck["art/puck.png"]
  notes["notes.txt"]
  old["src/old.ts"]
  subgraph release ["In the release"]
    main
    scoring
    puck
  end
```

This means you never list files by hand, and leftovers can't leak into a
release by accident.

```text
arena/
  game.json
  src/main.ts
  src/scoring.ts
  art/puck.png
```

## Games are self-contained

A game runs inside the room, in a sandbox, not in Node or a browser. It can't
use npm packages, Node modules or the web, so it can only import two kinds of
things:

- **Your own files,** by relative path.
- **The disko standard library:** `disko:math` and `disko:geometry`.

The globals `game`, `world` and `ui` are available in every module without
importing them. They're how your code talks to disko.

### Writing imports

A relative import starts with `./` or `../` and names the exact file,
extension and capitalization included: `./scoring.ts`, `../art/puck.png`.
There's no guessing: `./scoring` (no extension) and `./utils` (a folder) are
both errors. An import also can't reach outside the project folder.

```ts title="src/main.ts"
// Relative imports name the exact file, extension included.
import { goalScored } from "./scoring.ts";
// Type-only imports are erased; a module used only for types is not packaged.
import type { Score } from "./scoring.ts";
// Built-in standard-library modules.
import { clamp } from "disko:math";

let score: Score = { red: 0, blue: 0 };

game.on("crossing", ({ direction }) => {
  score = goalScored(score, direction === "forward" ? "red" : "blue");
  game.send(
    `Red ${String(score.red)} - ${String(clamp(score.blue, 0, 99))} Blue`,
  );
});
```

Anything else is rejected: npm package names, `node:` modules, URLs and
absolute paths.

Modules don't need to export anything; the entry's exports are ignored.
Circular imports work the way they do in any ES module code.

## Code that runs at load time

Top-level code runs once when the room loads your game, and it must finish
right away: it sets up the game and builds the first Menu within a short
startup deadline. So a few language features that wait or load code later
aren't allowed:

| Construct             | Problem code                                     |
| --------------------- | ------------------------------------------------ |
| `import(...)`         | [`import_dynamic`](/errors/import_dynamic)       |
| `import.meta`         | [`import_meta`](/errors/import_meta)             |
| Top-level `await`     | [`top_level_await`](/errors/top_level_await)     |
| `import … with { … }` | [`import_attributes`](/errors/import_attributes) |

Modules are ES2023 modules in strict mode.

## TypeScript

If `entry` is a `.ts` file, the project is TypeScript.

The build always uses its own copy of TypeScript with fixed settings: strict
checking, an ES2023 target, no browser (DOM) types, and disko's own types for
the globals, the `disko:` modules and asset imports. Your `tsconfig.json`
only configures your editor, so what builds on your machine builds everywhere.

A type error stops the build ([`ts_type_error`](/errors/ts_type_error)), the
same way a syntax error would.

- Import `.ts` files with their `.ts` extension; the build rewrites it to
  `.js`. You can mix in `.js` files too.
- `import type` disappears in the build, and a module imported only for its
  types isn't packaged.
- Types for the globals live in the `Disko` namespace, for example
  `Disko.Player`, `Disko.Disc` and `Disko.UiNode`.
- Not supported: `.tsx`, `.mts`, `.cts` and local `.d.ts` files,
  `import x = require()`, `export =`, `/// <reference>` directives and
  `declare module` augmentation.

JavaScript projects are packaged exactly as written, without minification.

## Limits

A release is at most 10 MiB compressed, 2 MiB per module and 16 MiB per
asset. See [game.json](/reference/game-json#limits) for every limit.
