feature:compile
Table of contents
- Introduction
- Compiling every feature of the product
- Rebuilding on every change
- Building for production
- Checking a bundle against its manifest
- A feature with frontend/
- Declaring the globals of the page
- Shipping vendored wheels
- The toolchain
- Legacy features
- Reference
- Requirements
- See also
Introduction
feature:compile builds the JavaScript and CSS bundle of a feature. A feature with a frontend/ folder builds in the SPLENT toolchain image, against its own dependencies, and needs no product. Run it after you change the frontend code, or leave it watching.
splent feature:compile notes
🚀 Compiling splent_feature_notes in the SPLENT toolchain...
npm installing frontend/node_modules from the lock (npm ci)...
âś… Successfully compiled splent_feature_notes in development mode!
The bundle lands in assets/dist, with a manifest of what it holds in assets/meta/manifest.json.
Compiling every feature of the product
splent feature:compile
Without a name, it builds every feature the selected product declares. Pinned features are skipped, since their release carries the bundle. When any build fails, it ends with Compilation failed for N feature(s): <features> and exits 1.
Rebuilding on every change
splent feature:compile notes --watch
đź‘€ Watching splent_feature_notes in development mode...
log: docker logs -f splent_frontend_splent_feature_notes stop: docker stop splent_frontend_splent_feature_notes
The rebuilds run in a detached container, splent_frontend_<feature>. Starting it again replaces the one that was running.
Building for production
splent feature:compile notes --prod
A production build is minified, has no source maps, and writes the licence texts of what it ships to assets/meta/third_party.json. FLASK_ENV=production does the same.
--release is what feature:release runs. A production build into an emptied assets/dist, which fails for a feature that would be skipped.
Checking a bundle against its manifest
splent feature:compile --verify
❌ splent_io/splent_feature_notes: the bundle does not match its manifest
assets/dist/notes.bundle.js changed after the build
--verify builds nothing and needs no Docker. Every file the manifest lists must be in assets/dist with its sha256, nothing else may be, and the lock must be the one the build installed. It exits 1 on any mismatch.
A pinned feature is checked where the product finds it, and must hold a production build. Each feature docker/frontend.lock.json lists must carry the manifest and version it names. The production Dockerfile runs --verify, so an image only serves the bundles its releases built.
A feature with frontend/
feature:create --frontend writes frontend/ for a new feature, and feature:frontend:init adds it to an existing one.
splent_feature_notes/
├── frontend/
│ ├── package.json # dependencies, peers and the "splent" block
│ ├── package-lock.json # committed
│ ├── .npmrc # save-exact, ignore-scripts, engine-strict
│ ├── webpack.extra.js # optional, merged over the preset
│ └── node_modules/ # ignored
└── src/splent_io/splent_feature_notes/assets/
├── js/ # your sources
├── dist/ # the bundle, served
└── meta/ # manifest and licence data, never served
{
"name": "@splent-io/splent_feature_notes",
"private": true,
"license": "LGPL-3.0-or-later",
"engines": { "node": "^24.22.1", "npm": "^11.19.1" },
"dependencies": { "chart.js": "4.5.1" },
"peerDependencies": { "bootstrap": "^5.2.0" },
"splent": {
"schema": 1,
"entries": { "notes": "./js/scripts.js" }
}
}
The feature brings no webpack and no config, the toolchain does. Manage dependencies with feature:npm.
Bootstrap and feather-icons come from the shell. Declare them under peerDependencies, and import { Modal } from "bootstrap" becomes window.bootstrap. As a dependency, or by an inner path such as bootstrap/js/dist/modal, they stop the build. A singleton such as dropzone may be bundled by one feature only, which product:validate checks.
splent key |
What it declares |
|---|---|
entries |
{"<name>": "./js/scripts.js"} builds dist/<name>.bundle.js. {"import": "...", "type": "module"} makes an ES module. {} builds none |
shell |
What templates take from the shell, such as {"bootstrap": "^5.2.0", "bootstrap-css": "^5.2.0", "icons": "feather"} |
provides |
What the feature puts on the page, {"<name>": {"kind": "lib", "global": "<global>", "version": "<semver>"}}, or kind api |
consumes |
What it takes from another feature, {"<name>": {"feature": "<provider>", "range": "^1"}} |
vendored |
Files from no npm package, see vendored wheels |
csp |
Sources its pages need, such as {"script-src": ["'wasm-unsafe-eval'"]}, which the product approves |
licenseTexts |
{"<package>": "<path in frontend/>"}, for a package without its licence files |
licenseExceptions |
[{"package", "reason", "expires"}], what the licence gate lets through until a date |
version and scripts are not read.
Declaring the globals of the page
Every build lints your own modules for names the page must define. A name passes when the browser defines it or something declares it.
| Global | Declared by |
|---|---|
SPLENT |
Always, the framework’s runtime |
bootstrap, feather |
A peer dependency on the singleton, or its entry in splent.shell |
The global of a splent.provides entry |
The feature itself |
The first segment of a splent.consumes key |
The feature it consumes from |
__webpack_public_path__, __webpack_nonce__ |
webpack |
Any other name stops the build, here and at the release gate.
[charts] ERROR in src/splent_io/splent_feature_charts/assets/js/scripts.js:12:5: 'KTApp' is not defined. A global of the page is declared where it comes from (a peerDependency, splent.shell, splent.consumes), or with a line // splent-global: <name> <why>.
When the page has the name for another reason, declare it in the file that reads it, with the reason. /* global */ and eslint-disable comments are not read.
// splent-global: Plotly drawn by the product's own layout
Shipping vendored wheels
An entry of splent.vendored whose path is under dist/ is copied there as it is, such as a Python wheel an in-browser runtime installs.
"vendored": [
{"name": "solver", "version": "1.8", "license": "MIT", "source": "pypi",
"path": "dist/runtime/solver-1.8-py3-none-any.whl", "sha256": "…"},
{"name": "engine", "version": "0.2.0", "license": "GPL-3.0-or-later",
"source": "vendor/ of this repository", "from": "wheels/engine-0.2.0-py3-none-any.whl",
"path": "dist/runtime/engine-0.2.0-py3-none-any.whl", "sha256": "…"}
]
Without from, the build fetches the wheel with pip download from pypi or the https URL in source, into the untracked frontend/vendored/. With from, it is a file the feature keeps under frontend/. A file with another sha256 stops the build.
The toolchain
The image splent-frontend:<hash> is built here the first time a command needs it, from splent_cli/toolchain/ in the CLI’s package. The tag is the sha256 of those files, so the same CLI always builds the same image. It carries Node 24.22.1, npm 11.19.1, webpack 5 with its loaders and plugins, eslint and the SPLENT preset.
A build runs with --network none, and can only write assets/dist and assets/meta. npm ci runs only when the lock changed. Modules resolve only from the feature’s own node_modules, and two features never collide on one page. frontend/webpack.extra.js, an object or a function of (preset, {mode, feature}), is merged over the preset.
Legacy features
A feature without frontend/package.json builds with its own assets/js/webpack.config.js, inside the product’s running web container, against the product’s node_modules.
splent feature:compile notes --dev
--dev and --prod choose the container, <product>_web or <product>_web_deploy, and default to SPLENT_ENV. It works from the host too.
Inside the product’s web container there is no Docker daemon, so a feature with
frontend/is left to the CLI, with a note.
Reference
Options
| Option | Description |
|---|---|
FEATURE_NAME |
Optional. notes, splent_feature_notes or splent-io/splent_feature_notes. Without it, every feature of the product |
--watch |
Rebuild on every change. Ignored in production mode |
--dev |
Legacy features only. Run in the development container, without changing the build mode |
--prod |
Production build. Legacy features also run in the production container |
--release |
The build a release ships, into an emptied assets/dist. Not with --watch |
--verify |
Build nothing, check each bundle against its manifest. Not with --watch or --release |
Errors
| Message | What to do |
|---|---|
frontend/package-lock.json is missing |
Install the dependencies with feature:npm, which writes the lock |
frontend/package.json changed after the lock was written |
Run splent feature:npm <feature> install |
'<name>' is not defined |
Declare the global |
No running container found for <product> (<env>) |
Legacy feature. Run splent product:up --<env> |
The <product> web container runs the workspace at <path>, not this one |
Legacy feature. Start this workspace’s product |
<feature> is pinned (<version>): a release builds the bundle of the working copy |
--release needs the editable copy |
Requirements
- A feature with
frontend/needs Docker, and the network the first time the image builds and whenever the lock changes. - A legacy feature, or no feature named, needs a selected product (
SPLENT_APP). A legacy build also needs the product’s web container running, withnode_modulesinstalled.
See also
feature:npmmanages a feature’s npm dependencies.feature:frontend:initmoves a legacy feature tofrontend/.- Asset registry draws the bundle in your templates.