Workflows & patterns · unreleased v4.10
Asset pipelines.
Put styles, scripts, images and generated data in one tracked build graph. Nift owns orchestration; specialist compilers and encoders can own each transformation.
These examples require the unreleased v4.10 build / depends features. Stable v4.9 does not provide this tracked-item contract.
One graph, several kinds of transformation
styles ────────┐
frontend-js ───┼→ asset-manifest → home
images ────────┘The three asset branches are independent and can build concurrently. The manifest waits for them; the page waits for the manifest. Order-only edges do not replace file dependencies for invalidation. Use this pattern for icons, fonts, downloadable archives, search indexes, feeds/RSS, sitemaps and generated data as well as CSS and JavaScript. Format correctness still belongs to the appropriate serializer, compiler or encoder.
Complete native example
Download the complete asset-pipeline project. It runs without external packages: CSS/SVG follow normal render/minification, two classic scripts are composed and explicitly minified, a JSON manifest records completed assets, and a page includes that manifest.
unzip asset-pipelines.zip
cd asset-pipelines
nift build --all
nift status -p
nift build home
nift buildcontent/styles.css
content/frontend-js.txt
content/images.svg
content/asset-manifest.txt
content/home.html
src/js/base.js
src/js/app.js
scripts/js.f
scripts/manifest.f
.nift/config.json
.nift/tracked.json{
"tracked": [
{
"name": "styles",
"title": "Styles",
"content-ext": ".css",
"output-ext": ".css",
"minify": true
},
{
"name": "frontend-js",
"title": "Frontend",
"output-ext": ".js",
"build": "scripts/js.f"
},
{
"name": "images",
"title": "Icon",
"content-ext": ".svg",
"output-ext": ".svg",
"minify": true
},
{
"name": "asset-manifest",
"title": "Assets",
"output-ext": ".json",
"build": "scripts/manifest.f",
"depends": [
"styles",
"frontend-js",
"images"
]
},
{
"name": "home",
"title": "Home",
"content-ext": ".html",
"output-ext": ".html",
"depends": [
"asset-manifest"
]
}
]
}Every input, script and .deps.json sidecar is included in the archive. Change a CSS declaration or the JavaScript source and run an incremental build; the manifest and page consume the changed bytes. The SVG branch is a text pass-through/minification example, not an image encoder.
CSS: orchestrate a specialist or compose plain CSS
For authored CSS, normal Nift text composition and final CSS minification may be enough. Sass, PostCSS, Tailwind and Lightning CSS remain specialist transformations where the project needs them. For Sass, a custom build may use this script after the compiler is installed:
result := cmd("sass", "src/styles/main.scss", getenv("NIFT_HOOK_OUTPUT")).cwd(getenv("NIFT_HOOK_ROOT")).run()
if(!result.launched || result.exit_code != 0) {
throw error("Sass failed: " + result.stderr, "build.tool_failed")
}
optimized := minify(getenv("NIFT_HOOK_OUTPUT"), {"in_place": true})
if(!optimized.ok) { throw error(optimized.error, "build.minify_failed") }
Use a .css tracked output, an existing content/recipe input, and ordinary dependencies for the stylesheet sources, imported files and compiler configuration. Nift does not infer a specialist’s import graph from its command. Ensure destination directories exist before invoking a compiler that does not create them; a custom build owns that preparation as well. Consult the Sass CLI reference for its input/output contract.
JavaScript: composition and bundling have different jobs
The native example concatenates two deliberately compatible classic scripts in a fixed order. It does not resolve ES-module imports, compile TypeScript, split code, tree-shake, generate source maps or implement module semantics. For those jobs, orchestrate a specialist such as esbuild, Rollup or a Vite project build. Nift can own the bundling workflow even when a specialist performs the transformation.
result := cmd("./node_modules/.bin/esbuild", "src/js/app.ts",
"--bundle", "--outfile=" + getenv("NIFT_HOOK_OUTPUT")).cwd(getenv("NIFT_HOOK_ROOT")).run()
if(!result.launched || result.exit_code != 0) {
throw error("Bundler failed: " + result.stderr, "build.tool_failed")
}
This example assumes a locally installed esbuild executable on a POSIX-style project. Use the appropriate executable path for your host. Keep source imports, configuration and relevant lockfiles in ordinary dependency metadata. Bundlers that emit chunks, maps or an asset directory need an explicit ownership/cleanup strategy; one tracked output does not automatically own every extra file. See esbuild’s installation and bundling reference.
Images and multiple formats
src/images/hero.png
├→ hero-webp (tracked .webp output)
└→ hero-avif (tracked .avif output)Use two tracked items when two formats should have separate declared outputs. Each can call the same image-encoding script, which takes the tracked output path from its item context:
{
"tracked": [
{
"name": "hero-webp",
"title": "Hero WebP",
"build": "scripts/image.f",
"output-ext": ".webp"
},
{
"name": "hero-avif",
"title": "Hero AVIF",
"build": "scripts/image.f",
"output-ext": ".avif"
}
]
}result := cmd("magick", "src/images/hero.png", getenv("NIFT_HOOK_OUTPUT")).cwd(getenv("NIFT_HOOK_ROOT")).run()
if(!result.launched || result.exit_code != 0) {
throw error("Image encoding failed: " + result.stderr, "build.tool_failed")
}
Both items need content descriptors and ordinary dependencies for src/images/hero.png. ImageMagick must have the required format delegates. Nift schedules the work; the encoder creates the pixels. The current tracked model declares one output per item. It has no outputs: [...] syntax. Scripts may create extra files, but Nift does not thereby promise individual tracking, cleanup or missing-output checks for an asset set.
Final minification: opt in at the actual artifact boundary
For normal Nift rendering, project minify-exts selects formats and a tracked boolean minify overrides that choice. This small example uses no project-wide minification and opts styles/SVG in individually:
{
"config": {
"content-dir": "content/",
"content-ext": ".txt",
"output-dir": "public/",
"output-ext": ".txt",
"default-template": "",
"build-threads": 4,
"incremental-mode": "hash",
"minify-exts": []
}
}minify: true forces supported normal output minification; false disables it; omission follows the project array. Supported final formats include HTML, CSS, JS/JSX, JSON, XML and SVG. Nift does not compile SCSS or TypeScript by minifying them.
A custom build bypasses normal render and automatic minification, even if minify: true or the output extension is selected. Explicitly call minify(...) in the script or post-build phase when wanted, check ok, and fail the phase on error. If the specialist already optimizes its output, another pass is a project choice. The Sass recipe above shows an explicit in-place final pass; the native JavaScript example minifies before saving.
The exact format behavior and configuration are in Minification.
Hooks around real outputs
Use pre-build for preparation and post-build for validation or a deliberate final pass. Each phase has a fresh native scope; use files for cross-phase state. Check every specialist exit status—printing an error or returning a process-result object alone does not fail a script. Build Scripts defines precedence, context, output invariants and failure behavior.
Inspect and evolve the graph
Use nift status -p for incremental reasons, nift info --tracking for tracked state and nift build -p for build detail. Explicit names, scripts, outputs and prerequisites make automation inspectable without treating every tool as part of Nift. General build systems explains migration from Make/Ninja and npm-script sequencing. This is an orchestration alternative, not a measured Make/Ninja performance claim.
Use tracked.json, Build Scripts, native scripting, ordinary file dependencies, incremental state, build/status/info commands, filesystem APIs and minification for the exact mechanics.