Farm.js

Bundle Analyzer

@farm.js/analyzer explains the final production output in Farm terms. It separates browser and server code, connects emitted HTML pages to their initial JavaScript and CSS, and can fail a build when a readable size limit is exceeded.

Install

pnpm add -D @farm.js/analyzer

Start with no configuration

farm.config.ts
import { analyzer } from "@farm.js/analyzer";
import { defineConfig } from "@farm.js/core";

export default defineConfig({
  plugins: [analyzer()],
});

Run a production build. Farm writes the visual report to .farm/analyze.html:

pnpm farm build

The report shows raw, gzip, and Brotli sizes for:

  • Each emitted HTML page and the initial JavaScript and CSS it loads
  • Every client JavaScript and CSS bundle
  • JavaScript in the final server output
  • Images, fonts, data, and other public assets

Static imports are followed, but lazy import() chunks are not charged to a page's initial load. They remain visible in the complete client bundle list.

Open the report automatically

Use open during local performance work:

analyzer({ open: true });

It is off by default so CI and remote builds do not try to launch a browser.

Protect size in CI

The limits use names that match the report:

farm.config.ts
analyzer({
  json: true,
  limits: {
    page: "200kb",
    asset: "100kb",
    client: "500kb",
    server: "2mb",
  },
});
LimitWhat it measures
pageInitial JavaScript and CSS for each emitted HTML page
assetEach individual emitted JavaScript or CSS file
clientAll emitted client JavaScript and CSS
serverAll JavaScript found in the final server output

Limits use gzip size by default. A number means bytes; a string accepts b, kb, mb, or gb. An exceeded limit fails the production build after the report is written, so the failure remains inspectable.

Use Brotli instead, or report limit failures without stopping the build:

analyzer({
  metric: "brotli",
  onLimit: "warn",
  limits: { page: "180kb" },
});

Save machine-readable results

json: true writes .farm/analyze.json next to the default HTML report. It includes the report, configured limits, and every violation. Pass a path when another tool expects a specific location:

analyzer({
  output: "reports/build.html",
  json: "reports/build.json",
});

Set output: false when only JSON and build limits are needed:

analyzer({
  output: false,
  json: true,
  limits: { client: "500kb" },
});

Options

OptionTypeDefaultPurpose
enabledbooleantrueTemporarily disable analysis.
outputstring | false.farm/analyze.htmlHTML report path, or no HTML report.
jsonboolean | stringfalseWrite companion JSON or use a custom path.
openbooleanfalseOpen the HTML report after the build.
metric"raw" | "gzip" | "brotli""gzip"Compression metric used by limits.
limitsAnalyzerLimits{}Optional page, asset, client, server limits.
onLimit"error" | "warn""error"Fail the build or print warnings.

How page attribution works

Farm reads emitted HTML, resolves its script, stylesheet, module-preload, and preload references, then follows static JavaScript and CSS imports. A base path in a public URL is normalized back to the emitted asset, and shared chunks are counted once per page.

This is exact for HTML that exists in the production output. A dynamic SSR route has no standalone HTML file to inspect, and its server work can depend on the request. The analyzer therefore does not invent a route number for it. Its client chunks remain in the client total and its runtime code remains in the server total.