bankai
Version:
The easiest way to compile JavaScript, HTML and CSS
241 lines (211 loc) • 7.96 kB
JavaScript
var Emitter = require('events').EventEmitter
var debug = require('debug')('bankai')
var graph = require('buffer-graph')
var assert = require('assert')
var path = require('path')
var pino = require('pino')
var localization = require('./localization')
var queue = require('./lib/queue')
var utils = require('./lib/utils')
var ServerRender = require('./ssr')
var assetsNode = require('./lib/graph-assets')
var documentNode = require('./lib/graph-document')
var faviconNode = require('./lib/graph-favicon')
var manifestNode = require('./lib/graph-manifest')
var reloadNode = require('./lib/graph-reload')
var scriptNode = require('./lib/graph-script')
var serviceWorkerNode = require('./lib/graph-service-worker')
var styleNode = require('./lib/graph-style')
module.exports = Bankai
function Bankai (entry, opts) {
if (!(this instanceof Bankai)) return new Bankai(entry, opts)
Emitter.call(this)
opts = opts || {}
this.local = localization(opts.language || 'en-US')
this.log = pino(opts.logStream || process.stdout)
assert.strictEqual(typeof entry, 'string', 'bankai: entry should be type string')
assert.ok(path.isAbsolute(entry), 'bankai: entry should be an absolute path. Received: ' + entry)
assert.strictEqual(typeof opts, 'object', 'bankai: opts should be type object')
var self = this
var methods = [
'manifest',
'assets',
'service-worker',
'scripts',
'styles',
'documents'
]
// Initialize data structures.
var key = Buffer.from('be intolerant of intolerance')
// TODO maybe use fs.stat here to check if it's a directory?
// That would be best but we may have to do a sync call, because graph nodes depend on this
// value being available immediately, which is less great.
this.dirname = path.extname(entry) === '' ? entry : utils.dirname(entry) // The base directory.
this.queue = queue(methods) // The queue caches requests until ready.
this.graph = graph(key) // The graph manages relations between deps.
this.ssr = new ServerRender(entry)
// Detect when we're ready to allow requests to go through.
this.graph.on('change', function (nodeName, edgeName, state) {
self.emit('change', nodeName, edgeName, state)
var eventName = nodeName + ':' + edgeName
var count = self.metadata.count
var queue = self.queue
if (eventName === 'assets:list') {
count['assets'] = String(self.graph.data.assets.list.buffer).split(',').length
queue['assets'].ready()
} else if (eventName === 'documents:list') {
count['documents'] = String(self.graph.data.documents.list.buffer).split(',').length
queue['documents'].ready()
} else if (eventName === 'manifest:bundle') {
count['manifest'] = 1
queue['manifest'].ready()
} else if (eventName === 'scripts:bundle') {
count['scripts'] = 1
queue['scripts'].ready()
} else if (eventName === 'service-worker:bundle') {
count['service-worker'] = 1
queue['service-worker'].ready()
} else if (eventName === 'styles:bundle') {
count['styles'] = 1
queue['styles'].ready()
}
})
// Handle errors so they can be logged.
this.graph.on('error', function () {
var args = ['error']
for (var len = arguments.length, i = 0; i < len; i++) {
args.push(arguments[i])
}
self.emit.apply(self, args)
})
this.graph.on('progress', function (chunk, value) {
self.emit('progress', chunk, value)
})
this.graph.on('ssr', function (result) {
self.emit('ssr', result)
})
// Insert nodes into the graph.
var documentDependencies = [ 'assets:list', 'manifest:bundle', 'styles:bundle', 'scripts:bundle', 'favicon:bundle' ]
if (opts.reload) {
documentDependencies.push('reload:bundle')
this.graph.node('reload', reloadNode)
}
this.graph.node('favicon', faviconNode)
this.graph.node('assets', assetsNode)
this.graph.node('documents', documentDependencies, documentNode)
this.graph.node('manifest', manifestNode)
this.graph.node('scripts', scriptNode)
this.graph.node('service-worker', [ 'assets:list', 'styles:bundle', 'scripts:bundle', 'documents:list' ], serviceWorkerNode)
this.graph.node('styles', [ 'scripts:style', 'scripts:bundle' ], styleNode)
// Kick off the graph.
this.graph.start({
dirname: this.dirname,
watch: opts.watch !== false,
babelifyDeps: opts.babelifyDeps !== false,
fullPaths: opts.fullPaths,
reload: Boolean(opts.reload),
log: this.log,
ssr: this.ssr,
watchers: {},
entry: entry,
opts: opts,
count: {
assets: 0,
documents: 0,
manifest: 0,
scripts: 0,
'service-worker': 0,
style: 0
}
})
this.metadata = this.graph.metadata
}
Bankai.prototype = Object.create(Emitter.prototype)
Bankai.prototype.scripts = function (filename, cb) {
assert.strictEqual(typeof filename, 'string')
assert.strictEqual(typeof cb, 'function')
var stepName = 'scripts'
var edgeName = filename.split('.')[0]
var self = this
this.queue[stepName].add(function () {
var data = self.graph.data[stepName][edgeName]
if (!data) return cb(new Error(`bankai.scripts: could not find a bundle for "${filename}"`))
cb(null, data)
})
}
Bankai.prototype.styles = function (filename, cb) {
assert.strictEqual(typeof filename, 'string')
assert.strictEqual(typeof cb, 'function')
var stepName = 'styles'
var edgeName = filename.split('.')[0]
var self = this
this.queue[stepName].add(function () {
var data = self.graph.data[stepName][edgeName]
if (!data) return cb(new Error('bankai.styles: could not find bundle'))
cb(null, data)
})
}
Bankai.prototype.documents = function (url, cb) {
assert.strictEqual(typeof url, 'string')
assert.strictEqual(typeof cb, 'function')
var filename = url.split('?')[0]
if (filename === '/') filename = 'index'
var stepName = 'documents'
var edgeName = filename.split('.')[0] + '.html'
var self = this
this.queue[stepName].add(function () {
var data = self.graph.data[stepName][edgeName]
if (!data) return cb(new Error('bankai.document: could not find a document for ' + filename))
cb(null, data)
})
}
Bankai.prototype.manifest = function (cb) {
assert.strictEqual(typeof cb, 'function')
var stepName = 'manifest'
var edgeName = 'bundle'
var self = this
this.queue[stepName].add(function () {
var data = self.graph.data[stepName][edgeName]
if (!data) return cb(new Error('bankai.manifest: could not find bundle'))
cb(null, data)
})
}
Bankai.prototype.serviceWorker = function (cb) {
assert.strictEqual(typeof cb, 'function')
var stepName = 'service-worker'
var edgeName = 'bundle'
var self = this
this.queue[stepName].add(function () {
var data = self.graph.data[stepName][edgeName]
if (!data) return cb(new Error('bankai.serviceWorker: could not find bundle'))
cb(null, data)
})
}
Bankai.prototype.assets = function (filename, cb) {
assert.strictEqual(typeof filename, 'string')
assert.strictEqual(typeof cb, 'function')
var stepName = 'assets'
var self = this
this.queue[stepName].add(function () {
filename = path.join(self.dirname, filename)
var data = self.metadata.assets[filename]
if (!data) return cb(new Error('bankai.asset: could not find a file for ' + filename))
cb(null, filename)
})
}
Bankai.prototype.sourceMaps = function (stepName, edgeName, cb) {
assert.strictEqual(typeof stepName, 'string')
assert.strictEqual(typeof edgeName, 'string')
assert.strictEqual(typeof cb, 'function')
edgeName = /\.map$/.test(edgeName) ? edgeName : edgeName + '.map'
var self = this
var data = self.graph.data[stepName][edgeName]
if (!data) return cb(new Error('bankai.sourceMaps: could not find a file for ' + stepName + ':' + edgeName))
cb(null, data)
}
Bankai.prototype.close = function () {
debug('closing all file watchers')
this.ssr.close()
this.graph.emit('close')
this.emit('close')
}