broccoli-eyeglass
Advanced tools
Comparing version 3.0.1 to 4.0.0
@@ -0,4 +1,7 @@ | ||
"use strict"; | ||
module.exports = function(grunt) { | ||
grunt.initConfig({ | ||
pkg: grunt.file.readJSON('package.json'), | ||
pkg: grunt.file.readJSON("package.json"), | ||
release: { | ||
@@ -9,5 +12,5 @@ options: { | ||
}); | ||
grunt.loadNpmTasks('grunt-release'); | ||
grunt.registerTask('default', []); | ||
grunt.loadNpmTasks("grunt-release"); | ||
grunt.registerTask("default", []); | ||
}; |
"use strict"; | ||
var path = require("path"); | ||
var crypto = require("crypto"); | ||
var debugGenerator = require("debug"); | ||
var debug = debugGenerator("broccoli-eyeglass"); | ||
var hotCacheDebug = debugGenerator("broccoli-eyeglass:hot-cache"); | ||
var persistentCacheDebug = debugGenerator("broccoli-eyeglass:persistent-cache"); | ||
var fs = require("fs"); | ||
var RSVP = require("rsvp"); | ||
var mkdirp = require("mkdirp"); | ||
const path = require("path"); | ||
const crypto = require("crypto"); | ||
const debugGenerator = require("debug"); | ||
const debug = debugGenerator("broccoli-eyeglass"); | ||
const hotCacheDebug = debugGenerator("broccoli-eyeglass:hot-cache"); | ||
const persistentCacheDebug = debugGenerator("broccoli-eyeglass:persistent-cache"); | ||
const fs = require("fs"); | ||
const RSVP = require("rsvp"); | ||
const mkdirp = require("mkdirp"); | ||
mkdirp.promise = mkdirp.promise || RSVP.denodeify(mkdirp); | ||
var BroccoliPlugin = require("broccoli-plugin"); | ||
var sass = require("node-sass"); | ||
var glob = require("glob"); | ||
var FSTree = require("fs-tree-diff"); | ||
var FSTreeFromEntries = FSTree.fromEntries; | ||
var walkSync = require("walk-sync"); | ||
var os = require("os"); | ||
var queue = require("async-promise-queue"); | ||
const BroccoliPlugin = require("broccoli-plugin"); | ||
const sass = require("node-sass"); | ||
const glob = require("glob"); | ||
const FSTree = require("fs-tree-diff"); | ||
const FSTreeFromEntries = FSTree.fromEntries; | ||
const walkSync = require("walk-sync"); | ||
const os = require("os"); | ||
const queue = require("async-promise-queue"); | ||
require("string.prototype.startswith"); | ||
const READ_AS_UTF_8 = {encoding: "utf8"}; | ||
const readFile = RSVP.denodeify(fs.readFile); | ||
const writeFile = RSVP.denodeify(fs.writeFile); | ||
function absolutizeEntries(entries) { | ||
// We make everything absolute because relative path comparisons don't work for us. | ||
entries.forEach(entry => { | ||
// TODO support windows paths | ||
entry.relativePath = path.join(entry.basePath, entry.relativePath); | ||
entry.basePath = "/"; | ||
}); | ||
} | ||
class Entry { | ||
constructor(path) { | ||
let stats = fs.statSync(path); | ||
this.relativePath = path; | ||
this.basePath = "/"; | ||
this.mode = stats.mode; | ||
this.size = stats.size; | ||
this.mtime = stats.mtime; | ||
} | ||
isDirectory() { | ||
return false; | ||
} | ||
} | ||
function unique(array) { | ||
var o = {}; | ||
var rv = []; | ||
for (var i = 0; i < array.length; i++) { | ||
let o = {}; | ||
let rv = []; | ||
for (let i = 0; i < array.length; i++) { | ||
o[array[i]] = array[i]; | ||
} | ||
for (var k in o) { | ||
for (let k in o) { | ||
if (o.hasOwnProperty(k)) { | ||
@@ -39,9 +68,7 @@ rv.push(o[k]); | ||
// Standard async rendering for node-sass. | ||
var renderSass = RSVP.denodeify(sass.render); | ||
const renderSass = RSVP.denodeify(sass.render); | ||
// This promise runs sass synchronously | ||
function renderSassSync(options) { | ||
return new RSVP.Promise(function(resolve) { | ||
resolve(sass.renderSync(options)); | ||
}); | ||
return new RSVP.Promise(resolve => resolve(sass.renderSync(options))); | ||
} | ||
@@ -51,4 +78,2 @@ | ||
function copyObject(obj) { | ||
var newObj, key; | ||
if (typeof obj !== "object") { | ||
@@ -58,10 +83,8 @@ return obj; | ||
if (obj.forEach) { | ||
newObj = []; | ||
obj.forEach(function(o) { | ||
newObj.push(copyObject(o)); | ||
}); | ||
let newObj = []; | ||
obj.forEach(o => newObj.push(copyObject(o))); | ||
return newObj; | ||
} else { | ||
newObj = {}; | ||
for (key in obj) { | ||
let newObj = {}; | ||
for (let key in obj) { | ||
if (obj.hasOwnProperty(key)) { | ||
@@ -77,4 +100,4 @@ newObj[key] = copyObject(obj[key]); | ||
if (arguments.length > 3) { | ||
var returnValues = []; | ||
for (var i = 2; i < arguments.length; i++) { | ||
let returnValues = []; | ||
for (let i = 2; i < arguments.length; i++) { | ||
returnValues[i - 2] = moveOption(source, destination, arguments[i]); | ||
@@ -95,4 +118,4 @@ } | ||
} | ||
var newFileNames = []; | ||
for (var i = 0; i < fileNames.length; i++) { | ||
let newFileNames = []; | ||
for (let i = 0; i < fileNames.length; i++) { | ||
if (fileNames[i].indexOf(prefix) === 0) { | ||
@@ -121,3 +144,3 @@ newFileNames[i] = fileNames[i].substring(prefix.length); | ||
} else { | ||
var parsed = { | ||
let parsed = { | ||
dir: path.dirname(pathname), | ||
@@ -188,604 +211,592 @@ base: path.basename(pathname), | ||
// | ||
function BroccoliSassCompiler(inputTree, options) { | ||
if (Array.isArray(inputTree)) { | ||
if (inputTree.length > 1) { | ||
console.warn("Support for passing several trees to BroccoliSassCompiler has been removed.\n" + | ||
"Passing the trees to broccoli-merge-trees with the overwrite option set,\n" + | ||
"but you should do this yourself if you need to compile CSS files from them\n" + | ||
"or use the node-sass includePaths option if you need to import from them."); | ||
var MergeTrees = require("broccoli-merge-trees"); | ||
inputTree = new MergeTrees(inputTree, {overwrite: true, annotation: "Sass Trees"}); | ||
} else { | ||
inputTree = inputTree[0]; | ||
module.exports = class BroccoliSassCompiler extends BroccoliPlugin { | ||
constructor(inputTree, options) { | ||
if (Array.isArray(inputTree)) { | ||
if (inputTree.length > 1) { | ||
console.warn( | ||
"Support for passing several trees to BroccoliSassCompiler has been removed.\n" + | ||
"Passing the trees to broccoli-merge-trees with the overwrite option set,\n" + | ||
"but you should do this yourself if you need to compile CSS files from them\n" + | ||
"or use the node-sass includePaths option if you need to import from them."); | ||
let MergeTrees = require("broccoli-merge-trees"); | ||
inputTree = new MergeTrees(inputTree, {overwrite: true, annotation: "Sass Trees"}); | ||
} else { | ||
inputTree = inputTree[0]; | ||
} | ||
} | ||
} | ||
options = options || {}; | ||
options.persistentOutput = true; | ||
BroccoliPlugin.call(this, [inputTree], options); | ||
options = options || {}; | ||
options.persistentOutput = true; | ||
super([inputTree], options); | ||
this.buildCount = 0; | ||
this.treeName = options.annotation; | ||
this.sass = sass; | ||
this.options = copyObject(options); | ||
var EventEmitter = require("chained-emitter").EventEmitter; | ||
this.events = new EventEmitter(); | ||
this.buildCount = 0; | ||
this.treeName = options.annotation; | ||
this.sass = sass; | ||
this.options = copyObject(options); | ||
let EventEmitter = require("chained-emitter").EventEmitter; | ||
this.events = new EventEmitter(); | ||
this.currentTree = null; | ||
this.dependencies = {}; | ||
this.outputs = {}; | ||
if (options.persistentCache) { | ||
var AsyncDiskCache = require("async-disk-cache"); | ||
this.persistentCache = new AsyncDiskCache(options.persistentCache); | ||
} | ||
this.currentTree = null; | ||
this.dependencies = {}; | ||
this.outputs = {}; | ||
if (options.persistentCache) { | ||
let AsyncDiskCache = require("async-disk-cache"); | ||
this.persistentCache = new AsyncDiskCache(options.persistentCache); | ||
} | ||
moveOption(this.options, this, "cssDir", "sassDir", | ||
"optionsGenerator", "fullException", | ||
"verbose", "renderSync", | ||
"discover", "sourceFiles", | ||
"maxListeners"); | ||
if (!this.cssDir) { | ||
throw new Error("Expected cssDir option."); | ||
} | ||
if (!this.optionsGenerator) { | ||
this.optionsGenerator = defaultOptionsGenerator; | ||
} | ||
if (!this.sourceFiles) { | ||
this.sourceFiles = []; | ||
if (this.discover === false) { | ||
throw new Error("sourceFiles are required when discovery is disabled."); | ||
} else { | ||
// Default to discovery mode if no sourcefiles are provided. | ||
this.discover = true; | ||
moveOption(this.options, this, "cssDir", "sassDir", | ||
"optionsGenerator", "fullException", | ||
"verbose", "renderSync", | ||
"discover", "sourceFiles", | ||
"maxListeners"); | ||
if (!this.cssDir) { | ||
throw new Error("Expected cssDir option."); | ||
} | ||
if (!this.optionsGenerator) { | ||
this.optionsGenerator = defaultOptionsGenerator; | ||
} | ||
if (!this.sourceFiles) { | ||
this.sourceFiles = []; | ||
if (this.discover === false) { | ||
throw new Error("sourceFiles are required when discovery is disabled."); | ||
} else { | ||
// Default to discovery mode if no sourcefiles are provided. | ||
this.discover = true; | ||
} | ||
} | ||
this.maxListeners = this.maxListeners || 10; | ||
forbidNodeSassOption(this.options, "file"); | ||
forbidNodeSassOption(this.options, "data"); | ||
forbidNodeSassOption(this.options, "outFile"); | ||
if (this.verbose) { | ||
this.colors = require("colors/safe"); | ||
this.events.on("compiled", this.logCompilationSuccess.bind(this)); | ||
this.events.on("failed", this.logCompilationFailure.bind(this)); | ||
} | ||
this.events.addListener("compiled", (details, result) => { | ||
this.addOutput(details.fullSassFilename, details.fullCssFilename); | ||
let depFiles = result.stats.includedFiles; | ||
this.addDependency(details.fullSassFilename, details.fullSassFilename); | ||
for (let i = 0; i < depFiles.length; i++) { | ||
this.addDependency(details.fullSassFilename, depFiles[i]); | ||
} | ||
}); | ||
} | ||
this.maxListeners = this.maxListeners || 10; | ||
forbidNodeSassOption(this.options, "file"); | ||
forbidNodeSassOption(this.options, "data"); | ||
forbidNodeSassOption(this.options, "outFile"); | ||
if (this.verbose) { | ||
this.colors = require("colors/safe"); | ||
this.events.on("compiled", this.logCompilationSuccess.bind(this)); | ||
this.events.on("failed", this.logCompilationFailure.bind(this)); | ||
logCompilationSuccess(details, result) { | ||
let timeInSeconds = result.stats.duration / 1000.0; | ||
if (timeInSeconds === 0) { | ||
timeInSeconds = "0.001"; // nothing takes zero seconds. | ||
} | ||
let action = this.colors.inverse.green("compile (" + timeInSeconds + "s)"); | ||
let message = this.scopedFileName(details.sassFilename) + " => " + details.cssFilename; | ||
console.log(action + " " + message); | ||
} | ||
this.events.addListener("compiled", function(details, result) { | ||
this.addOutput(details.fullSassFilename, details.fullCssFilename); | ||
var depFiles = result.stats.includedFiles; | ||
this.addDependency(details.fullSassFilename, details.fullSassFilename); | ||
for (var i = 0; i < depFiles.length; i++) { | ||
this.addDependency(details.fullSassFilename, depFiles[i]); | ||
logCompilationFailure(details, error) { | ||
let sassFilename = details.sassFilename; | ||
let action = this.colors.bgRed.white("error"); | ||
let message = this.colors.red(error.message); | ||
let location = "Line " + error.line + ", Column " + error.column; | ||
if (error.file.substring(error.file.length - sassFilename.length) !== sassFilename) { | ||
location = location + " of " + error.file; | ||
} | ||
}.bind(this)); | ||
} | ||
BroccoliSassCompiler.prototype = Object.create(BroccoliPlugin.prototype); | ||
BroccoliSassCompiler.prototype.constructor = BroccoliSassCompiler; | ||
BroccoliSassCompiler.prototype.logCompilationSuccess = function(details, result) { | ||
var timeInSeconds = result.stats.duration / 1000.0; | ||
if (timeInSeconds === 0) { | ||
timeInSeconds = "0.001"; // nothing takes zero seconds. | ||
console.log(action + " " + sassFilename + " (" + location + "): " + message); | ||
} | ||
var action = this.colors.inverse.green("compile (" + timeInSeconds + "s)"); | ||
var message = this.scopedFileName(details.sassFilename) + " => " + details.cssFilename; | ||
console.log(action + " " + message); | ||
}; | ||
BroccoliSassCompiler.prototype.logCompilationFailure = function(details, error) { | ||
var sassFilename = details.sassFilename; | ||
var action = this.colors.bgRed.white("error"); | ||
var message = this.colors.red(error.message); | ||
var location = "Line " + error.line + ", Column " + error.column; | ||
if (error.file.substring(error.file.length - sassFilename.length) !== sassFilename) { | ||
location = location + " of " + error.file; | ||
compileTree(srcPath, files, destDir) { | ||
let numConcurrentCalls = Number(process.env.JOBS) || os.cpus().length; | ||
let worker = queue.async.asyncify(file => { | ||
return RSVP.all(this.compileSassFile(srcPath, file, destDir)); | ||
}); | ||
return RSVP.resolve(queue(worker, files, numConcurrentCalls)); | ||
} | ||
console.log(action + " " + sassFilename + " (" + location + "): " + message); | ||
}; | ||
BroccoliSassCompiler.prototype.compileTree = function(srcPath, files, destDir) { | ||
var self = this; | ||
var numConcurrentCalls = Number(process.env.JOBS) || os.cpus().length; | ||
compileSassFile(srcPath, sassFilename, destDir) { | ||
let sassOptions = copyObject(this.options); | ||
sassOptions.file = path.join(srcPath, sassFilename); | ||
let parsedName = parsePath(sassFilename); | ||
var worker = queue.async.asyncify(function(file) { | ||
return RSVP.all(self.compileSassFile(srcPath, file, destDir)); | ||
}); | ||
return RSVP.resolve(queue(worker, files, numConcurrentCalls) | ||
.catch(function(reason) { | ||
throw reason; | ||
if (this.sassDir && parsedName.dir.slice(0, this.sassDir.length) === this.sassDir) { | ||
parsedName.dir = parsedName.dir.slice(this.sassDir.length + 1); | ||
} | ||
)); | ||
}; | ||
BroccoliSassCompiler.prototype.compileSassFile = function(srcPath, sassFilename, destDir) { | ||
var self = this; | ||
var sassOptions = copyObject(this.options); | ||
sassOptions.file = path.join(srcPath, sassFilename); | ||
var parsedName = parsePath(sassFilename); | ||
if (this.sassDir && parsedName.dir.slice(0, this.sassDir.length) === this.sassDir) { | ||
parsedName.dir = parsedName.dir.slice(this.sassDir.length + 1); | ||
parsedName.ext = ".css"; | ||
parsedName.base = parsedName.name + ".css"; | ||
let cssFileName = path.join(this.cssDir, formatPath(parsedName)); | ||
let promises = []; | ||
this.optionsGenerator( | ||
sassFilename, cssFileName, sassOptions, | ||
(resolvedCssFileName, resolvedOptions) => { | ||
let details = { | ||
srcPath: srcPath, | ||
sassFilename: sassFilename, | ||
fullSassFilename: resolvedOptions.file, | ||
destDir: destDir, | ||
cssFilename: resolvedCssFileName, | ||
fullCssFilename: path.join(destDir, resolvedCssFileName), | ||
options: copyObject(resolvedOptions) | ||
}; | ||
details.options.outFile = details.cssFilename; | ||
promises.push(this.compileCssFileMaybe(details)); | ||
}); | ||
return promises; | ||
} | ||
parsedName.ext = ".css"; | ||
parsedName.base = parsedName.name + ".css"; | ||
var cssFileName = path.join(this.cssDir, formatPath(parsedName)); | ||
var promises = []; | ||
this.optionsGenerator( | ||
sassFilename, cssFileName, sassOptions, | ||
function(resolvedCssFileName, resolvedOptions) { | ||
var details = { | ||
srcPath: srcPath, | ||
sassFilename: sassFilename, | ||
fullSassFilename: resolvedOptions.file, | ||
destDir: destDir, | ||
cssFilename: resolvedCssFileName, | ||
fullCssFilename: path.join(destDir, resolvedCssFileName), | ||
options: copyObject(resolvedOptions) | ||
}; | ||
details.options.outFile = details.cssFilename; | ||
promises.push(self.compileCssFileMaybe(details)); | ||
}); | ||
return promises; | ||
}; | ||
BroccoliSassCompiler.prototype.renderer = function() { | ||
if (this.renderSync) { | ||
return renderSassSync; | ||
} else { | ||
return renderSass; | ||
renderer() { | ||
if (this.renderSync) { | ||
return renderSassSync; | ||
} else { | ||
return renderSass; | ||
} | ||
} | ||
}; | ||
var READ_AS_UTF_8 = {encoding: "utf8"}; | ||
var readFile = RSVP.denodeify(fs.readFile); | ||
var writeFile = RSVP.denodeify(fs.writeFile); | ||
/* Check if a dependency's hash has changed. | ||
* | ||
* @argument srcDir The directory in which to resolve relative paths against. | ||
* @argument dep An array of two elements, the first is the file and | ||
* the second is the last known hash of that file. | ||
* | ||
* Returns a promise that resolves as true when the file hasn't changed from the specified hash. | ||
**/ | ||
checkDependency(srcDir, dep) { | ||
let file = path.isAbsolute(dep[0]) ? dep[0] : path.join(srcDir, dep[0]); | ||
let hexDigest = dep[1]; | ||
/* Check if a dependency's hash has changed. | ||
* | ||
* @argument srcDir The directory in which to resolve relative paths against. | ||
* @argument dep An array of two elements, the first is the file and | ||
* the second is the last known hash of that file. | ||
* | ||
* Returns a promise that resolves as true when the file hasn't changed from the specified hash. | ||
**/ | ||
BroccoliSassCompiler.prototype.checkDependency = function(srcDir, dep) { | ||
var self = this; | ||
var file = path.isAbsolute(dep[0]) ? dep[0] : path.join(srcDir, dep[0]); | ||
var hexDigest = dep[1]; | ||
return new RSVP.Promise(function(resolve, reject) { | ||
self.hashForFile(file).then(function(hash) { | ||
var hd = hash.digest("hex"); | ||
return this.hashForFile(file).then(hash => { | ||
let hd = hash.digest("hex"); | ||
if (hd === hexDigest) { | ||
resolve(true); | ||
return true; | ||
} else { | ||
reject(new Error("dependency changed: " + dep[0])); | ||
throw new Error("dependency changed: " + dep[0]); | ||
} | ||
}, reject); | ||
}); | ||
}; | ||
}); | ||
} | ||
/* get the cached output for a source file, or receive a cache miss. | ||
* | ||
* @argument srcDir The directory in which to resolve relative paths against. | ||
* @argument relativeFilename The filename relative to the srcDir that is being compiled. | ||
* @argument options The compilation options. | ||
* | ||
* @return Promise that resolves to the cached output of the compiled file or rejects with an | ||
* error explaining why the cache wasn't available. In most cases, the cache error will have | ||
* a property named `key` that can be used to populate the cache if it's missing. Some error | ||
* conditions can't set the key property -- for example when a file is removed or the cache | ||
* can't be created. | ||
**/ | ||
BroccoliSassCompiler.prototype.cachedOutput = function(srcDir, relativeFilename, options) { | ||
var self = this; | ||
return new RSVP.Promise(function(resolve, reject) { | ||
self.keyForSourceFile(srcDir, relativeFilename, options).then(function(key) { | ||
persistentCacheDebug("key for " + self.scopedFileName(relativeFilename) + ": " + key); | ||
self.persistentCache.get(self.dependenciesKey(key)).then(function(cacheEntry) { | ||
/* get the cached output for a source file, or receive a cache miss. | ||
* | ||
* @argument srcDir The directory in which to resolve relative paths against. | ||
* @argument relativeFilename The filename relative to the srcDir that is being compiled. | ||
* @argument options The compilation options. | ||
* | ||
* @return Promise that resolves to the cached output of the compiled file or rejects with an | ||
* error explaining why the cache wasn't available. In most cases, the cache error will have | ||
* a property named `key` that can be used to populate the cache if it's missing. Some error | ||
* conditions can't set the key property -- for example when a file is removed or the cache | ||
* can't be created. | ||
**/ | ||
cachedOutput(srcDir, relativeFilename, options) { | ||
debugger; | ||
return this.keyForSourceFile(srcDir, relativeFilename, options).then(key => { | ||
persistentCacheDebug("key for " + this.scopedFileName(relativeFilename) + ": " + key); | ||
return this.persistentCache.get(this.dependenciesKey(key)).then(cacheEntry => { | ||
if (cacheEntry.isCached) { | ||
var dependencies = JSON.parse(cacheEntry.value); | ||
var depChecks = dependencies.map(function(dep) { | ||
return self.checkDependency(srcDir, dep); | ||
}); | ||
RSVP.all(depChecks). | ||
then(function(depResults) { | ||
return self.persistentCache.get(self.outputKey(key)); | ||
}). | ||
then(function(cacheEntry) { | ||
let dependencies = JSON.parse(cacheEntry.value); | ||
let depChecks = dependencies.map(dep => this.checkDependency(srcDir, dep)); | ||
return RSVP.all(depChecks). | ||
then(depResults => this.persistentCache.get(this.outputKey(key))). | ||
then(cacheEntry => { | ||
if (cacheEntry.isCached) { | ||
var depFiles = dependencies.map(function(depAndHash) { | ||
return depAndHash[0]; | ||
}); | ||
resolve([depFiles, JSON.parse(cacheEntry.value)]); | ||
let depFiles = dependencies.map(depAndHash => depAndHash[0]); | ||
return [ | ||
depFiles, | ||
JSON.parse(cacheEntry.value) | ||
]; | ||
} else { | ||
var error = new Error("no output data for " + relativeFilename); | ||
error.key = key; | ||
reject(error); | ||
throw new Error("no output data for " + relativeFilename); | ||
} | ||
}). | ||
catch(function(error) { | ||
catch(error => { | ||
error.key = key; | ||
reject(error); | ||
throw error; | ||
}); | ||
} else { | ||
var error = new Error("no dependency data for " + relativeFilename); | ||
let error = new Error("no dependency data for " + relativeFilename); | ||
error.key = key; | ||
reject(error); | ||
throw error; | ||
} | ||
}); | ||
}, reject); | ||
}); | ||
}; | ||
}); | ||
} | ||
/* compute the hash for a file. | ||
* | ||
* @argument absolutePath The absolute path to the file. | ||
* @return Promise that resolves to a hash object. rejects if it can't read the file. | ||
**/ | ||
BroccoliSassCompiler.prototype.hashForFile = function(absolutePath) { | ||
return readFile(absolutePath, READ_AS_UTF_8).then(function(data) { | ||
return crypto.createHash("md5").update(data); | ||
}); | ||
}; | ||
/* compute the hash for a file. | ||
* | ||
* @argument absolutePath The absolute path to the file. | ||
* @return Promise that resolves to a hash object. rejects if it can't read the file. | ||
**/ | ||
hashForFile(absolutePath) { | ||
return readFile(absolutePath, READ_AS_UTF_8).then(data => { | ||
return crypto.createHash("md5").update(data); | ||
}); | ||
} | ||
/* construct a base cache key for a file to be compiled. | ||
* | ||
* @argument srcDir The directory in which to resolve relative paths against. | ||
* @argument relativeFilename The filename relative to the srcDir that is being compiled. | ||
* @argument options The compilation options. | ||
* | ||
* @return Promise that resolves to the cache key for the file or rejects if it can't read the file. | ||
**/ | ||
BroccoliSassCompiler.prototype.keyForSourceFile = function(srcDir, relativeFilename, options) { | ||
var absolutePath = path.join(srcDir, relativeFilename); | ||
return this.hashForFile(absolutePath).then(function(hash) { | ||
return relativeFilename + "@" + hash.digest("hex"); | ||
}); | ||
}; | ||
/* construct a base cache key for a file to be compiled. | ||
* | ||
* @argument srcDir The directory in which to resolve relative paths against. | ||
* @argument relativeFilename The filename relative to the srcDir that is being compiled. | ||
* @argument options The compilation options. | ||
* | ||
* @return Promise that resolves to the cache key for the file or rejects if | ||
* it can't read the file. | ||
**/ | ||
keyForSourceFile(srcDir, relativeFilename, options) { | ||
let absolutePath = path.join(srcDir, relativeFilename); | ||
return this.hashForFile(absolutePath).then(hash => { | ||
return relativeFilename + "@" + hash.digest("hex"); | ||
}); | ||
} | ||
/* construct a cache key for storing dependency hashes. | ||
* | ||
* @argument key The base cache key | ||
* @return String | ||
*/ | ||
BroccoliSassCompiler.prototype.dependenciesKey = function(key) { | ||
return "[[[dependencies of " + key + "]]]"; | ||
}; | ||
/* construct a cache key for storing dependency hashes. | ||
* | ||
* @argument key The base cache key | ||
* @return String | ||
*/ | ||
dependenciesKey(key) { | ||
return "[[[dependencies of " + key + "]]]"; | ||
} | ||
/* construct a cache key for storing output. | ||
* | ||
* @argument key The base cache key | ||
* @return String | ||
*/ | ||
BroccoliSassCompiler.prototype.outputKey = function(key) { | ||
return "[[[output of " + key + "]]]"; | ||
}; | ||
/* construct a cache key for storing output. | ||
* | ||
* @argument key The base cache key | ||
* @return String | ||
*/ | ||
outputKey(key) { | ||
return "[[[output of " + key + "]]]"; | ||
} | ||
/* retrieve the files from cache, write them, and populate the hot cache information | ||
* for rebuilds. | ||
*/ | ||
BroccoliSassCompiler.prototype.handleCacheHit = function(details, inputAndOutputFiles) { | ||
var self = this; | ||
var inputFiles = inputAndOutputFiles[0]; | ||
var outputFiles = inputAndOutputFiles[1]; | ||
persistentCacheDebug("%s is cached. Writing to %s.", | ||
details.sassFilename, | ||
details.fullCssFilename); | ||
/* retrieve the files from cache, write them, and populate the hot cache information | ||
* for rebuilds. | ||
*/ | ||
handleCacheHit(details, inputAndOutputFiles) { | ||
let inputFiles = inputAndOutputFiles[0]; | ||
let outputFiles = inputAndOutputFiles[1]; | ||
if (self.verbose) { | ||
var action = self.colors.inverse.green("cached"); | ||
var message = self.scopedFileName(details.sassFilename) + " => " + details.cssFilename; | ||
console.log(action + " " + message); | ||
persistentCacheDebug("%s is cached. Writing to %s.", | ||
details.sassFilename, | ||
details.fullCssFilename); | ||
if (this.verbose) { | ||
let action = this.colors.inverse.green("cached"); | ||
let message = this.scopedFileName(details.sassFilename) + " => " + details.cssFilename; | ||
console.log(action + " " + message); | ||
} | ||
let results = []; | ||
inputFiles.forEach(dep => { | ||
// populate the dependencies cache for rebuilds | ||
this.addDependency(details.fullSassFilename, path.resolve(details.srcPath, dep)); | ||
}); | ||
let files = Object.keys(outputFiles); | ||
persistentCacheDebug("cached output files for %s are: %s", | ||
details.sassFilename, files.join(", ")); | ||
files.forEach(f => { | ||
let data = outputFiles[f]; | ||
let outputFile = path.join(details.destDir, f); | ||
// populate the output cache for rebuilds | ||
this.addOutput(details.fullSassFilename, outputFile); | ||
let writePromise = mkdirp.promise(path.dirname(outputFile)). | ||
then(() => writeFile(outputFile, new Buffer(data, "base64"))); | ||
results.push(writePromise); | ||
}); | ||
return RSVP.all(results); | ||
} | ||
var results = []; | ||
inputFiles.forEach(function(dep) { | ||
// populate the dependencies cache for rebuilds | ||
self.addDependency(details.fullSassFilename, path.resolve(details.srcPath, dep)); | ||
}); | ||
var files = Object.keys(outputFiles); | ||
persistentCacheDebug("cached output files for %s are: %s", | ||
details.sassFilename, files.join(", ")); | ||
files.forEach(function(f) { | ||
var data = outputFiles[f]; | ||
var outputFile = path.join(details.destDir, f); | ||
// populate the output cache for rebuilds | ||
self.addOutput(details.fullSassFilename, outputFile); | ||
var writePromise = mkdirp.promise(path.dirname(outputFile)). | ||
then(function() { | ||
return writeFile(outputFile, new Buffer(data, "base64")); | ||
}); | ||
results.push(writePromise); | ||
}); | ||
return RSVP.all(results); | ||
}; | ||
BroccoliSassCompiler.prototype.scopedFileName = function(file) { | ||
file = this.relativize(file); | ||
if (this.treeName) { | ||
return this.treeName + "/" + file; | ||
} else { | ||
return file; | ||
scopedFileName(file) { | ||
file = this.relativize(file); | ||
if (this.treeName) { | ||
return this.treeName + "/" + file; | ||
} else { | ||
return file; | ||
} | ||
} | ||
}; | ||
BroccoliSassCompiler.prototype.relativize = function(file) { | ||
return removePathPrefix(this.inputPaths[0], [file])[0]; | ||
}; | ||
relativize(file) { | ||
return removePathPrefix(this.inputPaths[0], [file])[0]; | ||
} | ||
BroccoliSassCompiler.prototype.relativizeAll = function(files) { | ||
return removePathPrefix(this.inputPaths[0], files); | ||
}; | ||
relativizeAll(files) { | ||
return removePathPrefix(this.inputPaths[0], files); | ||
} | ||
BroccoliSassCompiler.prototype.hasDependenciesSet = function(file) { | ||
return this.dependencies[this.relativize(file)] !== undefined; | ||
}; | ||
hasDependenciesSet(file) { | ||
return this.dependencies[this.relativize(file)] !== undefined; | ||
} | ||
BroccoliSassCompiler.prototype.dependenciesOf = function(file) { | ||
return this.dependencies[this.relativize(file)] || new Set(); | ||
}; | ||
dependenciesOf(file) { | ||
return this.dependencies[this.relativize(file)] || new Set(); | ||
} | ||
BroccoliSassCompiler.prototype.outputsFrom = function(file) { | ||
return this.outputs[this.relativize(file)] || new Set(); | ||
}; | ||
outputsFrom(file) { | ||
return this.outputs[this.relativize(file)] || new Set(); | ||
} | ||
/* hash all dependencies asynchronously and return the files that exist | ||
* as an array of pairs (filename, hash). | ||
*/ | ||
BroccoliSassCompiler.prototype.hashDependencies = function(details) { | ||
var self = this; | ||
var dependencyPromises = []; | ||
self.dependenciesOf(details.fullSassFilename).forEach(function(f) { | ||
dependencyPromises.push(self.hashForFile(f).then(function(h) { | ||
if (f.startsWith(details.srcPath)) { | ||
f = f.substring(details.srcPath.length + 1); | ||
} | ||
return [f, h.digest("hex")]; | ||
}).catch(function(e) { | ||
if (e.code === "ENOENT") { | ||
persistentCacheDebug("Ignoring non-existent file: %s", f); | ||
return []; | ||
} else { | ||
throw e; | ||
} | ||
})); | ||
}); | ||
return RSVP.all(dependencyPromises).then(function(depsWithHashes) { | ||
return depsWithHashes.filter(function(dwh) { | ||
return dwh.length > 0; // prune out the dependencies that weren't files. | ||
/* hash all dependencies asynchronously and return the files that exist | ||
* as an array of pairs (filename, hash). | ||
*/ | ||
hashDependencies(details) { | ||
let dependencyPromises = []; | ||
this.dependenciesOf(details.fullSassFilename).forEach(f => { | ||
dependencyPromises.push(this.hashForFile(f).then(h =>{ | ||
if (f.startsWith(details.srcPath)) { | ||
f = f.substring(details.srcPath.length + 1); | ||
} | ||
return [ | ||
f, | ||
h.digest("hex") | ||
]; | ||
}).catch(e => { | ||
if (typeof e === "object" && e !== null && e.code === "ENOENT") { | ||
persistentCacheDebug("Ignoring non-existent file: %s", f); | ||
return []; | ||
} else { | ||
throw e; | ||
} | ||
})); | ||
}); | ||
}); | ||
}; | ||
/* read all output files asynchronously and return the contents | ||
* as a hash of relative filenames to base64 encoded strings. | ||
*/ | ||
BroccoliSassCompiler.prototype.readOutputs = function(details) { | ||
var readPromises = []; | ||
var outputs = this.outputsFrom(details.fullSassFilename); | ||
outputs.forEach(function(o) { | ||
readPromises.push(readFile(o).then(function(buffer) { | ||
return [o, buffer.toString("base64")]; | ||
})); | ||
}); | ||
return RSVP.all(readPromises).then(function(outputs) { | ||
var outputContents = {}; | ||
outputs.forEach(function(output) { | ||
var fileName = output[0]; | ||
var contents = output[1]; | ||
if (fileName.startsWith(details.destDir)) { | ||
outputContents[fileName.substring(details.destDir.length + 1)] = contents; | ||
} else { | ||
persistentCacheDebug("refusing to cache output file found outside the output tree: %s", fileName); | ||
} | ||
return RSVP.all(dependencyPromises).then(depsWithHashes => { | ||
// prune out the dependencies that weren't files. | ||
return depsWithHashes.filter(dwh => dwh.length > 0); | ||
}); | ||
return outputContents; | ||
}); | ||
}; | ||
} | ||
/* Writes the dependencies and output contents to the persistent cache */ | ||
BroccoliSassCompiler.prototype.populateCache = function(key, details, result) { | ||
var self = this; | ||
var cache = this.persistentCache; | ||
persistentCacheDebug("Populating cache for " + key); | ||
var setDeps = self.hashDependencies(details).then(function(depsWithHashes) { | ||
return cache.set(self.dependenciesKey(key), JSON.stringify(depsWithHashes)); | ||
}); | ||
/* read all output files asynchronously and return the contents | ||
* as a hash of relative filenames to base64 encoded strings. | ||
*/ | ||
readOutputs(details) { | ||
let readPromises = []; | ||
let outputs = this.outputsFrom(details.fullSassFilename); | ||
var setOutput = self.readOutputs(details).then(function(outputContents) { | ||
return cache.set(self.outputKey(key), JSON.stringify(outputContents)); | ||
}); | ||
outputs.forEach(output => { | ||
readPromises.push(readFile(output).then(buffer => { | ||
return [output, buffer.toString("base64")]; | ||
})); | ||
}); | ||
return RSVP.all([setDeps, setOutput]); | ||
}; | ||
return RSVP.all(readPromises).then(outputs => { | ||
let outputContents = {}; | ||
/* When the cache misses, we need to compile the file and then populate the cache if we can. */ | ||
BroccoliSassCompiler.prototype.handleCacheMiss = function(details, reason) { | ||
var self = this; | ||
var key = reason.key; | ||
if (key) { | ||
persistentCacheDebug("Persistent cache miss for %s. Reason: %s", | ||
details.sassFilename, | ||
reason.message); | ||
} else { | ||
persistentCacheDebug("Cannot cache %s. Reason: %s", | ||
details.sassFilename, | ||
reason.message); | ||
persistentCacheDebug("Stacktrace:", reason.stack); | ||
outputs.forEach(output => { | ||
let fileName = output[0]; | ||
let contents = output[1]; | ||
if (fileName.startsWith(details.destDir)) { | ||
outputContents[fileName.substring(details.destDir.length + 1)] = contents; | ||
} else { | ||
persistentCacheDebug("refusing to cache output file found outside the output tree: %s", | ||
fileName); | ||
} | ||
}); | ||
return outputContents; | ||
}); | ||
} | ||
return self.compileCssFile(details). | ||
then(function(result) { | ||
if (key) { | ||
return self.populateCache(key, details, result); | ||
} | ||
/* Writes the dependencies and output contents to the persistent cache */ | ||
populateCache(key, details, result) { | ||
let cache = this.persistentCache; | ||
persistentCacheDebug("Populating cache for " + key); | ||
let setDeps = this.hashDependencies(details).then(depsWithHashes => { | ||
return cache.set(this.dependenciesKey(key), JSON.stringify(depsWithHashes)); | ||
}); | ||
}; | ||
/* Compile the file if it's not in the cache. | ||
* Reuse cached output if it is. | ||
* | ||
* @argument details The compilation details object. | ||
* | ||
* @return A promise that resolves when the output files are written | ||
* either from cache or by compiling. Rejects on error. | ||
*/ | ||
BroccoliSassCompiler.prototype.compileCssFileMaybe = function(details) { | ||
var self = this; | ||
if (this.persistentCache) { | ||
return this.cachedOutput(details.srcPath, details.sassFilename, details.options). | ||
then(self.handleCacheHit.bind(self, details), | ||
self.handleCacheMiss.bind(self, details)); | ||
} else { | ||
return self.compileCssFile(details); | ||
let setOutput = this.readOutputs(details).then(outputContents => { | ||
return cache.set(this.outputKey(key), JSON.stringify(outputContents)); | ||
}); | ||
return RSVP.all([ | ||
setDeps, | ||
setOutput | ||
]); | ||
} | ||
}; | ||
BroccoliSassCompiler.prototype.compileCssFile = function(details) { | ||
var sass = this.renderer(); | ||
var success = this.handleSuccess.bind(this, details); | ||
var failure = this.handleFailure.bind(this, details); | ||
var self = this; | ||
/* When the cache misses, we need to compile the file and then populate the cache if we can. */ | ||
handleCacheMiss(details, reason) { | ||
let key = reason.key; | ||
return this.events.emit("compiling", details) | ||
.then(function() { | ||
var dependencyListener = function(absolutePath) { | ||
self.addDependency(details.fullSassFilename, absolutePath); | ||
}; | ||
if (key) { | ||
persistentCacheDebug("Persistent cache miss for %s. Reason: %s", | ||
details.sassFilename, | ||
reason.message); | ||
} else { | ||
persistentCacheDebug("Cannot cache %s. Reason: %s", | ||
details.sassFilename, | ||
reason.message); | ||
persistentCacheDebug("Stacktrace:", reason.stack); | ||
} | ||
return this.compileCssFile(details). | ||
then(result => { | ||
if (key) { | ||
return this.populateCache(key, details, result); | ||
} | ||
}); | ||
} | ||
var additionalOutputListener = function(filename) { | ||
self.addOutput(details.fullSassFilename, filename); | ||
}; | ||
/* Compile the file if it's not in the cache. | ||
* Reuse cached output if it is. | ||
* | ||
* @argument details The compilation details object. | ||
* | ||
* @return A promise that resolves when the output files are written | ||
* either from cache or by compiling. Rejects on error. | ||
*/ | ||
compileCssFileMaybe(details) { | ||
if (this.persistentCache) { | ||
return this.cachedOutput(details.srcPath, details.sassFilename, details.options). | ||
then(this.handleCacheHit.bind(this, details), | ||
this.handleCacheMiss.bind(this, details)); | ||
} else { | ||
return this.compileCssFile(details); | ||
} | ||
} | ||
self.events.addListener("additional-output", additionalOutputListener); | ||
self.events.addListener("dependency", dependencyListener); | ||
compileCssFile(details) { | ||
let sass = this.renderer(); | ||
return sass(details.options) | ||
.then(function(result) { | ||
self.events.removeListener("dependency", dependencyListener); | ||
self.events.removeListener("additional-output", additionalOutputListener); | ||
return success(result).then(function() { | ||
return result; | ||
}); | ||
}, failure); | ||
}); | ||
}; | ||
let success = this.handleSuccess.bind(this, details); | ||
let failure = this.handleFailure.bind(this, details); | ||
BroccoliSassCompiler.prototype.handleSuccess = function(details, result) { | ||
mkdirp.sync(path.dirname(details.fullCssFilename)); | ||
fs.writeFileSync(details.fullCssFilename, result.css); | ||
return this.events.emit("compiled", details, result); | ||
}; | ||
return this.events.emit("compiling", details) | ||
.then(() => { | ||
let dependencyListener = absolutePath => { | ||
this.addDependency(details.fullSassFilename, absolutePath); | ||
}; | ||
BroccoliSassCompiler.prototype.handleFailure = function(details, error) { | ||
var failed = this.events.emit("failed", details, error); | ||
var rethrow = failed.finally(function() { | ||
var message = error.message; | ||
var location = " at " + error.file + ":" + error.line + ":" + error.column; | ||
// TODO: implement fullException | ||
throw new Error(message + "\n" + location); | ||
}); | ||
return rethrow; | ||
}; | ||
let additionalOutputListener = filename => { | ||
this.addOutput(details.fullSassFilename, filename); | ||
}; | ||
BroccoliSassCompiler.prototype.filesInTree = function(srcPath) { | ||
var sassDir = this.sassDir || ""; | ||
var files = []; | ||
if (this.discover) { | ||
var pattern = path.join(srcPath, sassDir, "**", "[^_]*.scss"); | ||
files = glob.sync(pattern); | ||
this.events.addListener("additional-output", additionalOutputListener); | ||
this.events.addListener("dependency", dependencyListener); | ||
return sass(details.options) | ||
.then(result => { | ||
this.events.removeListener("dependency", dependencyListener); | ||
this.events.removeListener("additional-output", additionalOutputListener); | ||
return success(result).then(() => result); | ||
}, failure); | ||
}); | ||
} | ||
this.sourceFiles.forEach(function(sourceFile) { | ||
var pattern = path.join(srcPath, sassDir, sourceFile); | ||
files = files.concat(glob.sync(pattern)); | ||
}); | ||
return unique(files); | ||
}; | ||
function Entry(path) { | ||
var stats = fs.statSync(path); | ||
this.relativePath = path; | ||
this.basePath = "/"; | ||
this.mode = stats.mode; | ||
this.size = stats.size; | ||
this.mtime = stats.mtime; | ||
} | ||
handleSuccess(details, result) { | ||
mkdirp.sync(path.dirname(details.fullCssFilename)); | ||
fs.writeFileSync(details.fullCssFilename, result.css); | ||
Entry.prototype.isDirectory = function() { | ||
return false; | ||
}; | ||
return this.events.emit("compiled", details, result); | ||
} | ||
BroccoliSassCompiler.prototype.addOutput = function(sassFilename, outputFilename) { | ||
sassFilename = this.relativize(sassFilename); | ||
this.outputs[sassFilename] = | ||
this.outputs[sassFilename] || new Set(); | ||
this.outputs[sassFilename].add(outputFilename); | ||
}; | ||
handleFailure(details, error) { | ||
let failed = this.events.emit("failed", details, error); | ||
let rethrow = failed.finally(() => { | ||
let message = error.message; | ||
let location = " at " + error.file + ":" + error.line + ":" + error.column; | ||
// TODO: implement fullException | ||
throw new Error(message + "\n" + location); | ||
}); | ||
BroccoliSassCompiler.prototype.clearOutputs = function(files) { | ||
var self = this; | ||
this.relativizeAll(files).forEach(function(f) { | ||
if (self.outputs[f]) { | ||
delete self.outputs[f]; | ||
return rethrow; | ||
} | ||
filesInTree(srcPath) { | ||
let sassDir = this.sassDir || ""; | ||
let files = []; | ||
if (this.discover) { | ||
let pattern = path.join(srcPath, sassDir, "**", "[^_]*.scss"); | ||
files = glob.sync(pattern); | ||
} | ||
}); | ||
}; | ||
/* This method computes the output files that are only output for at least one given inputs | ||
* and never for an input that isn't provided. | ||
* | ||
* This is important because the same assets might be output from compiling several | ||
* different inputs for tools like eyeglass assets. | ||
* | ||
* @return Set<String> The full paths output files. | ||
*/ | ||
BroccoliSassCompiler.prototype.outputsFromOnly = function(inputs) { | ||
inputs = this.relativizeAll(inputs); | ||
var otherOutputs = new Set(); | ||
var onlyOutputs = new Set(); | ||
var allInputs = Object.keys(this.outputs); | ||
for (var i = 0; i < allInputs.length; i++) { | ||
var outputs = this.outputs[allInputs[i]]; | ||
if (inputs.indexOf(allInputs[i]) < 0) { | ||
outputs.forEach(function(output) { | ||
otherOutputs.add(output); | ||
}); | ||
} else { | ||
outputs.forEach(function(output) { | ||
onlyOutputs.add(output); | ||
}); | ||
} | ||
this.sourceFiles.forEach(sourceFile => { | ||
let pattern = path.join(srcPath, sassDir, sourceFile); | ||
files = files.concat(glob.sync(pattern)); | ||
}); | ||
return unique(files); | ||
} | ||
onlyOutputs.forEach(function(only) { | ||
if (otherOutputs.has(only)) { | ||
onlyOutputs.delete(only); | ||
} | ||
}); | ||
return onlyOutputs; | ||
}; | ||
BroccoliSassCompiler.prototype.addDependency = function(sassFilename, dependencyFilename) { | ||
sassFilename = this.relativize(sassFilename); | ||
this.dependencies[sassFilename] = | ||
this.dependencies[sassFilename] || new Set(); | ||
this.dependencies[sassFilename].add(dependencyFilename); | ||
}; | ||
addOutput(sassFilename, outputFilename) { | ||
sassFilename = this.relativize(sassFilename); | ||
BroccoliSassCompiler.prototype.clearDependencies = function(files) { | ||
var self = this; | ||
this.relativizeAll(files).forEach(function(f) { | ||
if (self.dependencies[f]) { | ||
delete self.dependencies[f]; | ||
this.outputs[sassFilename] = | ||
this.outputs[sassFilename] || new Set(); | ||
this.outputs[sassFilename].add(outputFilename); | ||
} | ||
clearOutputs(files) { | ||
this.relativizeAll(files).forEach(f => { | ||
if (this.outputs[f]) { | ||
delete this.outputs[f]; | ||
} | ||
}); | ||
} | ||
/* This method computes the output files that are only output for at least one given inputs | ||
* and never for an input that isn't provided. | ||
* | ||
* This is important because the same assets might be output from compiling several | ||
* different inputs for tools like eyeglass assets. | ||
* | ||
* @return Set<String> The full paths output files. | ||
*/ | ||
outputsFromOnly(inputs) { | ||
inputs = this.relativizeAll(inputs); | ||
let otherOutputs = new Set(); | ||
let onlyOutputs = new Set(); | ||
let allInputs = Object.keys(this.outputs); | ||
for (let i = 0; i < allInputs.length; i++) { | ||
let outputs = this.outputs[allInputs[i]]; | ||
if (inputs.indexOf(allInputs[i]) < 0) { | ||
outputs.forEach(output => otherOutputs.add(output)); | ||
} else { | ||
outputs.forEach(output => onlyOutputs.add(output)); | ||
} | ||
} | ||
}); | ||
}; | ||
onlyOutputs.forEach(only => { | ||
if (otherOutputs.has(only)) { | ||
onlyOutputs.delete(only); | ||
} | ||
}); | ||
return onlyOutputs; | ||
} | ||
BroccoliSassCompiler.prototype.knownDependencies = function() { | ||
var deps = new Set(); | ||
var sassFiles = Object.keys(this.dependencies); | ||
for (var i = 0; i < sassFiles.length; i++) { | ||
var sassFile = sassFiles[i]; | ||
deps.add(sassFile); | ||
this.dependencies[sassFile].forEach(function (dep) { | ||
deps.add(dep); | ||
addDependency(sassFilename, dependencyFilename) { | ||
sassFilename = this.relativize(sassFilename); | ||
this.dependencies[sassFilename] = | ||
this.dependencies[sassFilename] || new Set(); | ||
this.dependencies[sassFilename].add(dependencyFilename); | ||
} | ||
clearDependencies(files) { | ||
this.relativizeAll(files).forEach(f => { | ||
delete this.dependencies[f]; | ||
}); | ||
} | ||
var entries = []; | ||
deps.forEach(function(d) { | ||
knownDependencies() { | ||
let deps = new Set(); | ||
let sassFiles = Object.keys(this.dependencies); | ||
for (let i = 0; i < sassFiles.length; i++) { | ||
let sassFile = sassFiles[i]; | ||
deps.add(sassFile); | ||
this.dependencies[sassFile].forEach(dep => deps.add(dep)); | ||
} | ||
let entries = []; | ||
deps.forEach(d => { | ||
try { | ||
@@ -796,41 +807,30 @@ entries.push(new Entry(d)); | ||
} | ||
}); | ||
return entries; | ||
}; | ||
}); | ||
BroccoliSassCompiler.prototype.hasKnownDependencies = function() { | ||
return Object.keys(this.dependencies).length > 0; | ||
}; | ||
return entries; | ||
} | ||
function absolutizeEntries(entries) { | ||
// We make everything absolute because relative path comparisons don't work for us. | ||
entries.forEach(function(entry) { | ||
// TODO support windows paths | ||
entry.relativePath = path.join(entry.basePath, entry.relativePath); | ||
entry.basePath = "/"; | ||
}); | ||
} | ||
hasKnownDependencies() { | ||
return Object.keys(this.dependencies).length > 0; | ||
} | ||
BroccoliSassCompiler.prototype.knownDependenciesTree = function(inputPath) { | ||
var entries = walkSync.entries(inputPath); | ||
absolutizeEntries(entries); | ||
var tree = new FSTreeFromEntries(entries); | ||
tree.addEntries(this.knownDependencies(), {sortAndExpand: true}); | ||
return tree; | ||
}; | ||
knownDependenciesTree(inputPath) { | ||
let entries = walkSync.entries(inputPath); | ||
absolutizeEntries(entries); | ||
let tree = new FSTreeFromEntries(entries); | ||
tree.addEntries(this.knownDependencies(), {sortAndExpand: true}); | ||
return tree; | ||
} | ||
BroccoliSassCompiler.prototype.build = function() { | ||
this.buildCount = this.buildCount + 1; | ||
var self = this; | ||
var nextTree = null; | ||
var patches = []; | ||
var inputPath = this.inputPaths[0]; | ||
var outputPath = this.outputPath; | ||
var currentTree = this.currentTree; | ||
_build() { | ||
let inputPath = this.inputPaths[0]; | ||
let outputPath = this.outputPath; | ||
let currentTree = this.currentTree; | ||
function doBuild() { | ||
if (self.hasKnownDependencies()) { | ||
let nextTree = null; | ||
let patches = []; | ||
if (this.hasKnownDependencies()) { | ||
hotCacheDebug("Has known dependencies"); | ||
nextTree = self.knownDependenciesTree(inputPath); | ||
self.currentTree = nextTree; | ||
nextTree = this.knownDependenciesTree(inputPath); | ||
this.currentTree = nextTree; | ||
currentTree = currentTree || new FSTree(); | ||
@@ -846,13 +846,16 @@ patches = currentTree.calculatePatch(nextTree); | ||
// TODO: handle indented syntax files. | ||
var treeFiles = removePathPrefix(inputPath, self.filesInTree(inputPath)); | ||
treeFiles = treeFiles.filter(function(f, i) { | ||
let treeFiles = removePathPrefix(inputPath, this.filesInTree(inputPath)); | ||
treeFiles = treeFiles.filter(f => { | ||
f = path.join(inputPath, f); | ||
if (!self.hasDependenciesSet(f)) { | ||
hotCacheDebug("no deps for", self.scopedFileName(f)); | ||
if (!this.hasDependenciesSet(f)) { | ||
hotCacheDebug("no deps for", this.scopedFileName(f)); | ||
return true; | ||
} | ||
var deps = self.dependenciesOf(f); | ||
let deps = this.dependenciesOf(f); | ||
hotCacheDebug("dependencies are", deps); | ||
for (var p = 0; p < patches.length; p++) { | ||
var entry = patches[p][2]; | ||
let entry = patches[p][2]; | ||
hotCacheDebug("looking for", entry.relativePath); | ||
@@ -864,6 +867,6 @@ if (deps.has(entry.relativePath)) { | ||
} | ||
if (self.verbose) { | ||
var action = self.colors.inverse.green("unchanged"); | ||
var message = f; | ||
var message = self.scopedFileName(f); | ||
if (this.verbose) { | ||
let action = this.colors.inverse.green("unchanged"); | ||
let message = this.scopedFileName(f); | ||
console.log(action + " " + message); | ||
@@ -874,6 +877,6 @@ } | ||
// Cleanup any unneeded output files | ||
var removed = []; | ||
let removed = []; | ||
for (var p = 0; p < patches.length; p++) { | ||
if (patches[p][0] === "unlink") { | ||
var entry = patches[p][2]; | ||
let entry = patches[p][2]; | ||
if (entry.relativePath.indexOf(inputPath) === 0) { | ||
@@ -884,5 +887,6 @@ removed.push(entry.relativePath); | ||
} | ||
if (removed.length > 0) { | ||
var outputs = self.outputsFromOnly(removed); | ||
outputs.forEach(function(output) { | ||
let outputs = this.outputsFromOnly(removed); | ||
outputs.forEach(output => { | ||
if (output.indexOf(outputPath) === 0) { | ||
@@ -894,3 +898,3 @@ fs.unlinkSync(output); | ||
}); | ||
self.clearOutputs(removed); | ||
this.clearOutputs(removed); | ||
} | ||
@@ -900,21 +904,19 @@ | ||
var absoluteTreeFiles = treeFiles.map(function(f) { | ||
return path.join(inputPath, f); | ||
}); | ||
let absoluteTreeFiles = treeFiles.map(f => path.join(inputPath, f)); | ||
self.clearDependencies(absoluteTreeFiles); | ||
self.clearOutputs(absoluteTreeFiles); | ||
this.clearDependencies(absoluteTreeFiles); | ||
this.clearOutputs(absoluteTreeFiles); | ||
var internalListeners = absoluteTreeFiles.length * 2 + // 1 dep & 1 output listeners each | ||
1 + // one compilation listener | ||
(self.verbose ? 2 : 0); // 2 logging listeners if in verbose mode | ||
let internalListeners = absoluteTreeFiles.length * 2 + // 1 dep & 1 output listeners each | ||
1 + // one compilation listener | ||
(this.verbose ? 2 : 0); // 2 logging listeners if in verbose mode | ||
debug("There are %d internal event listeners.", internalListeners); | ||
debug("Setting max external listeners to %d via the maxListeners option (default: 10).", | ||
self.maxListeners); | ||
self.events.setMaxListeners(internalListeners + self.maxListeners); | ||
this.maxListeners); | ||
this.events.setMaxListeners(internalListeners + this.maxListeners); | ||
return self.compileTree(inputPath, treeFiles, outputPath).finally(function() { | ||
if (!self.currentTree) { | ||
self.currentTree = self.knownDependenciesTree(inputPath); | ||
return this.compileTree(inputPath, treeFiles, outputPath).finally(() => { | ||
if (!this.currentTree) { | ||
this.currentTree = this.knownDependenciesTree(inputPath); | ||
} | ||
@@ -924,10 +926,13 @@ }); | ||
if (self.buildCount === 1 && process.env["BROCCOLI_EYEGLASS"] === "forceInvalidateCache") { | ||
persistentCacheDebug("clearing cache because forceInvalidateCache was set."); | ||
return this.persistentCache.clear().then(doBuild); | ||
} else { | ||
return doBuild(); | ||
build() { | ||
this.buildCount = this.buildCount + 1; | ||
if (this.buildCount === 1 && process.env.BROCCOLI_EYEGLASS === "forceInvalidateCache") { | ||
persistentCacheDebug("clearing cache because forceInvalidateCache was set."); | ||
debugger; | ||
return this.persistentCache.clear().then(this._build.bind(this)); | ||
} else { | ||
return this._build(); | ||
} | ||
} | ||
}; | ||
module.exports = BroccoliSassCompiler; |
286
lib/index.js
"use strict"; | ||
var BroccoliSassCompiler = require("./broccoli_sass_compiler"); | ||
var RSVP = require("rsvp"); | ||
var crypto = require("crypto"); | ||
var merge = require("lodash.merge"); | ||
var path = require("path"); | ||
var sortby = require("lodash.sortby"); | ||
var stringify = require("json-stable-stringify"); | ||
var debugGenerator = require("debug"); | ||
var persistentCacheDebug = debugGenerator("broccoli-eyeglass:persistent-cache"); | ||
const BroccoliSassCompiler = require("./broccoli_sass_compiler"); | ||
const RSVP = require("rsvp"); | ||
const crypto = require("crypto"); | ||
const merge = require("lodash.merge"); | ||
const path = require("path"); | ||
const sortby = require("lodash.sortby"); | ||
const stringify = require("json-stable-stringify"); | ||
const debugGenerator = require("debug"); | ||
const persistentCacheDebug = debugGenerator("broccoli-eyeglass:persistent-cache"); | ||
function httpJoin() { | ||
var joined = []; | ||
for (var i = 0; i < arguments.length; i++) { | ||
let joined = []; | ||
for (let i = 0; i < arguments.length; i++) { | ||
if (arguments[i]) { | ||
var segment = arguments[i]; | ||
let segment = arguments[i]; | ||
if (path.sep !== "/") { | ||
@@ -24,3 +24,3 @@ segment = segment.replace(path.sep, "/"); | ||
} | ||
var result = joined.join("/"); | ||
let result = joined.join("/"); | ||
result = result.replace("///", "/"); | ||
@@ -31,147 +31,159 @@ result = result.replace("//", "/"); | ||
function EyeglassCompiler(inputTrees, options) { | ||
options = merge({}, options); | ||
this.pristineOptions = merge({}, options); | ||
this.Eyeglass = require("eyeglass"); | ||
if (!Array.isArray(inputTrees)) { | ||
inputTrees = [inputTrees]; | ||
} | ||
if (options.configureEyeglass) { | ||
this.configureEyeglass = options.configureEyeglass; | ||
delete options.configureEyeglass; | ||
} | ||
module.exports = class EyeglassCompiler extends BroccoliSassCompiler { | ||
constructor(inputTrees, options) { | ||
options = merge({}, options); | ||
let pristineOptions = merge({}, options); | ||
if (!Array.isArray(inputTrees)) { | ||
inputTrees = [inputTrees]; | ||
} | ||
let configureEyeglass, assetDirectories, assetsHttpPrefix; | ||
this.relativeAssets = options.relativeAssets; | ||
delete options.relativeAssets; | ||
if (options.configureEyeglass) { | ||
configureEyeglass = options.configureEyeglass; | ||
delete options.configureEyeglass; | ||
} | ||
if (options.assets) { | ||
this.assetDirectories = options.assets; | ||
if (typeof this.assetDirectories === "string") { | ||
this.assetDirectories = [this.assetDirectories]; | ||
let relativeAssets = options.relativeAssets; | ||
delete options.relativeAssets; | ||
if (options.assets) { | ||
assetDirectories = options.assets; | ||
if (typeof assetDirectories === "string") { | ||
assetDirectories = [assetDirectories]; | ||
} | ||
delete options.assets; | ||
} | ||
delete options.assets; | ||
} | ||
if (options.assetsHttpPrefix) { | ||
this.assetsHttpPrefix = options.assetsHttpPrefix; | ||
delete options.assetsHttpPrefix; | ||
} | ||
if (options.assetsHttpPrefix) { | ||
assetsHttpPrefix = options.assetsHttpPrefix; | ||
delete options.assetsHttpPrefix; | ||
} | ||
// TODO: this should not be accessed before super (ES6 Aligment); | ||
BroccoliSassCompiler.call(this, inputTrees, options); | ||
this.events.on("compiling", this.handleNewFile.bind(this)); | ||
} | ||
super(inputTrees, options); | ||
EyeglassCompiler.prototype = Object.create(BroccoliSassCompiler.prototype); | ||
EyeglassCompiler.prototype.constructor = EyeglassCompiler; | ||
EyeglassCompiler.prototype.handleNewFile = function(details) { | ||
if (!details.options.eyeglass) { | ||
details.options.eyeglass = {}; | ||
this.pristineOptions = pristineOptions; | ||
this.Eyeglass = require("eyeglass"); | ||
this.configureEyeglass = configureEyeglass; | ||
this.relativeAssets = relativeAssets; | ||
this.assetDirectories = assetDirectories; | ||
this.assetsHttpPrefix = assetsHttpPrefix; | ||
this.events.on("compiling", this.handleNewFile.bind(this)); | ||
} | ||
if ((this.assetsHttpPrefix || this.assetsRelativeTo) && !details.options.eyeglass.assets) { | ||
details.options.eyeglass.assets = {}; | ||
} | ||
if (this.assetsHttpPrefix) { | ||
details.options.eyeglass.assets.httpPrefix = this.assetsHttpPrefix; | ||
} | ||
if (this.relativeAssets) { | ||
details.options.eyeglass.assets.relativeTo = | ||
httpJoin(details.options.eyeglass.httpRoot || "/", path.dirname(details.cssFilename)); | ||
} | ||
handleNewFile(details) { | ||
if (!details.options.eyeglass) { | ||
details.options.eyeglass = {}; | ||
} | ||
if ((this.assetsHttpPrefix || this.assetsRelativeTo) && !details.options.eyeglass.assets) { | ||
details.options.eyeglass.assets = {}; | ||
} | ||
if (this.assetsHttpPrefix) { | ||
details.options.eyeglass.assets.httpPrefix = this.assetsHttpPrefix; | ||
} | ||
details.options.eyeglass.buildDir = details.destDir; | ||
details.options.eyeglass.engines = details.options.eyeglass.engines || {}; | ||
details.options.eyeglass.engines.sass = details.options.eyeglass.engines.sass || this.sass; | ||
if (this.relativeAssets) { | ||
details.options.eyeglass.assets.relativeTo = | ||
httpJoin(details.options.eyeglass.httpRoot || "/", path.dirname(details.cssFilename)); | ||
} | ||
var eyeglass = new this.Eyeglass(details.options); | ||
details.options.eyeglass.buildDir = details.destDir; | ||
details.options.eyeglass.engines = details.options.eyeglass.engines || {}; | ||
details.options.eyeglass.engines.sass = details.options.eyeglass.engines.sass || this.sass; | ||
// set up asset dependency tracking | ||
var self = this; | ||
var realResolve = eyeglass.assets.resolve; | ||
eyeglass.assets.resolve = function(filepath, fullUri, cb) { | ||
self.events.emit("dependency", filepath); | ||
realResolve.call(eyeglass.assets, filepath, fullUri, cb); | ||
}; | ||
var realInstall = eyeglass.assets.install; | ||
eyeglass.assets.install = function(file, uri, cb) { | ||
realInstall.call(eyeglass.assets, file, uri, function(error, file) { | ||
if (!error) { | ||
self.events.emit("additional-output", file); | ||
let eyeglass = new this.Eyeglass(details.options); | ||
// set up asset dependency tracking | ||
let self = this; | ||
let realResolve = eyeglass.assets.resolve; | ||
eyeglass.assets.resolve = function(filepath, fullUri, cb) { | ||
self.events.emit("dependency", filepath); | ||
realResolve.call(eyeglass.assets, filepath, fullUri, cb); | ||
}; | ||
let realInstall = eyeglass.assets.install; | ||
eyeglass.assets.install = function(file, uri, cb) { | ||
realInstall.call(eyeglass.assets, file, uri, (error, file) => { | ||
if (!error) { | ||
self.events.emit("additional-output", file); | ||
} | ||
cb(error, file); | ||
}); | ||
}; | ||
if (this.assetDirectories) { | ||
for (var i = 0; i < this.assetDirectories.length; i++) { | ||
eyeglass.assets.addSource(path.resolve(eyeglass.options.eyeglass.root, | ||
this.assetDirectories[i]), | ||
{ | ||
globOpts: { | ||
ignore: ["**/*.js", "**/*.s[ac]ss"] | ||
} | ||
}); | ||
} | ||
cb(error, file); | ||
}); | ||
}; | ||
} | ||
if (this.assetDirectories) { | ||
for (var i = 0; i < this.assetDirectories.length; i++) { | ||
eyeglass.assets.addSource(path.resolve(eyeglass.options.eyeglass.root, | ||
this.assetDirectories[i]), | ||
{ | ||
globOpts: { | ||
ignore: ["**/*.js", "**/*.s[ac]ss"] | ||
} | ||
}); | ||
if (this.configureEyeglass) { | ||
this.configureEyeglass(eyeglass, this.sass, details); | ||
} | ||
details.options = eyeglass.options; | ||
details.options.eyeglass.engines.eyeglass = eyeglass; | ||
} | ||
if (this.configureEyeglass) { | ||
this.configureEyeglass(eyeglass, this.sass, details); | ||
cachableOptions(rawOptions) { | ||
rawOptions = merge({}, rawOptions); | ||
delete rawOptions.file; | ||
if (rawOptions.eyeglass) { | ||
delete rawOptions.eyeglass.engines; | ||
delete rawOptions.eyeglass.modules; | ||
} | ||
return rawOptions; | ||
} | ||
details.options = eyeglass.options; | ||
details.options.eyeglass.engines.eyeglass = eyeglass; | ||
}; | ||
EyeglassCompiler.prototype.cachableOptions = function(rawOptions) { | ||
rawOptions = merge({}, rawOptions); | ||
delete rawOptions.file; | ||
if (rawOptions.eyeglass) { | ||
delete rawOptions.eyeglass.engines; | ||
delete rawOptions.eyeglass.modules; | ||
static currentVersion() { | ||
let selfDir = path.resolve(path.join(__dirname, "..")); | ||
let selfPkg = require(path.join(selfDir, "package.json")); | ||
return selfPkg.version; | ||
} | ||
return rawOptions; | ||
}; | ||
EyeglassCompiler.currentVersion = function() { | ||
var selfDir = path.resolve(path.join(__dirname, "..")); | ||
var selfPkg = require(path.join(selfDir, "package.json")); | ||
return selfPkg.version; | ||
}; | ||
dependenciesHash(srcDir, relativeFilename, options) { | ||
return new RSVP.Promise(resolve => { | ||
if (!this._dependenciesHash) { | ||
let hashForDep = require("hash-for-dep"); | ||
let eyeglass = new this.Eyeglass(options); | ||
let hash = crypto.createHash("sha1"); | ||
let cachableOptions = stringify(this.cachableOptions(options)); | ||
EyeglassCompiler.prototype.dependenciesHash = function(srcDir, relativeFilename, options) { | ||
var self = this; | ||
return new RSVP.Promise(function(resolve, reject) { | ||
if (!self._dependenciesHash) { | ||
var hashForDep = require("hash-for-dep"); | ||
var eyeglass = new self.Eyeglass(options); | ||
var hash = crypto.createHash("sha1"); | ||
persistentCacheDebug("cachableOptions are %s", cachableOptions); | ||
hash.update(cachableOptions); | ||
hash.update("broccoli-eyeglass@" + EyeglassCompiler.currentVersion()); | ||
var cachableOptions = stringify(self.cachableOptions(options)); | ||
persistentCacheDebug("cachableOptions are %s", cachableOptions); | ||
hash.update(cachableOptions); | ||
hash.update("broccoli-eyeglass@" + EyeglassCompiler.currentVersion()); | ||
var egModules = sortby(eyeglass.modules.list, function(m) { | ||
return m.name; | ||
}); | ||
egModules.forEach(function(mod) { | ||
if (mod.inDevelopment || mod.eyeglass.inDevelopment) { | ||
hash.update(mod.name+"@"+hashForDep(mod.path)); | ||
} else { | ||
hash.update(mod.name+"@"+mod.version); | ||
} | ||
}); | ||
self._dependenciesHash = hash.digest("hex"); | ||
} | ||
resolve(self._dependenciesHash); | ||
}); | ||
}; | ||
let egModules = sortby(eyeglass.modules.list, m => m.name); | ||
EyeglassCompiler.prototype.keyForSourceFile = function(srcDir, relativeFilename, options) { | ||
var keyPromise = BroccoliSassCompiler.prototype.keyForSourceFile.call(this, | ||
srcDir, relativeFilename, options); | ||
var dependenciesPromise = this.dependenciesHash(srcDir, relativeFilename, options); | ||
return RSVP.all([keyPromise, dependenciesPromise]).then(function(results) { | ||
var mungedKey = results[0] + "+" + results[1]; | ||
return mungedKey; | ||
}); | ||
egModules.forEach(mod => { | ||
if (mod.inDevelopment || mod.eyeglass.inDevelopment) { | ||
hash.update(mod.name+"@"+hashForDep(mod.path)); | ||
} else { | ||
hash.update(mod.name+"@"+mod.version); | ||
} | ||
}); | ||
this._dependenciesHash = hash.digest("hex"); | ||
} | ||
resolve(this._dependenciesHash); | ||
}); | ||
} | ||
keyForSourceFile(srcDir, relativeFilename, options) { | ||
let keyPromise = super.keyForSourceFile(srcDir, relativeFilename, options); | ||
let dependenciesPromise = this.dependenciesHash(srcDir, relativeFilename, options); | ||
return RSVP.all([ | ||
keyPromise, | ||
dependenciesPromise | ||
]).then(results => { | ||
let mungedKey = results[0] + "+" + results[1]; | ||
return mungedKey; | ||
}); | ||
} | ||
}; | ||
module.exports = EyeglassCompiler; |
{ | ||
"name": "broccoli-eyeglass", | ||
"description": "Sass compiler for Broccoli with Eyeglass Integration", | ||
"version": "3.0.1", | ||
"version": "4.0.0", | ||
"author": "Chris Eppstein <chris@eppsteins.net>", | ||
@@ -13,3 +13,4 @@ "main": "lib/index.js", | ||
"scripts": { | ||
"test": "gulp test" | ||
"test": "mocha test/test_*.js", | ||
"test:debug": "mocha debug test/test_*.js" | ||
}, | ||
@@ -26,3 +27,3 @@ "keywords": [ | ||
"async-promise-queue": "^1.0.1", | ||
"broccoli-merge-trees": "^1.1.4", | ||
"broccoli-merge-trees": "^2.0.0", | ||
"broccoli-plugin": "^1.2.2", | ||
@@ -34,4 +35,4 @@ "chained-emitter": "^0.1.2", | ||
"fs-tree-diff": "^0.5.3", | ||
"glob": "^5.0.3", | ||
"hash-for-dep": "^1.0.3", | ||
"glob": "^7.1.2", | ||
"hash-for-dep": "^1.2.0", | ||
"json-stable-stringify": "^1.0.1", | ||
@@ -42,21 +43,19 @@ "lodash.merge": "^4.6.0", | ||
"node-sass": "^4.0.0 || ^3.10.1", | ||
"rsvp": "^3.0.21", | ||
"rsvp": "^4.0.1", | ||
"string.prototype.startswith": "^0.2.0", | ||
"walk-sync": "^0.3.1" | ||
}, | ||
"engines": { | ||
"node": "6.* || >= 7.*" | ||
}, | ||
"devDependencies": { | ||
"broccoli": "^0.16.5", | ||
"eslint": "^0.22.0", | ||
"eyeglass-dev-eslint": "*", | ||
"fixturify": "^0.3.0", | ||
"grunt": "^0.4.5", | ||
"grunt": "^1.0.1", | ||
"grunt-release": "^0.12.0", | ||
"gulp": "^3.9.0", | ||
"gulp-eslint": "1.1.1", | ||
"gulp-jshint": "^2.0.0", | ||
"gulp-mocha": "^2.2.0", | ||
"jshint": "^2.8.0", | ||
"mocha": "^3.0.2", | ||
"mocha": "^3.5.0", | ||
"mocha-eslint": "^4.1.0", | ||
"rimraf": "^2.5.4" | ||
} | ||
} |
@@ -1,5 +0,5 @@ | ||
var debug = false; | ||
var EyeglassCompiler = require(".."); | ||
const debug = false; | ||
const EyeglassCompiler = require(".."); | ||
var tree = new EyeglassCompiler(["sass"], { | ||
const tree = new EyeglassCompiler(["sass"], { | ||
cssDir: "css", | ||
@@ -13,5 +13,5 @@ verbose: true, | ||
if (debug) { | ||
var instrument = require("broccoli-debug").instrument; | ||
let instrument = require("broccoli-debug").instrument; | ||
tree = instrument.print(tree); | ||
} | ||
module.exports = tree; |
/* Copyright 2016 LinkedIn Corp. Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. You may obtain a copy of | ||
* you may not use this file except in compliance with the License. You may obtain a copy of | ||
* the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
@@ -12,12 +12,12 @@ * | ||
var assert = require("assert"); | ||
var path = require("path"); | ||
var fs = require("fs"); | ||
var rimraf = require("rimraf"); | ||
var fixturify = require("fixturify"); | ||
var broccoli = require("broccoli"); | ||
var RSVP = require("rsvp"); | ||
var glob = require("glob"); | ||
var EyeglassCompiler = require("../lib/index"); | ||
var AsyncDiskCache = require("async-disk-cache"); | ||
const assert = require("assert"); | ||
const path = require("path"); | ||
const fs = require("fs"); | ||
const rimraf = require("rimraf"); | ||
const fixturify = require("fixturify"); | ||
const broccoli = require("broccoli"); | ||
const RSVP = require("rsvp"); | ||
const glob = require("glob"); | ||
const EyeglassCompiler = require("../lib/index"); | ||
const AsyncDiskCache = require("async-disk-cache"); | ||
@@ -36,6 +36,7 @@ function fixtureDir(name) { | ||
var fixtureDirCount = 0; | ||
let fixtureDirCount = 0; | ||
function makeFixtures(name, files) { | ||
fixtureDirCount = fixtureDirCount + 1; | ||
var dirname = fixtureDir(name + fixtureDirCount + ".tmp"); | ||
let dirname = fixtureDir(name + fixtureDirCount + ".tmp"); | ||
fs.mkdirSync(dirname); | ||
@@ -49,20 +50,17 @@ fixturify.writeSync(dirname, files); | ||
return RSVP.Promise.resolve() | ||
.then(function() { | ||
return builder.build(); | ||
}) | ||
.then(function(hash) { | ||
return builder.tree.outputPath; | ||
}); | ||
.then(() => builder.build()) | ||
.then(() => builder.tree.outputPath); | ||
} | ||
function assertEqualDirs(actualDir, expectedDir) { | ||
var actualFiles = glob.sync("**/*", {cwd: actualDir}).sort(); | ||
var expectedFiles = glob.sync("**/*", {cwd: expectedDir}).sort(); | ||
let actualFiles = glob.sync("**/*", {cwd: actualDir}).sort(); | ||
let expectedFiles = glob.sync("**/*", {cwd: expectedDir}).sort(); | ||
assert.deepEqual(actualFiles, expectedFiles); | ||
actualFiles.forEach(function(file) { | ||
var actualPath = path.join(actualDir, file); | ||
var expectedPath = path.join(expectedDir, file); | ||
var stats = fs.statSync(actualPath); | ||
actualFiles.forEach(file => { | ||
let actualPath = path.join(actualDir, file); | ||
let expectedPath = path.join(expectedDir, file); | ||
let stats = fs.statSync(actualPath); | ||
if (stats.isFile()) { | ||
@@ -82,3 +80,3 @@ assert.equal(fs.readFileSync(actualPath).toString(), | ||
var rectangleSVG = svg( | ||
const rectangleSVG = svg( | ||
' <rect x="10" y="10" height="100" width="100"\n' + | ||
@@ -88,3 +86,3 @@ ' style="stroke:#ff0000; fill: #0000ff"/>\n' | ||
var circleSVG = svg( | ||
const circleSVG = svg( | ||
'<circle cx="40" cy="40" r="24" style="stroke:#006600; fill:#00cc00"/>' | ||
@@ -95,3 +93,3 @@ ); | ||
it("can be instantiated", function () { | ||
var optimizer = new EyeglassCompiler(fixtureSourceDir("basicProject"), { | ||
let optimizer = new EyeglassCompiler(fixtureSourceDir("basicProject"), { | ||
cssDir: "." | ||
@@ -103,10 +101,10 @@ }); | ||
it("compiles sass files", function () { | ||
var optimizer = new EyeglassCompiler(fixtureSourceDir("basicProject"), { | ||
let optimizer = new EyeglassCompiler(fixtureSourceDir("basicProject"), { | ||
cssDir: "." | ||
}); | ||
var builder = new broccoli.Builder(optimizer); | ||
let builder = new broccoli.Builder(optimizer); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, fixtureOutputDir("basicProject")); | ||
@@ -117,3 +115,3 @@ }); | ||
it("passes unknown options to eyeglass", function() { | ||
var optimizer = new EyeglassCompiler(fixtureSourceDir("basicProject"), { | ||
let optimizer = new EyeglassCompiler(fixtureSourceDir("basicProject"), { | ||
cssDir: ".", | ||
@@ -129,3 +127,3 @@ foo: true | ||
assert.throws( | ||
function() { | ||
() => { | ||
new EyeglassCompiler(fixtureSourceDir("basicProject"), { | ||
@@ -142,3 +140,3 @@ cssDir: ".", | ||
assert.throws( | ||
function() { | ||
() => { | ||
new EyeglassCompiler(fixtureSourceDir("basicProject"), { | ||
@@ -155,3 +153,3 @@ cssDir: ".", | ||
assert.throws( | ||
function() { | ||
() => { | ||
new EyeglassCompiler(fixtureSourceDir("basicProject"), { | ||
@@ -167,3 +165,3 @@ cssDir: ".", | ||
it("outputs exceptions when the fullException option is set", function() { | ||
var optimizer = new EyeglassCompiler(fixtureSourceDir("errorProject"), { | ||
let optimizer = new EyeglassCompiler(fixtureSourceDir("errorProject"), { | ||
cssDir: ".", | ||
@@ -173,8 +171,8 @@ fullException: true | ||
var builder = new broccoli.Builder(optimizer); | ||
let builder = new broccoli.Builder(optimizer); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, fixtureOutputDir("basicProject")); | ||
}, function(error) { | ||
}, error => { | ||
assert.equal("property \"asdf\" must be followed by a ':'", error.message.split("\n")[0]); | ||
@@ -185,3 +183,3 @@ }); | ||
it("supports manual modules", function() { | ||
var optimizer = new EyeglassCompiler(fixtureSourceDir("usesManualModule"), { | ||
let optimizer = new EyeglassCompiler(fixtureSourceDir("usesManualModule"), { | ||
cssDir: ".", | ||
@@ -196,6 +194,6 @@ fullException: true, | ||
var builder = new broccoli.Builder(optimizer); | ||
let builder = new broccoli.Builder(optimizer); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, fixtureOutputDir("usesManualModule")); | ||
@@ -206,6 +204,5 @@ }); | ||
function cleanupTempDirs() { | ||
var tmpDirs = glob.sync(path.join(path.resolve(__dirname, "fixtures"),"**", "*.tmp")); | ||
tmpDirs.forEach(function(tmpDir) { | ||
rimraf.sync(tmpDir); | ||
}); | ||
let tmpDirs = glob.sync(path.join(path.resolve(__dirname, "fixtures"),"**", "*.tmp")); | ||
tmpDirs.forEach(tmpDir => rimraf.sync(tmpDir)); | ||
} | ||
@@ -217,8 +214,8 @@ | ||
it("caches when an unrelated file changes", function() { | ||
var sourceDir = fixtureSourceDir("basicProject"); | ||
var unusedSourceFile = path.join(sourceDir, "styles", "_unused.scss"); | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(sourceDir, { | ||
let sourceDir = fixtureSourceDir("basicProject"); | ||
let unusedSourceFile = path.join(sourceDir, "styles", "_unused.scss"); | ||
let compiledFiles = []; | ||
let compiler = new EyeglassCompiler(sourceDir, { | ||
cssDir: ".", | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
optionsGenerator(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
@@ -231,6 +228,6 @@ cb(cssFile, options); | ||
var builder = new broccoli.Builder(compiler); | ||
let builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, fixtureOutputDir("basicProject")); | ||
@@ -240,3 +237,3 @@ assert.equal(1, compiledFiles.length); | ||
fs.writeFileSync(unusedSourceFile, "// changed but still not used."); | ||
return build(builder).then(function(outputDir2) { | ||
return build(builder).then(outputDir2 => { | ||
assert.equal(outputDir, outputDir2); | ||
@@ -249,7 +246,7 @@ assert.equal(1, compiledFiles.length); | ||
it("doesn't cache when there's a change", function() { | ||
var sourceDir = fixtureSourceDir("basicProject"); | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(sourceDir, { | ||
let sourceDir = fixtureSourceDir("basicProject"); | ||
let compiledFiles = []; | ||
let compiler = new EyeglassCompiler(sourceDir, { | ||
cssDir: ".", | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
optionsGenerator(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
@@ -260,12 +257,12 @@ cb(cssFile, options); | ||
var builder = new broccoli.Builder(compiler); | ||
let builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, fixtureOutputDir("basicProject")); | ||
assert.equal(1, compiledFiles.length); | ||
var sourceFile = path.join(sourceDir, "styles", "foo.scss"); | ||
var originalSource = fs.readFileSync(sourceFile); | ||
var newSource = "@import \"used\";\n" + | ||
let sourceFile = path.join(sourceDir, "styles", "foo.scss"); | ||
let originalSource = fs.readFileSync(sourceFile); | ||
let newSource = "@import \"used\";\n" + | ||
"$color: blue;\n" + | ||
@@ -276,14 +273,15 @@ ".foo {\n" + | ||
var newExpectedOutput = ".foo {\n" + | ||
let newExpectedOutput = ".foo {\n" + | ||
" color: blue; }\n"; | ||
fs.writeFileSync(sourceFile, newSource); | ||
return build(builder) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.equal(outputDir, outputDir2); | ||
var outputFile = path.join(outputDir2, "styles", "foo.css"); | ||
let outputFile = path.join(outputDir2, "styles", "foo.css"); | ||
assert.equal(newExpectedOutput, fs.readFileSync(outputFile)); | ||
assert.equal(2, compiledFiles.length); | ||
}) | ||
.finally(function() { | ||
.finally(() => { | ||
fs.writeFileSync(sourceFile, originalSource); | ||
@@ -295,18 +293,18 @@ }); | ||
it("caches on the 3rd build", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": '@import "external";', | ||
"_unrelated.scss": "/* This is unrelated to anything. */" | ||
}); | ||
var includeDir = makeFixtures("includeDir", { | ||
let includeDir = makeFixtures("includeDir", { | ||
"external.scss": ".external { float: left; }" | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": ".external {\n float: left; }\n" | ||
}); | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(projectDir, { | ||
let compiledFiles = []; | ||
let compiler = new EyeglassCompiler(projectDir, { | ||
cssDir: ".", | ||
includePaths: [includeDir], | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
optionsGenerator(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
@@ -317,6 +315,6 @@ cb(cssFile, options); | ||
var builder = new broccoli.Builder(compiler); | ||
let builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -331,3 +329,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builder) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.equal(outputDir, outputDir2); | ||
@@ -342,3 +340,3 @@ assert.equal(compiledFiles.length, 0); | ||
return build(builder) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.equal(outputDir, outputDir2); | ||
@@ -353,17 +351,17 @@ assert.equal(compiledFiles.length, 0); | ||
it("busts cache when file reached via includePaths changes", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": '@import "external";' | ||
}); | ||
var includeDir = makeFixtures("includeDir", { | ||
let includeDir = makeFixtures("includeDir", { | ||
"external.scss": ".external { float: left; }" | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": ".external {\n float: left; }\n" | ||
}); | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(projectDir, { | ||
let compiledFiles = []; | ||
let compiler = new EyeglassCompiler(projectDir, { | ||
cssDir: ".", | ||
includePaths: [includeDir], | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
optionsGenerator(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
@@ -374,6 +372,6 @@ cb(cssFile, options); | ||
var builder = new broccoli.Builder(compiler); | ||
let builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -392,3 +390,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builder) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.equal(outputDir, outputDir2); | ||
@@ -402,17 +400,17 @@ assert.equal(compiledFiles.length, 1); | ||
it("busts cache when file mode changes", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": '@import "external";' | ||
}); | ||
var includeDir = makeFixtures("includeDir", { | ||
let includeDir = makeFixtures("includeDir", { | ||
"external.scss": ".external { float: left; }" | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": ".external {\n float: left; }\n" | ||
}); | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(projectDir, { | ||
let compiledFiles = []; | ||
let compiler = new EyeglassCompiler(projectDir, { | ||
cssDir: ".", | ||
includePaths: [includeDir], | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
optionsGenerator(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
@@ -423,6 +421,6 @@ cb(cssFile, options); | ||
var builder = new broccoli.Builder(compiler); | ||
let builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -435,3 +433,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builder) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.equal(outputDir, outputDir2); | ||
@@ -445,6 +443,6 @@ assert.equal(compiledFiles.length, 1); | ||
it("busts cache when an eyeglass module is upgraded", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": '@import "eyeglass-module";' | ||
}); | ||
var eyeglassModDir = makeFixtures("eyeglassmod", { | ||
let eyeglassModDir = makeFixtures("eyeglassmod", { | ||
"package.json": "{\n" + | ||
@@ -465,10 +463,10 @@ ' "name": "is_a_module",\n' + | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": ".eyeglass-mod {\n content: eyeglass; }\n" | ||
}); | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(projectDir, { | ||
let compiledFiles = []; | ||
let compiler = new EyeglassCompiler(projectDir, { | ||
cssDir: ".", | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
optionsGenerator(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
@@ -484,6 +482,6 @@ cb(cssFile, options); | ||
var builder = new broccoli.Builder(compiler); | ||
let builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -504,3 +502,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builder) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.equal(outputDir, outputDir2); | ||
@@ -514,3 +512,3 @@ assert.equal(compiledFiles.length, 1); | ||
it("busts cache when an eyeglass asset changes", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": | ||
@@ -520,3 +518,3 @@ '@import "eyeglass-module/assets";\n' + | ||
}); | ||
var eyeglassModDir = makeFixtures("eyeglassmod2", { | ||
let eyeglassModDir = makeFixtures("eyeglassmod2", { | ||
"package.json": "{\n" + | ||
@@ -548,3 +546,3 @@ ' "name": "is_a_module",\n' + | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"eyeglass-module": { | ||
@@ -556,6 +554,6 @@ "shape.svg": rectangleSVG | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(projectDir, { | ||
let compiledFiles = []; | ||
let compiler = new EyeglassCompiler(projectDir, { | ||
cssDir: ".", | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
optionsGenerator(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
@@ -571,6 +569,6 @@ cb(cssFile, options); | ||
var builder = new broccoli.Builder(compiler); | ||
let builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -595,3 +593,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builder) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.equal(outputDir, outputDir2); | ||
@@ -605,20 +603,20 @@ assert.equal(compiledFiles.length, 1); | ||
it("busts cache when file reached via ../ outside the load path changes", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": '@import "external";' | ||
}); | ||
var relativeIncludeDir = makeFixtures("relativeIncludeDir", { | ||
let relativeIncludeDir = makeFixtures("relativeIncludeDir", { | ||
"relative.scss": ".external { float: left; }" | ||
}); | ||
var includeDir = makeFixtures("includeDir", { | ||
let includeDir = makeFixtures("includeDir", { | ||
"external.scss": '@import "../relativeIncludeDir' + fixtureDirCount + '.tmp/relative";' | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": ".external {\n float: left; }\n" | ||
}); | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(projectDir, { | ||
let compiledFiles = []; | ||
let compiler = new EyeglassCompiler(projectDir, { | ||
cssDir: ".", | ||
includePaths: [includeDir], | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
optionsGenerator(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
@@ -629,6 +627,6 @@ cb(cssFile, options); | ||
var builder = new broccoli.Builder(compiler); | ||
let builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -647,3 +645,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builder) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.equal(outputDir, outputDir2); | ||
@@ -657,13 +655,13 @@ assert.equal(compiledFiles.length, 1); | ||
it("removes a css file when the corresponding sass file is removed", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": "/* project */" | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": "/* project */\n" | ||
}); | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(projectDir, { | ||
let compiledFiles = []; | ||
let compiler = new EyeglassCompiler(projectDir, { | ||
cssDir: ".", | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
optionsGenerator(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
@@ -674,6 +672,6 @@ cb(cssFile, options); | ||
var builder = new broccoli.Builder(compiler); | ||
let builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -692,3 +690,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builder) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.equal(outputDir, outputDir2); | ||
@@ -702,3 +700,3 @@ assert.equal(compiledFiles.length, 0); | ||
it("removes an asset file when the corresponding sass file is removed", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": | ||
@@ -708,3 +706,3 @@ '@import "eyeglass-module/assets";\n' + | ||
}); | ||
var eyeglassModDir = makeFixtures("eyeglassmod", { | ||
let eyeglassModDir = makeFixtures("eyeglassmod", { | ||
"package.json": "{\n" + | ||
@@ -736,3 +734,3 @@ ' "name": "is_a_module",\n' + | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"eyeglass-module": { | ||
@@ -744,6 +742,6 @@ "shape.svg": rectangleSVG | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(projectDir, { | ||
let compiledFiles = []; | ||
let compiler = new EyeglassCompiler(projectDir, { | ||
cssDir: ".", | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
optionsGenerator(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
@@ -759,6 +757,6 @@ cb(cssFile, options); | ||
var builder = new broccoli.Builder(compiler); | ||
let builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -780,3 +778,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builder) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.equal(outputDir, outputDir2); | ||
@@ -795,3 +793,3 @@ assert.equal(compiledFiles.length, 0); | ||
afterEach(function() { | ||
var cache = new AsyncDiskCache("test"); | ||
let cache = new AsyncDiskCache("test"); | ||
return cache.clear(); | ||
@@ -801,7 +799,8 @@ }); | ||
function warmBuilders(count, dir, options, compilationListener) { | ||
var builders = []; | ||
let builders = []; | ||
for (var i = 0; i < count; i++) { | ||
var compiler = new EyeglassCompiler(dir, options); | ||
let compiler = new EyeglassCompiler(dir, options); | ||
compiler.events.on("compiled", compilationListener); | ||
var builder = new broccoli.Builder(compiler); | ||
let builder = new broccoli.Builder(compiler); | ||
builder.compiler = compiler; | ||
@@ -816,15 +815,15 @@ builders.push(builder); | ||
it("preserves cache across builder instances", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": '@import "related";', | ||
"_related.scss": "/* This is related to something. */" | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": "/* This is related to something. */\n" | ||
}); | ||
var compiledFiles = []; | ||
var builders = warmBuilders(2, projectDir, { | ||
let compiledFiles = []; | ||
let builders = warmBuilders(2, projectDir, { | ||
cssDir: ".", | ||
persistentCache: "test" | ||
}, function(details) { | ||
}, details => { | ||
compiledFiles.push(details.fullSassFilename); | ||
@@ -834,3 +833,3 @@ }); | ||
return build(builders[0]) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -841,3 +840,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builders[1]) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.notEqual(outputDir, outputDir2); | ||
@@ -851,15 +850,15 @@ assert.equal(compiledFiles.length, 0); | ||
it("invalidates when a dependent file changes.", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": '@import "related";', | ||
"_related.scss": "/* This is related to something. */" | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": "/* This is related to something. */\n" | ||
}); | ||
var compiledFiles = []; | ||
var builders = warmBuilders(2, projectDir, { | ||
let compiledFiles = []; | ||
let builders = warmBuilders(2, projectDir, { | ||
cssDir: ".", | ||
persistentCache: "test" | ||
}, function(details) { | ||
}, details => { | ||
compiledFiles.push(details.fullSassFilename); | ||
@@ -869,3 +868,3 @@ }); | ||
return build(builders[0]) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -884,3 +883,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builders[1]) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.notEqual(outputDir, outputDir2); | ||
@@ -894,3 +893,3 @@ assert.equal(compiledFiles.length, 1); | ||
it("restored side-effect outputs when cached.", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"sass": { | ||
@@ -904,3 +903,3 @@ "project.scss": '@import "assets";\n' + | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": '.shape {\n content: url("/shape.svg"); }\n', | ||
@@ -910,4 +909,4 @@ "shape.svg": rectangleSVG | ||
var compiledFiles = []; | ||
var builders = warmBuilders(2, projectDir, { | ||
let compiledFiles = []; | ||
let builders = warmBuilders(2, projectDir, { | ||
sassDir: "sass", | ||
@@ -920,3 +919,3 @@ assets: "assets", | ||
} | ||
}, function(details) { | ||
}, details => { | ||
compiledFiles.push(details.fullSassFilename); | ||
@@ -926,3 +925,3 @@ }); | ||
return build(builders[0]) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -933,3 +932,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builders[1]) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.notEqual(outputDir, outputDir2); | ||
@@ -943,3 +942,3 @@ assert.equal(compiledFiles.length, 0); | ||
it("invalidates when non-sass file dependencies change.", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"sass": { | ||
@@ -954,3 +953,3 @@ "project.scss": '@import "assets";\n' + | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": '.shape {\n content: url("/shape.svg"); }\n', | ||
@@ -960,4 +959,4 @@ "shape.svg": rectangleSVG | ||
var compiledFiles = []; | ||
var builders = warmBuilders(2, projectDir, { | ||
let compiledFiles = []; | ||
let builders = warmBuilders(2, projectDir, { | ||
sassDir: "sass", | ||
@@ -970,3 +969,3 @@ assets: "assets", | ||
} | ||
}, function(details) { | ||
}, details => { | ||
compiledFiles.push(details.fullSassFilename); | ||
@@ -976,3 +975,3 @@ }); | ||
return build(builders[0]) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -993,3 +992,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builders[1]) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.notEqual(outputDir, outputDir2); | ||
@@ -1003,7 +1002,7 @@ assert.equal(compiledFiles.length, 1); | ||
it("invalidates when eyeglass modules javascript files changes.", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": | ||
".foo { content: foo(); }\n" | ||
}); | ||
var eyeglassModDir = makeFixtures("eyeglassmod", { | ||
let eyeglassModDir = makeFixtures("eyeglassmod", { | ||
"package.json": "{\n" + | ||
@@ -1044,9 +1043,9 @@ ' "name": "is_a_module",\n' + | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": ".foo {\n content: foo; }\n" | ||
}); | ||
var compiledFiles = []; | ||
let compiledFiles = []; | ||
var builders = warmBuilders(2, projectDir, { | ||
let builders = warmBuilders(2, projectDir, { | ||
cssDir: ".", | ||
@@ -1059,3 +1058,3 @@ persistentCache: "test", | ||
} | ||
}, function(details) { | ||
}, details => { | ||
compiledFiles.push(details.fullSassFilename); | ||
@@ -1066,3 +1065,3 @@ }); | ||
return build(builders[0]) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -1087,3 +1086,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builders[1]) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.notEqual(outputDir, outputDir2); | ||
@@ -1097,7 +1096,7 @@ assert.equal(compiledFiles.length, 1); | ||
it("invalidates when eyeglass modules javascript files changes (package.json).", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": | ||
".foo { content: foo(); }\n" | ||
}); | ||
var eyeglassModDir = makeFixtures("eyeglassmod3", { | ||
let eyeglassModDir = makeFixtures("eyeglassmod3", { | ||
"package.json": "{\n" + | ||
@@ -1138,9 +1137,9 @@ ' "name": "is_a_module",\n' + | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": ".foo {\n content: foo; }\n" | ||
}); | ||
var compiledFiles = []; | ||
let compiledFiles = []; | ||
var builders = warmBuilders(2, projectDir, { | ||
let builders = warmBuilders(2, projectDir, { | ||
cssDir: ".", | ||
@@ -1153,3 +1152,3 @@ persistentCache: "test", | ||
} | ||
}, function(details) { | ||
}, details => { | ||
compiledFiles.push(details.fullSassFilename); | ||
@@ -1160,3 +1159,3 @@ }); | ||
return build(builders[0]) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -1181,3 +1180,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builders[1]) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.notEqual(outputDir, outputDir2); | ||
@@ -1191,15 +1190,15 @@ assert.equal(compiledFiles.length, 1); | ||
it("can force invalidate the persistent cache", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": '@import "related";', | ||
"_related.scss": "/* This is related to something. */" | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": "/* This is related to something. */\n" | ||
}); | ||
var compiledFiles = []; | ||
var builders = warmBuilders(2, projectDir, { | ||
let compiledFiles = []; | ||
let builders = warmBuilders(2, projectDir, { | ||
cssDir: ".", | ||
persistentCache: "test" | ||
}, function(details) { | ||
}, details => { | ||
compiledFiles.push(details.fullSassFilename); | ||
@@ -1209,3 +1208,3 @@ }); | ||
return build(builders[0]) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -1218,3 +1217,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builders[1]) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.notEqual(outputDir, outputDir2); | ||
@@ -1224,3 +1223,3 @@ assert.equal(1, compiledFiles.length); | ||
}) | ||
.finally(function() { | ||
.finally(() => { | ||
delete process.env["BROCCOLI_EYEGLASS"]; | ||
@@ -1232,15 +1231,15 @@ }); | ||
it("busts cache when options used for compilation are different", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": '@import "related";', | ||
"_related.scss": "/* This is related to something. */" | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": "/* This is related to something. */\n" | ||
}); | ||
var compiledFiles = []; | ||
var builders = warmBuilders(2, projectDir, { | ||
let compiledFiles = []; | ||
let builders = warmBuilders(2, projectDir, { | ||
cssDir: ".", | ||
persistentCache: "test" | ||
}, function(details) { | ||
}, details => { | ||
compiledFiles.push(details.fullSassFilename); | ||
@@ -1250,3 +1249,3 @@ }); | ||
return build(builders[0]) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -1259,3 +1258,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builders[1]) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.notEqual(outputDir, outputDir2); | ||
@@ -1269,20 +1268,20 @@ assert.equal(compiledFiles.length, 1); | ||
it("can use the rebuild cache after restoring from the persistent cache.", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": '@import "related";', | ||
"_related.scss": "/* This is related to something. */" | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": "/* This is related to something. */\n" | ||
}); | ||
var compiledFiles = []; | ||
var hotCompiledFiles = []; | ||
var builders = warmBuilders(2, projectDir, { | ||
let compiledFiles = []; | ||
let hotCompiledFiles = []; | ||
let builders = warmBuilders(2, projectDir, { | ||
cssDir: ".", | ||
persistentCache: "test", | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
optionsGenerator(sassFile, cssFile, options, cb) { | ||
hotCompiledFiles.push(sassFile); | ||
cb(cssFile, options); | ||
} | ||
}, function(details) { | ||
}, details => { | ||
compiledFiles.push(details.fullSassFilename); | ||
@@ -1292,3 +1291,3 @@ }); | ||
return build(builders[0]) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -1301,3 +1300,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builders[1]) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.notEqual(outputDir, outputDir2); | ||
@@ -1312,3 +1311,3 @@ assert.equal(compiledFiles.length, 0); | ||
return build(builders[1]) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.notEqual(outputDir, outputDir2); | ||
@@ -1324,20 +1323,20 @@ assert.equal(compiledFiles.length, 0); | ||
it("busts the rebuild cache after restoring from the persistent cache.", function() { | ||
var projectDir = makeFixtures("projectDir", { | ||
let projectDir = makeFixtures("projectDir", { | ||
"project.scss": '@import "related";', | ||
"_related.scss": "/* This is related to something. */" | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
let expectedOutputDir = makeFixtures("expectedOutputDir", { | ||
"project.css": "/* This is related to something. */\n" | ||
}); | ||
var compiledFiles = []; | ||
var hotCompiledFiles = []; | ||
var builders = warmBuilders(2, projectDir, { | ||
let compiledFiles = []; | ||
let hotCompiledFiles = []; | ||
let builders = warmBuilders(2, projectDir, { | ||
cssDir: ".", | ||
persistentCache: "test", | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
optionsGenerator(sassFile, cssFile, options, cb) { | ||
hotCompiledFiles.push(sassFile); | ||
cb(cssFile, options); | ||
} | ||
}, function(details) { | ||
}, details => { | ||
compiledFiles.push(details.fullSassFilename); | ||
@@ -1347,3 +1346,3 @@ }); | ||
return build(builders[0]) | ||
.then(function(outputDir) { | ||
.then(outputDir => { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
@@ -1356,3 +1355,3 @@ assert.equal(1, compiledFiles.length); | ||
return build(builders[1]) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.notEqual(outputDir, outputDir2); | ||
@@ -1371,7 +1370,8 @@ assert.equal(compiledFiles.length, 0); | ||
fixturify.writeSync(expectedOutputDir, { | ||
"project.css": "/* This is related to something. */\n.something {\n color: red; }\n" | ||
"project.css": "/* This is related to something. */\n" + | ||
".something {\n color: red; }\n" | ||
}); | ||
return build(builders[1]) | ||
.then(function(outputDir2) { | ||
.then(outputDir2 => { | ||
assert.notEqual(outputDir, outputDir2); | ||
@@ -1378,0 +1378,0 @@ assert.equal(compiledFiles.length, 1); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
219493
8
0
2174
+ Addedbroccoli-merge-trees@2.0.1(transitive)
+ Addedmerge-trees@1.0.1(transitive)
+ Addedrsvp@4.8.5(transitive)
- Removedblank-object@1.0.2(transitive)
- Removedbroccoli-merge-trees@1.2.4(transitive)
- Removedfast-ordered-set@1.0.3(transitive)
Updatedbroccoli-merge-trees@^2.0.0
Updatedglob@^7.1.2
Updatedhash-for-dep@^1.2.0
Updatedrsvp@^4.0.1