Build Plugins
A build plugin reads each file that matches a glob and writes one file for it. The build runs plugins before it scans src, so an output in src/ or static/ is part of the app. Use a plugin to shrink SVG files, compile Sass, add a banner to CSS or make a new file kind. This code runs at build time. The server runs none of it, so a request costs nothing more.
There are two kinds:
- A command plugin is a line of
build_pluginsin[package.metadata.wisp]. It runs a tool and works with any language. - A Rust plugin is a
wisp_build::Pluginvalue thatbuild.rspasses towisp_build::run_with.
A command plugin
# Cargo.toml
[package.metadata.wisp]
build_plugins = ["icons assets/icons/**/*.svg static/icons ext=svg timeout=20 -- svgo -i {in} -o {out}"]
The line is name glob out [ext=E] [timeout=S] [version=V] -- program args:
| Part | Meaning |
|---|---|
name | 1 to 32 letters, digits, - or _. Each name is used once. Messages and the cache use it. |
glob | The inputs, relative to the app, with /. It must start with a folder. * and ? match inside one name. A whole part ** matches any number of folders. |
out | The output folder. A file keeps its path below the glob's folder. |
ext=E | The extension of the outputs, with no dot. Two inputs that make one output are an error. |
timeout=S | The time limit of one run: 1 to 600 seconds. The default is 30. |
version=V | Any text, with no space. It is part of the cache key. Change it after a tool upgrade, because the command line does not change then. |
program args | The command. The build splits it at spaces and starts it with no shell, with the app's root as the working folder. There are no quotes, so an argument cannot hold a space: put a complex call in a script. |
Three ways to pass the data:
{in}in the arguments is the input path.{out}is the output path. Both are relative to the app's root.- With no
{in}, the tool reads the input on stdin. - With no
{out}, the tool writes the output on stdout.
An input file name with a backslash or a control character (a newline) is an error. A run that stops part way (an error, Ctrl-C) keeps its manifest, so the next build still owns the outputs that it wrote.
A command can use the variables WISP_PLUGIN (the plugin name) and WISP_PLUGIN_FILE (the input path). On Windows, write the file name of a script with its extension (svgo.cmd), because the build starts the program itself.
A Rust plugin
// build.rs
use wisp_build::Plugin;
fn main() {
wisp_build::run_with(&[Plugin {
name: "banner",
glob: "assets/css/*.css",
out: "static/css",
ext: None,
version: "1",
transform: |_path, css| Ok([&b"/* made by build.rs */\n"[..], css].concat()),
}]);
}
The transform function gets the input path and the bytes of the file. It returns the bytes of the output or an error message. Change version when the function changes, because the build then makes every output again.
wisp_build::run() is run_with(&[]). Code before the call is a start hook. Code after it is an end hook.
Order, cache and clean-up
- The plugins run in order: the ones in
Cargo.toml, then the Rust ones. An output can be the input of a later plugin. A plugin never reads its own output folder. - The build keeps each output in
.wisp/plugins/<name>/. It runs the tool again only when the input, its path or the command line changed. After you update a tool, delete.wisp/pluginsor changeversion(version=Vin a command line,versionin a Rust plugin). - An output whose input is gone is deleted, with its empty folders. This includes the outputs of an earlier
outand of a plugin that you took out of the list. A file in the output folder that no plugin made stays. - A plugin never overwrites a file that exists and that no plugin made, unless the content is the same.
out = "src"oversrc/main.rsis an error: delete the file or changeout. Two plugins cannot make one output: the error names both. - Two builds at the same time (Cargo and your editor) can share the cache. A tool that starts a background process cannot hang the build: the build stops the whole process tree at the time limit, and waits 2 seconds for the output pipes after the tool ends.
- A glob folder that does not exist gives a build warning. The build then watches the nearest folder that exists, so a new folder starts a build. A link in an input folder is not an input.
- Add the output folder to
.gitignore.
Errors and the dev loop
A failed plugin stops the build. The message names the plugin and the file, then the tool's error text:
wisp: build plugin `icons`: assets/icons/logo.svg: `svgo` failed with exit code: 1:
Error: unclosed tag
The status text is exit code: 1 on Windows. On Linux and macOS it is exit status: 1.
A tool that does not exit before its time limit is stopped. A tool that does not start gets a message with the file name it tried. A tool that writes no {out} file is an error.
wisp dev rebuilds when a file in a glob folder changes. A failed build keeps the last good app running and shows the error. An app with build plugins builds with cargo on every change, because the plugins run in the build script.
Recipes
Copy a line, then change the folders.
Shrink SVG (needs svgo from NPM):
build_plugins = ["svg assets/svg/**/*.svg static/svg -- svgo.cmd -i {in} -o {out}"]
Compile Sass (needs sass; the build reads the result as a static file):
build_plugins = ["sass assets/css/*.scss static/css ext=css -- sass.cmd {in} {out}"]
Filter through any program (here tr of Unix): with no placeholder the program reads stdin and writes stdout.
build_plugins = ["upper assets/notes/*.txt static/notes -- tr a-z A-Z"]
Make a file kind in Rust: the example examples/plugins in the Wisp repository turns assets/notes/*.txt into static/notes/*.html with ext: Some("html"), adds a banner to CSS and runs cargo --version as a command plugin.
Limits
- At most 20000 input files for one plugin. Each input file, each stdout run and each
{out}file can be 64 MiB at most. - When the time limit stops a command, the build stops the programs that it started too. On Windows, a program that the command started can keep the output open after the command exits. The plugin then fails after 2 seconds with "still holds its output".
- On Windows, the build writes an output file by a rename over the old file. If another program holds the old file open, the build tries again 10 times, 50 ms apart, then fails.
- The build runs one command at a time. The cache key holds the command line. Add
version=Vto make the build run the plugin again when the tool changes.
Is This Page Useful?