Skip to content

Quick start (JavaScript / TypeScript)

This guide takes you from an empty directory to a TypeScript package that is compiled, tested and runnable. The whole project is a PROJECT.fabr file and your sources: there is no package.json, tsconfig.json, test configuration or lockfile to write. To convert a project you already have, see Migrating an npm, Yarn or pnpm project.

Fabr needs Node.js 22.19 or later to run. Install the fabr command globally:

Terminal window
npm install -g @fabr-build/cli

That also installs @fabr-build/js, the plugin with the JavaScript and TypeScript rules, and @fabr-build/js-tools, the compiler driver, test runners and other programs the plugin runs.

Those programs, your tests and your own scripts run on a Node.js release that fabr downloads on the first build, 24.22.1 unless you choose another, rather than on the Node.js you installed, so the version on your machine doesn’t affect what is built; see The Node.js version.

Create PROJECT.fabr in an empty directory:

plugin @fabr-build/js;
js_package mylib {
srcs = src:**/*.ts;
}

and a source file, src/index.ts:

export const greet = (name: string): string => `Hello, ${name}!`;

Then build it:

Terminal window
fabr build mylib

Fabr downloads the TypeScript compiler, compiles the source, and assembles a package with a generated package.json. The package isn’t written into your project; it is kept in fabr’s cache, and you look at it with fabr:

Terminal window
fabr ls mylib # list the files in the package
fabr cat mylib/index.js # print one of them
fabr cp mylib ./out # copy the package to ./out/mylib/

srcs = src:**/*.ts means “the .ts files under src, named without the src/ prefix”, which is why the package contains index.js and not src/index.js.

Two settings are worth making explicit from the start: the compiler version, and the JavaScript to emit. Both have defaults (typescript 5.6.3 and es6-esm):

TYPESCRIPT = @npm:typescript:5.6.3;
JS_TARGET = es2022-esm;

TypeScript compilation lists what else you would have put in tsconfig.json and where it goes.

Declare an npm dependency with an @npm: reference, @npm:<package>:<version>, in the target’s deps:

js_package mylib {
srcs = src:**/*.ts;
deps = @npm:lodash:4.17.21 @npm:@types/lodash:4.17.7;
}

There is no install step and no lockfile: fabr fetches packages as the build needs them, and the same build file always selects the same versions. When several targets share dependencies, declare them once in a catalog and refer to its members by name:

catalog @pkg {
deps = @npm:typescript:5.6.3 @npm:@types/node:22.15.3
@npm:chai:4.3.6 @npm:@types/chai:4.3.1;
}
TYPESCRIPT = @pkg:typescript;
js_package mylib {
srcs = src:**/*.ts;
deps = @pkg:@types/node;
}

Dependencies covers version ranges, how versions are chosen, resolving version conflicts, patching a package, and private registries.

Add a tests property naming the test files, and test_deps for anything only the tests need. Test files are left out of the built package.

js_package mylib {
srcs = src:**/*.ts;
tests = src:**/*.test.ts;
deps = @pkg:@types/node;
test_deps = @pkg:chai @pkg:@types/chai;
}

By default, tests run with Node.js’s built-in test runner. describe, it and the hooks are globals, and assertions come from whichever library you add:

import { expect } from "chai";
import { greet } from "./index";
describe("greet", () => {
it("greets by name", () => {
expect(greet("world")).to.equal("Hello, world!");
});
});
Terminal window
fabr test mylib

A summary such as mylib: 1 test passed is printed for each target, followed by any failed tests, and fabr test exits with status 1 if any test failed. A run is cached, passing or failing, so running fabr test again without changes reports the same result without re-running the tests; fabr test --retry-failed mylib runs failed tests again. Other options:

Terminal window
fabr test -t 'greets' mylib # only the tests whose names match a regular expression
fabr test --coverage mylib # also measure coverage, writing coverage/lcov.info

To run an existing jest or vitest suite, or for snapshots, DOM tests and coverage, see Testing.

A js_script is a Node.js program: entry is the file to run, and deps the packages it imports. It runs on the same downloaded Node.js release as your tests.

js_script tool {
entry = src:main.ts;
deps = @npm:chalk:5.3.0 @pkg:@types/node;
}
Terminal window
fabr run tool arg1 arg2

The program runs in your current directory with your terminal, and everything after the target name is passed to it. Options for fabr itself go before the target.

Command What it does
fabr build <target> Build the target.
fabr test <target> Compile and run the target’s tests.
fabr run <target> [args…] Build a program and run it.
fabr ls <reference> List the files a target produces.
fabr cat <reference> Print them.
fabr cp <reference…> <dir> Copy them into a directory.
fabr deps <target> Show the dependency versions the target resolves to.
fabr audit <target> Check those dependencies for known security advisories.
fabr list-targets List the project’s targets and what each can do.

Two options work with most of them:

  • -D<NAME>=<VALUE> changes a setting for one run: fabr build -DBUILD_TYPE=release mylib.
  • -w keeps fabr running and rebuilds, retests or restarts as your sources change; see Watch mode & dev servers.

The command-line reference has the rest.