npm / flash-theater-compiler v0.0.1

flash-theater-compiler

The .thr/.flsh → .xml/.brs compiler. Parses with flash-parser's own vendored grammar — never hand-parses BrightScript or XML itself — and generates static SceneGraph markup plus generated observer wiring.

How it works

📝

.thr source

field / derived / private-public function, template markup that is always valid XML.

🧬

flash-parser

Lossless CST + typed AST — the compiler's only parsing layer.

📄

.xml + .brs

Static SceneGraph tree, generated init()/onChange handlers — zero vdom, zero diffing.

📺

Real Roku device

Sideloaded and verified live — reactive fields update the screen with no hand-written observer.

Install

terminal
npm install --save-dev flash-theater-compiler

Requires Node.js ≥ 24.

CLI

The primary way to use this package — compiles a whole project convention-based, like tsc reading tsconfig.json, no glob argument. Installing the package puts flash-theater on your PATH (via npx flash-theater or an npm script).

terminal
flash-theater compile [--check] [--src-dir <dir>] [--out-dir <dir>] [--env <name>]
flash-theater zip [--out-dir <dir>] [--env <name>] [--app-name <name>]

compile

terminal
flash-theater compile
# Compiles every .thr/.flsh file under src/ into out/ (per
# flash-theater.config.json, or the srcDir/outDir defaults), copying
# every other src/ file (manifest, images/, source/Main.brs, ...)
# through untouched. Wipes and fully regenerates out/ every run.

flash-theater compile --check
# Same discovery/compile pass, but writes nothing — just reports
# "OK <path>" per file, or the first compile error. Use in CI.

flash-theater compile --src-dir client --out-dir build
# Override flash-theater.config.json's srcDir/outDir for this run.

flash-theater compile --env staging
# Loads environments/staging.config.json (+ an optional, git-ignored
# environments/staging.local.config.json layered on top), writes to
# out-staging/ instead of out/, and patches src/manifest's declared
# manifestOverrides into the copied manifest. Falls back to the
# FLASH_THEATER_ENV env var when --env isn't passed, so it flows
# through an npm script's "compile && zip" chain unchanged.

zip

terminal
flash-theater zip
# Zips out/ into dist/<app-name>.zip — <app-name> defaults to the
# current directory's package.json "name" field, or its own directory
# name if there's no package.json.

flash-theater zip --app-name my-channel --out-dir build
# Explicit app name and a matching --out-dir (zip never reads srcDir —
# there's nothing under src/ it needs).

flash-theater zip --env staging
# Zips out-staging/ (matching a prior "compile --env staging") into
# dist/<app-name>-staging-<version>.zip.

Configuration — flash-theater.config.json

The file itself is entirely optional — sibling of src/ and out/. But once it exists, for any reason (even just to set srcDir/outDir), designResolution becomes mandatory in it — whether or not the project uses scale anywhere. With no config file at all, defaults apply and using scale anywhere becomes a compile error instead (dsl/scale-requires-config).

flash-theater.config.json
// flash-theater.config.json — sibling of src/, out/, package.json
// The file itself is entirely optional. But once it exists — for ANY
// reason, even just to set srcDir/outDir — "designResolution" becomes
// mandatory in it, whether or not this project uses "scale" anywhere.
{
  "designResolution": "fhd",   // "hd" | "fhd" — mandatory once this file exists
  "srcDir": "src",             // optional, defaults to "src"
  "outDir": "out",             // optional, defaults to "out"
  "exclude": ["**/*.snap.thr"] // optional glob patterns, relative to srcDir
}

environments/<name>.config.json (for --env) is a separate, per-environment file — see GRAMMAR.md's "Environments" section for its own variables/manifestOverrides/exclude/include shape, and GRAMMAR.md's "scale" section for what designResolution actually controls.

Library API

For embedding the compiler directly — a build tool integration, a playground, a linter. compileThrSource/compileFlshSource compile one file in isolation and throw a CompileError on any diagnostic (never a partial/best-effort result).

example.ts
import { compileThrSource } from 'flash-theater-compiler';
import { CompileError } from 'flash-theater-compiler/dsl-ast';

try {
  const { xml, brs, usesStore, usesFocusSystem, usesRouter } = compileThrSource(
    `<script>
field count: integer = 0
derived doubled: integer = count * 2
</script>
<component>
  <Label id="label" text="{doubled}" />
</component>`,
    'DoubledCounter',
  );

  // xml — a static SceneGraph component definition
  // brs — generated init()/setFields()/onChange handlers, no hand-written observers
  // usesStore/usesFocusSystem/usesRouter/... — see "What compileThrSource does NOT do" below
} catch (err) {
  if (err instanceof CompileError) {
    // err.diagnostic — { code: string, message: string, span?: { line: number } }
    console.error(`[${err.diagnostic.code}] ${err.diagnostic.message}`);
  }
}

What compileThrSource does NOT do

It does not copy the built-in runtime components (the focus manager, router, store, task manager, or any of the Safe*/Scale/Stream/Http codegen helpers) into your output — it only tells you which ones this one file needs, via the usesStore/usesFocusSystem/usesRouter/usesTaskManager/usesComparisonHelper/usesSafeNotHelper/usesStreamHelper/usesHttpRequestHelper/usesScaleHelper/usesRelationalHelper boolean flags on its return value. For a whole project's worth of files, compiled and wired together the same way the CLI does it (this is exactly what runCompileCommand calls), use compileApp(inputs, srcRoot, outRoot, config, envVariables) from the same package instead — see cli.ts for the exact reference implementation (which runtime asset gets copied for which flag).

Browser-safe subpath

This site's own live playground on the homepage imports the compiler this way — no Node built-ins pulled into the browser bundle.

example.ts
// A browser-safe subpath (no node:fs/node:path pulled in, unlike the
// package's main entry point which also re-exports the Node-only CLI) —
// what this site's own live playground imports.
import { compileThrSource } from 'flash-theater-compiler/compile';
import { CompileError } from 'flash-theater-compiler/dsl-ast';

Documentation