1 | var EventEmitter = require('events').EventEmitter
|
2 | var gzipMaybe = require('http-gzip-maybe')
|
3 | var assert = require('assert')
|
4 | var path = require('path')
|
5 | var pump = require('pump')
|
6 | var send = require('send')
|
7 |
|
8 | var Router = require('./lib/regex-router')
|
9 | var bankai = require('./')
|
10 |
|
11 | module.exports = start
|
12 |
|
13 | function 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]
|
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 |
|
103 |
|
104 |
|
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
|
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()
|
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 |
|
209 | handler.state = state
|
210 |
|
211 | handler.compiler = compiler
|
212 | return handler
|
213 |
|
214 |
|
215 | function handler (req, res, next) {
|
216 | router.match(req, res, next)
|
217 | }
|
218 | }
|
219 |
|
220 | function gzip (buffer, req, res) {
|
221 | var zipper = gzipMaybe(req, res)
|
222 | pump(zipper, res)
|
223 | zipper.end(buffer)
|
224 | }
|