makestatic-core
Version:
Generic file processing library
415 lines (360 loc) • 8.37 kB
JavaScript
const path = require('path')
const sequence = require('when/sequence')
const when = require('when')
const Config = require('./lib/config')
const Context = require('./lib/context')
const Phase = require('./lib/phase')
/**
* Core library for the processing lifecycle.
*
* @class LifeCycle
*/
class LifeCycle {
/**
* Creates a new LifeCycle.
*
* @constructor LifeCycle
* @param {Object} options the processing options.
*/
constructor (options = {}) {
this._config = new Config(options)
}
/**
* The lifecycle configuration.
*
* @property {Config} config
* @member LifeCycle
* @readonly
*/
get config () {
return this._config
}
/**
* The processing context for this lifecycle.
*
* @property {Context} context
* @member LifeCycle
* @readonly
*/
get context () {
return this._context
}
/**
* List of standard lifecycle phases.
*
* @property {Array} main
* @member LifeCycle
* @readonly
*/
get main () {
return standard
}
/**
* List of deploy lifecycle phases.
*
* @property {Array} deploy
* @member LifeCycle
* @readonly
*/
get deploy () {
return [Audit, Deploy]
}
/**
* Builds the list of phases to execute.
*
* @function getPlugins
* @member LifeCycle
* @param {Object} argv map of option overrides.
*
* @returns array list of phase plugins.
*/
getPlugins (argv = {}) {
// configure initial phases
let plugins = [Clean, Build, Load]
// using pack phase
if (argv.pack !== false) {
plugins.push(Pack)
// if we are packing the webpack plugin is responsible
// for executing the rest of the lifecycle
return plugins
}
plugins = plugins.concat(standard)
// must write out files
plugins.push(Write)
// add deploy phases
plugins = plugins.concat(this.deploy)
return plugins
}
/**
* Abstract method to get a default lifecycle configuration.
*
* @abstract
* @function getLifecycleConfig
* @member LifeCycle
* @param {Object} opts map of computed options.
* @param {Object} argv map of option overrides.
*
* @returns object map of lifecycle phase configurations.
*/
getLifecycleConfig (opts = {}/*, argv = {} */) {
return opts.lifecycle || {}
}
/**
* Process the entire lifecycle.
*
* This method will set the computed options on the `config` based on
* the passed `argv` options merged with the options specified when
* the instance was created.
*
* It will then merge the result of calling `getLifecycleConfig()` into the
* `lifecycle` property of the computed options before retrieving the
* list of plugin phases to execute returned by `getPlugins()`.
*
* Finally it creates a processing `context` for this lifecycle before
* deferring execution to the `run()` method.
*
* @function process
* @member LifeCycle
* @param {Object} argv map of option overrides.
* @param {Array} phases list of phases to execute.
*
* @returns a promise that resolves when all phases have completed.
*/
process (argv = {}, phases = null) {
// configure options
let opts
try {
opts = this.config.setComputedOptions(argv)
} catch (e) {
return when.reject(e)
}
// get the lifecycle configuration
let config = this.getLifecycleConfig(opts, argv)
opts.lifecycle = config
let lifecycle = phases || this.getPlugins(argv)
// configure processing context
this._context = new Context(this, this.config)
this._phases = []
return this.run(lifecycle)
}
/**
* Name of the currently executing phase.
*
* @property {String} phase
* @member LifeCycle
* @readonly
*/
get phase () {
return this._phase
}
/**
* List of phases that have already executed.
*
* @property {Array} phases
* @member LifeCycle
* @readonly
*/
get phases () {
return this._phases
}
/**
* Run lifecycle processing phases.
*
* @function run
* @member LifeCycle
* @param {Array} phases list of phases to execute.
*
* @returns a promise that resolves when all phases have completed.
*/
run (phases) {
const context = this.context
const opts = context.options
// execute the phases
phases = phases.map((Plugin) => {
let plugin = new Plugin(opts)
return () => {
return plugin.validate(context)
.then(() => {
let name = plugin.constructor.name
this._phase = name.toLowerCase()
//console.log(this._phase)
return plugin.process(context, opts, name)
.catch((e) => {
throw e
})
})
}
})
return sequence(phases)
.then(() => context)
}
}
/**
* Clean output files.
*
* @private {class} Clean
* @inherits Phase
*/
class Clean extends Phase {}
/**
* Build source files.
*
* @private {class} Build
* @inherits Phase
*/
class Build extends Phase {}
/**
* Loads source files from disc.
*
* @private {class} Load
* @inherits Phase
*/
class Load extends Phase {
/**
* Validates that a `root` directory has been specified.
*
* @private {function} validate
* @member Load
*/
validate (context) {
return when.promise((resolve, reject) => {
if (!context.options.root) {
return reject(new Error(`root directory is required`))
}
resolve()
})
}
}
/**
* Package source files, typically this would mean compiling with webpack.
*
* @private {class} Pack
* @inherits Phase
*/
class Pack extends Phase {}
/**
* Parse source files to ensure there is an abstract syntax tree.
*
* @private {class} Parse
* @inherits Phase
*/
class Parse extends Phase {}
/**
* Create a graph of the site resources for the compiled output files.
*
* @private {class} Graph
* @inherits Phase
*/
class Graph extends Phase {}
/**
* Perform transformations on the parsed abstract syntax trees.
*
* @private {class} Transform
* @inherits Phase
*/
class Transform extends Phase {}
/**
* Verify document abstract syntax trees.
*
* @private {class} Verify
* @inherits Phase
*/
class Verify extends Phase {}
/**
* Resolve the output path for a file and seal the file contents.
*
* @private {class} Resolve
* @inherits Phase
*/
class Resolve extends Phase {}
/**
* Validate output files.
*
* Typically used to validate HTML files but you can add any plugins you want
* to this phase.
*
* @private {class} Validate
* @inherits Phase
*/
class Validate extends Phase {}
/**
* Optimize compiled assets.
*
* @private {class} Optimize
* @inherits Phase
*/
class Optimize extends Phase {}
/**
* Emit additional assets such as static gz files.
*
* @private {class} Emit
* @inherits Phase
*/
class Emit extends Phase {}
/**
* Generate file manifest.
*
* @private {class} Manifest
* @inherits Phase
*/
class Manifest extends Phase {
/**
* Validates the `manifest` option.
*
* @private {function} validate
* @member Load
*/
validate (context) {
const opts = context.options
// configure manifest file output path
if (opts.manifest) {
let manifest = opts.manifest
if (opts.manifest === true) {
manifest = 'manifest.json'
}
if (opts.manifest && typeof manifest !== 'string') {
return when.reject(
new Error(`manifest file path should be a string`))
}
// cannot set path, must be basename as is always written to
// the output directory
manifest = path.basename(manifest)
opts.manifest = manifest
}
return when.resolve(opts)
}
}
/**
* Write files to disc.
*
* @private {class} Write
* @inherits Phase
*/
class Write extends Phase {}
/**
* Audit files prior to deployment.
*
* @private {class} Audit
* @inherits Phase
*/
class Audit extends Phase {}
/**
* Deploy files to a provider.
*
* @private {class} Deploy
* @inherits Phase
*/
class Deploy extends Phase {}
// standard plugin phases
const standard = [
Parse,
Graph,
Transform,
Verify,
Resolve,
Validate,
Optimize,
Emit,
Manifest
]
module.exports = LifeCycle