lazy-build
v5.0.1
Published
A lazy build system using glob targets
Downloads
5
Maintainers
Readme
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
orhyperdrive
.
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 justnode build.js
, which makes all targets) will create files for all three data points:target/1.json
,target/2.json
, andtarget/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 matchingtarget/*.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 filetarget/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 deletestarget/3.json
, and rebuildstarget/1.json
andtarget/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:
- We've replaced
gulp.dest
with an asynchronous iteration callingtarget.write
. This ensures that only the requested CSS files are written to the file system. - In
gulp.src
we're usingtarget.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