src | ||
.eslintrc.yaml | ||
.gitignore | ||
LICENSE | ||
package.json | ||
README.md | ||
rollup.config.js |
Defiler
A small, strange building block.
Motivation
Defiler is a small build tool framework with strange opinions. It was born out of a desire to redo the build process for my various personal websites. I wanted something that was very flexible, kept everything in memory as it was building, and could handle arbitrary dependencies between files so that when something changed, only the necessary files would be re-built.
Concepts
Defiler's concept of a file is something that can come from one of two places: a physical file on the disk, or a virtual file that is generated by a callback you pass to Defiler. These two types of files differ slightly in how they are treated, but for the most part Defiler handles them both the same.
Files of both types are run through the gamut of transforms you register with Defiler. Each transform mutates the object representing the file in-place, and returns a promise indicating when it's finished.
Files' names can be changed as they're transformed, but the main way to refer to them will continue to be by their original path. This makes Defiler's job a bit easier, but is also probably more useful anyway. If you want to translate LESS into CSS and then inject it into a particular script, you're going to want to write import './path/to/file.less'
not import './path/to/file.css'
.
Files can be made to depend on other files, so that changes to a dependency cause the depender to be re-transformed. For physical files, the file does not need to be re-read from the disk before it can be re-transformed, as the original version is kept in memory.
Any transform or generator can also create additional files (which will then be run through all of the transforms). There's currently no way to make this additional file depend on any others for the purposes of automatic re-transformation, as the file would generally just be re-added when that transform or generator is run again.
Gaze is used as the underlying watcher for the simple reason that it provided an easy way to get a list of all of the currently watched files, so I didn't have to traverse the directory's initial contents at the start of the build.
If you need to write the transformed files to disk, that's its own transform. Just leave the file object untouched but write the file to disk in the appropriate location and return a promise indicating when you're done.
If you need some task management, that's outside the scope of this library. Just use await
and Promise.all
.
Requirements
API
The API consists of two classes, File
and Defiler
.
A File
represents a physical file on the disk, or a generated virtual file with no particular corresponding file in the filesystem, or a partially or fully transformed physical or virtual/generated file.
A Defiler
represents a set of watched files on the disk, plus a set of generated files, plus a set of transforms to execute on them.
File
Constructor
new File(path)
A new File
instance to serve as the representation of a physical file, a generated file, or a transformed file. path
is the relative path to the file, from some understood root.
Properties
path
The file's path can be retrieved or updated by getting and setting path
.
ext
The file's extension (including the preceding .
) can be retrieved or updated by getting and setting path
. The ext
and path
properties are kept in sync.
bytes
The file's contents can be updated by getting or setting bytes
, which is a Buffer
.
text
The file's contents can also be updated by getting of setting text
, which is a string. Mutating the bytes
Buffer
will not be reflected in text
, but reassigning the entire bytes
or text
properties will keep the other in sync.
Defiler
Constructor
new Defiler()
Properties
ready
A promise that's resolved once we've completed the initial wave of processing. This remains unchanged as later watches or changes are triggered.
origFiles
A map of relative paths -> File
instances for the original physical files.
files
A map of (original) relative paths -> File
instances for the transformed files.
origPaths
A sorted array of the relative paths of all of the physical files. This can be used whether or not we've completed the initial wave of processing.
Configuration
addGaze(gaze, rootPath, read = true)
Register a Gaze instance.
gaze
- the Gaze instancerootPath
- the path that all of our paths should be relative toread
- whether to actually read in the contents of the files for this Gaze. Iffalse
, the files will still be run through all of the transforms, but they will have nullbytes
andtext
Returns the Defiler
instance for chaining.
addTransform(transform)
Register a new transform to be applied to all files.
transform(file)
- a transformer function, which is passed aFile
instance to mutate. In your function,this
will be the currentDefiler
instance. The function should return a promise to indicate when it's done
Returns the Defiler
instance for chaining.
if(condition)
/ else()
/ end()
Allows conditionally skipping certain transforms in the pipe.
condition(file)
is passed theFile
instance. In your function,this
will be the currentDefiler
instance. The function should returntrue
orfalse
Returns the Defiler
instance for chaining.
This is used like: defiler.if(file => someTest(file)).addTransform(onlyIfTrue).else().addTransform(onlyIfFalse).end()
.
addGeneratedFile(path, generator)
Register a new generated file, not directly sourced from a physical file.
path
- the relative path of the file to register the generator forgenerator(file)
- a function that is passed a newFile
instance containing only a path, which it should then mutate. In your function,this
will be the currentDefiler
instance. The function should return a promise to indicate when it's done
Returns the Defiler instance for chaining.
Execution
exec()
Starts the Defiler running. No additional configuration (registering Gazes, transforms, or generated files) can happen after this.
Operation
use(path, origin)
Waits for a file or array of files to be ready.
path
- The path or paths to wait for to become available.origin
- (optional) A path of a file to re-process if any of the file or files given inpath
change. (Typically, this is the path to the file you are currently transforming or generating.)
Returns a promise resolving to the File
instance or an array of File
instances.
This can be asked for physical or generated files. If you ask for one or more physical files during the initial wave of processing before everything has been read in and processed, it will wait for the file or files to be ready (and transformed). Asking for something that is neither a known physical file nor a registered generated file will not throw an error, but will instead simple return null.
If a path origin
is passed, origin
is registered as depending on the file or files in path
. When the file or files in path
change, the file at origin
will be automatically re-transformed (using refile
, below). If you're calling use
inside a transform or generator, origin
is typically going to be the path of the file you're transforming or generating.
refile(path)
Manually re-transform a file. This can be from a physical file or a generated once. Returns a promise to indicate when all processing is complete. Re-transforming a physical file will use the version of it that was last read into memory. Re-transforming a generated file will call its generator again.
Returns a promise to indicate when all processing is complete.
Typically, you would not need to call this directly, as it would be automatically handled by the dependencies registered by use
.
addFile(defile)
Manually insert a non-physical file, running it through all the transforms.
Returns a promise to indicate when all processing is complete.
close()
Close all of the attached Gazes.
Events
Defiler
extends Node's EventEmitter
, and emits four events.
origFile(file)
An origFile
event is emitted when the original version of a physical file has been read in. It's emitted with one argument, the File
instance.
file(file)
A file
event is emitted after all transforms on a file are complete. It's emitted with one argument, the fully transformed File
instance.
deleted(path)
A deleted
event is emitted when a watched physical file has been deleted. It's emitted with one argument, the relative path
to the file.
error(file, err)
An error
event is emitted if a file transform or a file generator throws an exception or returns a promise that rejects. It's emitted with two arguments, the File
instance in question and the thrown err
.
License
Copyright (c) 2017 Conduitry
MIT License