Unverified Commit cae451d1 authored by Renée Kooi's avatar Renée Kooi Committed by GitHub
Browse files

Move UI code out of http.js (#417)

* WIP -  non-fancy log output

* Split UI from HTTP

* Move more UI things out of /http

* Lint fixes

* trim unused UI code

* lint

* Only switch to alternate buffer for fancy ui

* Rerender terminal UI when SSR completes

* Make progress work

* Initialise UI earlier

* Fix lint and extract fatal error formatter helper
parent 5134829b
......@@ -6,7 +6,6 @@ require('v8-compile-cache')
var ansi = require('ansi-escape-sequences')
var minimist = require('minimist')
var dedent = require('dedent')
var path = require('path')
var USAGE = `
......@@ -133,7 +132,6 @@ var argv = minimist(process.argv.slice(2), {
} else if (cmd === 'inspect') {
require('./lib/cmd-inspect')(path.join(entry), argv)
} else if (cmd === 'start') {
if (!argv.q) alternateBuffer()
require('./lib/cmd-start')(path.join(entry), argv)
} else {
console.log(NOCOMMAND)
......@@ -144,50 +142,3 @@ var argv = minimist(process.argv.slice(2), {
function clr (text, color) {
return process.stdout.isTTY ? ansi.format(text, color) : text
}
// Switch to an alternate terminal buffer,
// switch back to the main terminal buffer on exit.
function alternateBuffer () {
var q = Buffer.from('q')
var esc = Buffer.from([0x1B])
process.stdout.write('\x1b[?1049h') // Enter alternate buffer.
process.stdout.write('\x1b[H') // Reset screen to top.
process.stdout.write('\x1b[?25l') // Hide cursor
process.on('unhandledRejection', onexit)
process.on('uncaughtException', onexit)
process.on('SIGTERM', onexit)
process.on('SIGINT', onexit)
process.on('exit', onexit)
process.stdin.on('data', handleKey)
function handleKey (buf) {
if (buf.compare(q) === 0 || buf.compare(esc) === 0) {
onexit()
}
}
function onexit (statusCode) {
process.stdout.write('\x1b[?1049l') // Enter to main buffer.
process.stdout.write('\x1b[?25h') // Restore cursor
if (statusCode instanceof Error) {
console.error('A critical error occured, forcing Bankai to abort:\n')
console.error(clr(statusCode.stack, 'red') + '\n')
console.error(dedent`
If you think this might be a bug in Bankai, please consider helping
improve Bankai's stability by submitting an error to:
${' ' + clr('https://github.com/choojs/bankai/issues/new', 'underline')}
Please include the steps to reproduce this error, the stack trace
printed above, your version of Node, and your version of npm. Thanks!
${clr('— Team Choo', 'italic')}
` + '\n')
statusCode = 1
}
process.exit(statusCode)
}
}
var EventEmitter = require('events').EventEmitter
var gzipMaybe = require('http-gzip-maybe')
var gzipSize = require('gzip-size')
var assert = require('assert')
var path = require('path')
var pump = require('pump')
var send = require('send')
var Router = require('./lib/regex-router')
var ui = require('./lib/ui')
var bankai = require('./')
var files = [
'assets',
'documents',
'scripts',
'manifest',
'styles',
'service-worker'
]
module.exports = start
function start (entry, opts) {
......@@ -27,42 +16,15 @@ function start (entry, opts) {
assert.equal(typeof entry, 'string', 'bankai/http: entry should be type string')
assert.equal(typeof opts, 'object', 'bankai/http: opts should be type object')
var quiet = !!opts.quiet
opts = Object.assign({ reload: true }, opts)
var compiler = bankai(entry, opts)
var router = new Router()
var emitter = new EventEmitter()
var id = 0
var state = {
count: compiler.metadata.count,
files: {},
sse: 0,
size: 0
sse: 0
}
files.forEach(function (filename) {
state.files[filename] = {
name: filename,
progress: 0,
timestamp: ' ',
size: 0,
status: 'pending',
done: false
}
})
if (!quiet) var render = ui(state)
compiler.on('error', function (topic, sub, err) {
if (err.pretty) state.error = err.pretty
else state.error = `${topic}:${sub} ${err.message}\n${err.stack}`
if (!quiet) render()
})
compiler.on('progress', function () {
state.error = null
if (!quiet) render()
})
compiler.on('ssr', function (result) {
state.ssr = result
})
......@@ -70,30 +32,8 @@ function start (entry, opts) {
compiler.on('change', function (nodeName, edgeName, nodeState) {
var node = nodeState[nodeName][edgeName]
var name = nodeName + ':' + edgeName
var data = {
name: nodeName,
progress: 100,
timestamp: time(),
size: 0,
status: 'done',
done: true
}
state.files[nodeName] = data
if (name === 'documents:index.html') emitter.emit('documents:index.html', node)
if (name === 'styles:bundle') emitter.emit('styles:bundle', node)
// Only calculate the gzip size if there's a buffer. Apparently zipping
// an empty file means it'll pop out with a 20B base size.
if (node.buffer.length) {
gzipSize(node.buffer)
.then(function (size) { data.size = size })
.catch(function () { data.size = node.buffer.length })
.then(function () {
if (!quiet) render()
})
}
if (!quiet) render()
})
router.route(/^\/manifest.json$/, function (req, res, params) {
......@@ -182,7 +122,7 @@ function start (entry, opts) {
emitter.on('documents:index.html', reloadScript)
emitter.on('styles:bundle', reloadStyle)
state.sse += 1
if (!quiet) render()
compiler.emit('sse-connect')
res.writeHead(200, {
'Content-Type': 'text/event-stream',
......@@ -208,7 +148,7 @@ function start (entry, opts) {
emitter.removeListener('styles:bundle', reloadStyle)
connected = false
state.sse -= 1
if (!quiet) render()
compiler.emit('sse-disconnect')
}
}
......@@ -277,16 +217,3 @@ function gzip (buffer, req, res) {
pump(zipper, res)
zipper.end(buffer)
}
function time () {
var date = new Date()
var hours = numPad(date.getHours())
var minutes = numPad(date.getMinutes())
var seconds = numPad(date.getSeconds())
return `${hours}:${minutes}:${seconds}`
}
function numPad (num) {
if (num < 10) num = '0' + num
return num
}
......@@ -21,6 +21,9 @@ 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)
......
......@@ -3,17 +3,22 @@ var getPort = require('get-port')
var isElectronProject = require('./is-electron-project')
var http = require('./http-server')
var bankai = require('../http')
var createTui = require('./ui')
var createLogUi = require('./ui-basic')
module.exports = start
function start (entry, opts) {
var handler = bankai(entry, opts)
var state = handler.state
var createUi = opts.simple ? createLogUi : createTui
var render = createUi(handler.compiler, state)
isElectronProject(handler.compiler.dirname, function (err, bool) {
if (err) throw err
opts.electron = bool
var state = handler.state // TODO: move all UI code into this file
var server = http.createServer(function (req, res) {
if (req.type === 'OPTIONS') return cors(req, res)
handler(req, res, function () {
......@@ -22,6 +27,8 @@ function start (entry, opts) {
})
})
render()
getPort({port: 8080})
.then(function (port) {
server.listen(port, function () {
......
var ansi = require('ansi-escape-sequences')
var dedent = require('dedent')
function clr (text, color) {
return process.stdout.isTTY ? ansi.format(text, color) : text
}
module.exports = function fatalError (err) {
return dedent`
A critical error occured, forcing Bankai to abort:
${clr(err.stack, 'red')}
If you think this might be a bug in Bankai, please consider helping
improve Bankai's stability by submitting an error to:
${clr('https://github.com/choojs/bankai/issues/new', 'underline')}
Please include the steps to reproduce this error, the stack trace
printed above, your version of Node, and your version of npm. Thanks!
${clr('— Team Choo', 'italic')}
`
}
......@@ -17,7 +17,6 @@ var dirs = [
// 3. Estimate total size of all files combined, and emit `size`.
//
// TODO: optimize assets (on the fly); e.g. convert images to webp, etc.
// TODO: also emit `progress`.
module.exports = node
......@@ -43,7 +42,7 @@ function node (state, createEdge) {
})
tracker.on('progress', function (progress) {
// self.emit('progress', 'assets', progress)
self.emit('progress', 'assets', progress)
})
this.on('close', function () {
......
var nanoraf = require('nanoraf')
var pretty = require('prettier-bytes')
var gzipSize = require('gzip-size')
module.exports = createLogUI
var files = [
'assets',
'documents',
'scripts',
'styles',
'manifest',
'service-worker'
]
function createLogUI (compiler, state) {
Object.assign(state, {
count: compiler.metadata.count,
files: {},
size: 0
})
files.forEach(function (filename) {
state.files[filename] = {
size: 0,
status: 'pending'
}
})
var render = nanoraf(onrender, raf)
compiler.on('change', function (nodeName, edgeName, nodeState) {
var node = nodeState[nodeName][edgeName]
var data = {
size: 0,
status: 'done'
}
state.files[nodeName] = data
// Only calculate the gzip size if there's a buffer. Apparently zipping
// an empty file means it'll pop out with a 20B base size.
if (node.buffer.length) {
gzipSize(node.buffer)
.then(function (size) { data.size = size })
.catch(function () { data.size = node.buffer.length })
.then(render)
} else {
render()
}
})
compiler.on('progress', render)
compiler.on('sse-connect', render)
compiler.on('sse-disconnect', render)
var diff = new Differ(state)
var renderRaf = nanoraf(onrender, raf)
return renderRaf
function onrender () {
diff.update(state)
}
}
function raf (cb) {
setTimeout(cb, 50)
}
function view (state) {
var ssrState = 'Pending'
if (state.ssr) {
if (state.ssr.success) ssrState = 'Success'
else ssrState = 'Skipped - ' + state.ssr.error.message
}
var sseStatus = state.sse > 0 ? 'connected' : state.port ? 'ready' : 'starting'
var httpStatus = state.port ? 'https://localhost:' + state.port : 'starting'
var allFilesDone = true
var size = Object.keys(state.files).reduce(function (num, filename) {
var file = state.files[filename]
if (file.status !== 'done') allFilesDone = false
return num + file.size
}, 0)
var files = state.files
var output = [
`bankai: HTTP Status: ${httpStatus}`,
`bankai: Live Reload: ${sseStatus}`,
`bankai: Server Side Rendering: ${ssrState}`,
`bankai: assets ${files.assets ? files.assets.status : 'starting'}`,
`bankai: documents ${files.documents ? files.documents.status : 'starting'}`,
`bankai: scripts ${files.scripts ? files.scripts.status : 'starting'}`,
`bankai: styles ${files.styles ? files.styles.status : 'starting'}`,
`bankai: manifest ${files.manifest ? files.manifest.status : 'starting'}`,
`bankai: service-worker ${files['service-worker'] ? files['service-worker'].status : 'starting'}`,
`bankai: Total File size: ${allFilesDone ? pretty(size).replace(' ', '') : 'pending'} `
]
return output
}
function Differ (state) {
var logLines = view(state)
console.log(logLines.join('\n'))
this.oldState = logLines
}
Differ.prototype.update = function (state) {
var newState = view(state)
this.oldState.forEach((line, i) => {
if (line !== newState[i]) console.log(newState[i])
})
this.oldState = newState
}
var ansi = require('ansi-escape-sequences')
var differ = require('ansi-diff')
var pretty = require('prettier-bytes')
var gzipSize = require('gzip-size')
var keypress = require('keypress')
var differ = require('ansi-diff')
var strip = require('strip-ansi')
var nanoraf = require('nanoraf')
var fatalError = require('./fatal-error')
var StartDelimiter = '|'
var EndDelimiter = '|'
......@@ -20,13 +22,71 @@ var files = [
'service-worker'
]
module.exports = render
module.exports = createUi
function render (state) {
function createUi (compiler, state) {
var diff = differ()
alternateBuffer()
Object.assign(state, {
count: compiler.metadata.count,
files: {},
size: 0
})
var render = nanoraf(onrender, raf)
files.forEach(function (filename) {
state.files[filename] = {
name: filename,
progress: 0,
timestamp: ' ',
size: 0,
status: 'pending',
done: false
}
})
compiler.on('error', function (topic, sub, err) {
if (err.pretty) state.error = err.pretty
else state.error = `${topic}:${sub} ${err.message}\n${err.stack}`
render()
})
compiler.on('ssr', render)
compiler.on('progress', function (nodeName, progress) {
state.error = null
state.files[nodeName].progress = progress
render()
})
compiler.on('change', function (nodeName, edgeName, nodeState) {
var node = nodeState[nodeName][edgeName]
var data = {
name: nodeName,
progress: 100,
timestamp: time(),
size: 0,
status: 'done',
done: true
}
state.files[nodeName] = data
// Only calculate the gzip size if there's a buffer. Apparently zipping
// an empty file means it'll pop out with a 20B base size.
if (node.buffer.length) {
gzipSize(node.buffer)
.then(function (size) { data.size = size })
.catch(function (size) { data.size = node.buffer.length })
.then(render)
}
render()
})
compiler.on('sse-connect', render)
compiler.on('sse-disconnect', render)
process.stdout.on('resize', onresize)
if (process.stdin.isTTY) {
......@@ -115,7 +175,7 @@ function view (state) {
// header
function header (state) {
var SSEStatus = state.sse > 0
var sseStatus = state.sse > 0
? clr('connected', 'green')
: state.port
? 'ready'
......@@ -126,7 +186,7 @@ function header (state) {
: clr('starting', 'yellow')
var left = `HTTP: ${httpStatus}`
var right = `Live Reload: ${SSEStatus}`
var right = `Live Reload: ${sseStatus}`
return spaceBetween(left, right)
}
......@@ -208,3 +268,50 @@ function spaceBetween (left, right) {
}
return left + space + right
}
function time () {
var date = new Date()
var hours = numPad(date.getHours())
var minutes = numPad(date.getMinutes())
var seconds = numPad(date.getSeconds())
return `${hours}:${minutes}:${seconds}`
}
function numPad (num) {
if (num < 10) num = '0' + num
return num
}
function alternateBuffer () {
var q = Buffer.from('q')
var esc = Buffer.from([0x1B])
process.stdout.write('\x1b[?1049h') // Enter alternate buffer.
process.stdout.write('\x1b[H') // Reset screen to top.
process.stdout.write('\x1b[?25l') // Hide cursor
process.on('unhandledRejection', onexit)
process.on('uncaughtException', onexit)
process.on('SIGTERM', onexit)
process.on('SIGINT', onexit)
process.on('exit', onexit)
process.stdin.on('data', handleKey)
function handleKey (buf) {
if (buf.compare(q) === 0 || buf.compare(esc) === 0) {
onexit()
}
}
function onexit (statusCode) {
process.stdout.write('\x1b[?1049l') // Enter to main buffer.
process.stdout.write('\x1b[?25h') // Restore cursor
if (statusCode instanceof Error) {
console.error(fatalError(statusCode))
statusCode = 1
}
process.exit(statusCode)
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment