Use build with a BuildConfig to produce a matching asset and manifest set. Compiler, browser assets and server inputs must describe the same build. This reference documents the library API; the build guide covers commands and project setup.
Read the usage guide or all entry points.
20 exports · published 3.0.0 declarations
import { build } from '@getforma/build';Values and functions
build#
declare function build(userConfig: BuildConfig): Promise<BuildResult>;Run the Forma build pipeline.
Orchestrates: clean, CSS generation, font copying, esbuild bundling (with optional SSR IR emission), island registry generation, content hashing, compression, manifest generation, service worker generation, and budget warnings.
A production build writes into a staging directory beside outputDir and
swaps it in only when every step has succeeded, so a build that fails part
way leaves the previous output exactly as it was, and nothing beside it
(L-21). The application embeds outputDir in its binary, and used to embed
half a build after a failure. Watch mode writes in place, as before.
Verified by: packages/build/tests/build.test.ts > "leaves the previous build byte-identical when a build fails after writing files"
Verified by: packages/build/tests/build.test.ts > "a failed first build leaves no output directory and nothing beside it"
Related declarations: BuildConfig, BuildResult
Exported types
AssetManifest#
interface AssetManifest {
version: 2;
writer: string;
app: string;
build: string;
aliases: Record<string, string>;
assets: Record<string, AssetMetadata>;
routes: Record<string, RouteManifest>;
}Related declarations: AssetMetadata, RouteManifest
Member notes
build
The deployment identity (FORMA-186, DR-29): 16 lowercase hexadecimal
characters computed from the build's own outputs and the writers'
versions before anything is named, compiled into every JavaScript bundle
(__FORMA_BUILD__, which core compares with the page's
data-forma-build), and the same for identical inputs however often they
are built, from any directory when built from the project root (esbuild
hashes input paths relative to its working directory). dev in watch mode.
AssetMetadata#
interface AssetMetadata {
immutable: boolean;
integrity: string;
retained?: boolean;
}Member notes
immutable
The build guarantees this exact filename is immutable, independent of its spelling.
integrity
Full SHA-384 of the final uncompressed file, used by SRI and the worker.
retained
A file of the previous release kept for tabs still open on it (FORMA-186, L-25).
BuildConfig#
interface BuildConfig {
entryPoints: BuildEntry[];
routes: Record<string, RouteMapping>;
cssEntries?: CssEntry[];
outputDir: string;
formaAlias?: string;
codeSplitting?: boolean;
serviceWorker?: 'on-demand';
app?: string;
previewFrame?: boolean;
fontDir?: string;
ssr?: boolean;
ssrEntryPoints?: Record<string, string>;
watch?: boolean;
budgetThreshold?: number;
serverInlined?: string[];
strict?: boolean;
diagnosticsJson?: string;
previousDist?: string;
security?: boolean;
}Related declarations: BuildEntry, CssEntry, RouteMapping
Member notes
entryPoints
JavaScript entry points to build
routes
Route-to-asset mapping for manifest generation
cssEntries
CSS entries to generate (tailwind and/or concatenation)
outputDir
Output directory for all built assets
formaAlias
3.x migration bridge for 'formajs'; warns once per build() with importing files. Prefer '@getforma/core'.
codeSplitting
Bundle one shared ESM graph, preserving import() as separately loaded chunks.
serviceWorker
Production worker policy; defaults to on-demand. Only requested, manifest-listed immutable code/style/WASM assets with verified bytes enter its cache.
app
Application/worker ID: 1–63 lowercase letters, digits or hyphens, starting with a letter or digit. If omitted, a valid nearest Cargo package name is used. Otherwise supply it explicitly; it is always written to manifest2.
previewFrame
Copy @getforma/core's preview-frame runtime into the output, for
forma-server's preview frame (PageResponse::frame, FORMA-125; FORMA-175).
Off by default, where the output is what it was.
On, the build copies dist/formajs-frame.global.js (core 2.1.0 and later;
never core's dist/forma-runtime.js, the CDN alias of the auto-mounting
runtime) from the core the bundles import, byte for byte, as
forma-runtime.<hash>.js, and names it in the manifest under
aliases["forma-runtime.js"], where PreviewFrame::load reads it at start
and allows it by its SHA-256 hash. Which core: before anything is
written, each entry is bundled in memory with the bundles' own esbuild
options (so formaAlias, resolved from the working directory, and
tsconfig paths lead where they lead the bundles), and every file it
loads is mapped to its package; when the bundles import none, the core
installed where the build runs. Watch mode writes it unhashed as
forma-runtime.js, named the same way in the dev manifest. It is
inlined, never fetched: no compressed copies, and no place in the
worker's lists. A core without it fails the build with BUILD-805, a
runtime forma-server would refuse to inline (empty, not UTF-8,
</script, <!--, a carriage return or a NUL) with BUILD-806, and
bundles that import cores whose runtimes differ with BUILD-807, before
anything is written.
fontDir
Directory containing .woff2 font files to copy
ssr
Enable SSR IR emission
ssrEntryPoints
SSR entry points for IR emission (page name -> entry path)
watch
Enable watch mode
budgetThreshold
Budget threshold in bytes (Brotli-compressed), default 200_000
serverInlined
Files that should keep unhashed copies (e.g. for server-inlined assets). A zero-JS SSR owner cannot request executable server inlining.
strict
Legacy compatibility option. SSR publication always requires the 3.0 server-renderable subset. False and FORMA_STRICT=0 cannot downgrade errors. Every page is checked; watch retains its previous good product on failure.
diagnosticsJson
Also write every SSR diagnostic as JSON to this path (relative to the
working directory), whether or not the build fails: the machine-readable
report an agent can act on (N12). It must be outside outputDir, which
the build replaces whole and serves.
previousDist
The previous release's dist/, which the application's pipeline keeps as a
release artifact (FORMA-186, DR-29, L-25). The build copies that release's
own hashed assets into the new dist/, listed in the manifest retained
and immutable, so a tab opened before a deploy still loads its chunks
from the one dist/ the application embeds. One generation: what the
previous release itself retained is not copied again. A directory other
than outputDir, which the build replaces whole. Leave it out for a first
deploy, or when no release is live. The previous dist is trusted like the source: the check below catches a damaged or
altered file, not someone who can also rewrite its manifest. Every copied file is checked against
the SHA-384 the previous manifest recorded (BUILD-821); a path that is
not a usable previous release fails the build (BUILD-820). Ignored in
watch mode.
security
Mark this release as one that fixes a vulnerability (L-16, SR45): nothing is
retained from previousDist, so no earlier build's asset is served and a
revoked build's tabs meet 404s for their lazy chunks. The application's
deploy pipeline must mark every such release, a Forma security upgrade
included. FORMA_SECURITY_RELEASE=1 (or true, any case) in the environment marks it too; any
value other than 1, true, 0, false or empty fails the build (BUILD-822); and a
false here does not unmark it.
BuildDiagnostic#
interface BuildDiagnostic {
code: string;
severity: 'error' | 'warning' | 'note';
page: string;
file: string;
line: number;
column: number;
message: string;
why: string;
fix: string;
chain?: BuildDiagnosticChainLink[];
}One coded SSR diagnostic (FORMA-119). The catalogue, one entry per code
with its fix, is packages/compiler/docs/subset.md.
Related declarations: BuildDiagnosticChainLink
Member notes
code
The stable code, e.g. BUILD-101.
severity
Unsupported SSR constructs are always error; note never fails a build.
page
The page (entry outfile without .js) whose module reported it.
file
The file to edit; empty for a build-level diagnostic.
line
1-based line and column in file, as authored; 0 when unknown.
message
The specific report: the construct, what happens, and its fix.
why
The catalogue's why and fix for the code.
chain
BUILD-403: the composition chain from the page down.
BuildDiagnosticChainLink#
interface BuildDiagnosticChainLink {
component: string;
file: string;
line: number;
column: number;
}One link of a composition chain, from the page down (BUILD-403).
BuildEntry#
interface BuildEntry {
entry: string;
outfile: string;
ssr?: boolean;
}getforma /build — Type definitions
Configuration and result types for the Forma build pipeline.
Member notes
entry
Entry point file path
outfile
Output file path (relative to outputDir)
ssr
Disable SSR for this entry in an otherwise SSR-enabled application.
BuildResult#
interface BuildResult {
manifest: AssetManifest;
buildHash: string;
warnings: string[];
diagnostics: BuildDiagnostic[];
sizes: RouteSize[];
sizeReport: SizeReport;
}Related declarations: AssetManifest, BuildDiagnostic, RouteSize, SizeReport
Member notes
diagnostics
Every coded SSR diagnostic the build reported, page by page.
sizes
The size report's routes (FORMA-021, FORMA-190): per route, its class, what
it loads cold in raw, gzip -9 and Brotli bytes, Forma's code
(@getforma/core and alien-signals as bundled) beside the application's,
the service worker it registers, and its ceilings. A production build fails
with BUILD-819 before writing its output when a route is over one:
Forma's code over N2's FORMA_CODE_CEILING_GZIP (28,100 B; decision brief
L-64), all of its JavaScript over its class's (none for a route without an
interactive region, 75 KiB, 365 KiB for a canvas). Empty in watch mode,
whose unminified bytes are not what a route ships. The same objects as
sizeReport.routes.
sizeReport
The one report (FORMA-190): sizes with the service worker, the preview
frame's runtime when previewFrame is on (the frame document row), and the
build's times by phase. A production build prints it and writes it, without
the times (so the output stays the same bytes for the same sources), as
size-report.json beside manifest.json. No routes in watch mode.
BuildTimes#
interface BuildTimes {
totalMs: number;
phases: {
bundle: number;
hash: number;
compress: number;
manifest: number;
};
}Wall-clock milliseconds, per phase of a production build and in all (N7).
ByteSizes#
interface ByteSizes {
raw: number;
gzip: number;
brotli: number;
}Raw (minified), gzip -9 and Brotli-11 bytes.
CssEntry#
interface CssEntry {
input: string | string[];
outfile: string;
tailwind?: boolean;
}Member notes
input
Glob or array of CSS files to concatenate
outfile
Output file path (relative to outputDir)
tailwind
If true, run
tailwindcss /cli on the first input file
FileSize#
interface FileSize extends ByteSizes {
file: string;
}A file the build writes that is not a route's: the service worker, the preview frame's runtime.
Related declarations: ByteSizes
FrameSize#
interface FrameSize extends FileSize {
sourceRaw: number;
}The preview frame's runtime in the output: the served file, and core's before the build minified it.
Related declarations: FileSize
Member notes
sourceRaw
Raw bytes of core's dist/formajs-frame.global.js.
RouteCeilings#
interface RouteCeilings {
forma: number;
javascript: number;
}What a class may ship, bytes gzip -9: Forma's code, and all of the route's JavaScript.
RouteClass#
type RouteClass = 'no-interactive-region' | 'islands' | 'whole-page-mount';How a route is classified (DR-35).
RouteManifest#
interface RouteManifest {
mode: 'ssr' | 'browser';
js: string[];
modulepreload?: string[];
css: string[];
fonts: string[];
total_size_br: number;
budget_warn_threshold: number;
ir?: string;
}Member notes
modulepreload
Static ESM dependencies only; dynamic import() chunks are intentionally excluded.
RouteMapping#
interface RouteMapping {
js: string[];
css: string[];
fonts?: string[];
canvas?: boolean;
}Member notes
js
Configured entry base names. The first selects the native owner before static SSR removes its browser output; additional entries must be Browser.
css
Base names (without extension) of CSS files for this route
fonts
Font file names for this route (optional, defaults to all fonts in outputDir)
canvas
Declare the route a canvas (N2, FORMA-190): a page that hosts many modules and is JavaScript-heavy by design. The size report holds all of its JavaScript to 365 KiB gzip -9 instead of the 75 KiB of other pages; Forma's own code stays under N2's 28,100 B either way. It raises nothing for a route without JavaScript. A heavy page that is not a canvas is trimmed or split, not declared.
RouteSize#
interface RouteSize extends ByteSizes {
route: string;
class: RouteClass;
canvas: boolean;
files: RouteSizeFile[];
forma: ByteSizes;
application: ByteSizes;
worker: ByteSizes;
javascript: ByteSizes;
ceilings: RouteCeilings;
exceeded: Array<'forma' | 'javascript'>;
over: boolean;
}One route's size report.
Related declarations: ByteSizes, RouteCeilings, RouteClass, RouteSizeFile
Member notes
class
What the route is: the ceilings follow from it (ceilings).
canvas
The route is declared a canvas (canvas: true): 365 KiB of JavaScript instead of 75 KiB.
files
The files the route loads cold: its js and modulepreload lists. Empty for a route with no JavaScript.
forma
@getforma/core and alien-signals as bundled: N2's figure is forma.gzip.
application
Everything else in the route's files: the application's own code and its other dependencies.
worker
The service worker the route registers: sw.js for a route with JavaScript, zero for one without.
javascript
All the JavaScript the route ships: files and worker. The class's JavaScript ceiling is measured on javascript.gzip.
exceeded
Which ceilings the route is over: Forma's code, all of its JavaScript, or both.
over
exceeded is not empty: the build fails.
RouteSizeFile#
interface RouteSizeFile extends ByteSizes {
file: string;
forma: ByteSizes;
}One file a route loads cold, with Forma's share of it.
Related declarations: ByteSizes
SizeReport#
interface SizeReport {
version: 1;
ceilings: {
forma: number;
page: number;
canvas: number;
};
routes: RouteSize[];
worker?: FileSize;
frame?: FrameSize;
times?: BuildTimes;
}The one report of a production build.
Related declarations: BuildTimes, FileSize, FrameSize, RouteSize
Member notes
ceilings
The policy: Forma's code per route, then all of a page's JavaScript, then a canvas's (gzip -9 bytes).
worker
The generated sw.js.
frame
The preview frame's runtime, inlined into every preview document and never fetched (previewFrame); the frame document row.
times
In the printed report and the build result; never in the file.