Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 62 additions & 41 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const path = require('path')
const micromatch = require('micromatch')
const W = require('when')
const node = require('when/node')
const File = require('filewrap')
const SingleEntryDependency = require('webpack/lib/dependencies/SingleEntryDependency')
const MultiEntryDependency = require('webpack/lib/dependencies/MultiEntryDependency')

Expand All @@ -19,79 +20,83 @@ module.exports = class SpikeUtils {
* 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.
* @param {Object} compilation - object from plugin
* @param {Array} files - array of absolute paths to files
* @param {Array|String} files - absolute path(s) to files
* @return {Promise.<Array>} compilation.addEntry return value for each file
*/
addFilesAsWebpackEntries (compilation, files) {
return W.all(Array.prototype.concat(files).map((f) => {
const name = this.getOutputPath(f)
const relativePath = f.replace(this.conf.context, '.')
const dep = new MultiEntryDependency([new SingleEntryDependency(relativePath)], name)
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, name)
return node.call(addEntryFn, this.conf.context, dep, file.relative)
}))
}

/**
* Given a source file path, outputs the file's destination as spike
* will write it, relative to `public`.
* Given a source file path, returns the path to the final output.
* @param {String} file - path to source file
* @return {String} output path
* @return {File} object containing relative and absolute paths
*/
getOutputPath (file) {
let rel = file.replace(this.conf.context, '')
getOutputPath (f) {
let file = new File(this.conf.context, f)

this.conf.spike.dumpDirs.forEach((d) => {
const re = new RegExp(`^${path.sep}${d}`)
if (rel.match(re)) { rel = rel.replace(`${path.sep}${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 rel.substring(1)
return file
}

/**
* 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.conf.spike.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} _files - array of absolute paths to processed files
* @param {Array|String} _files - path(s) to source files, relative/absolute
* @param {Function} done - callback
*/
removeAssets (compilation, _files) {
// assets are set in compilation.assets as the output path, relative to
// the root, with '.js' at the end, so we transform to that format for
// comparison
let files = Array.prototype.concat(_files).map((f) => {
return this.getOutputPath(f).replace(`${this.conf.context}/`, '') + '.js'
const file = new File(this.conf.context, f)
return `${file.relative}.js`
})
// now we go through all the assets and remove the ones we have processed
// with spike so that they are not written to the js file

for (const a in compilation.assets) {
if (files.indexOf(a) > -1) { delete compilation.assets[a] }
}
}

/**
* Takes a relative path like `img/foo.png` and resolves it to an absolute
* path. Function respects your dumpDirs and knows how to resolve it's
* absolute source path.
* @param {String} file - a relative path
* @return {String} absolute path to the file
*/
resolveRelativeSourcePath (file) {
let rel = file.replace(this.conf.context, '')

// check to see if the file is from a dumpDir path
/* istanbul ignore next */
// https://github.com/bcoe/nyc/issues/259
glob.sync(`${this.conf.context}/*(${this.conf.spike.dumpDirs.join('|')})/**`).forEach((d) => {
const test = new RegExp(rel)
if (d.match(test)) { rel = d }
})

return path.join(rel)
}

/**
* Boolean return whether a file matches any of the configured ignores.
* @param {String} file - absolute file path
Expand All @@ -101,8 +106,24 @@ module.exports = class SpikeUtils {
return micromatch.any(file, this.conf.spike.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)
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"bugs": "https://github.com/static-dev/spike-utils/issues",
"dependencies": {
"filewrap": "0.1.0",
"glob": "^7.0.3",
"micromatch": "^2.3.8",
"when": "^3.7.7"
Expand Down
13 changes: 9 additions & 4 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ test.cb('EVERYTHING WORKS', (t) => {
injectFile: path.join(fixturePath, 'views/index.txt')
})

t.plan(7)
t.plan(10)

plugin.on('addFilesAsWebpackEntries', (comp) => {
const mod = comp.modules.find((m) => m.rawRequest === './views/index.txt')
Expand All @@ -22,11 +22,16 @@ test.cb('EVERYTHING WORKS', (t) => {
// TODO: this also needs to be tested with watch mode
plugin.on('runAll', () => { t.truthy(true) })
plugin.on('removeAssets', () => { t.truthy(true) })
plugin.on('getOutputPath', (p) => { t.truthy(p, 'index.txt') })
plugin.on('isFileIgnored', (r) => { t.truthy(r) })
plugin.on('matchGlobs', (r) => { t.truthy(r.length === 2) })
plugin.on('getOutputPath', (p) => {
t.truthy(p.relative, 'index.txt')
t.truthy(p.absolute.match('public'))
})

plugin.on('resolveRelativeSourcePath', (p) => {
t.truthy(p.replace(fixturePath, '') === '/views/index.txt')
plugin.on('getSourcePath', (p) => {
t.truthy(p.relative === 'views/index.txt')
t.falsy(p.absolute.match('public'))
})

const project = new Spike({
Expand Down
5 changes: 3 additions & 2 deletions test/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ module.exports = class TestPlugin extends EventEmitter {
this.util.runAll(compiler, this.run.bind(this))

this.emit('getOutputPath', this.util.getOutputPath(this.injectFile))
this.emit('resolveRelativeSourcePath', this.util.resolveRelativeSourcePath('index.txt'))
this.emit('getSourcePath', this.util.getSourcePath('index.txt'))
this.emit('isFileIgnored', this.util.isFileIgnored('/views/ignoreme.txt'))
this.emit('matchGlobs', this.util.matchGlobs(['a/foo', 'a/bar', 'b/foo'], ['a/*']))

compiler.plugin('make', (compilation, done) => {
this.util.addFilesAsWebpackEntries(compilation, this.injectFile)
.then(() => this.emit('addFilesAsWebpackEntries', compilation))
.done(() => done())
.done(() => done(), done)
})

compiler.plugin('compilation', (compilation) => {
Expand Down