1 | var ansi = require('ansi-escape-sequences')
|
2 | var pinoColada = require('pino-colada')
|
3 | var async = require('async-collection')
|
4 | var fsCompare = require('fs-compare')
|
5 | var pumpify = require('pumpify')
|
6 | var mkdirp = require('mkdirp')
|
7 | var path = require('path')
|
8 | var zlib = require('zlib')
|
9 | var pump = require('pump')
|
10 | var fs = require('fs')
|
11 |
|
12 | var bankai = require('../')
|
13 | var utils = require('./utils')
|
14 |
|
15 | module.exports = build
|
16 |
|
17 | function 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 |
|
74 |
|
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 |
|
128 |
|
129 |
|
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 |
|
182 |
|
183 |
|
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 |
|
254 | function clr (text, color) {
|
255 | return process.stdout.isTTY ? ansi.format(text, color) : text
|
256 | }
|