Skip to content

Commit

Permalink
compile when SASS dependencies change
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonsanjose committed May 30, 2014
1 parent ed3ae9e commit 12cb35d
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 44 deletions.
24 changes: 6 additions & 18 deletions Compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ define(function (require, exports, module) {
var _domainPath = ExtensionUtils.getModulePath(module, "node/SASSDomain"),
_nodeDomain = new NodeDomain("sass", _domainPath);

var RE_FILE = /^[^_].*\.scss$/, /* Add .sass later...*/
RE_FILE_EXT = /\.(sass|scss)$/,
var RE_FILE_EXT = /\.(sass|scss)$/,
PREF_ENABLED = "enabled",
PREF_OPTIONS = "options";

Expand Down Expand Up @@ -78,17 +77,19 @@ define(function (require, exports, module) {
outputFile = FileSystem.getFileForPath(file.parentPath + outputName);

options = _.defaults(options || {}, {
includePaths: [],
outputStyle: "nested",
sourceComments: "map",
sourceMap: outputFile.fullPath + ".map"
sourceMap: outputFile.name + ".map"
});

// Initialize sourceMap with full path
options.sourceMap = outputFile.parentPath + options.sourceMap;

return {
enabled: enabled,
options: options,
outputCSSFile: outputFile,
outputSourceMapFile: options.sourceMap && FileSystem.getFileForPath(outputFile.parentPath + options.sourceMap)
outputSourceMapFile: FileSystem.getFileForPath(options.sourceMap)
};
}

Expand Down Expand Up @@ -267,24 +268,11 @@ define(function (require, exports, module) {
function deleteTempFiles() {
return _nodeDomain.exec("deleteTempFiles");
}

function _fileSystemChange(event, entry, added, removed) {
// Skip directories and SASS partials, e.g. _variables.scss
if (!entry || !entry.isFile || !entry.name.match(RE_FILE)) {
return;
}

compile(entry);
}

function _prefChangeHandler(event) {
// TODO compile all files?
// _compileWithPreferences();
}

// All SASS files get compiled when changed on disk
// TODO preferences to compile on demand, filter for file paths, etc.?
FileSystem.on("change", _fileSystemChange);

// FIXME why is change fired during app init?
// Register preferences
Expand Down
2 changes: 1 addition & 1 deletion SASSAgent.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ define(function (require, exports, module) {
var sourceMapPromise = SourceMapManager.getSourceMap(data.cssFile);

sourceMapPromise.then(function (sourceMap) {
return Compiler.preview(sourceMap._localSources[0], data.docs).then(function (css) {
return Compiler.preview(sourceMap.sassFile, data.docs).then(function (css) {
Inspector.CSS.setStyleSheetText(data.header.styleSheetId, css);

// TODO look for added/removed docs?
Expand Down
16 changes: 10 additions & 6 deletions SourceMapManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,16 @@ define(function (require, exports, module) {
sourceMap.sources.forEach(function (source) {
// Gather the source document(s) that generated this CSS file
var localSourceFile = FileSystem.getFileForPath(parentPath + source),
dependencies = self._dependencyMap[localSourceFile.fullPath] || [];
dependencies = self._dependencyMap[localSourceFile.fullPath] || {};

localSources.push(localSourceFile);

// Map each source as a dependency for the input cssFile
self._dependencyMap[localSourceFile.fullPath] = dependencies;
self._dependencyMap[localSourceFile.fullPath].push(cssFile);
self._dependencyMap[localSourceFile.fullPath][cssFile.fullPath] = {
cssFile: cssFile,
sourceMap: sourceMap
};
});

// Set input SASS document
Expand All @@ -260,11 +263,12 @@ define(function (require, exports, module) {
};

/**
*
* @param {!File} cssFile
*
* @param {!File} sassFile
* @return {Object.<string,{{cssFile: File, sourceMap: SourceMapConsumer}}>}
*/
SourceMapManager.prototype.getUsageForFile = function (cssFile) {
return this._dependencyMap[cssFile.fullPath] || [];
SourceMapManager.prototype.getUsageForFile = function (sassFile) {
return this._dependencyMap[sassFile.fullPath] || {};
};

return new SourceMapManager();
Expand Down
139 changes: 120 additions & 19 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
/*jslint nomen:true, vars:true, regexp:true*/
/*jslint nomen:true, vars:true, regexp:true, plusplus: true*/
/*global window, console, define, brackets, $, Mustache*/

define(function (require, exports, module) {
Expand All @@ -36,22 +36,11 @@ define(function (require, exports, module) {
CSSUtils = brackets.getModule("language/CSSUtils"),
CodeInspection = brackets.getModule("language/CodeInspection"),
DocumentManager = brackets.getModule("document/DocumentManager"),
FileSystem = brackets.getModule("filesystem/FileSystem");

// Return pending promises until preview completes
$(Compiler).on("sourceMapPreviewStart", function (event, sassFile, cssFile) {
SourceMapManager.setSourceMapPending(cssFile);
});

// Update source map when preview completes and resolve promise
$(Compiler).on("sourceMapPreviewEnd", function (event, sassFile, data) {
SourceMapManager.setSourceMap(data.css.file, data.sourceMap.file, data.sourceMap.contents);
});

// Reject promise waiting for a source map
$(Compiler).on("sourceMapPreviewError", function (event, sassFile, cssFile, errors) {
SourceMapManager.setSourceMap(cssFile);
});
FileUtils = brackets.getModule("file/FileUtils"),
FileSystem = brackets.getModule("filesystem/FileSystem"),
ProjectManager = brackets.getModule("project/ProjectManager");

var RE_FILE = /^[^_].*\.scss$/;

// Augment CSSUtils.findMatchingRules to support source maps
var baseFindMatchingRules = CSSUtils.findMatchingRules;
Expand Down Expand Up @@ -79,7 +68,8 @@ define(function (require, exports, module) {
SourceMapManager.getSourceDocument(cssFile, origPos.source).then(function (doc) {
var selectors = selectorCache[doc.file.fullPath],
selector,
origLine = origPos.line - 1;
origLine = origPos.line - 1,
i;

// HACK? Use CSSUtils to parse SASS selectors
if (!selectors) {
Expand All @@ -88,7 +78,7 @@ define(function (require, exports, module) {
}

// Find the original SASS selector based on the sourceMap position
for (var i = 0; i < selectors.length; i++) {
for (i = 0; i < selectors.length; i++) {
selector = selectors[i];

if ((origLine >= selector.ruleStartLine) && (origLine <= selector.selectorEndLine)) {
Expand Down Expand Up @@ -193,13 +183,124 @@ define(function (require, exports, module) {

return promise;
}

function _scanForSourceMaps() {
// Get all .css.map files
var promise = ProjectManager.getAllFiles(function (file) {
return file.name.match(/\.css\.map$/i) !== null;
});

// Convert .css.map paths to .css output paths
promise = promise.then(function (sourceMapFiles) {
return sourceMapFiles.map(function (sourceMapFile) {
// Get associated CSS file by dropping ".map"
return {
sourceMapFile: sourceMapFile,
cssFilePath: sourceMapFile.fullPath.replace(/\.map$/i, "")
};
});
});

// Resolve css file paths to Files
var resolvedPairs = [];
promise = promise.then(function (pairs) {
return Async.doInParallel(pairs, function (pair) {
var deferred = new $.Deferred();

FileSystem.resolve(pair.cssFilePath, function (err, cssFile) {
if (err) {
deferred.reject();
} else {
pair.cssFile = cssFile;
resolvedPairs.push(pair);
deferred.resolve();
}
});

return deferred.promise();
});
});

// Read .css file sourceMappingURL
promise.always(function () {
resolvedPairs.forEach(function (pair) {
var sourceMapFile = pair.sourceMapFile,
cssFile = pair.cssFile;

FileUtils.readAsText(cssFile).done(function (cssText) {
var sourceMapRelPath = SourceMapManager.getSourceMappingURL(cssText),
regExp = new RegExp(sourceMapRelPath + "$");

// Confirm CSS sourceMappingURL matches the source map path
if (!sourceMapRelPath || !regExp.exec(sourceMapFile.fullPath)) {
return;
}

SourceMapManager.setSourceMapFile(cssFile, sourceMapFile);
});
});
});
}

function _appReady() {
CodeInspection.register("scss", {
name: "SCSS",
scanFileAsync: _scanFileAsync
});

_scanForSourceMaps();
}

$(ProjectManager).on("projectOpen", function (event, root) {
// TODO Reset SourceMapManager when project changes

// Scan for source maps in the new project
_scanForSourceMaps();
});

// Return pending promises until preview completes
$(Compiler).on("sourceMapPreviewStart", function (event, sassFile, cssFile) {
// TODO ignore partials
SourceMapManager.setSourceMapPending(cssFile);
});

// Update source map when preview completes and resolve promise
$(Compiler).on("sourceMapPreviewEnd", function (event, sassFile, data) {
// TODO ignore partials
SourceMapManager.setSourceMap(data.css.file, data.sourceMap.file, data.sourceMap.contents);
});

// Reject promise waiting for a source map
$(Compiler).on("sourceMapPreviewError", function (event, sassFile, cssFile, errors) {
// TODO ignore partials
SourceMapManager.setSourceMap(cssFile);
});

// All SASS files get compiled when changed on disk
// TODO preferences to compile on demand, filter for file paths, etc.?
FileSystem.on("change", function (event, entry, added, removed) {
var filesToCompile = [];

// Skip directories
if (!entry || !entry.isFile) {
return;
}

// Check if this file is referenced in one or more source maps
var usages = SourceMapManager.getUsageForFile(entry),
cssFilePaths = Object.keys(usages);

cssFilePaths.forEach(function (cssFilePath) {
filesToCompile.push(usages[cssFilePath].sourceMap.sassFile);
});

// Compile a SASS file that does not have a source map
if (filesToCompile.length === 0 && entry.name.match(RE_FILE)) {
filesToCompile.push(entry);
}

filesToCompile.forEach(Compiler.compile);
});

// Delay initialization until `appReady` event is fired
AppInit.appReady(_appReady);
Expand Down

0 comments on commit 12cb35d

Please sign in to comment.