lazy-build

5.0.1 • Public • Published

lazy-build

A lazy build system that...

  • Follows the "code over configuration" approach introduced by Gulp.
  • Applies it to clearly defined build targets as with GNU Make.
  • Decides which files to build using simple glob patterns.
  • Supports virtual file systems, like DatArchive or hyperdrive.

Getting started

Basic usage

Let's first create a single file programmatically. This is a basic build configuration that does just that:

// build.js
 
var Build = require('lazy-build/cli')
 
var build = new Build('target'))
 
build.add('test.json', function (target) {
  return target.write({
    path: target.path,
    contents: JSON.stringify({
      type: 'random',
      data: true
    })
  })
})
 
build.make()

If you now run node build.js test.json, the requested file will be created at target/test.json.

Multiple files

You can also define a single target to build multiple files, using glob patterns. Here's an example:

// build.js
 
var Build = require('lazy-build/cli')
 
var build = new Build('target'))
 
build.add('*.json', async function (target) {
  await target.prune()
 
  var data = [
    { dit: 32, dat: true },
    { dit: 0, dat: true},
    { dit: 501, dat: false}
  ]
 
  var targets = data.map((item, i) => {
    var number = i + 1
 
    return target.write({
      path: number + '.json',
      contents: JSON.stringify(item, null, 2)
    })
  })
 
  return Promise.all(targets)
})
 
build.make()

