├── .gitignore ├── .travis.yml ├── .editorconfig ├── .jshintrc ├── readme.md ├── package.json └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 4, 10 | "newcap": true, 11 | "noarg": true, 12 | "quotmark": "single", 13 | "regexp": true, 14 | "undef": true, 15 | "unused": true, 16 | "strict": true, 17 | "trailing": true, 18 | "smarttabs": true 19 | } 20 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | > Read the import graph from passed sass files to work with dependencies 3 | 4 | Only pass along Sass files that have changed, along with dependencies. 5 | 6 | 7 | ## Install 8 | 9 | Install with [npm](https://npmjs.org/package/gulp-sass-graph) 10 | 11 | ``` 12 | npm install --save-dev gulp-sass-graph 13 | ``` 14 | 15 | 16 | ## Example 17 | 18 | ```js 19 | gulp.task('watch-sass', function(cb) { 20 | return watch({glob:'resources/assets/css/**/*.scss', emitOnGlob: false, name: "Sass"}) 21 | .pipe(sassGraph(sassLoadPaths)) 22 | .pipe(sass({loadPath: sassLoadPaths})) 23 | .pipe(notify('Sass compiled <%= file.relative %>')) 24 | .pipe(gulp.dest('web/dist/css')) 25 | .pipe(livereload()); 26 | }); 27 | ``` 28 | 29 | ## License 30 | 31 | MIT © [Lachlan Donald](http://lachlan.me) 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-sass-graph", 3 | "version": "1.0.1", 4 | "description": "Only pass along Sass files that have changed, along with dependencies. ", 5 | "license": "MIT", 6 | "repository": "lox/gulp-sass-graph", 7 | "author": { 8 | "name": "Lachlan Donald", 9 | "email": "lachlan@ljd.cc", 10 | "url": "https://lachlan.me" 11 | }, 12 | "engines": { 13 | "node": ">=0.10.0" 14 | }, 15 | "scripts": { 16 | "test": "mocha" 17 | }, 18 | "files": [ 19 | "index.js" 20 | ], 21 | "keywords": [ 22 | "gulpplugin", 23 | "sass", 24 | "changed", 25 | "modification" 26 | ], 27 | "dependencies": { 28 | "lodash": "~2.4.0", 29 | "gulp-util": "~2.2.0", 30 | "through2": "~0.4.0", 31 | "vinyl": "~0.2.3", 32 | "glob": "~3.2.8" 33 | }, 34 | "devDependencies": { 35 | "mocha": "*", 36 | "rimraf": "~2.2.6" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var gutil = require('gulp-util'); 5 | var through = require('through2'); 6 | var _ = require('lodash'); 7 | var path = require('path'); 8 | var glob = require('glob'); 9 | var File = require('vinyl'); 10 | 11 | module.exports = function (loadPaths) { 12 | var graph = {} 13 | 14 | // adds a sass file to a graph of dependencies 15 | var addToGraph = function(filepath, contents, parent) { 16 | var entry = graph[filepath] = graph[filepath] || { 17 | path: filepath, 18 | imports: [], 19 | importedBy: [], 20 | modified: fs.statSync(filepath).mtime 21 | }; 22 | var imports = sassImports(contents()); 23 | var cwd = path.dirname(filepath) 24 | 25 | for (var i in imports) { 26 | var resolved = sassResolve(imports[i], loadPaths.concat([cwd])); 27 | if (!resolved) return false; 28 | 29 | // recurse into dependencies if not already enumerated 30 | if(!_.contains(entry.imports, resolved)) { 31 | entry.imports.push(resolved); 32 | addToGraph(resolved, function() { 33 | return fs.readFileSync((path.extname(resolved) != "" ? 34 | resolved : resolved+".scss"),'utf8') 35 | }, filepath); 36 | } 37 | } 38 | 39 | // add link back to parent 40 | if(parent != null) { 41 | entry.importedBy.push(parent); 42 | } 43 | 44 | return true; 45 | } 46 | 47 | // visits all files that are ancestors of the provided file 48 | var visitAncestors = function(filepath, callback, visited) { 49 | visited = visited || []; 50 | var edges = graph[filepath].importedBy; 51 | 52 | for(var i in edges) { 53 | if(!_.contains(visited, edges[i])) { 54 | visited.push(edges[i]); 55 | callback(graph[edges[i]]); 56 | visitAncestors(edges[i], callback, visited); 57 | } 58 | } 59 | } 60 | 61 | // parses the imports from sass 62 | var sassImports = function(content) { 63 | var re = /\@import (["'])(.+?)\1;/g, match = {}, results = []; 64 | 65 | // strip comments 66 | content = new String(content).replace(/\/\*.+?\*\/|\/\/.*(?=[\n\r])/g, ''); 67 | 68 | // extract imports 69 | while (match = re.exec(content)) { 70 | results.push(match[2]); 71 | } 72 | 73 | return results; 74 | }; 75 | 76 | // resolve a relative path to an absolute path 77 | var sassResolve = function(path, loadPaths) { 78 | for(var p in loadPaths) { 79 | var scssPath = loadPaths[p] + "/" + path + ".scss" 80 | if (fs.existsSync(scssPath)) { 81 | return scssPath; 82 | } 83 | var partialPath = scssPath.replace(/\/([^\/]*)$/, '/_$1'); 84 | if (fs.existsSync(partialPath)) { 85 | return partialPath 86 | } 87 | } 88 | 89 | console.warn("failed to resolve %s from ", path, loadPaths) 90 | return false; 91 | } 92 | 93 | // builds the graph 94 | _(loadPaths).forEach(function(path) { 95 | _(glob.sync(path+"/**/*.scss", {})).forEach(function(file){ 96 | if(!addToGraph(file, function() { return fs.readFileSync(file) })) { 97 | console.warn("failed to add %s to graph", file) 98 | } 99 | }); 100 | }); 101 | 102 | return through.obj(function (file, enc, cb) { 103 | if (file.isNull()) { 104 | this.push(file); 105 | return cb(); 106 | } 107 | 108 | if (file.isStream()) { 109 | this.emit('error', 110 | new gutil.PluginError('gulp-sass-graph', 'Streaming not supported')); 111 | return cb(); 112 | } 113 | 114 | fs.stat(file.path, function (err, stats) { 115 | if (err) { 116 | // pass through if it doesn't exist 117 | if (err.code === 'ENOENT') { 118 | this.push(file); 119 | return cb(); 120 | } 121 | 122 | this.emit('error', new gutil.PluginError('gulp-sass-graph', err)); 123 | this.push(file); 124 | return cb(); 125 | } 126 | 127 | var relativePath = file.path.substr(file.cwd.length+1).replace(/\\/g, '/'); 128 | console.log("processing %s", relativePath); 129 | 130 | if(!graph[relativePath]) { 131 | addToGraph(relativePath, function() { return file.contents.toString('utf8') }); 132 | } 133 | 134 | this.push(file); 135 | 136 | // push ancestors into the pipeline 137 | visitAncestors(relativePath, function(node){ 138 | console.log("processing %s, which depends on %s", node.path, relativePath) 139 | this.push(new File({ 140 | cwd: file.cwd, 141 | base: file.base, 142 | path: node.path, 143 | contents: fs.readFileSync(node.path) 144 | })); 145 | }.bind(this)); 146 | 147 | cb(); 148 | }.bind(this)); 149 | }); 150 | }; 151 | --------------------------------------------------------------------------------