Use when working with the Stacks build system - building component libraries, CLI binaries, server Docker images, documentation, or the framework core. Covers @stacksjs/build, buddy build commands, build actions, and the server build pipeline.
Install
npx skillscat add stacksjs/stacks/stacks-build Install via the SkillsCat registry.
Stacks Build System
Key Paths
- Core package:
storage/framework/core/build/ - Build actions:
storage/framework/core/actions/src/build/ - Main build action:
storage/framework/core/actions/src/build.ts - Buddy commands:
storage/framework/core/buddy/src/commands/build.ts - Server build:
storage/framework/server/build.ts - Server Dockerfile:
storage/framework/server/Dockerfile - Types:
storage/framework/core/types/src/cli.ts
Source Files
build/src/
├── index.ts # intro() and outro() - build logging and timing
├── web-types.ts # Web types generation for IDE support
└── build.ts # Package build config (Bun.build)
actions/src/build/
├── cli.ts # Build Buddy CLI binary
├── server.ts # Build server Docker image
├── core.ts # Build all framework core packages
├── stacks.ts # Orchestrate CLI + core builds
├── component-libs.ts # Build the `components` library packages
├── libs.ts # Build every configured library package
├── docs.ts # Build documentation
├── desktop.ts # Build desktop app
└── views.ts # Build frontend viewsBuild Types
type BuildOption =
| 'components' | 'webComponents' | 'elements'
| 'functions' | 'libs' | 'docs' | 'views' | 'stacks' | 'all' | 'buddy' | 'server'
type BuildOptions = { [key in BuildOption]: boolean } & CliOptions
interface CliOptions {
verbose?: boolean
silent?: boolean
quiet?: boolean
cwd?: string
background?: boolean
timeoutMs?: number
project?: string
}CLI Commands
buddy build # Interactive build
buddy build components # All component libraries
buddy build:components # Alias
buddy build:web-components # Web Components library only
buddy build:functions # The `functions` library packages
buddy build:libs # Every configured library package
buddy build:cli # Buddy CLI binary
buddy build:server # Server Docker image
buddy build:core # All framework core packages
buddy build:stacks # CLI + core (full framework)
buddy build:docs # Documentation site
buddy build:desktop # Desktop application
buddy build:views # Frontend views
# Flags
buddy build -c # --components
buddy build -w # --web-components
buddy build -f # --functions
buddy build -l # --libs
buddy build -d # --docs
buddy build -b # --buddy
buddy build -s # --stacks
buddy build --server --verbose --project [name]Standard Build Pattern
For a library barrel or a package with public subpaths, preserve the module
graph with the shared transpiler. Identifier-minifying bundles have produced
invalid re-export bindings on supported Bun versions. Separate bundles also
duplicate shared objects across entrypoints, such as validation's schema proxy.
import { rm } from 'node:fs/promises'
import { frameworkExternal, intro, outro, transpilePackage } from '../build/src'
const { startTime } = await intro({ dir: import.meta.dir })
await rm('./dist', { recursive: true, force: true })
await transpilePackage({ dir: import.meta.dir, external: frameworkExternal() })
await outro({ dir: import.meta.dir, startTime, result: { errors: [], warnings: [] } })The helper emits every source module and its declaration with whitespace-only
minification. Keep package-specific assets, declaration rewriting, and runtime
probes around this call. Use Bun.build for intentional executable bundles and
verify the emitted artifact. ORM inference builds validation before ORM because
the published ORM imports validation's narrow runtime entry.
Releasing libraries out of resources/
resources/functions and resources/components are not limited to one npm
package each. config/library.ts carries a packages array, and each entry
claims a slice of one of those directories by glob and becomes its own package
with its own name, manifest, dist and version:
// config/library.ts
packages: [
// A file may be claimed by more than one package.
{ name: '@acme/fx', kind: 'functions', include: ['*.ts'], exclude: ['internal/**'] },
{ name: '@acme/fx-dates', kind: 'functions', include: ['dates/**'] },
{ name: '@acme/ui', kind: 'components', include: ['ui/**'], prefix: 'acme' },
{ name: '@acme/elements', kind: 'web-components', include: ['ui/**'], prefix: 'acme' },
]The older single-package functions / webComponents keys still work: they
normalize into the same list, so a config written before packages existed
keeps building exactly one package. When packages is set, they are ignored.
| Kind | Built by | Published entry |
|---|---|---|
functions |
sources staged into the package's src/, then transpilePackage |
dist/index.js, one module per source |
components |
stx buildComponentLibrary |
dist/index.js, tree-shakeable per-component modules |
web-components |
the same compile | dist/bundle.js, one self-registering script |
buddy libs # what each package resolved to, on disk
buddy libs --json # the same, machine-readable
buddy build:libs # build them all (also `buddy build --libs`)
buddy build:functions # just the `functions` packages
buddy build:components # just the `components` packages
buddy build:web-components # just the `web-components` packages
buddy libs:publish --dry-run
buddy libs:publish # after a build; refuses a package with no distPackages build in storage/framework/libs/packages/<dir>/, where <dir> is
the unscoped npm name unless the entry sets dir. The whole directory is
generated and gitignored.
Things that will stop a build, on purpose
- A package that matches no files. Nearly always a typo'd glob, and the
alternative is publishing an empty tarball. The release path skips unmatched
packages instead, so an app that never filled inresources/is not blocked. - Two packages that resolve to the same directory (
@acme/uiand@other/uiboth unscope toui). Give one an explicitdir. - stx ambient globals in a
functionspackage.state,useDarkand the
rest are injected into an stx page entry and exported by nothing, so a
bundled copy compiles and then throwsReferenceErroron the consumer's
first call. Either import the names explicitly, or setruntime: 'stx'on
the package to declare that it is only ever consumed from inside an stx app.
Release flow
buddy release runs generate/lib-entries, which stages sources and writes
each manifest without compiling - so a broken library config fails before a tag
exists rather than after. Versions follow the project version unless a package
pins its own, which is why the build (not the generate) is what CI runs after
the bump, and buddy libs:publish after that.
Build Utilities (index.ts)
async function intro(options: { dir: string, pkgName?: string, styled?: boolean }): Promise<{ startTime: number }>
async function outro(options: { dir: string, startTime: number, result: any, pkgName?: string }): Promise<void>outro() handles two result formats:
- Bun.build: checks
result.successandresult.logs - esbuild: checks
result.errorsarray
Server Build Pipeline (7 stages)
1. Stop running stacks-server Docker container
2. Clean previous build artifacts (app, config, dist, docs, storage)
3. Build framework server (compile ./src/index.ts)
4. Build user app files (compile ./app/**/*.{ts,js})
5. Fix import paths (replace storage/framework/server → dist)
6. Clean unwanted exports from dist files
7. Build Docker image (if cloud deployment enabled)Build Action Enums
enum Action {
BuildViews = 'build/views'
BuildStacks = 'build/stacks'
BuildComponentLibs = 'build/component-libs'
BuildVueComponentLib = 'build-vue-component-lib'
BuildWebComponentLib = 'build-web-component-lib'
BuildFunctionLib = 'build-function-lib'
BuildCli = 'build/cli'
BuildCore = 'build/core'
BuildDesktop = 'build/desktop'
BuildDocs = 'build/docs'
BuildServer = 'build/server'
}Build Tool Stack
| Tool | Usage |
|---|---|
| Bun | Primary bundler (target: 'bun', format: 'esm') |
| stx | Component library builds |
| bun-plugin-dtsx | TypeScript declaration generation |
| Docker | Server containerization |
| @babel/traverse | AST traversal for export cleanup |
Gotchas
- Two result formats -
outro()must handle both Bun.build and esbuild formats - Server build mutates import paths - stage 5 rewrites references in compiled output
- Core build is sequential - packages built one at a time, failures collected and reported
- Docker build requires cloud config - only builds if cloud deployment is enabled
- Component libraries compile through stx, not Vite.
buildComponentLibrary
emits its own index and each generated component registers its custom element
on import, which is why aweb-componentspackage needs no entry file of its
own - it publishesbundle.jsinstead ofindex.js. - A library barrel is generated with
.tsspecifiers on purpose.transpilePackagerewrites relative.tsto.json the way intodist. An
extensionless specifier survives as-is and only resolves under Bun, so the
package installs cleanly and fails on first import from Node or Vite. build:stacksbuilds CLI first - Buddy binary compiled before core packages- Server build cleans aggressively - deletes app, config, dist, docs, storage before rebuild
- The build package has @babel deps - uses Babel for AST traversal during export cleanup
Package build contracts
@stacksjs/build exports transpilePackage andframeworkExternal(extras?) alongside intro/outro. Inspect a neighboring
core build.ts before configuring declaration roots or external dependencies;
the source example is a pattern, not a universal package manifest. Type-only
dependencies that escape the package degrade consumers even when local paths
resolve.
validateDeclarations(dir) and validateRuntimeExports(dir) are native
checks used by the build contract. A type declaration export without a matching
runtime file is a package failure, not a successful build. The current public
implementation is storage/framework/core/build/src/index.ts. Use Bun/TS
for supporting build scripts and pickier for lint.