There's a couple of commands you can run now:

  • node build.js *.json (or just node build.js, which makes all targets) will create files for all three data points: target/1.json, target/2.json, and target/3.json.
  • You can (re)build any file separately too, for example node build.js 2.json. The other files will remain untouched.
  • node build.js --clean deletes all the files matching target/*.json.

Now let's assume you change your dataset, removing the last item. This is where the await target.prune() call goes to work.

  • node build.js --prune 3.json will now delete the file target/3.json, because the corresponding data point wasn't found. The other files are left untouched.
  • node build.js --prune *.json on the other hand deletes target/3.json, and rebuilds target/1.json and target/2.json as well.

From the file system

There is no builtin way to read files from lazy-build. Just using Node's fs module directly might suffice in a lot of cases. Sometimes you may need something more comprehensive though.

Vfile

One excellent option is to dive into the vfile ecosystem. Vfiles are supported in lazy-build as first class citizens, meaning they can be passed to target.write without adaption. This example uses to-vfile to read some markdown files and then transforms them to HTML using unified plugins:

var Build = require('lazy-build/cli')
var doc = require('rehype-document')
var fg = require('fast-glob')
var format = require('rehype-format')
var rehype = require('remark-rehype')
var remark = require('remark-parse')
var stringify = require('rehype-stringify')
var unified = require('unified')
var vfile = require('to-vfile')
 
var build = new Build('target')
 
build.add('*.html', async function (target) {
  await target.prune()
 
  var page = target.wildcards[0]
  var sources = fg.stream(`src/${page}.md`)
 
  for await (var source of sources) {
    var file = await vfile.read(source)
    file.dirname = ''
    file.extname = '.html'
    file = await unified()
      .use(remark)
      .use(rehype)
      .use(doc)
      .use(format)
      .use(stringify)
      .process(file)
 
    await target.write(file)
  }
})
 
build.make()

Gulp

The same goes for the Vinyl objects used by Gulp. They too can be handed to target.write without adaptation. This makes it possible to reuse Gulp workflows with only small adjustments. Take for example this typical Less-to-CSS pipeline:

var Build = require('lazy-build/cli')
var autoprefixer = require('gulp-autoprefixer')
var cssnano = require('gulp-cssnano')
var less = require('gulp-less')
var gulp = require('vinyl-fs')
 
var build = new Build('target')
 
build.add('*.css', async function (target) {
  await target.prune()
 
  var name = target.wildcards[0]
  var pipeline = gulp.src(`src/${name}.less`))
    .pipe(less())
    .pipe(autoprefixer())
    .pipe(cssnano())
 
  for await (var file of pipeline) {
    await target.write(file)
  }
})
 
build.make()

There's two main changes compared to a standard Gulp stream:

  1. We've replaced gulp.dest with an asynchronous iteration calling target.write. This ensures that only the requested CSS files are written to the file system.
  2. In gulp.src we're using target.wildcards[0] instead of a regular glob pattern. This makes building individual CSS files more efficient.

Remote sources

Sometimes we might want to include some remote resources in our build. Content from a headless CMS for example, or data from a REST API. Here's a very basic example to get started:

var Build  require('lazy-build/cli')
var got = require('got')
 
var build = new Build('target')
 
build.add('example.html', async function (target) {
  try {
    var res = await got('http://example.com')
    if (res.statusCode === 410) await target.prune()
    if (res.statusCode !== 200) return
 
    await target.write({
      path: target.path,
      contents: res.body
    })
  } catch (err) {
    console.warn('Failed to fetch remote version of example.html')
  }
})
 
build.make()

This example also shows why it's necessary to call target.prune manually. Here we first try to fetch the resource, and then only delete the old version if the server responds with the HTTP status "410 Gone". Then if the status code is anything else than 200, we don't write anything, leaving the old version in place. This makes our build process more resilient against downtime of external services, or just allows us to continue our work when offline.

More examples

All the examples above are available in the examples folder of this repository, as well as some other interesting use cases. If you have some alternative ideas of your own, PRs are always welcome!

API

var build = new Build(destination, options)

Create a new lazy-build instance.

destination

Type: string or object (required)

Destination folder for the build files.

If a path string is passed in, it will be used to create a scoped-fs instance.

If an object is used, it should implement all asynchronous fs methods, like for example dat-node or hyperdrive.

options.isPrune

Type: boolean (default: false)

Determines whether old files for targets should be pruned before rebuilding.

options.strictMode

Type: boolean (default: false)

Determines whether errors should be thrown on potential user errors.

build.add(path, handler [, options])

Add a new handler for building file(s).

path

Type: string (required)

Glob pattern matching the file(s) to be built by this handler.

handler(target [, callback])

Type: function (required)

Method containing the logic to create the file(s) matching path.

target.path

Type: string

The path originally passed into build.add.

target.wildcards

Type: Array<string>

A list of values to resolve the wildcards in the path glob pattern.

target.prune([callback])

Type: function

Returns Promise if no callback is passed in. Don't use callback unless options.useCallback === true. (see below)

Method that prunes existing files matching the path glob.

target.write(file, [callback])

Type: function

Returns Promise if no callback is passed in. Don't use callback unless options.useCallback === true. (see below)

Method to write a file to the build destination folder. File can be an object of type Vfile, Vinyl, or just a literal with path and contents properties set.

callback

Type: function (optional)

Only use if options.useCallback === true. (see below)

options.useCallback

Type: boolean (default: false)

Determines whether callback usage is allowed in the handler function.

build.clean([callback])

Type: function

Returns Promise if no callback is passed in.

Deletes all the files in the destination folder matching any of the build targets.

build.has(pattern)

Type: function

Returns boolean indicating if a build target is defined for pattern.

build.config(opts)

Type: function

Sets build options (see constructor).

build.make(patterns [, callback])

Type: function

Returns Promise if no callback is passed in.

Build all files matching the requested glob patterns.

patterns

Type: Array<string> or string

Should be a (list of) glob pattern(s) matching the files to be built.

build.resolve(path)

Type: function

Returns string representing the path relative to the build destination folder for a given path from the local file system.

Throws an error if the requested path is outside the destination folder.

License

Apache-2.0

Package Sidebar

Install

npm i lazy-build

Weekly Downloads

1

Version

5.0.1

License

Apache-2.0

Unpacked Size

42.5 kB

Total Files

28

Last publish

Collaborators

  • savelbr