UNPKG

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