From f4f9522a31a30f7787dc3f1927dc309d17f7b00a Mon Sep 17 00:00:00 2001 From: Jeff Escalante Date: Fri, 27 May 2016 12:34:24 -0400 Subject: [PATCH] refactor to add filewrap, better path methods --- lib/index.js | 103 +++++++++++++++++++++++++++++-------------------- package.json | 1 + test/index.js | 13 +++++-- test/plugin.js | 5 ++- 4 files changed, 75 insertions(+), 47 deletions(-) diff --git a/lib/index.js b/lib/index.js index ea7ba68..37bf5d0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -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') @@ -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.} 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 @@ -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) + } } diff --git a/package.json b/package.json index aa53840..e3e7bbb 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/test/index.js b/test/index.js index 583a08d..55160c7 100644 --- a/test/index.js +++ b/test/index.js @@ -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') @@ -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({ diff --git a/test/plugin.js b/test/plugin.js index 2681dd4..4dff87f 100644 --- a/test/plugin.js +++ b/test/plugin.js @@ -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) => {