Node projects
Dependency installs cached apart from builds, generated code layered in, and the result served.
nodeProject builds anything with a package.json. The design point worth
understanding before anything else: installing dependencies and building are
separate, separately cached runs.
import { nodeProject } from "bldr/node-tools";
import nodeImage from "node";
const dist = nodeProject({
scope,
image: nodeImage,
source: member.rootDirectory,
}).build({ outDir: "dist" });
scope.addOutputDirectory("site", dist);npm ci runs over the manifests alone, so its cache key is your lockfile. It
survives every source edit. Editing a component re-runs the build and not the
install, which is the difference between a fast loop and a slow one.
The install is also the step with internet access. Build, test and lint pods
have none: they see node_modules as the install left it, and a script that
downloads at build time (npx of a package that is not a dependency, a browser
fetched on first run) fails there.
Run a different script
.build({ outDir: "build/client", script: "build:prod" })script is the package.json script, outDir is what to keep.
Layer in generated code
Code you generate belongs in the tree at content level, not copied in by a command inside the container:
nodeProject({
scope,
image: nodeImage,
source: member.rootDirectory,
generated: [{ tree: apiClient(), dest: "src/gen" }],
});The project sees src/gen as if it were checked in, and the generated tree is
part of the cache key, so regenerating the client rebuilds what depends on it.
Depend on another member's package
import { pkg as designSystem } from "bldr/design-system";
nodeProject({
scope,
image: nodeImage,
source: member.rootDirectory,
deps: { "@bldr/design-system": designSystem() },
});The dependency is vendored under .bldr-deps/ and your package.json refers
to it as a file: dependency, so npm resolves it normally.
Serve it
import { staticSite } from "bldr/node-tools";
import nginxImage from "nginx";
scope.addRunnable("site", staticSite({
image: nginxImage,
site: dist,
port: 8080,
fallback: "/index.html", // for a single-page app
}));Tests as a target
import { fromJestJson } from "bldr";
const testOut = nodeProject({ scope, image: nodeImage, source: member.rootDirectory })
.build({ outDir: "test-out", script: "test:json" });
scope.addTest("unit", fromJestJson(testOut, "**/*.json"));Have the script write a JSON report and end with || true, so a failing case
becomes data in the report rather than a failed pod. The test target then
reports per-case results instead of just failing.
When the lockfile goes stale
Adding a dependency to package.json without updating the lockfile makes
npm ci fail with Missing: … from lock file. Only npm can write that file,
so refresh it with a build:
bldr build --export <member>/lockfile:members/<dir>That runs npm install --package-lock-only and writes the refreshed lock back
into your member.