· Brayen Gavilanes · SysAdmin  · 8 min read

Boost Your Development Workflow with a Faster CI Pipeline

Learn how to optimize your development workflow with a faster CI pipeline using Rust-based development utilities.

Learn how to optimize your development workflow with a faster CI pipeline using Rust-based development utilities.

For one of my website projects, using AstroJS, I wrote a useful CI pipeline:

( pnpm check || pnpm fix ) && pnpm build && pnpm preview

Where provided scripts in the package manifesto follows:

(...)
  "scripts": {
    "dev": "astro dev",
    "start": "astro dev",
    "build": "astro build",
    "preview": "astro preview",
    "astro": "astro",
    "check": "npm run check:astro && npm run check:eslint && npm run check:prettier",
    "check:astro": "astro check",
    "check:eslint": "eslint .",
    "check:prettier": "prettier --check .",
    "fix": "npm run fix:eslint && npm run fix:prettier",
    "fix:eslint": "eslint --fix .",
    "fix:prettier": "prettier -w ."
  },
(...)

The behaviour of my pipeline being:

  1. First, I run ( pnpm check || pnpm fix ).
    • This is quite interesting. The pnpm check command runs the check script defined in the package.json file.
      • This script checks the code for errors and warnings using tools like ESLint, Prettier, and Astro.
  2. If the pnpm check command fails, i.e., returns a non-zero exit code, then the pnpm fix command is executed.
    • This command runs the fix script defined in the package.json file.
      • This script attempts to fix any errors or warnings found by the check script.
  3. Next, if the previous command is successful, i.e., returns a zero exit code, then the pnpm build command is executed.
    • This is quite useful. The pnpm build command runs the build script defined in the package.json file.
      • This script builds the project using Astro.
  4. Finally, if the build command is successful, then the pnpm preview command is executed.
    • The pnpm preview command runs the preview script defined in the package.json file.
      • This script starts a development server and previews the built project.

And, altought this was quite handy, I did have some hesitation: the pipeline was slow. Hella mad about that.

Don’t get me wrong: ESLint and Prettier are almost a standart in projects such as this. However, I do development in terminal-first environtment; and, it was painful to make a small change and wait for all the checks, especially pnpm astro check, to finish. Moreover, npm and friends are not task runners: as seen in the provided JSON, declaring targets depdencies is finicky at best.

The Rise of Rust-based development utilities

The first experience I got for using a Rust-based toolkit around a easier-to-implement, but not that performant, language was ruff. I wrote a webscrapper using Selenium, in python. I used poetry as a package and dependency manager; and, for linter a formatter, the mentioned ruff. The pipeline for that project is roughtly describe as:

( ruff check --fix && ruff format ) && poetry run

Which:

  1. First, I run ( ruff check --fix && ruff format ).

    • The ruff check --fix command runs the ruff linter with the --fix option, which attempts to automatically fix any errors or warnings found in the code as per the linter specification.
  2. If the ruff check --fix command is successful, i.e., returns a zero exit code, then the ruff format command is executed.

    • This command runs the ruff formatter to format the code according to the project’s style guidelines.
  3. Next, if the previous command is successful, i.e., returns a zero exit code, then the poetry run command is executed.

    • The poetry run command runs the project’s main command or script using poetry

It was a pleasant experience to work with such a toolkit. Parametrize, write a Makefile, containerize it and call it a day. Crack a cold one open with the boys, yeah!

It was a surprise to know there was, indeed, similar workflow for web development. As I hinted, this works best when you use a proper task runner.

make as task runner

make, or more precisely in modern Linux boxes, gmake — for GNU Make —, is a build system for C/C++ projects. It’s part of the POSIX set of standarts, so you can rely it’s present in most UNIX-based systems. Using .PHONY targest, as per GNU Make spec, or FRC — for For ReCompoline — convention, you can use make as your task runner, almost for free.

The Makefile for this webpage is as follows:

all: dev

check: FRC
	dprint fmt
	biome check --write

build: check
	pnpm run build

preview: build
	pnpm run preview

dev: check
	pnpm run dev

FRC:

Biome and Dprint as rust-based toolkit for web development

biome is

A toolchain for web projects, aimed to provide functionalities to maintain them. Biome offers formatter and linter, usable via CLI and LSP.

that’s performant. I was hesitant to migrate from ESLint + Prettier for several reasons; but two things change my mind:

  1. biome import CLI interface is quite handy to manage correspondent migrations,
  2. In the benchmark section, the developers proved Biome’s implementation were around 20 times faster than xargs -P
    • Reading throught the the provided README, not only they wrote the benchmark using a highly optmize GNU find and GNU xargs combo; but even wrote them using hyperfine

In that moment, I knew Biome’s developers did know what they are into.

Migrate fast, and often

Once all doubts settle, I began installing appropiate software:

nix-shell -p dprint biome

and then

biome init
biome migrate eslint --write
biome migrate prettier --write

