ember-introjs
Version:
An Ember Component for intro.js
274 lines (223 loc) • 7.49 kB
JavaScript
var Plugin = require('broccoli-plugin');
var FSTree = require('fs-tree-diff');
var path = require('path');
var fs = require('fs-extra');
var merge = require('lodash.merge');
var omit = require('lodash.omit');
var uniq = require('lodash.uniq');
var walkSync = require('walk-sync');
var ensurePosix = require('ensure-posix-path');
var ensureNoGlob = require('./lib/utils/ensure-no-glob');
var isDirectory = require('./lib/utils/is-directory');
var makeIndex = require('./lib/utils/make-index');
module.exports = Concat;
Concat.prototype = Object.create(Plugin.prototype);
Concat.prototype.constructor = Concat;
var id = 0;
function Concat(inputNode, options, Strategy) {
if (!(this instanceof Concat)) {
return new Concat(inputNode, options, Strategy);
}
if (!options || !options.outputFile) {
throw new Error('the outputFile option is required');
}
var inputNodes;
id++;
if (process.env.CONCAT_STATS) {
inputNodes = Concat.inputNodesForConcatStats(inputNode, id, options.outputFile);
} else {
inputNodes = [inputNode];
}
Plugin.call(this, inputNodes, {
annotation: options.annotation,
name: (Strategy.name || 'Unknown') + 'Concat',
persistentOutput: true
});
this.id = id;
if (Strategy === undefined) {
throw new TypeError('Concat requires a concat Strategy');
}
this.Strategy = Strategy;
this.sourceMapConfig = omit(options.sourceMapConfig || {}, 'enabled');
this.allInputFiles = uniq([].concat(options.headerFiles || [], options.inputFiles || [], options.footerFiles || []));
this.inputFiles = options.inputFiles;
this.outputFile = options.outputFile;
this.allowNone = options.allowNone;
this.header = options.header;
this.headerFiles = options.headerFiles;
this._headerFooterFilesIndex = makeIndex(options.headerFiles, options.footerFiles);
this.footer = options.footer;
this.footerFiles = options.footerFiles;
this.separator = (options.separator != null) ? options.separator : '\n';
ensureNoGlob('headerFiles', this.headerFiles);
ensureNoGlob('footerFiles', this.footerFiles);
this._lastTree = FSTree.fromEntries([]);
this._hasBuilt = false;
this.encoderCache = {};
}
Concat.inputNodesForConcatStats = function(inputNode, id, outputFile) {
var dir = process.cwd() + '/concat-stats-for';
fs.mkdirpSync(dir);
return [
require('broccoli-stew').debug(inputNode, {
dir: dir,
name: id + '-' + path.basename(outputFile)
})
];
};
Concat.prototype.calculatePatch = function() {
var currentTree = this.getCurrentFSTree();
var patch = this._lastTree.calculatePatch(currentTree);
this._lastTree = currentTree;
return patch;
};
Concat.prototype.build = function() {
var patch = this.calculatePatch();
// We skip building if this is a rebuild with a zero-length patch
if (patch.length === 0 && this._hasBuilt) {
return;
}
this._hasBuilt = true;
if (this.Strategy.isPatchBased) {
return this._doPatchBasedBuild(patch);
} else {
return this._doLegacyBuild();
}
};
Concat.prototype._doPatchBasedBuild = function(patch) {
if (!this.concat) {
this.concat = new this.Strategy(merge(this.sourceMapConfig, {
separator: this.separator,
header: this.header,
headerFiles: this.headerFiles,
footerFiles: this.footerFiles,
footer: this.footer
}));
}
for (var i = 0; i < patch.length; i++) {
var operation = patch[i];
var method = operation[0];
var file = operation[1];
switch (method) {
case 'create':
this.concat.addFile(file, this._readFile(file));
break;
case 'change':
this.concat.updateFile(file, this._readFile(file));
break;
case 'unlink':
this.concat.removeFile(file);
break;
}
}
var outputFile = path.join(this.outputPath, this.outputFile);
var content = this.concat.result();
// If content is undefined, then we the concat had no input files
if (content === undefined) {
if (!this.allowNone) {
throw new Error('Concat: nothing matched [' + this.inputFiles + ']');
} else {
content = '';
}
}
if (process.env.CONCAT_STATS) {
var fileSizes = this.concat.fileSizes();
var outputPath = process.cwd() + '/concat-stats-for/' + this.id + '-' + path.basename(this.outputFile) + '.json';
fs.mkdirpSync(path.dirname(outputPath));
fs.writeFileSync(outputPath, JSON.stringify({
outputFile: this.outputFile,
sizes: fileSizes
}, null, 2));
}
fs.outputFileSync(outputFile, content);
};
Concat.prototype._readFile = function(file) {
return fs.readFileSync(path.join(this.inputPaths[0], file), 'UTF-8');
};
Concat.prototype._doLegacyBuild = function() {
var separator = this.separator;
var firstSection = true;
var outputFile = path.join(this.outputPath, this.outputFile);
fs.mkdirpSync(path.dirname(outputFile));
this.concat = new this.Strategy(merge(this.sourceMapConfig, {
outputFile: outputFile,
baseDir: this.inputPaths[0],
cache: this.encoderCache,
pluginId: this.id
}));
return this.concat.end(function(concat) {
function beginSection() {
if (firstSection) {
firstSection = false;
} else {
concat.addSpace(separator);
}
}
if (this.header) {
beginSection();
concat.addSpace(this.header);
}
if (this.headerFiles) {
this.headerFiles.forEach(function(file) {
beginSection();
concat.addFile(file);
});
}
this.addFiles(beginSection);
if (this.footerFiles) {
this.footerFiles.forEach(function(file) {
beginSection();
concat.addFile(file);
});
}
if (this.footer) {
beginSection();
concat.addSpace(this.footer + '\n');
}
}, this);
};
Concat.prototype.getCurrentFSTree = function() {
return FSTree.fromEntries(this.listEntries());
}
Concat.prototype.listEntries = function() {
// If we have no inputFiles at all, use undefined as the filter to return
// all files in the inputDir.
var filter = this.allInputFiles.length ? this.allInputFiles : undefined;
var inputDir = this.inputPaths[0];
return walkSync.entries(inputDir, filter);
};
/**
* Returns the full paths for any matching inputFiles.
*/
Concat.prototype.listFiles = function() {
var inputDir = this.inputPaths[0];
return this.listEntries().map(function(entry) {
return ensurePosix(path.join(inputDir, entry.relativePath));
});
};
Concat.prototype.addFiles = function(beginSection) {
var headerFooterFileOverlap = false;
var posixInputPath = ensurePosix(this.inputPaths[0]);
var files = this.listFiles().filter(function(file) {
var relativePath = file.replace(posixInputPath + '/', '');
// * remove inputFiles that are already contained within headerFiles and footerFiles
// * allow duplicates between headerFiles and footerFiles
if (this._headerFooterFilesIndex[relativePath] === true) {
headerFooterFileOverlap = true;
return false;
}
return !isDirectory(file);
}, this);
// raise IFF:
// * headerFiles or footerFiles overlapped with inputFiles
// * nothing matched inputFiles
if (headerFooterFileOverlap === false &&
files.length === 0 &&
!this.allowNone) {
throw new Error('Concat: nothing matched [' + this.inputFiles + ']');
}
files.forEach(function(file) {
beginSection();
this.concat.addFile(file.replace(posixInputPath + '/', ''));
}, this);
};