UNPKG

5.45 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 yoyoify = require('yo-yoify')
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.equal(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 var babelPresets = [
50 [babelPresetEnv, {
51 targets: { browsers: browsers }
52 }]
53 ]
54
55 if (state.metadata.watch) {
56 b = watchify(b)
57 debug('watching ' + entry)
58 this.on('close', function () {
59 debug('closing file watcher')
60 b.close()
61 })
62 }
63
64 b.ignore('sheetify/insert')
65 b.transform(sheetify)
66 b.transform(glslify)
67 // Dependencies should be transformed, but their .babelrc should be ignored.
68 b.transform(tfilter(babelify, { include: /node_modules/ }), {
69 global: true,
70 babelrc: false,
71 presets: babelPresets
72 })
73 // In our own code, .babelrc files should be used.
74 b.transform(tfilter(babelify, { exclude: /node_modules/ }), {
75 global: true,
76 babelrc: true,
77 presets: babelPresets
78 })
79 b.transform(brfs, { global: true })
80 b.transform(yoyoify, { global: true })
81
82 if (!fullPaths) b.plugin(cssExtract, { out: bundleStyles })
83
84 // split-require does not support `fullPaths: true` at the moment.
85 // the next best thing is to bundle everything, because the byte counts
86 // shown for individiual modules in discify will still be correct.
87 if (!fullPaths) {
88 b.plugin(splitRequire, {
89 filename: function (record) {
90 return 'bundle-' + record.index + '.js'
91 },
92 output: bundleDynamicBundle,
93 sri: 'sha512'
94 })
95 // Run exorcist as part of the split-require pipeline, so that
96 // it can generate correct hashes for dynamic bundles.
97 b.on('split.pipeline', function (pipeline, entry, name) {
98 pipeline.get('wrap').push(exorciseDynamicBundle(name))
99 })
100 }
101
102 if (shouldMinify) {
103 b.plugin(tinyify)
104 b.on('split.pipeline', function (pipeline) {
105 tinyify.applyToPipeline(pipeline, b._options)
106 })
107 } else {
108 var env = Object.assign({
109 NODE_ENV: 'development'
110 }, process.env)
111 b.transform(envify(env), { global: true })
112 }
113
114 bundleScripts()
115 b.on('update', bundleScripts)
116
117 var dynamicBundles
118 function bundleScripts (files) {
119 if (files) debug('triggering update because of changes in', files)
120 self.emit('progress', 'scripts', 30)
121
122 dynamicBundles = []
123 b.bundle(function (err, bundle) {
124 if (err) {
125 delete err.stream
126 err = ttyError('scripts', 'browserify.bundle', err)
127 return self.emit('error', 'scripts', 'browserify.bundle', err)
128 }
129 var mapName = 'bundle.js.map'
130 exorcise(bundle, mapName, function (err, bundle, map) {
131 if (err) return self.emit('error', 'scripts', 'exorcise', err)
132 createEdge(mapName, Buffer.from(map), {
133 mime: 'application/json'
134 })
135 createEdge('bundle', bundle, {
136 mime: 'application/javascript',
137 dynamicBundles: dynamicBundles
138 })
139 self.emit('progress', 'scripts', 100)
140 })
141 })
142 }
143
144 function exorciseDynamicBundle (bundleName) {
145 var mapName = bundleName + '.map'
146 return exorcist(concat({ encoding: 'buffer' }, function (map) {
147 createEdge(mapName, map, {
148 mime: 'application/json'
149 })
150 }), mapName)
151 }
152
153 function bundleDynamicBundle (bundleName) {
154 var edgeName = bundleName.replace(/\.js$/, '')
155 var stream = concat({ encoding: 'buffer' }, function (bundle) {
156 dynamicBundles.push(bundleName)
157 createEdge(edgeName, bundle, {
158 mime: 'application/javascript'
159 })
160
161 // Inform the main bundle about this file's full name.
162 stream.emit('name', state.scripts[edgeName].hash.toString('hex').slice(0, 16) + '/' + bundleName)
163 })
164 return stream
165 }
166
167 function bundleStyles () {
168 return concat({ encoding: 'buffer' }, function (buf) {
169 createEdge('style', buf, {
170 mime: 'text/css'
171 })
172 })
173 }
174}
175
176function browserifyOpts (entries, fullPaths) {
177 assert.ok(Array.isArray(entries), 'browserifyOpts: entries should be an array')
178 return {
179 debug: true,
180 fullPaths: fullPaths,
181 entries: entries,
182 packageCache: {},
183 cache: {}
184 }
185}