UNPKG

6.53 kBJavaScriptView Raw
1var EventEmitter = require('events').EventEmitter
2var gzipMaybe = require('http-gzip-maybe')
3var assert = require('assert')
4var path = require('path')
5var pump = require('pump')
6var send = require('send')
7
8var Router = require('./lib/regex-router')
9var bankai = require('./')
10
11module.exports = start
12
13function start (entry, opts) {
14 opts = opts || {}
15
16 assert.strictEqual(typeof entry, 'string', 'bankai/http: entry should be type string')
17 assert.strictEqual(typeof opts, 'object', 'bankai/http: opts should be type object')
18
19 opts = Object.assign({ reload: true }, opts)
20 var compiler = bankai(entry, opts)
21 var router = new Router()
22 var emitter = new EventEmitter()
23 var id = 0
24 var state = {
25 sse: 0
26 }
27
28 compiler.on('ssr', function (result) {
29 state.ssr = result
30 })
31
32 compiler.on('change', function (nodeName, edgeName, nodeState) {
33 var node = nodeState[nodeName][edgeName]
34 var name = nodeName + ':' + edgeName
35 if (name === 'documents:index.html') emitter.emit('documents:index.html', node)
36 if (name === 'styles:bundle') emitter.emit('styles:bundle', node)
37 })
38
39 router.route(/^\/(favicon\.)(ico|png|gif)$/, function (req, res, params) {
40 var filename = params[1] + params[2]
41 pump(send(req, filename), res)
42 })
43
44 router.route(/^\/manifest\.json$/, function (req, res, params) {
45 compiler.manifest(function (err, node) {
46 if (err) {
47 res.statusCode = 404
48 return res.end(err.message)
49 }
50 res.setHeader('content-type', 'application/json')
51 gzip(node.buffer, req, res)
52 })
53 })
54
55 router.route(/\/(service-worker\.js)|(\/sw\.js)$/, function (req, res, params) {
56 compiler.serviceWorker(function (err, node) {
57 if (err) {
58 res.statusCode = 404
59 return res.end(err.message)
60 }
61 res.setHeader('content-type', 'application/javascript')
62 gzip(node.buffer, req, res)
63 })
64 })
65
66 router.route(/^\/(assets|content|public)\/([^?]*)(\?.*)?$/, function (req, res, params) {
67 var prefix = params[1] // asset dir
68 var name = prefix + '/' + params[2]
69 compiler.assets(name, function (err, filename) {
70 if (err) {
71 res.statusCode = 404
72 return res.end(err.message)
73 }
74 pump(send(req, filename), res)
75 })
76 })
77
78 router.route(/\/([a-zA-Z0-9-_.]+)\.js(\?.*)?$/, function (req, res, params) {
79 var name = params[1]
80 compiler.scripts(name, function (err, node) {
81 if (err) {
82 res.statusCode = 404
83 return res.end(err.message)
84 }
85 res.setHeader('content-type', 'application/javascript')
86 gzip(node.buffer, req, res)
87 })
88 })
89
90 router.route(/\/([a-zA-Z0-9-_.]+)\.css(\?.*)?$/, function (req, res, params) {
91 var name = params[1]
92 compiler.styles(name, function (err, node) {
93 if (err) {
94 res.statusCode = 404
95 return res.end(err.message)
96 }
97 res.setHeader('content-type', 'text/css')
98 gzip(node.buffer, req, res)
99 })
100 })
101
102 // Source maps. Each source map is stored as 'foo.js.map' within their
103 // respective node. So in order to figure out the right source map we must
104 // derive figure out where the extension comes from.
105 router.route(/\/([a-zA-Z0-9-_.]+)\.map$/, function (req, res, params) {
106 var source = params[1]
107 var ext = path.extname(source.replace(/\.map$/, ''))
108 var type = source === 'bankai-reload.js'
109 ? 'reload'
110 : source === 'bankai-service-worker.js'
111 ? 'service-worker'
112 : ext === '.js'
113 ? 'scripts'
114 : 'unknown'
115 compiler.sourceMaps(type, source, function (err, node) {
116 if (err) {
117 res.statusCode = 404
118 return res.end(err.message)
119 }
120 res.setHeader('content-type', 'application/json')
121 gzip(node.buffer, req, res)
122 })
123 })
124
125 router.route(/\/reload/, function sse (req, res) {
126 var connected = true
127 emitter.on('documents:index.html', reloadScript)
128 emitter.on('styles:bundle', reloadStyle)
129 state.sse += 1
130 compiler.emit('sse-connect')
131
132 res.writeHead(200, {
133 'Content-Type': 'text/event-stream',
134 'X-Accel-Buffering': 'no',
135 'Cache-Control': 'no-cache'
136 })
137 res.write('retry: 10000\n')
138
139 var interval = setInterval(function () {
140 if (res.finished) return // prevent writes after stream has closed
141 res.write(`id:${id++}\ndata:{ "type:": "heartbeat" }\n\n`)
142 }, 4000)
143
144 req.on('error', disconnect)
145 res.on('error', disconnect)
146 res.on('close', disconnect)
147 res.on('finish', disconnect)
148
149 function disconnect () {
150 clearInterval(interval)
151 if (connected) {
152 emitter.removeListener('documents:index.html', reloadScript)
153 emitter.removeListener('styles:bundle', reloadStyle)
154 connected = false
155 state.sse -= 1
156 compiler.emit('sse-disconnect')
157 }
158 }
159
160 function reloadScript (node) {
161 var msg = JSON.stringify({ type: 'scripts' })
162 res.write(`id:${id++}\ndata:${msg}\n\n`)
163 }
164
165 function reloadStyle (node) {
166 var msg = JSON.stringify({
167 type: 'styles',
168 bundle: node.buffer.toString()
169 })
170 res.write(`id:${id++}\ndata:${msg}\n\n`)
171 }
172 })
173
174 router.default(function (req, res, next) {
175 var url = req.url
176
177 if (state.ssr && state.ssr.renderRoute) {
178 state.ssr.renderRoute(url, function (err, buffer) {
179 if (err) {
180 state.ssr.success = false
181 state.ssr.error = err
182 return sendDocument(url, req, res, next)
183 }
184
185 res.setHeader('content-type', 'text/html')
186 gzip(buffer, req, res)
187 })
188 } else {
189 return sendDocument(url, req, res, next)
190 }
191 })
192
193 function sendDocument (url, req, res, next) {
194 compiler.documents(url, function (err, node) {
195 if (err) {
196 return compiler.documents('/404', function (err, node) {
197 if (err) return next() // No matches found, call next
198 res.statusCode = 404
199 res.setHeader('content-type', 'text/html')
200 gzip(node.buffer, req, res)
201 })
202 }
203 res.setHeader('content-type', 'text/html')
204 gzip(node.buffer, req, res)
205 })
206 }
207
208 // TODO: move all UI code out of this file
209 handler.state = state
210 // Expose compiler so we can use it in `bankai start`
211 handler.compiler = compiler
212 return handler
213
214 // Return a handler to listen.
215 function handler (req, res, next) {
216 router.match(req, res, next)
217 }
218}
219
220function gzip (buffer, req, res) {
221 var zipper = gzipMaybe(req, res)
222 pump(zipper, res)
223 zipper.end(buffer)
224}