Commit 7caffda6 by Brett Wilkins

Merge branch 'new-api'

parents e5c3c8ae d23939ee
...@@ -16,17 +16,36 @@ Find it on npm: <https://npmjs.org/package/node-sass> ...@@ -16,17 +16,36 @@ Find it on npm: <https://npmjs.org/package/node-sass>
```javascript ```javascript
var sass = require('node-sass'); var sass = require('node-sass');
sass.render(scss_content, callback [, options]); sass.render({
file: scss_filename,
success: callback
[, options..]
});
// OR // OR
var css = sass.renderSync(scss_content [, options]); var css = sass.renderSync({
data: scss_content
[, options..]
});
``` ```
### Options ### Options
The options argument is optional, though it's use is recommended. It support two attributes: `includePaths` and `outputStyle`. The API for using node-sass has changed, so that now there is only one variable - an options hash. Some of these options are optional, and in some circumstances some are mandatory.
#### file
`file` is a `String` of the path to an `scss` file for libsass to render. One of this or `data` options are required, for both render and renderSync.
#### data
`data` is a `String` containing the scss to be rendered by libsass. One of this or `file` options are required, for both render and renderSync. It is recommended that you use the `includePaths` option in conjunction with this, as otherwise libsass may have trouble finding files imported via the `@import` directive.
#### success
`success` is a `Function` to be called upon successful rendering of the scss to css. This option is required but only for the render function. If provided to renderSync it will be ignored.
#### error
`error` is a `Function` to be called upon occurance of an error when rendering the scss to css. This option is optional, and only applies to the render function. If provided to renderSync it will be ignored.
#### includePaths #### includePaths
`includePaths` is an `Array` of path `String`s to look for any `@import`ed files. It is recommended that you use this option if you have **any** `@import` directives, as otherwise libsass may not find your depended-on files. `includePaths` is an `Array` of path `String`s to look for any `@import`ed files. It is recommended that you use this option if you are using the `data` option and have **any** `@import` directives, as otherwise libsass may not find your depended-on files.
#### outputStyle #### outputStyle
`outputStyle` is a `String` to determine how the final CSS should be rendered. Its value should be one of `'nested', 'expanded', 'compact', 'compressed'`. `outputStyle` is a `String` to determine how the final CSS should be rendered. Its value should be one of `'nested', 'expanded', 'compact', 'compressed'`.
...@@ -36,13 +55,28 @@ The options argument is optional, though it's use is recommended. It support two ...@@ -36,13 +55,28 @@ The options argument is optional, though it's use is recommended. It support two
```javascript ```javascript
var sass = require('node-sass'); var sass = require('node-sass');
sass.render('body{background:blue; a{color:black;}}', function(err, css){ sass.render({
console.log(css) data: 'body{background:blue; a{color:black;}}',
}/*, { includePaths: [ 'lib/', 'mod/' ], outputStyle: 'compressed' }*/); success: function(css){
console.log(css)
},
error: function(error) {
console.log(error);
},
includePaths: [ 'lib/', 'mod/' ],
outputStyle: 'compressed'
});
// OR // OR
console.log(sass.renderSync('body{background:blue; a{color:black;}}')/*, { includePaths: [ 'lib/', 'mod/' ], outputStyle: 'compressed' }*/); console.log(sass.renderSync({
data: 'body{background:blue; a{color:black;}}'),
outputStyle: 'compressed'
});
``` ```
### Edge-case behaviours
* In the case that both `file` and `data` options are set, node-sass will only attempt to honour the `file` directive.
## Connect/Express middleware ## Connect/Express middleware
Recompile `.scss` files automatically for connect and express based http servers Recompile `.scss` files automatically for connect and express based http servers
......
...@@ -16,7 +16,7 @@ void WorkOnContext(uv_work_t* req) { ...@@ -16,7 +16,7 @@ void WorkOnContext(uv_work_t* req) {
sass_compile(ctx); sass_compile(ctx);
} }
void MakeCallback(uv_work_t* req) { void MakeOldCallback(uv_work_t* req) {
HandleScope scope; HandleScope scope;
TryCatch try_catch; TryCatch try_catch;
sass_context_wrapper* ctx_w = static_cast<sass_context_wrapper*>(req->data); sass_context_wrapper* ctx_w = static_cast<sass_context_wrapper*>(req->data);
...@@ -46,7 +46,7 @@ void MakeCallback(uv_work_t* req) { ...@@ -46,7 +46,7 @@ void MakeCallback(uv_work_t* req) {
sass_free_context_wrapper(ctx_w); sass_free_context_wrapper(ctx_w);
} }
Handle<Value> Render(const Arguments& args) { Handle<Value> OldRender(const Arguments& args) {
HandleScope scope; HandleScope scope;
sass_context* ctx = sass_new_context(); sass_context* ctx = sass_new_context();
sass_context_wrapper* ctx_w = sass_new_context_wrapper(); sass_context_wrapper* ctx_w = sass_new_context_wrapper();
...@@ -66,6 +66,63 @@ Handle<Value> Render(const Arguments& args) { ...@@ -66,6 +66,63 @@ Handle<Value> Render(const Arguments& args) {
ctx_w->callback = Persistent<Function>::New(callback); ctx_w->callback = Persistent<Function>::New(callback);
ctx_w->request.data = ctx_w; ctx_w->request.data = ctx_w;
int status = uv_queue_work(uv_default_loop(), &ctx_w->request, WorkOnContext, (uv_after_work_cb)MakeOldCallback);
assert(status == 0);
return scope.Close(Undefined());
}
void MakeCallback(uv_work_t* req) {
HandleScope scope;
TryCatch try_catch;
sass_context_wrapper* ctx_w = static_cast<sass_context_wrapper*>(req->data);
sass_context* ctx = static_cast<sass_context*>(ctx_w->ctx);
if (ctx->error_status == 0) {
// if no error, do callback(null, result)
const unsigned argc = 1;
Local<Value> argv[argc] = {
Local<Value>::New(String::New(ctx->output_string))
};
ctx_w->callback->Call(Context::GetCurrent()->Global(), argc, argv);
} else {
// if error, do callback(error)
const unsigned argc = 1;
Local<Value> argv[argc] = {
Local<Value>::New(String::New(ctx->error_message))
};
ctx_w->errorCallback->Call(Context::GetCurrent()->Global(), argc, argv);
}
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
sass_free_context_wrapper(ctx_w);
}
Handle<Value> Render(const Arguments& args) {
HandleScope scope;
sass_context* ctx = sass_new_context();
sass_context_wrapper* ctx_w = sass_new_context_wrapper();
char *source;
String::AsciiValue astr(args[0]);
Local<Function> callback = Local<Function>::Cast(args[1]);
Local<Function> errorCallback = Local<Function>::Cast(args[2]);
String::AsciiValue bstr(args[3]);
source = new char[strlen(*astr)+1];
strcpy(source, *astr);
ctx->source_string = source;
ctx->options.include_paths = new char[strlen(*bstr)+1];
strcpy(ctx->options.include_paths, *bstr);
// ctx->options.output_style = SASS_STYLE_NESTED;
ctx->options.output_style = args[4]->Int32Value();
ctx_w->ctx = ctx;
ctx_w->callback = Persistent<Function>::New(callback);
ctx_w->errorCallback = Persistent<Function>::New(errorCallback);
ctx_w->request.data = ctx_w;
int status = uv_queue_work(uv_default_loop(), &ctx_w->request, WorkOnContext, (uv_after_work_cb)MakeCallback); int status = uv_queue_work(uv_default_loop(), &ctx_w->request, WorkOnContext, (uv_after_work_cb)MakeCallback);
assert(status == 0); assert(status == 0);
...@@ -74,7 +131,6 @@ Handle<Value> Render(const Arguments& args) { ...@@ -74,7 +131,6 @@ Handle<Value> Render(const Arguments& args) {
Handle<Value> RenderSync(const Arguments& args) { Handle<Value> RenderSync(const Arguments& args) {
HandleScope scope; HandleScope scope;
TryCatch try_catch;
sass_context* ctx = sass_new_context(); sass_context* ctx = sass_new_context();
char *source; char *source;
String::AsciiValue astr(args[0]); String::AsciiValue astr(args[0]);
...@@ -90,15 +146,108 @@ Handle<Value> RenderSync(const Arguments& args) { ...@@ -90,15 +146,108 @@ Handle<Value> RenderSync(const Arguments& args) {
sass_compile(ctx); sass_compile(ctx);
if (ctx->error_status == 0) { if (ctx->error_status == 0) {
return scope.Close(Local<Value>::New(String::New(ctx->output_string))); return scope.Close(Local<Value>::New(String::New(ctx->output_string)));
}
ThrowException(Exception::Error(String::New(ctx->error_message)));
return scope.Close(Undefined());
}
/**
Rendering Files
**/
void WorkOnFileContext(uv_work_t* req) {
sass_file_context_wrapper* ctx_w = static_cast<sass_file_context_wrapper*>(req->data);
sass_file_context* ctx = static_cast<sass_file_context*>(ctx_w->ctx);
sass_compile_file(ctx);
}
void MakeFileCallback(uv_work_t* req) {
HandleScope scope;
TryCatch try_catch;
sass_file_context_wrapper* ctx_w = static_cast<sass_file_context_wrapper*>(req->data);
sass_file_context* ctx = static_cast<sass_file_context*>(ctx_w->ctx);
if (ctx->error_status == 0) {
// if no error, do callback(null, result)
const unsigned argc = 1;
Local<Value> argv[argc] = {
Local<Value>::New(String::New(ctx->output_string))
};
ctx_w->callback->Call(Context::GetCurrent()->Global(), argc, argv);
} else { } else {
ThrowException(Exception::Error(String::New("Input does not appear to be valid SCSS."))); // if error, do callback(error)
const unsigned argc = 1;
Local<Value> argv[argc] = {
Local<Value>::New(String::New(ctx->error_message))
};
ctx_w->errorCallback->Call(Context::GetCurrent()->Global(), argc, argv);
}
if (try_catch.HasCaught()) {
node::FatalException(try_catch);
}
sass_free_file_context_wrapper(ctx_w);
}
Handle<Value> RenderFile(const Arguments& args) {
HandleScope scope;
sass_file_context* ctx = sass_new_file_context();
sass_file_context_wrapper* ctx_w = sass_new_file_context_wrapper();
char *filename;
String::AsciiValue astr(args[0]);
Local<Function> callback = Local<Function>::Cast(args[1]);
Local<Function> errorCallback = Local<Function>::Cast(args[2]);
String::AsciiValue bstr(args[3]);
filename = new char[strlen(*astr)+1];
strcpy(filename, *astr);
ctx->input_path = filename;
ctx->options.include_paths = new char[strlen(*bstr)+1];
strcpy(ctx->options.include_paths, *bstr);
// ctx->options.output_style = SASS_STYLE_NESTED;
ctx->options.output_style = args[4]->Int32Value();
ctx_w->ctx = ctx;
ctx_w->callback = Persistent<Function>::New(callback);
ctx_w->errorCallback = Persistent<Function>::New(errorCallback);
ctx_w->request.data = ctx_w;
int status = uv_queue_work(uv_default_loop(), &ctx_w->request, WorkOnFileContext, (uv_after_work_cb)MakeFileCallback);
assert(status == 0);
return scope.Close(Undefined());
}
Handle<Value> RenderFileSync(const Arguments& args) {
HandleScope scope;
sass_file_context* ctx = sass_new_file_context();
char *filename;
String::AsciiValue astr(args[0]);
String::AsciiValue bstr(args[1]);
filename = new char[strlen(*astr)+1];
strcpy(filename, *astr);
ctx->input_path = filename;
ctx->options.include_paths = new char[strlen(*bstr)+1];
strcpy(ctx->options.include_paths, *bstr);
ctx->options.output_style = args[2]->Int32Value();
sass_compile_file(ctx);
if (ctx->error_status == 0) {
return scope.Close(Local<Value>::New(String::New(ctx->output_string)));
} }
ThrowException(Exception::Error(String::New(ctx->error_message)));
return scope.Close(Undefined()); return scope.Close(Undefined());
} }
void RegisterModule(v8::Handle<v8::Object> target) { void RegisterModule(v8::Handle<v8::Object> target) {
NODE_SET_METHOD(target, "oldRender", OldRender);
NODE_SET_METHOD(target, "render", Render); NODE_SET_METHOD(target, "render", Render);
NODE_SET_METHOD(target, "renderSync", RenderSync); NODE_SET_METHOD(target, "renderSync", RenderSync);
NODE_SET_METHOD(target, "renderFile", RenderFile);
NODE_SET_METHOD(target, "renderFileSync", RenderFileSync);
} }
NODE_MODULE(binding, RegisterModule); NODE_MODULE(binding, RegisterModule);
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
...@@ -21,20 +21,61 @@ var SASS_OUTPUT_STYLE = { ...@@ -21,20 +21,61 @@ var SASS_OUTPUT_STYLE = {
compressed: 3 compressed: 3
}; };
exports.render = function(css, callback, options) { var prepareOptions = function(options) {
var paths, style; var paths, style;
options = typeof options !== 'object' ? {} : options; options = typeof options !== 'object' ? {} : options;
paths = options.include_paths || options.includePaths || []; paths = options.include_paths || options.includePaths || [];
style = SASS_OUTPUT_STYLE[options.output_style || options.outputStyle] || 0; style = SASS_OUTPUT_STYLE[options.output_style || options.outputStyle] || 0;
return binding.render(css, callback, paths.join(':'), style);
return {
paths: paths,
style: style
};
}
var deprecatedRender = function(css, callback, options) {
options = prepareOptions(options);
return binding.oldRender(css, callback, options.paths.join(':'), options.style);
};
var deprecatedRenderSync = function(css, options) {
options = prepareOptions(options);
return binding.renderSync(css, options.paths.join(':'), options.style);
}; };
exports.renderSync = function(css, options) { exports.render = function(options) {
var paths, style; var newOptions;
options = typeof options !== 'object' ? {} : options;
paths = options.include_paths || options.includePaths || []; if (typeof arguments[0] === 'string') {
style = SASS_OUTPUT_STYLE[options.output_style || options.outputStyle] || 0; return deprecatedRender.apply(this, arguments);
return binding.renderSync(css, paths.join(':'), style); }
newOptions = prepareOptions(options);
options.error = options.error || function(){};
if (options.file !== undefined && options.file !== null) {
return binding.renderFile(options.file, options.success, options.error, newOptions.paths.join(':'), newOptions.style);
}
//Assume data is present if file is not. binding/libsass will tell the user otherwise!
return binding.render(options.data, options.success, options.error, newOptions.paths.join(":"), newOptions.style);
};
exports.renderSync = function(options) {
var newOptions;
if (typeof arguments[0] === 'string') {
return deprecatedRenderSync.apply(this, arguments);
}
newOptions = prepareOptions(options);
if (options.file !== undefined && options.file !== null) {
return binding.renderFileSync(options.file, newOptions.paths.join(':'), newOptions.style);
}
//Assume data is present if file is not. binding/libsass will tell the user otherwise!
return binding.renderSync(options.data, newOptions.paths.join(":"), newOptions.style);
}; };
exports.middleware = require('./lib/middleware'); exports.middleware = require('./lib/middleware');
...@@ -15,4 +15,16 @@ extern "C" { ...@@ -15,4 +15,16 @@ extern "C" {
free(ctx_w); free(ctx_w);
} }
sass_file_context_wrapper* sass_new_file_context_wrapper()
{
return (sass_file_context_wrapper*) calloc(1, sizeof(sass_file_context_wrapper));
}
void sass_free_file_context_wrapper(sass_file_context_wrapper* ctx_w)
{
if (ctx_w->ctx) sass_free_file_context(ctx_w->ctx);
free(ctx_w);
}
} }
...@@ -9,11 +9,22 @@ struct sass_context_wrapper { ...@@ -9,11 +9,22 @@ struct sass_context_wrapper {
sass_context* ctx; sass_context* ctx;
uv_work_t request; uv_work_t request;
v8::Persistent<v8::Function> callback; v8::Persistent<v8::Function> callback;
v8::Persistent<v8::Function> errorCallback;
}; };
struct sass_context_wrapper* sass_new_context_wrapper(void); struct sass_context_wrapper* sass_new_context_wrapper(void);
void sass_free_context_wrapper(struct sass_context_wrapper* ctx); void sass_free_context_wrapper(struct sass_context_wrapper* ctx);
struct sass_file_context_wrapper {
sass_file_context* ctx;
uv_work_t request;
v8::Persistent<v8::Function> callback;
v8::Persistent<v8::Function> errorCallback;
};
struct sass_file_context_wrapper* sass_new_file_context_wrapper(void);
void sass_free_file_context_wrapper(struct sass_file_context_wrapper* ctx);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#h1 { #navbar {
width: 100%; width: 80%;
span { height: 23px;
color: red; }
&.active { #navbar ul {
color: pink; list-style-type: none;
} }
#navbar li {
float: left;
a {
font-weight: bold;
} }
} }
\ No newline at end of file
...@@ -28,8 +28,11 @@ var expectedRender = '#navbar {\n\ ...@@ -28,8 +28,11 @@ var expectedRender = '#navbar {\n\
#navbar li a {\n\ #navbar li a {\n\
font-weight: bold; }\n'; font-weight: bold; }\n';
var badSampleFilename = 'sample.scss';
var sampleFilename = require('path').resolve(__dirname, 'sample.scss');
describe("compile scss", function() {
describe("DEPRECATED: compile scss", function() {
it("should compile with render", function(done) { it("should compile with render", function(done) {
sass.render(scssStr, function(err, css) { sass.render(scssStr, function(err, css) {
done(err); done(err);
...@@ -60,3 +63,80 @@ describe("compile scss", function() { ...@@ -60,3 +63,80 @@ describe("compile scss", function() {
})); }));
}); });
}); });
describe("compile scss", function() {
it("should compile with render", function(done) {
sass.render({
data: scssStr,
success: function(css) {
done(assert.ok(css));
}
});
});
it("should compile with renderSync", function(done) {
done(assert.ok(sass.renderSync({data: scssStr})));
});
it("should match compiled string with render", function(done) {
sass.render({
data: scssStr,
success: function(css) {
done(assert.equal(css, expectedRender));
},
error: function(error) {
done(error);
}
});
});
it("should match compiled string with renderSync", function(done) {
done(assert.equal(sass.renderSync({data: scssStr}), expectedRender));
});
it("should throw an exception for bad input", function(done) {
done(assert.throws(function() {
sass.renderSync({data: badInput});
}));
});
});
describe("compile file", function() {
it("should compile with render", function(done) {
sass.render({
file: sampleFilename,
success: function (css) {
done(assert.ok(css));
},
error: function (error) {
done(error);
}
});
});
it("should compile with renderSync", function(done) {
done(assert.ok(sass.renderSync({file: sampleFilename})));
});
it("should match compiled string with render", function(done) {
sass.render({
file: sampleFilename,
success: function(css) {
done(assert.equal(css, expectedRender));
},
error: function(error) {
done(error);
}
});
});
it("should match compiled string with renderSync", function(done) {
done(assert.equal(sass.renderSync({file: sampleFilename}), expectedRender));
});
it("should throw an exception for bad input", function(done) {
done(assert.throws(function() {
sass.renderSync({file: badSampleFilename});
}));
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment