UNPKG

7.96 kBJavaScriptView Raw
1var Emitter = require('events').EventEmitter
2var debug = require('debug')('bankai')
3var graph = require('buffer-graph')
4var assert = require('assert')
5var path = require('path')
6var pino = require('pino')
7
8var localization = require('./localization')
9var queue = require('./lib/queue')
10var utils = require('./lib/utils')
11var ServerRender = require('./ssr')
12
13var assetsNode = require('./lib/graph-assets')
14var documentNode = require('./lib/graph-document')
15var faviconNode = require('./lib/graph-favicon')
16var manifestNode = require('./lib/graph-manifest')
17var reloadNode = require('./lib/graph-reload')
18var scriptNode = require('./lib/graph-script')
19var serviceWorkerNode = require('./lib/graph-service-worker')
20var styleNode = require('./lib/graph-style')
21
22module.exports = Bankai
23
24function Bankai (entry, opts) {
25 if (!(this instanceof Bankai)) return new Bankai(entry, opts)
26
27 Emitter.call(this)
28
29 opts = opts || {}
30 this.local = localization(opts.language || 'en-US')
31 this.log = pino(opts.logStream || process.stdout)
32
33 assert.strictEqual(typeof entry, 'string', 'bankai: entry should be type string')
34 assert.ok(path.isAbsolute(entry), 'bankai: entry should be an absolute path. Received: ' + entry)
35 assert.strictEqual(typeof opts, 'object', 'bankai: opts should be type object')
36
37 var self = this
38 var methods = [
39 'manifest',
40 'assets',
41 'service-worker',
42 'scripts',
43 'styles',
44 'documents'
45 ]
46
47 // Initialize data structures.
48 var key = Buffer.from('be intolerant of intolerance')
49 // TODO maybe use fs.stat here to check if it's a directory?
50 // That would be best but we may have to do a sync call, because graph nodes depend on this
51 // value being available immediately, which is less great.
52 this.dirname = path.extname(entry) === '' ? entry : utils.dirname(entry) // The base directory.
53 this.queue = queue(methods) // The queue caches requests until ready.
54 this.graph = graph(key) // The graph manages relations between deps.
55
56 this.ssr = new ServerRender(entry)
57
58 // Detect when we're ready to allow requests to go through.
59 this.graph.on('change', function (nodeName, edgeName, state) {
60 self.emit('change', nodeName, edgeName, state)
61 var eventName = nodeName + ':' + edgeName
62 var count = self.metadata.count
63 var queue = self.queue
64
65 if (eventName === 'assets:list') {
66 count['assets'] = String(self.graph.data.assets.list.buffer).split(',').length
67 queue['assets'].ready()
68 } else if (eventName === 'documents:list') {
69 count['documents'] = String(self.graph.data.documents.list.buffer).split(',').length
70 queue['documents'].ready()
71 } else if (eventName === 'manifest:bundle') {
72 count['manifest'] = 1
73 queue['manifest'].ready()
74 } else if (eventName === 'scripts:bundle') {
75 count['scripts'] = 1
76 queue['scripts'].ready()
77 } else if (eventName === 'service-worker:bundle') {
78 count['service-worker'] = 1
79 queue['service-worker'].ready()
80 } else if (eventName === 'styles:bundle') {
81 count['styles'] = 1
82 queue['styles'].ready()
83 }
84 })
85
86 // Handle errors so they can be logged.
87 this.graph.on('error', function () {
88 var args = ['error']
89 for (var len = arguments.length, i = 0; i < len; i++) {
90 args.push(arguments[i])
91 }
92 self.emit.apply(self, args)
93 })
94
95 this.graph.on('progress', function (chunk, value) {
96 self.emit('progress', chunk, value)
97 })
98
99 this.graph.on('ssr', function (result) {
100 self.emit('ssr', result)
101 })
102
103 // Insert nodes into the graph.
104 var documentDependencies = [ 'assets:list', 'manifest:bundle', 'styles:bundle', 'scripts:bundle', 'favicon:bundle' ]
105
106 if (opts.reload) {
107 documentDependencies.push('reload:bundle')
108 this.graph.node('reload', reloadNode)
109 }
110 this.graph.node('favicon', faviconNode)
111 this.graph.node('assets', assetsNode)
112 this.graph.node('documents', documentDependencies, documentNode)
113 this.graph.node('manifest', manifestNode)
114 this.graph.node('scripts', scriptNode)
115 this.graph.node('service-worker', [ 'assets:list', 'styles:bundle', 'scripts:bundle', 'documents:list' ], serviceWorkerNode)
116 this.graph.node('styles', [ 'scripts:style', 'scripts:bundle' ], styleNode)
117
118 // Kick off the graph.
119 this.graph.start({
120 dirname: this.dirname,
121 watch: opts.watch !== false,
122 babelifyDeps: opts.babelifyDeps !== false,
123 fullPaths: opts.fullPaths,
124 reload: Boolean(opts.reload),
125 log: this.log,
126 ssr: this.ssr,
127 watchers: {},
128 entry: entry,
129 opts: opts,
130 count: {
131 assets: 0,
132 documents: 0,
133 manifest: 0,
134 scripts: 0,
135 'service-worker': 0,
136 style: 0
137 }
138 })
139
140 this.metadata = this.graph.metadata
141}
142Bankai.prototype = Object.create(Emitter.prototype)
143
144Bankai.prototype.scripts = function (filename, cb) {
145 assert.strictEqual(typeof filename, 'string')
146 assert.strictEqual(typeof cb, 'function')
147 var stepName = 'scripts'
148 var edgeName = filename.split('.')[0]
149 var self = this
150 this.queue[stepName].add(function () {
151 var data = self.graph.data[stepName][edgeName]
152 if (!data) return cb(new Error(`bankai.scripts: could not find a bundle for "${filename}"`))
153 cb(null, data)
154 })
155}
156
157Bankai.prototype.styles = function (filename, cb) {
158 assert.strictEqual(typeof filename, 'string')
159 assert.strictEqual(typeof cb, 'function')
160 var stepName = 'styles'
161 var edgeName = filename.split('.')[0]
162 var self = this
163 this.queue[stepName].add(function () {
164 var data = self.graph.data[stepName][edgeName]
165 if (!data) return cb(new Error('bankai.styles: could not find bundle'))
166 cb(null, data)
167 })
168}
169
170Bankai.prototype.documents = function (url, cb) {
171 assert.strictEqual(typeof url, 'string')
172 assert.strictEqual(typeof cb, 'function')
173
174 var filename = url.split('?')[0]
175
176 if (filename === '/') filename = 'index'
177 var stepName = 'documents'
178 var edgeName = filename.split('.')[0] + '.html'
179 var self = this
180 this.queue[stepName].add(function () {
181 var data = self.graph.data[stepName][edgeName]
182 if (!data) return cb(new Error('bankai.document: could not find a document for ' + filename))
183 cb(null, data)
184 })
185}
186
187Bankai.prototype.manifest = function (cb) {
188 assert.strictEqual(typeof cb, 'function')
189 var stepName = 'manifest'
190 var edgeName = 'bundle'
191 var self = this
192 this.queue[stepName].add(function () {
193 var data = self.graph.data[stepName][edgeName]
194 if (!data) return cb(new Error('bankai.manifest: could not find bundle'))
195 cb(null, data)
196 })
197}
198
199Bankai.prototype.serviceWorker = function (cb) {
200 assert.strictEqual(typeof cb, 'function')
201 var stepName = 'service-worker'
202 var edgeName = 'bundle'
203 var self = this
204 this.queue[stepName].add(function () {
205 var data = self.graph.data[stepName][edgeName]
206 if (!data) return cb(new Error('bankai.serviceWorker: could not find bundle'))
207 cb(null, data)
208 })
209}
210
211Bankai.prototype.assets = function (filename, cb) {
212 assert.strictEqual(typeof filename, 'string')
213 assert.strictEqual(typeof cb, 'function')
214 var stepName = 'assets'
215 var self = this
216 this.queue[stepName].add(function () {
217 filename = path.join(self.dirname, filename)
218 var data = self.metadata.assets[filename]
219 if (!data) return cb(new Error('bankai.asset: could not find a file for ' + filename))
220 cb(null, filename)
221 })
222}
223
224Bankai.prototype.sourceMaps = function (stepName, edgeName, cb) {
225 assert.strictEqual(typeof stepName, 'string')
226 assert.strictEqual(typeof edgeName, 'string')
227 assert.strictEqual(typeof cb, 'function')
228 edgeName = /\.map$/.test(edgeName) ? edgeName : edgeName + '.map'
229 var self = this
230 var data = self.graph.data[stepName][edgeName]
231 if (!data) return cb(new Error('bankai.sourceMaps: could not find a file for ' + stepName + ':' + edgeName))
232 cb(null, data)
233}
234
235Bankai.prototype.close = function () {
236 debug('closing all file watchers')
237 this.ssr.close()
238 this.graph.emit('close')
239 this.emit('close')
240}