UNPKG

7.79 kBJavaScriptView Raw
1var ansi = require('ansi-escape-sequences')
2var pinoColada = require('pino-colada')
3var async = require('async-collection')
4var fsCompare = require('fs-compare')
5var pumpify = require('pumpify')
6var mkdirp = require('mkdirp')
7var path = require('path')
8var zlib = require('zlib')
9var pump = require('pump')
10var fs = require('fs')
11
12var bankai = require('../')
13var utils = require('./utils')
14
15module.exports = build
16
17function build (entry, outdir, opts) {
18 var bankaiOpts = {
19 logStream: pumpify(pinoColada(), process.stdout),
20 watch: false,
21 base: opts.base
22 }
23 var compiler = bankai(entry, bankaiOpts)
24 var log = compiler.log
25
26 if (!outdir) {
27 outdir = path.join(compiler.dirname, 'dist')
28 }
29
30 mkdirp(outdir, function (err) {
31 if (err) return console.error(err)
32
33 log.info('Compiling & compressing files\n')
34 created(compiler.dirname, outdir + '/')
35
36 compiler.on('error', function (topic, sub, err) {
37 if (err.pretty) log.error(err.pretty)
38 else {
39 log.error(`${topic}:${sub} ${err.message}\n${err.stack}`)
40 }
41 })
42
43 compiler.on('ssr', function (result) {
44 if (!result.success) log.warn('Server Side Rendering Skipped due to error: ' + result.error.message)
45 })
46
47 compiler.on('change', function (nodeName, edgeName, nodeState) {
48 var stepName = nodeName + ':' + edgeName
49 if (stepName === 'assets:list') writeAssets('assets')
50 if (stepName === 'documents:list') writeDocuments('documents')
51 if (stepName === 'scripts:list') writeScripts('scripts')
52 if (stepName === 'service-worker:bundle') {
53 var filename = compiler.graph.metadata.serviceWorker
54 compiler.serviceWorker(writeSimple(filename, 'service-worker'))
55 }
56 })
57
58 compiler.manifest(writeSimple('manifest.json', 'manifest'))
59 compiler.styles('bundle.css', writeSingle('bundle.css', 'styles'))
60 compiler.scripts('bundle.js', writeSingle('bundle.js', 'scripts'))
61
62 function writeAssets () {
63 var assets = compiler.graph.metadata.assets
64 var list = Object.keys(assets)
65 async.mapLimit(list, 3, copyFile, function (err) {
66 if (err) return log.error(err)
67 completed('assets')
68 })
69
70 // TODO: It'd be sick if we could optimize our paths to assets,
71 // add etags to our tags and put them in the right dir.
72 function copyFile (src, done) {
73 var dest = path.join(outdir, path.relative(compiler.dirname, src))
74 var dirname = utils.dirname(dest)
75
76 if (dirname === dest) {
77 copy(done)
78 } else {
79 mkdirp(dirname, function (err) {
80 if (err) return done(err)
81 copy(done)
82 })
83 }
84
85 // Node <= 8.x does not have fs.copyFile(). This API is cool because
86 // on some OSes it is zero-copy all the way; e.g. never leaves the
87 // kernel! :D
88 function copy (done) {
89 fsCompare.mtime(src, dest, function (err, diff) {
90 if (err) return done(err)
91 if (diff === 1) {
92 if (fs.copyFile) fs.copyFile(src, dest, fin)
93 else pump(fs.createReadStream(src), fs.createWriteStream(dest), fin)
94 }
95 })
96 function fin (err) {
97 if (err) return done(err)
98 created(compiler.dirname, dest)
99 done()
100 }
101 }
102 }
103 }
104
105 function writeScripts (step) {
106 var node = compiler.graph.data[step].list
107 var list = String(node.buffer)
108 list = list.length ? list.split(',') : []
109 async.mapLimit(list, 3, iterator, function (err) {
110 if (err) return log.error(err)
111 completed(step)
112 })
113
114 function iterator (filename, done) {
115 compiler[step](filename, writeSingle(filename, step))
116 }
117 }
118
119 function writeDocuments (step) {
120 var write = writeCompressed
121
122 var node = compiler.graph.data[step].list
123 var list = String(node.buffer).split(',')
124 async.mapLimit(list, 3, iterator, function (err) {
125 if (err) return log.error(err)
126 completed(step)
127 })
128
129 function iterator (filename, done) {
130 // if (filename === '/') filename = 'index.html'
131
132 // skip over all partial files
133 if (/[:*]/.test(filename)) return done()
134 compiler[step](filename, function (err, node) {
135 if (err) return done(err)
136 filename = path.join(outdir, filename, 'index.html')
137 var dirname = utils.dirname(filename)
138 if (dirname === filename) {
139 write(filename, node.buffer, done)
140 } else {
141 mkdirp(dirname, function (err) {
142 if (err) return done(err)
143 write(filename, node.buffer, done)
144 })
145 }
146 })
147 }
148 }
149
150 function writeSimple (filename, type) {
151 return function (err, node) {
152 if (err) return log.error(err)
153 filename = path.join(outdir, filename)
154 writeCompressed(filename, node.buffer, function (err) {
155 if (err) return log.error(err)
156 completed(type)
157 })
158 }
159 }
160
161 function writeSingle (filename, type) {
162 return function (err, node) {
163 if (err) return log.error(err)
164 var dirname = path.join(outdir, node.hash.toString('hex').slice(0, 16))
165 mkdirp(dirname, function (err) {
166 if (err) return log.error(err)
167 var sourcemapNode = type === 'scripts' && compiler.graph.data[type][`${filename}.map`]
168 var sourcemap = path.join(dirname, `${filename}.map`)
169 filename = path.join(dirname, filename)
170 writeCompressed(filename, node.buffer, function (err) {
171 if (err) return log.error(err)
172 if (!sourcemapNode) {
173 return completed(type)
174 }
175 writeCompressed(sourcemap, sourcemapNode.buffer, function (err) {
176 if (err) return log.error(err)
177 completed(type)
178 })
179 })
180 })
181 }
182 }
183
184 function created (basedir, filename) {
185 var relative = path.relative(process.cwd(), filename)
186 log.info(`${clr('created', 'magenta')}: ${relative}`)
187 }
188
189 function completed (step) {
190 log.debug(`${clr('completed', 'green')}: ${step}`)
191 }
192
193 function writeCompressed (filename, buffer, done) {
194 async.series([
195 function raw (done) {
196 fs.writeFile(filename, buffer, function (err) {
197 if (err) return done(err)
198 created(compiler.dirname, filename)
199 done()
200 })
201 },
202 function gzip (done) {
203 zlib.gzip(buffer, function (err, compressed) {
204 if (err) return done(err)
205 var outfile = filename + '.gz'
206 fs.writeFile(outfile, compressed, function (err) {
207 if (err) return done(err)
208 created(compiler.dirname, outfile)
209 done()
210 })
211 })
212 },
213 function deflate (done) {
214 zlib.deflate(buffer, function (err, compressed) {
215 if (err) return done(err)
216 var outfile = filename + '.deflate'
217 fs.writeFile(outfile, compressed, function (err) {
218 if (err) return done(err)
219 created(compiler.dirname, outfile)
220 done()
221 })
222 })
223 },
224 function compressBrotli (done) {
225 utils.brotli(buffer).then(function (compressed) {
226 var outfile = filename + '.br'
227 fs.writeFile(outfile, compressed, function (err) {
228 if (err) return done(err)
229 created(compiler.dirname, outfile)
230 done()
231 })
232 }).catch(done)
233 }
234 ], done)
235 }
236 })
237}
238
239function clr (text, color) {
240 return process.stdout.isTTY ? ansi.format(text, color) : text
241}