Logo Haradkou SDET
My experience of using zx library

My experience of using zx library

September 6, 2022
5 min read
Table of Contents

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.

Environment

Local

MacBook Pro 13-inch, 2020, Intel Core i5

  • node.js LTS (16)
  • npm 7

CI (AWS Codebuild)

  • node.js 14
  • npm 6

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
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
  }
}
The Build Command

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 👋