CHOMP
Chomp is a frontend task runner with advanced features focused on ease-of-use and not getting in the way!
- An advanced task runner with a single command!
- Easily adapt existing projects / task systems - no need for a rewrite.
- You enable and manage advanced task runner features with single-line updates.
Chomp is a great option for frontend projects where the goal is getting advanced task runner features (like smart caching) without complexity and overhead.
One-line migration from npm scripts
Chomp can import a project's established package.json
scripts without breaking them, as it supports the same features:
chomp --init --import-scripts
Now you can run your npm scripts using Chomp!
i.e
npm run <task>
becomeschomp <task>
and behaves the same, and you can opt in to further features as needed.
The only difference is, with Chomp — it's faster. And, with a few more tweaks, you can enable smart caching, parallelism, and more!
What features does Chomp provide?
Chomp is an advanced task runner. It provides features similar to turbo and nx but focuses on ease of use, *not monorepos. It's based on the same principles as traditional make files.
Parallelism
Chomp runs tasks in parallel, based on an extecuted task's dependencies!
Watch/Serve
Chomp watches any task by including a --watch
or --serve
option! Read more about the power of --watch
and --serve
.
A JS extension system
Chomp has a JS extension system that allows you to extend Chomp with your own custom tasks
Smart caching
Chomp caches tasks based on task dependencies like other tasks or updated files. You don't have to worry about it!
*Chomp works for monrepos but it's architected for ease of use and not getting in the way first.
Install
If you use Cargo, run:
cargo install chompbuild
If you don't use Cargo, run:
npm install -g chomp
Note: npm scripts add over 100ms to the script run time.
Common platform binaries are also available for all releases.
To quickly set up Chomp in a GitHub Actions CI workflow, see the Chomp GitHub Action.
Documentation
Getting Started
Migrating from npm Scripts
To convert an existing project using npm "scripts"
to Chomp, run:
$ chomp --init --import-scripts
√ chompfile.toml created with 2 package.json script tasks imported.
or the shorter version:
$ chomp -Ii
√ chompfile.toml created with 2 package.json script tasks imported.
Then use chomp <name>
instead of npm run <name>
, and enjoy the new features of task dependence, incremental builds, and parallelism!
Hello World
chomp
works against a chompfile.toml
TOML configuration in the same directory as the chomp
command is run.
Chomp builds up tasks as trees of files which depend on other files, then runs those tasks with maximum parallelism.
For example, here's a task called hello
which builds hello.txt
based on the contents of name.txt
, which itself is built by another command:
chompfile.toml
version = 0.1
[[task]]
target = 'name.txt'
run = '''
echo "No name.txt, writing one."
echo "World" > name.txt
'''
[[task]]
name = 'hello'
target = 'hello.txt'
dep = 'name.txt'
run = '''
echo "Hello $(cat name.txt)" > hello.txt
'''
with this file saved, the hello command will run all dependency commands before executing its own command:
$ chomp hello
🞂 name.txt
No name.txt, writing one.
√ name.txt [4.4739ms]
🞂 hello.txt
√ hello.txt [5.8352ms]
$ cat hello.txt
Hello World
Finally it populates the hello.txt
file with the combined output.
Subsequent runs use the mtime of the target files to determine what needs to be rerun.
Rerunning the hello
command will see that the hello.txt
target is defined, and that the name.txt
dependency didn't change, so it will skip running the command again:
chomp hello
● name.txt [cached]
● hello.txt [cached]
Changing the contents of name.txt
will then invalidate the hello.txt
target only, not rerunning the name.txt
command:
$ echo "Chomp" > name.txt
$ chomp hello
● name.txt [cached]
hello.txt invalidated by name.txt
🞂 hello.txt
√ hello.txt [5.7243ms]
$ cat hello.txt
Hello Chomp
The deps
array can be defined for targets, whose targets will then be run first with invalidation based on target / deps mtime comparisons per the standard Makefile approach.
Powershell is used on Windows, while Bash is used on POSIX systems. Since both echo
and >
are defined on both systems, the examples above work cross-platform (Powershell is automatically put into UTF-8 mode for >
to work similarly).
Note that &&
and ||
are not supported in Powershell, so multiline scripts and ;
are preferred instead.
JS Tasks
Alternatively we can use engine = 'node'
or engine = 'deno'
to write JavaScript in the run
function instead:
chompfile.toml
version = 0.1
[[task]]
target = 'name.txt'
engine = 'node'
run = '''
import { writeFile } from 'fs/promises';
console.log("No name.txt, writing one.");
await writeFile(process.env.TARGET, 'World');
'''
[[task]]
name = 'hello'
target = 'hello.txt'
deps = ['name.txt']
engine = 'node'
run = '''
import { readFile, writeFile } from 'fs/promises';
const name = (await readFile(process.env.DEP, 'utf8')).trim();
await writeFile(process.env.TARGET, `Hello ${name}`);
'''
Tasks are run with maximum parallelism as permitted by the task graph, which can be controlled via the -j
flag to limit the number of simultaneous executions.
Using the --watch
flag watches all dependencies and applies incremental rebuilds over invalidations only.
Or, using chomp hello --serve
runs a static file server with watched rebuilds.
See the task documentation for further details.
Monorepos
There is no first-class monorepo support in chomp, but some simple techniques can achieve the same result.
For example, consider a monorepo where packages/[pkgname]/chompfile.toml
defines per-package tasks.
A base-level chompfile.toml
could run the test
task of all the sub-packages with the following chompfile.toml
:
[[task]]
name = 'test'
dep = 'packages/#/chompfile.toml'
run = 'chomp -c $DEP test'
chomp test
will then use task interpolation to run the multiple sub-package test tasks in parallel. A similar approach can also be used for a basic unit testing.
By adding serial = 'true'
, the interpolation can be made to run in series rather than in parallel.
Cross-project dependencies are not currently supported. Instead, if packages/a/chompfile.toml
's build task depends on packages/b/chompfile.toml
's build task to run first, then packages/a/chompfile.toml
might look like:
[[task]]
name = 'build'
run = 'cargo build'
dep = 'build:deps'
[[task]]
name = 'build:deps'
run = 'chomp -c ../a build'
This would still be fast, so long as packages/a/chompfile.toml
's build
task has its targets and dependencies properly configured to do zero work if the all target mtimes are greater than their dependencies.
Extensions
Extensions are able to register task templates for use in Chompfiles.
Extensions are loaded using the extensions
list, which can be any local or remote JS file:
version = 0.1
extensions = [
"./local.js",
"https://remote.com/extension.js"
]
A core extensions library is provided with useful templates for the JS ecosystem, with
the short protocol chomp:ext
, a shorthand for the @chompbuild/extensions
package contents.
A simple example is included below.
See the @chompbuild/extensions package for extension descriptions and examples.
Example: TypeScript with SWC
To compile TypeScript with the SWC template:
version = 0.1
extensions = ['[email protected]:swc']
[[task]]
name = 'build:typescript'
template = 'swc'
target = 'lib/##.js'
deps = ['src/##.ts']
In the above, all src/**/*.ts
files will be globbed, have SWC run on them, and output into lib/[file].js
along with their source maps.
The ##
and #
interpolation syntax is special because, unlike glob dependencies (which are also supported), there must be a 1:1 relationship between a dependency and its target.
Only non-existent files, or files whose src
mtimes are invalidated will be rebuilt. If SWC itself is updated, all files that depend on it will be re-built.
Specific files or patterns can be built directly by name as well, skipping all other build work:
chomp lib/main.js lib/dep.js
🞂 lib/dep.js
🞂 lib/app.js
√ lib/dep.js [317.2838ms]
√ lib/app.js [310.0831ms]
Patterns are also supported for building tasks by name or filename (the below two commands are equivalent):
$ chomp lib/*.js
$ chomp :build:*
To remove the template magic, run chomp --eject
to convert the chompfile.toml
into its untemplated form:
$ chomp --eject
√ chompfile.toml template tasks ejected
Resulting in the updated chompfile.toml:
version = 0.1
[[task]]
name = 'build:typescript'
target = 'lib/##.js'
dep = 'src/##.ts'
stdio = 'stderr-only'
run = 'node ./node_modules/@swc/cli/bin/swc.js $DEP -o $TARGET --no-swcrc --source-maps -C jsc.parser.syntax=typescript -C jsc.parser.importAssertions=true -C jsc.parser.topLevelAwait=true -C jsc.parser.importMeta=true -C jsc.parser.privateMethod=true -C jsc.parser.dynamicImport=true -C jsc.target=es2016 -C jsc.experimental.keepImportAssertions=true'
License
Apache-2.0