bankai
Version:
The easiest way to compile JavaScript, HTML and CSS
196 lines (172 loc) • 5.82 kB
JavaScript
var debug = require('debug')('bankai.node-script')
var concat = require('concat-stream')
var exorcist = require('exorcist')
var tfilter = require('tfilter')
var assert = require('assert')
var babelPresetEnv = require('babel-preset-env')
var splitRequire = require('split-require')
var browserslist = require('browserslist')
var cssExtract = require('css-extract')
var browserify = require('browserify')
var babelify = require('babelify')
var watchify = require('watchify')
var sheetify = require('sheetify')
var nanohtml = require('nanohtml')
var tinyify = require('tinyify')
var glslify = require('glslify')
var envify = require('envify/custom')
var brfs = require('brfs')
var ttyError = require('./tty-error')
var exorcise = require('./exorcise')
var defaultBrowsers = [
'last 2 Chrome versions',
'last 2 Firefox versions',
'last 2 Safari versions',
'last 2 Edge versions',
'> 1%' // Cover all other browsers that are widely used.
]
module.exports = node
function node (state, createEdge) {
assert.strictEqual(typeof state.metadata.entry, 'string', 'state.metadata.entries should be type string')
this.emit('progress', 'scripts', 0)
var self = this
var entry = state.metadata.entry
var fullPaths = Boolean(state.metadata.fullPaths)
var b = browserify(browserifyOpts([entry], fullPaths))
var shouldMinify = !state.metadata.watch
// Lookup browsers to support in Babel.
var browsers = browserslist(null, { path: entry })
if (!browsers.length) browsers = defaultBrowsers
if (state.metadata.watch) {
b = watchify(b)
debug('watching ' + entry)
this.on('close', function () {
debug('closing file watcher')
b.close()
})
}
b.ignore('sheetify/insert')
b.transform(sheetify)
b.transform(glslify)
if (state.metadata.babelifyDeps) {
// Dependencies should be transformed, but their .babelrc should be ignored.
b.transform(tfilter(babelify, { include: /node_modules/ }), {
global: true,
babelrc: false,
presets: [
[babelPresetEnv, {
// browserify resolves the commonjs version of modules anyway,
// and the modules transform in babel-preset-env rewrites top level `this`
// to `undefined` which breaks some modules (underscore, cuid, ...)
modules: false,
targets: { browsers: browsers }
}]
]
})
}
// In our own code, .babelrc files should be used.
b.transform(tfilter(babelify, { exclude: /node_modules/ }), {
global: true,
babelrc: true,
presets: [
[babelPresetEnv, {
targets: { browsers: browsers }
}]
]
})
b.transform(brfs, { global: true })
b.transform(nanohtml, { global: true })
if (!fullPaths) b.plugin(cssExtract, { out: bundleStyles })
// split-require does not support `fullPaths: true` at the moment.
// the next best thing is to bundle everything, because the byte counts
// shown for individiual modules in discify will still be correct.
if (!fullPaths) {
b.plugin(splitRequire, {
filename: function (record) {
return 'bundle-' + record.index + '.js'
},
output: bundleDynamicBundle,
sri: 'sha512'
})
// Run exorcist as part of the split-require pipeline, so that
// it can generate correct hashes for dynamic bundles.
b.on('split.pipeline', function (pipeline, entry, name) {
pipeline.get('wrap').push(exorciseDynamicBundle(name))
})
}
if (shouldMinify) {
b.plugin(tinyify)
b.on('split.pipeline', function (pipeline) {
tinyify.applyToPipeline(pipeline, b._options)
})
} else {
var env = Object.assign({
NODE_ENV: 'development'
}, process.env)
b.transform(envify(env), { global: true })
}
bundleScripts()
b.on('update', bundleScripts)
var dynamicBundles
function bundleScripts (files) {
if (files) debug('triggering update because of changes in', files)
self.emit('progress', 'scripts', 30)
dynamicBundles = []
b.bundle(function (err, bundle) {
if (err) {
delete err.stream
err = ttyError('scripts', 'browserify.bundle', err)
return self.emit('error', 'scripts', 'browserify.bundle', err)
}
var mapName = 'bundle.js.map'
exorcise(bundle, mapName, function (err, bundle, map) {
if (err) return self.emit('error', 'scripts', 'exorcise', err)
createEdge(mapName, Buffer.from(map), {
mime: 'application/json'
})
createEdge('bundle', bundle, {
mime: 'application/javascript',
dynamicBundles: dynamicBundles
})
self.emit('progress', 'scripts', 100)
})
})
}
function exorciseDynamicBundle (bundleName) {
var mapName = bundleName + '.map'
return exorcist(concat({ encoding: 'buffer' }, function (map) {
createEdge(mapName, map, {
mime: 'application/json'
})
}), mapName)
}
function bundleDynamicBundle (bundleName) {
var edgeName = bundleName.replace(/\.js$/, '')
var stream = concat({ encoding: 'buffer' }, function (bundle) {
dynamicBundles.push(bundleName)
createEdge(edgeName, bundle, {
mime: 'application/javascript'
})
// Inform the main bundle about this file's full name.
stream.emit('name', state.scripts[edgeName].hash.toString('hex').slice(0, 16) + '/' + bundleName)
})
return stream
}
function bundleStyles () {
return concat({ encoding: 'buffer' }, function (buf) {
createEdge('style', buf, {
mime: 'text/css'
})
})
}
}
function browserifyOpts (entries, fullPaths) {
assert.ok(Array.isArray(entries), 'browserifyOpts: entries should be an array')
return {
debug: true,
fullPaths: fullPaths,
entries: entries,
packageCache: {},
cache: {}
}
}