spike-util
Version:
utilities for spike plugins
188 lines (167 loc) • 6.83 kB
JavaScript
/**
* @module SpikeUtils
*/
const glob = require('glob')
const path = require('path')
const micromatch = require('micromatch')
const W = require('when')
const node = require('when/node')
const File = require('filewrap')
let SingleEntryDependency
let MultiEntryDependency
module.exports = class SpikeUtils {
constructor (config) {
this.conf = config
}
/**
* Adds a number of files to webpack's pipeline as entires, so that webpack
* processes them even if they are not required in the main js file.
* TODO: move into spike core, its only really safe to use there
* !!! WARNING: requires a webpack dependency to use !!!
* @param {Object} compilation - object from plugin
* @param {Array|String} files - absolute path(s) to files
* @return {Promise.<Array>} compilation.addEntry return value for each file
*/
addFilesAsWebpackEntries (compilation, files) {
// require these if necessary
if (!SingleEntryDependency) { SingleEntryDependency = require('webpack/lib/dependencies/SingleEntryDependency') }
if (!MultiEntryDependency) { MultiEntryDependency = require('webpack/lib/dependencies/MultiEntryDependency') }
return W.all(Array.prototype.concat(files).map((f) => {
const file = new File(this.conf.context, f)
const dep = new MultiEntryDependency([new SingleEntryDependency(`./${file.relative}`)], file.relative)
const addEntryFn = compilation.addEntry.bind(compilation)
return node.call(addEntryFn, this.conf.context, dep, file.relative)
}))
}
/**
* Returns spike-specific options from the webpack config
* @return {Object} spike options
*/
getSpikeOptions () {
return this.conf.plugins.find((p) => p.name === 'spikePlugin').options
}
/**
* Given a source file path, returns the path to the final output.
* @param {String} file - path to source file
* @return {File} object containing relative and absolute paths
*/
getOutputPath (f) {
let file = new File(this.conf.context, f)
file = new File(this.conf.output.path, file.relative)
this.getSpikeOptions().dumpDirs.forEach((d) => {
const re = new RegExp(`^${d}\\${path.sep}`)
if (file.relative.match(re)) {
file = new File(this.conf.output.path, file.relative.replace(re, ''))
}
})
return file
}
/**
* Given a source file path and a desired output path, sets spike to write
* the given file to the specified output path
* @param {String} f - path to the source file, relative or absolute
* @param {String} outPath - desired output path
*/
modifyOutputPath (f, outPath) {
const match = this.getSpikeOptions().files.process.find((x) => {
return x.path === new File(this.conf.context, f).absolute
})
match.outPath = new File(this.conf.context, outPath).absolute
}
/**
* Given a file's output path, returns the path to the source file.
* @param {String} f - path to an output file, relative or absolute
* @return {File} object containing relative and absolute paths
*/
getSourcePath (f) {
let file = new File(this.conf.output.path, f)
// check to see if the file is from a dumpDir path
// https://github.com/bcoe/nyc/issues/259
/* istanbul ignore next */
glob.sync(`${this.conf.context}/*(${this.getSpikeOptions().dumpDirs.join('|')})/**`).forEach((d) => {
const test = new RegExp(file.relative)
if (d.match(test)) file = new File(this.conf.context, d)
})
return file
}
/**
* Removes assets from the webpack compilation, so that static files are not
* written to the js file, since we already write them as static files.
*
* The `addFilesAsWebpackEntries` function uses the relative source path
* to name files, and `this.conf.output.name` appends '.js' to the end, so
* this is how the files appear in the compilation assets.
*
* As such, we transform the array of paths to ensure that we are getting a
* relative source path, then append '.js' to the end. We then loop through
* webpack's compilation assets and remove the ones we don't want to write.
*
* @param {Object} compilation - webpack compilation object from plugin
* @param {Array|String} _files - path(s) to source files, relative/absolute
* @param {Array} [chunks] - webpack chunks object
* @param {Function} done - callback
*/
removeAssets (compilation, _files, chunks) {
const files = Array.prototype.concat(_files).map((f) => {
return (new File(this.conf.context, f)).relative
})
// first we remove the files from webpack's `assets`
const chunksByName = compilation.records.chunks.byName
const chunkNames = Object.keys(compilation.assets)
for (let k in chunksByName) {
// if the chunk name is in our spike-processed files, we remove
// that chunk from the assets to be written
if (files.indexOf(k) > -1) {
delete compilation.assets[chunkNames[chunksByName[k]]]
}
}
// Now we remove any chunks that are not further processed.
// Directly modifying the webpack object is tricky, we need a mutating
// method (splice), and we need the mutations not to affect the indices,
// hence the reverse.
if (chunks) {
const staticChunks = chunks.reduce((m, c, i) => {
if (files.indexOf(c.name) > -1) m.push(i); return m
}, [])
staticChunks.reverse().forEach((i) => chunks.splice(i, 1))
}
}
/**
* Boolean return whether a file matches any of the configured ignores.
* @param {String} file - absolute or relative file path
* @return {Boolean} whether the file is ignored or not
*/
isFileIgnored (file) {
const f = new File(this.conf.context, file)
return micromatch.any(f.absolute, this.getSpikeOptions().ignore)
}
/**
* A shortcut to run a plugin on initialization in compile and watch mode.
* @param {Compiler} compiler - webpack compiler instance from plugin
* @param {Function} cb - function to be run in watch and compile mode
*/
runAll (compiler, cb) {
compiler.plugin('run', cb)
compiler.plugin('watch-run', cb)
}
/**
* Micromatch alias for simple glob matching.
* @param {Array} strings - array of strings to match against
* @param {Array|String} patterns - glob patterns for matching
* @param {Object} [options] - micromatch options
* @return {Array} array of matching strings
*/
matchGlobs () {
return micromatch.apply(micromatch, arguments)
}
/**
* Converts an array of paths to a regex that matches those paths. Useful for
* when you need to add a loader that only matches specific files.
*/
pathsToRegex (paths) {
if (!paths.length) { return new RegExp('^.^') }
return new RegExp(paths.map((p, i) => {
return p.replace(/\//g, '\\/')
}).join('|'))
}
}