diff --git a/lib/assets/Assets.js b/lib/assets/Assets.js index 80d97aca..9ceea35b 100644 --- a/lib/assets/Assets.js +++ b/lib/assets/Assets.js @@ -14,6 +14,8 @@ function Assets(eyeglass, sass) { this.eyeglass = eyeglass; // create a master collection this.collection = new AssetsCollection(); + // and keep a list of module collections + this.moduleCollections = []; // Expose these temporarily for back-compat reasons function deprecate(method) { @@ -57,6 +59,7 @@ Assets.prototype.addSource = function(src, opts) { */ Assets.prototype.export = function(src, opts) { var assets = new AssetsCollection(); + this.moduleCollections.push(assets); return assets.addSource(src, opts); }; diff --git a/lib/assets/AssetsCollection.js b/lib/assets/AssetsCollection.js index fbbf6fed..dd9eeec1 100644 --- a/lib/assets/AssetsCollection.js +++ b/lib/assets/AssetsCollection.js @@ -52,4 +52,14 @@ AssetsCollection.prototype.asAssetImport = function (name) { }, '@import "eyeglass/assets";\n'); }; +/** + * Build a string suitable for caching an instance of this + * @returns {String} the cache key + */ +AssetsCollection.prototype.cacheKey = function(name) { + return this.sources.map(function(source) { + return source.cacheKey(name); + }).sort().join(":"); +}; + module.exports = AssetsCollection; diff --git a/lib/assets/AssetsSource.js b/lib/assets/AssetsSource.js index 041d4d14..49d36348 100644 --- a/lib/assets/AssetsSource.js +++ b/lib/assets/AssetsSource.js @@ -6,6 +6,7 @@ var path = require("path"); var merge = require("lodash.merge"); var URI = require("../util/URI"); var fileUtils = require("../util/files"); +var stringify = require("json-stable-stringify"); /* class AssetsSource * @@ -82,4 +83,34 @@ AssetsSource.prototype.toString = function() { return this.srcPath + "/" + this.pattern; }; +// don't include these globOpts in the cacheKey +function skipSomeKeys(key, value) { + // these are set to this.srcPath, which is already included in the cacheKey + if (key === "cwd" || key === "root") { + return undefined; + } + // these are added inside glob and always set to true, which happens after this string + // is created when glob.sync() is run in #getAssets() + // (see #setopts() in glob/common.js) + if (key === "nonegate" || key === "nocomment") { + return undefined; + } + return value; +} + +/** + * Build a string suitable for caching an instance of this + * @returns {String} the cache key + */ +AssetsSource.prototype.cacheKey = function(namespace) { + return "[" + + "httpPrefix=" + (this.httpPrefix ? this.httpPrefix : "") + + ";name=" + (this.name ? this.name : (namespace ? namespace : "")) + + ";srcPath=" + this.srcPath + + ";pattern=" + this.pattern + + // json-stable-stringify orders keys when stringifying (JSON.stringify does not) + ";opts=" + stringify(this.globOpts, {replacer: skipSomeKeys}) + + "]"; +}; + module.exports = AssetsSource; diff --git a/package.json b/package.json index 2b5df254..ced1f041 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "ensure-symlink": "^1.0.0", "fs-extra": "^0.30.0", "glob": "^7.1.0", + "json-stable-stringify": "^1.0.1", "lodash.includes": "^4.3.0", "lodash.merge": "^4.6.0", "node-sass": "^4.0.0 || ^3.10.1", diff --git a/test/test_assets.js b/test/test_assets.js index 04d2d4e7..16b6bd8c 100644 --- a/test/test_assets.js +++ b/test/test_assets.js @@ -9,6 +9,7 @@ var glob = require("glob"); var Eyeglass = require("../lib"); var AssetsSource = require("../lib/assets/AssetsSource"); +var AssetsCollection = require("../lib/assets/AssetsCollection"); function escapeBackslash(str) { return str.replace(/\\/g, "\\\\"); @@ -874,6 +875,26 @@ describe("assets", function () { testutils.assertCompiles(eg, expected, done); }); + it("should keep track of module collections", function() { + var rootDir = testutils.fixtureDirectory("app_assets"); + + var egMod = new Eyeglass({ + // file containing '@import "mod-one/assets"' + file: path.join(rootDir, "sass", "uses_mod_1.scss"), + eyeglass: { + root: rootDir, + installWithSymlinks: installWithSymlinks, + engines: { + sass: sass + } + } + }); + + assert.ok(egMod.assets.moduleCollections !== undefined); + assert.ok(Array.isArray(egMod.assets.moduleCollections)); + assert.equal(egMod.assets.moduleCollections.length, 1); + }); + describe("path separator normalization", function() { var originalEnv = process.env.EYEGLASS_NORMALIZE_PATHS; var merge = require("lodash.merge"); @@ -983,6 +1004,143 @@ describe("assets", function () { }); }); }); + + describe("cache keys", function() { + var rootDir = testutils.fixtureDirectory("app_assets"); + var rootDir2 = testutils.fixtureDirectory("app_assets_odd_names"); + + it("Assets.cacheKey includes httpPrefix", function() { + var source1 = new AssetsSource(rootDir, { + httpPrefix: "foo", + }); + var source2 = new AssetsSource(rootDir, { + httpPrefix: "foo", + }); + var source3 = new AssetsSource(rootDir, { + httpPrefix: "bar", + }); + assert.equal(source1.cacheKey(), source2.cacheKey()); + assert.notEqual(source1.cacheKey(), source3.cacheKey()); + }); + + it("Assets.cacheKey includes name", function() { + var source1 = new AssetsSource(rootDir, { + name: "foo", + }); + var source2 = new AssetsSource(rootDir, { + name: "foo", + }); + var source3 = new AssetsSource(rootDir, { + name: "bar", + }); + assert.equal(source1.cacheKey(), source2.cacheKey()); + assert.notEqual(source1.cacheKey(), source3.cacheKey()); + }); + + it("Assets.cacheKey includes namespace", function() { + var source1 = new AssetsSource(rootDir, {}); + var source2 = new AssetsSource(rootDir, {}); + assert.equal(source1.cacheKey("foo"), source2.cacheKey("foo")); + assert.notEqual(source1.cacheKey("foo"), source2.cacheKey("bar")); + }); + + it("Assets.cacheKey includes srcPath", function() { + var source1 = new AssetsSource(rootDir, {}); + var source2 = new AssetsSource(rootDir, {}); + var source3 = new AssetsSource(rootDir2, {}); + assert.equal(source1.cacheKey(), source2.cacheKey()); + assert.notEqual(source1.cacheKey(), source3.cacheKey()); + }); + + it("Assets.cacheKey includes pattern", function() { + var source1 = new AssetsSource(rootDir, { + pattern: "images/**/*", + }); + var source2 = new AssetsSource(rootDir, { + pattern: "images/**/*", + }); + var source3 = new AssetsSource(rootDir, { + pattern: "images/**/*.jpg", + }); + assert.equal(source1.cacheKey(), source2.cacheKey()); + assert.notEqual(source1.cacheKey(), source3.cacheKey()); + }); + + it("Assets.cacheKey includes globOpts", function() { + var source1 = new AssetsSource(rootDir, {}); + var source2 = new AssetsSource(rootDir, {}); + var source3 = new AssetsSource(rootDir, { + globOpts: { + dot: true + } + }); + assert.equal(source1.cacheKey(), source2.cacheKey()); + assert.notEqual(source1.cacheKey(), source3.cacheKey()); + }); + + it("Assets.cacheKey ignores globOpts that are overridden", function() { + var source1 = new AssetsSource(rootDir, {}); + var source2 = new AssetsSource(rootDir, { + globOpts: { + nonegate: false + } + }); + var source3 = new AssetsSource(rootDir, { + globOpts: { + nonegate: true + } + }); + assert.equal(source1.cacheKey(), source2.cacheKey()); + assert.equal(source1.cacheKey(), source3.cacheKey()); + }); + + it("Assets.cacheKey globOpts order doesn't matter", function() { + var source1 = new AssetsSource(rootDir, { + globOpts: { + dot: true, + nonull: true + } + }); + var source2 = new AssetsSource(rootDir, { + globOpts: { + nonull: true, + dot: true + } + }); + assert.equal(source1.cacheKey(), source2.cacheKey()); + }); + + it("AssetsCollection.cacheKey includes collection sources", function() { + var collection1 = new AssetsCollection(); + var collection2 = new AssetsCollection(); + var collection3 = new AssetsCollection(); + var collection4 = new AssetsCollection(); + + collection1.addSource(rootDir); + collection2.addSource(rootDir); + collection3.addSource(rootDir2); + collection4.addSource(rootDir); + collection4.addSource(rootDir2); + + assert.equal(collection1.cacheKey(), collection2.cacheKey()); + assert.notEqual(collection1.cacheKey(), collection3.cacheKey()); + assert.notEqual(collection1.cacheKey(), collection4.cacheKey()); + }); + + it("AssetsCollection.cacheKey source order doesn't matter", function() { + var collection1 = new AssetsCollection(); + var collection2 = new AssetsCollection(); + + collection1.addSource(rootDir); + collection1.addSource(rootDir2); + + collection2.addSource(rootDir2); + collection2.addSource(rootDir); + + assert.equal(collection1.cacheKey(), collection2.cacheKey()); + }); + + }); }); }); });