UNPKG

5.82 kBJavaScriptView Raw
1var debug = require('debug')('bankai.node-script')
2var concat = require('concat-stream')
3var exorcist = require('exorcist')
4var tfilter = require('tfilter')
5var assert = require('assert')
6
7var babelPresetEnv = require('babel-preset-env')
8var splitRequire = require('split-require')
9var browserslist = require('browserslist')
10var cssExtract = require('css-extract')
11var browserify = require('browserify')
12var babelify = require('babelify')
13var watchify = require('watchify')
14var sheetify = require('sheetify')
15var nanohtml = require('nanohtml')
16var tinyify = require('tinyify')
17var glslify = require('glslify')
18var envify = require('envify/custom')
19var brfs = require('brfs')
20
21var ttyError = require('./tty-error')
22var exorcise = require('./exorcise')
23
24var defaultBrowsers = [
25 'last 2 Chrome versions',
26 'last 2 Firefox versions',
27 'last 2 Safari versions',
28 'last 2 Edge versions',
29 '> 1%' // Cover all other browsers that are widely used.
30]
31
32module.exports = node
33
34function node (state, createEdge) {
35 assert.strictEqual(typeof state.metadata.entry, 'string', 'state.metadata.entries should be type string')
36
37 this.emit('progress', 'scripts', 0)
38
39 var self = this
40 var entry = state.metadata.entry
41 var fullPaths = Boolean(state.metadata.fullPaths)
42 var b = browserify(browserifyOpts([entry], fullPaths))
43 var shouldMinify = !state.metadata.watch
44
45 // Lookup browsers to support in Babel.
46 var browsers = browserslist(null, { path: entry })
47 if (!browsers.length) browsers = defaultBrowsers
48
49 if (state.metadata.watch) {
50 b = watchify(b)
51 debug('watching ' + entry)
52 this.on('close', function () {
53 debug('closing file watcher')
54 b.close()
55 })
56 }
57
58 b.ignore('sheetify/insert')
59 b.transform(sheetify)
60 b.transform(glslify)
61
62 if (state.metadata.babelifyDeps) {
63 // Dependencies should be transformed, but their .babelrc should be ignored.
64 b.transform(tfilter(babelify, { include: /node_modules/ }), {
65 global: true,
66 babelrc: false,
67 presets: [
68 [babelPresetEnv, {
69 // browserify resolves the commonjs version of modules anyway,
70 // and the modules transform in babel-preset-env rewrites top level `this`
71 // to `undefined` which breaks some modules (underscore, cuid, ...)
72 modules: false,
73 targets: { browsers: browsers }
74 }]
75 ]
76 })
77 }
78 // In our own code, .babelrc files should be used.
79 b.transform(tfilter(babelify, { exclude: /node_modules/ }), {
80 global: true,
81 babelrc: true,
82 presets: [
83 [babelPresetEnv, {
84 targets: { browsers: browsers }
85 }]
86 ]
87 })
88
89 b.transform(brfs, { global: true })
90 b.transform(nanohtml, { global: true })
91
92 if (!fullPaths) b.plugin(cssExtract, { out: bundleStyles })
93
94 // split-require does not support `fullPaths: true` at the moment.
95 // the next best thing is to bundle everything, because the byte counts
96 // shown for individiual modules in discify will still be correct.
97 if (!fullPaths) {
98 b.plugin(splitRequire, {
99 filename: function (record) {
100 return 'bundle-' + record.index + '.js'
101 },
102 output: bundleDynamicBundle,
103 sri: 'sha512'
104 })
105 // Run exorcist as part of the split-require pipeline, so that
106 // it can generate correct hashes for dynamic bundles.
107 b.on('split.pipeline', function (pipeline, entry, name) {
108 pipeline.get('wrap').push(exorciseDynamicBundle(name))
109 })
110 }
111
112 if (shouldMinify) {
113 b.plugin(tinyify)
114 b.on('split.pipeline', function (pipeline) {
115 tinyify.applyToPipeline(pipeline, b._options)
116 })
117 } else {
118 var env = Object.assign({
119 NODE_ENV: 'development'
120 }, process.env)
121 b.transform(envify(env), { global: true })
122 }
123
124 bundleScripts()
125 b.on('update', bundleScripts)
126
127 var dynamicBundles
128 function bundleScripts (files) {
129 if (files) debug('triggering update because of changes in', files)
130 self.emit('progress', 'scripts', 30)
131
132 dynamicBundles = []
133 b.bundle(function (err, bundle) {
134 if (err) {
135 delete err.stream
136 err = ttyError('scripts', 'browserify.bundle', err)
137 return self.emit('error', 'scripts', 'browserify.bundle', err)
138 }
139 var mapName = 'bundle.js.map'
140 exorcise(bundle, mapName, function (err, bundle, map) {
141 if (err) return self.emit('error', 'scripts', 'exorcise', err)
142 createEdge(mapName, Buffer.from(map), {
143 mime: 'application/json'
144 })
145 createEdge('bundle', bundle, {
146 mime: 'application/javascript',
147 dynamicBundles: dynamicBundles
148 })
149 self.emit('progress', 'scripts', 100)
150 })
151 })
152 }
153
154 function exorciseDynamicBundle (bundleName) {
155 var mapName = bundleName + '.map'
156 return exorcist(concat({ encoding: 'buffer' }, function (map) {
157 createEdge(mapName, map, {
158 mime: 'application/json'
159 })
160 }), mapName)
161 }
162
163 function bundleDynamicBundle (bundleName) {
164 var edgeName = bundleName.replace(/\.js$/, '')
165 var stream = concat({ encoding: 'buffer' }, function (bundle) {
166 dynamicBundles.push(bundleName)
167 createEdge(edgeName, bundle, {
168 mime: 'application/javascript'
169 })
170
171 // Inform the main bundle about this file's full name.
172 stream.emit('name', state.scripts[edgeName].hash.toString('hex').slice(0, 16) + '/' + bundleName)
173 })
174 return stream
175 }
176
177 function bundleStyles () {
178 return concat({ encoding: 'buffer' }, function (buf) {
179 createEdge('style', buf, {
180 mime: 'text/css'
181 })
182 })
183 }
184}
185
186function browserifyOpts (entries, fullPaths) {
187 assert.ok(Array.isArray(entries), 'browserifyOpts: entries should be an array')
188 return {
189 debug: true,
190 fullPaths: fullPaths,
191 entries: entries,
192 packageCache: {},
193 cache: {}
194 }
195}