.chr source format
What a .chr file is, how to name and import them, and how the dev server and build treat them.
A .chr file is a JavaScript ES module with a Chroma-specific extension. Application UI and logic live in src/**/*.chr; configuration stays in JSON (chroma.json, chroma-fs.json, plugin manifests). There is no separate .chr compiler: the dev server serves the files as JavaScript and the build bundles them with esbuild. Editors and the linter use the extension to recognize Chroma code (language id chroma).
Naming and imports
| Rule | Example |
|---|---|
Extension is .chr, one module per file | Dashboard.chr |
PascalCase for screens and components, main for the entry | src/main.chr, src/screens/Settings.chr |
No .js or .ts for app logic under src/ | CSS and images are fine |
| Imports include the extension | import { App } from "./App.chr" |
Leaving off the extension (from "./App") fails in the browser, which resolves modules without guessing extensions. Engine imports use the relative path to pkg/: ../pkg/chroma.js from src/, ../../pkg/chroma.js from src/components/.
Folder layout
my-app/
├── chroma.json
├── index.html
├── src/
│ ├── main.chr # bootstrap: renders the root component
│ ├── App.chr # root component
│ ├── screens/ # one file per screen
│ │ ├── Dashboard.chr
│ │ └── Settings.chr
│ ├── components/
│ │ └── Button.chr
│ └── lib/
│ └── format.chr # shared helpers
└── plugins/chroma new creates main.chr, App.chr, and components/; screens/ and lib/ are conventions you add when the app grows.
A multi-file example
// src/lib/format.chr
export function formatPrice(cents) {
return (cents / 100).toFixed(2);
}// src/screens/Catalog.chr
import { e } from "../../pkg/chroma.js";
import { formatPrice } from "../lib/format.chr";
export function Catalog() {
return e("p", null, "Total: $", formatPrice(1250));
}// src/App.chr
import { e } from "../pkg/chroma.js";
import { Catalog } from "./screens/Catalog.chr";
export function App() {
return e("main", null, e(Catalog));
}// src/main.chr
import { render } from "../pkg/chroma.js";
import { App } from "./App.chr";
render(App, document.getElementById("app"));index.html loads the entry with <script type="module" src="./src/main.chr"></script>. To switch between screens, use the screen router.
How the toolchain treats .chr
| Stage | Behavior |
|---|---|
chroma dev | The dev server answers .chr with Content-Type: text/javascript, so the browser loads it as a module. |
chroma build (dynamic) | esbuild runs with --loader:.chr=js and bundles everything reachable from build.jsEntry into one file. |
chroma validate | Accepts a .chr build.jsEntry and warns about leftover .js files under src/. Warnings do not fail the command. |
chroma-lint / VS Code | .chr mode turns on automatically when build.jsEntry ends in .chr or any .chr file exists under src/. Then .js, .jsx, .mjs, .cjs, .ts, and .tsx files under src/ are errors, and build.jsEntry must end in .chr. Details and the opt-in keys: Linter. |
Set build.jsEntry in chroma.json to your entry module (src/main.chr in the templates). Treat dist/ and bundle.js as build output, not source.
Migrate from .js
- Rename
src/**/*.jsto.chr. - Add
.chrto every import of a renamed file. - Point
build.jsEntryatsrc/main.chrand the<script>inindex.htmlat./src/main.chr. - Run
chroma validate, thenchroma dev.
Related
- Linter: the
chroma/chr-only-extensionrule and other checks - VS Code extension: syntax support for
.chr - CLI reference