Biome currently has partial support for HTML specs and superset so I use dprint, a similar project — but just for formatting, no linting —, for at least formatting everything not at least now supported by Biome.

dprint init
Select plugins (use the spacebar to select/deselect and then press enter when finished):
  [x] dprint-plugin-typescript
  [x] dprint-plugin-json
  [x] dprint-plugin-markdown
  [x] dprint-plugin-toml
  [x] dprint-plugin-dockerfile
  [ ] dprint-plugin-biome
  [ ] dprint-plugin-ruff
  [ ] dprint-plugin-jupyter
  [x] g-plane/malva
  [x] g-plane/markup_fmt
> [x] g-plane/pretty_yaml
  [ ] g-plane/pretty_graphql

Makeshift testing for UNIX lads, lasses, and others

We’ll use hyperfine as a benchmark util.

nix-shell -p hyperfine

First, let’s do a simple test; whether the — almost same — intended UNIX pipe does perform better with another toolset:

hyperfine -w 1 -r 1 '(pnpm check || pnpm fix) && pnpm build' 'pnpm astro check && dprint fmt && biome check --write && pnpm build'
Benchmark 1: (pnpm check || pnpm fix) && pnpm build
	Time (abs ):        50.117 s               [User: 91.089 s, System: 10.807 s]

Benchmark 2: pnpm astro check && dprint fmt && biome check --write && pnpm build
	Time (abs ):        31.803 s               [User: 63.479 s, System: 7.478 s]

Indeed, it does!

Summary
  pnpm astro check && dprint fmt && biome check --write && pnpm build ran
    1.58 times faster than (pnpm check || pnpm fix) && pnpm build

However, this is a not-so-fair test. pnpm astro check is a bottleneck, and we are not testing for the whole pipeline performance, but for ESLint + Prettier vs dprint + biome’s.

hyperfine -w 1 -r 1 '(pnpm check || pnpm fix) && pnpm build' 'dprint fmt && biome check --write && pnpm build'
Benchmark 1: (pnpm check || pnpm fix) && pnpm build
	Time (abs ):        25.641 s               [User: 52.933 s, System: 7.568 s]
 
Benchmark 2: dprint fmt && biome check --write && pnpm build
	Time (abs ):        14.619 s               [User: 29.942 s, System: 4.688 s]
Summary
  dprint fmt && biome check --write && pnpm build ran
    1.75 times faster than (pnpm check || pnpm fix) && pnpm build

Now, let’s do a full check:

hyperfine -w 3 -r 10 '(pnpm check || pnpm fix) && pnpm build' 'dprint fmt && biome check --write 
&& pnpm build'
Benchmark 1: (pnpm check || pnpm fix) && pnpm build
	Time (mean ± σ):     25.358 s ±  0.616 s    [User: 51.724 s, System: 7.522 s]
	Range (min  max):   24.839 s … 26.294 s    10 runs
 
Benchmark 2: dprint fmt && biome check --write && pnpm build
	Time (mean ± σ):     14.245 s ±  0.083 s    [User: 29.403 s, System: 4.612 s]
	Range (min  max):   14.122 s … 14.386 s    10 runs
Summary
  dprint fmt && biome check --write && pnpm build ran
    1.78 ± 0.04 times faster than (pnpm check || pnpm fix) && pnpm build

I would like to point out that even in this test, we are not testing just our toolset’s performance, for there’s the pnpm build — assigned to pnpm astro build — step.

hyperfine -w 1 -r 1 'pnpm fix' 'dprint fmt && biome check --write'
Benchmark 1: pnpm fix
Time (abs ):        11.463 s               [User: 23.603 s, System: 3.205 s]
 
Benchmark 2: dprint fmt && biome check --write
Time (abs ):        615.1 ms               [User: 1420.3 ms, System: 203.6 ms]
Summary
  dprint fmt && biome check --write ran
   18.64 times faster than pnpm fix

astro components — and pnpm invocations — are the bottleneck in our pipeline.

Next steps

One of the problems with the scripts declared in the package manifesto is that the abstraction pnpm check is too wasteful: astro check should be done by itself; and then, prettier check . || prettier --write ., the same for ESLint.

On the case of astro check: in my testing, it just checked for Astro-releated typesafety features, so I’m asuming ESLint and biome are sufficient. However, I would at least invoke it once, as a pre-build procedure.

Even if those optimizations were implemented, dprint + biome is faster. Don’t forget the fact that we are using dprint as a replacement for when biome does support HTML + supersets, that includes .astro.

I wouldn’t advice for the use of a JS/TS-based task runner; but, I do know that make is not well known, even though is part of a UNIX standart.

Performant, local-first development for freedom volunteers

My collegue, Kernel Chaos, and I are co-leading a meta-community, Ecuador in Tech that fosters and empowers tech-related communities. For the community, by the community! We are all volunteers!, and we intend to provide tools, capacitation and infraestructure for the betterment of our communities and volunteers.

Please, drop in to Ecuador in Tech and say hello!

Back to Blog

Related Posts

View All Posts »