Skip to main content

My experience of using zx library

Image of the author

Vitali Haradkouvitalics

Spiral Shell
Last updated: September 19, 2022

Context

My projects are very huge and complex, we’re working on automotive industry and our mission - make users happy with minimal involving. The user should fullfil financial info and upload driver license - and then he can purchase any car, matched by his financial capabilities. As I mentioned before - this is huge project with a lot of involving teams. On this project my role - lead and drive core changes of automation framework.

After the reading this article about zx, i’m shocked about capabilities of this library, because as you know, the node.js is not the best solution to write DevOps-like scripts, like build with extra CLI options, working with console styles, etc. If you’re creating such solution - you need an extra libraries, like fs-extra - fs with better api, chalk - console styling, yargs - cli options parser, and so on.

Project structure

Project Structure
  • Directory.github
    • CODEOWNERS
  • Directoryscripts
    • some.sh - sh script for aws pipeline
  • Directorysrc
    • Directorycli
      • index.ts
      • Directorycommands
        • build.ts - this exactly what we need
        • lint.ts
  • .env
  • package.json

Environment

Local

MacBook Pro 13-inch, 2020, Intel Core i5

CI (AWS Codebuild)

The Problem

Each command, like build, lint, test, has been written in typescript and runs with following options:

npm run build 'options' -> node -r ts-node/register -r tsconfig-paths/register 'path' 'options' -> execute '.ts' file with 'options'

Now lets see build command sources

src/cli/build.ts
// imports with relative paths
export default class BuildCommand extends notifiable(
messagable(CommandWithSpinner)
) {
skip = false;
async execute() {
// 1) run rimraf (cleanup _dist folder + remove extra folders, like reports)
// 2) ttsc - compile typescript sources via ttypescript library
// 3) copy (copy system files for future tests run)
// 4) send OS notification of --notify args has been sended to CLI script
}
}

As we see - each command extends at least 2 mixins(notifiable, messagable) with spinner class.

This approach cannot compile himself, since CLI is under src folder and ttsc compile whole project under src

Solution

The nodejs starts from 14 version - has possibility to run .mjs files.

First of all, I sync my local node.js with CI environment.

Secondly, Install zx script npm i zx.

Thirdly, I create scripts/build.mjs with following context:

scripts/build.mjs
import { $, fs, path, argv } from "zx";
import copyfiles from "copyfiles";
// notification module, since each script can be "notifiable"
import notification, { Images, Icons } from "./notification.mjs";
/**
* send OS notification
* @example
* ``` bash
* npm run xz scripts/build.mjs -- --notify
* ```
* @default
* false
*/
const notify = argv.notify || argv.n || false;
// OS notification settings
const settings = {
title: "Build",
contentImage: Images.typescript,
};
let message = "";
try {
// 1. rimraf
await rimraf();
// 2. build
await $`node node_modules/ttypescript/bin/tsc`;
// 3. copy files
await copy();
message = Icons.success + " Build: completed successfully";
} catch (error) {
message = Icons.error = " Build: failed with error: " + error.message;
} finally {
// write output
console.log(message);
if (notify) {
// write OS notification
notification({ ...settings, message });
}
}
async function rimraf() {
const reports = path.resolve("reports");
if (fs.existsSync(reports)) {
console.log("rimraf ", reports);
await fs.rm(reports, { recursive: true, force: true });
}
}
// copy files
function copy() {
const destinationDirPath = "_dist/visual-regression";
const inputGrep = "src/visual-regression/screenshots/**/*.png";
return new Promise((resolve, reject) => {
copyfiles([inputGrep, destinationDirPath], { up: 2 }, (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}

Benefits:

  1. This a is plain js file, no need to compile and use transpilers.
  2. scripts are under codeowners. it means you need to pass code review from the core team before new changes have been merged.

Cons:

  1. This is not a typescript, internal modules from src folder unavailable, since script folder not compiles.

Performance

Before:

Execution time takes 1 minute for my machine.

After:

20 seconds with clean build.

10 seconds with next build runs, since in our tsconfig.json we enable typescript caching. (incremental build is enabled by default)

Note: Cache is not relevant option for CI, since for each build CI starts new git checkout and starts fresh build.

SolutionBuild(no cache)Build(cache)
previous(Local)1 minute30-40 seconds
zx(Local)20 seconds10-15 seconds
previous(CI)1.5 minuteN/A
zx(CI)30 secondsN/A

Table 1. Total execution time by different approach.

Conclusion

I’m happy to use zx library, It’s maintained by Google, and I’m glad to use all spectre of his capabilities. Migration to zx increased speed of building about 2 times.

Have a good day, Bye 👋

All my Links: