Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scss files support #63

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions lib/file_types/code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
sc_require('../mixins/content_filters');

BT.CodeFile = BT.File.extend(BT.ContentFiltersMixin,
{
isCode: true,

content: null,

contentObservers: null,

updateContent: function()
{
var raw = this.get('rawContent');
this.set('content', raw ? this.filterContent(raw.toString()) : null);

var deps = this.getPath('contentObservers.content');
if(deps) for(var i = 0, len = deps.get('length'); i < len; ++i) deps[i].updateContent();
}.observes('rawContent'),
})
107 changes: 107 additions & 0 deletions lib/file_types/scss.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
sc_require('code');

BT.SCSSFile = BT.CodeFile.extend(
{
extension: 'scss',
isSCSSFile: true,
isStylesheet: true,
contentType: 'text/css',
language: 'any',

contentFilters: [
'filterStopIfPartial',
'filterParseSASS',
],

_sass_importer: function(path, from, done)
{
var pathlib = require('path');

var pathBasename = pathlib.basename(path);
if('_' !== pathBasename.charAt(0)) pathBasename = '_' + pathBasename;
if('.scss' !== pathBasename.substr(-5).toLowerCase()) pathBasename = pathBasename + '.scss';
path = pathlib.join(pathlib.dirname(path), pathBasename);

var fullpath = ('stdin' === from)
? pathlib.join(pathlib.dirname(this.get('path')), path)
: pathlib.join(pathlib.dirname(from), path);
var file = this.getPath('framework.files.stylesheets').findProperty('path', fullpath)
if(file && file.isSCSSFile)
{
var observers = file.contentObservers;
if(!observers) observers = file.contentObservers = BT.DependenciesController.create();
if(!observers.contains(this)) observers.addObject(this);
}

return null;
},

_sass_sc_static_handler: function(url)
{
var sass = require('node-sass');
var SassString = sass.types.String;
var className = SC._object_className(this.constructor);

var res = this.get('framework').findResourceFor(url);
if(!res || 0 === res.length)
{
BT.Logger.warn(className + "#_sass_sc_static_handler: found no files for %@ in file %@".fmt(url, this.get('path')));
return new SassString('url(/* ' + url + ' */)');
}

var file = res[0];
if(res.length > 1)
{
BT.Logger.warn(className + "#_sass_sc_static_handler: found multiple files for %@ in file %@, taking the first (%@)".fmt(url, this.get('path'), file.get('path')));
}

var deps = this.resourceDependencies;
if(!deps.contains(file)) deps.addObject(file);

var ret = this.getPath('framework.belongsTo.doRelativeBuild')
? file.get('relativeUrl')
: file.get('url')

return new SassString('url(' + ret + ')');
},

/**
Parses an .scss file.
Paths in sc_static() or static_url() are relative to the calling file.
*/
filterParseSASS: function(content)
{
var self = this;
var ret = null

try
{
ret = require('node-sass').renderSync({
data: content,
includePaths: [require('path').dirname(this.get('path'))],
functions: {
'sc_static($url)': function(url) { return self._sass_sc_static_handler(url.getValue()) },
'static_url($url)': function(url) { return self._sass_sc_static_handler(url.getValue()) },
},
importer: function(path, from, done) { return self._sass_importer(path, from, done) },
}).css;
}
catch(e) { BT.Logger.warn("node-sass: " + e.formatted) }
return ret;
},

/**
Stops the processing of content because partials should not produce any output,
but should be imported from usual SCSS files.
*/
filterStopIfPartial: function(content)
{
return this.get('isPartial') ? null : content;
},

isPartial: function()
{
return '_' === require('path').basename(this.get('path')).charAt(0);
}.property('path').cacheable(),

})
2 changes: 2 additions & 0 deletions lib/filetypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
sc_require('file_types/script');
sc_require('file_types/json');
sc_require('file_types/css');
sc_require('file_types/scss');
sc_require('file_types/image');
sc_require('file_types/module_script');
sc_require('file_types/appcache');
Expand All @@ -13,6 +14,7 @@ sc_require('file_types/html');
BT.projectManager.registerFileClass("js", BT.ScriptFile);
BT.projectManager.registerFileClass("json", BT.JSONFile);
BT.projectManager.registerFileClass("css", BT.CSSFile);
BT.projectManager.registerFileClass("scss", BT.SCSSFile);
BT.projectManager.registerFileClass("ejs", BT.TemplateFile);
BT.projectManager.registerFileClass("html", BT.HTMLFile);

Expand Down
55 changes: 55 additions & 0 deletions lib/mixins/content_filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
BT.ContentFiltersMixin =
{
hasContentFiltersSupport: true,

/**
Array of content filters.
Example filter configuration:
```contentFilters: [
function(content) { ... }, // anonymous function will be called
'methodName', // this.methodName(content) method will be called
['methodName', param1, param2, ...], // this.methodName(content, param1, param2) will be called with arbitrary number of parameters
[['SC.Object', 'methodName'], param1, param2, ...] // SC.Object.methodName(content)
[[SC.Object, function(content, param1) {}], param1, ...],
SC.Object.create(BT.ContentFilterMixin), // Apply filters from another object
],```
A filter may return `null` or an empty string to stop further filtering.
*/
contentFilters: [],

filterContent: function(content)
{
var filters = this.get('contentFilters');
for(var i = 0, len = filters.get('length'); i < len; ++i)
{
var filter = filters[i];
if(filter.hasContentFiltersSupport)
{
content = filter.filterContent(content);
}
else
{
switch(SC.typeOf(filter))
{
case SC.T_STRING: content = this[filter](content); break;
case SC.T_FUNCTION: content = filter.call(this, content); break;
case SC.T_ARRAY: {
var target = this, handler = filter.shift();
if(SC.T_ARRAY === SC.typeOf(handler))
{
target = handler.shift();
handler = handler.shift();
}
if(SC.T_STRING === SC.typeOf(target)) target = SC.objectForPropertyPath(target);
filter.unshift(content);
if(SC.T_STRING === SC.typeOf(handler)) content = target[handler].apply(target, filter);
else handler.apply(target, filter);
}
break;
}
}
if(SC.empty(content)) break;
}
return content;
},
}