Skip to content

Commit

Permalink
API: Provides support for array of importers.
Browse files Browse the repository at this point in the history
* In order to skip the importer, user must
  return `sass.types.NULL` (or the shorter
  alias `sass.NULL`).
* See the added test on usage.
* Backward compatible: part of me want still
  wants to make it non-backward compatible
  because:
  * We can: in major version v3.0.
  * It will keep the API clean.
  * It will keep the docs clear: type of
    `options.importers` is array of functions.
* Updates docs.

Issue URL: sass#821.
PR URL: sass#832.
  • Loading branch information
am11 committed Apr 5, 2015
1 parent b7fde3b commit 0a08d2a
Show file tree
Hide file tree
Showing 13 changed files with 181 additions and 73 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,21 @@ var result = sass.renderSync({

## Options
### file
Type: `String | null`
Type: `String`
Default: `null`
**Special**: `file` or `data` must be specified

Path to a file for [libsass] to render.

### data
Type: `String | null`
Type: `String`
Default: `null`
**Special**: `file` or `data` must be specified

A string to pass to [libsass] to render. It is recommended that you use `includePaths` in conjunction with this so that [libsass] can find files when using the `@import` directive.

### importer (>= v2.0.0)
Type: `Function` signature `function(url, prev, done)`
Type: `Function | Function[]` signature `function(url, prev, done)`
Default: `undefined`

Function Parameters and Information:
Expand All @@ -89,6 +89,8 @@ When returning or calling `done()` with `{ contents: "String" }`, the string val

Starting from v3.0.0, `this` refers to a contextual scope for the immediate run of `sass.render` or `sass.renderSync`

Starting from v3.0.0, importer can be an array of functions, which will be called by libsass in the order of their occurance in array. This helps user specify special importer for particular kind of path (filesystem, http). If the importer does not handle particular path, it should return `sass.NULL`. See [functions section](#functions) for more details on Sass types.

### functions (>= v3.0.0)
`functions` is an `Object` that holds a collection of custom functions that may be invoked by the sass files being compiled. They may take zero or more input parameters and must return a value either synchronously (`return ...;`) or asynchronously (`done();`). Those parameters will be instances of one of the constructors contained in the `require('node-sass').types` hash. The return value must be of one of these types as well. See the list of available types below:

Expand Down
56 changes: 42 additions & 14 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,26 +282,42 @@ module.exports.render = function(options, cb) {
var importer = options.importer;

if (importer) {
options.importer = function(file, prev, bridge) {
function done(data) {
bridge.success(data);
}
if (Array.isArray(importer)) {
importer.forEach(function(subject, index) {
options.importer[index] = function(file, prev, bridge) {
function done(data) {
bridge.success(data);
}

var result = subject.call(options.context, file, prev, done);

if (result) {
done(result === module.exports.NULL ? null : result);
}
};
});
} else {
options.importer = function(file, prev, bridge) {
function done(data) {
bridge.success(data);
}

var result = importer.call(options.context, file, prev, done);
var result = importer.call(options.context, file, prev, done);

if (result) {
done(result);
}
};
if (result) {
done(result === module.exports.NULL ? null : result);
}
};
}
}

var functions = options.functions;

if (functions) {
options.functions = {};

Object.keys(functions).forEach(function(signature) {
var cb = normalizeFunctionSignature(signature, functions[signature]);
Object.keys(functions).forEach(function(subject) {
var cb = normalizeFunctionSignature(subject, functions[subject]);

options.functions[cb.signature] = function() {
var args = Array.prototype.slice.call(arguments),
Expand Down Expand Up @@ -336,9 +352,21 @@ module.exports.renderSync = function(options) {
var importer = options.importer;

if (importer) {
options.importer = function(file, prev) {
return importer.call(options.context, file, prev);
};
if (Array.isArray(importer)) {
importer.forEach(function(subject, index) {
options.importer[index] = function(file, prev) {
var result = subject.call(options.context, file, prev);

return result === module.exports.NULL ? null : result;
};
});
} else {
options.importer = function(file, prev) {
var result = importer.call(options.context, file, prev);

return result === module.exports.NULL ? null : result;
};
}
}

var functions = options.functions;
Expand Down
69 changes: 50 additions & 19 deletions src/binding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,24 @@
#include "create_string.h"
#include "sass_types/factory.h"

struct Sass_Import** sass_importer(const char* file, const char* prev, void* cookie)
Sass_Import_List sass_importer(const char* cur_path, Sass_Importer_Entry cb, struct Sass_Compiler* comp)
{
void* cookie = sass_importer_get_cookie(cb);
struct Sass_Import* previous = sass_compiler_get_last_import(comp);
const char* prev_path = sass_import_get_path(previous);
sass_context_wrapper* ctx_w = static_cast<sass_context_wrapper*>(cookie);
CustomImporterBridge& bridge = *(ctx_w->importer_bridge);
CustomImporterBridge& bridge = *(static_cast<CustomImporterBridge*>(cookie));

std::vector<void*> argv;
argv.push_back((void*)file);
argv.push_back((void*)prev);
argv.push_back((void*)cur_path);
argv.push_back((void*)prev_path);

return bridge(argv);
}

union Sass_Value* sass_custom_function(const union Sass_Value* s_args, void* cookie)
union Sass_Value* sass_custom_function(const union Sass_Value* s_args, Sass_Function_Entry cb, struct Sass_Options* opts)
{
void* cookie = sass_function_get_cookie(cb);
CustomFunctionBridge& bridge = *(static_cast<CustomFunctionBridge*>(cookie));

std::vector<void*> argv;
Expand Down Expand Up @@ -65,13 +69,6 @@ void ExtractOptions(Local<Object> options, void* cptr, sass_context_wrapper* ctx
ctx_w->error_callback = new NanCallback(error_callback);
}

Local<Function> importer_callback = Local<Function>::Cast(options->Get(NanNew("importer")));

if (importer_callback->IsFunction()) {
ctx_w->importer_bridge = new CustomImporterBridge(new NanCallback(importer_callback), ctx_w->is_sync);
sass_option_set_importer(sass_options, sass_make_importer(sass_importer, ctx_w));
}

if (!is_file) {
ctx_w->file = create_string(options->Get(NanNew("file")));
sass_option_set_input_path(sass_options, ctx_w->file);
Expand Down Expand Up @@ -106,25 +103,58 @@ void ExtractOptions(Local<Object> options, void* cptr, sass_context_wrapper* ctx
sass_option_set_indent(sass_options, ctx_w->indent);
sass_option_set_linefeed(sass_options, ctx_w->linefeed);

Local<Object> custom_functions = Local<Object>::Cast(options->Get(NanNew("functions")));
Local<Value> importer_callback = options->Get(NanNew("importer"));

if (importer_callback->IsFunction()) {
Local<Function> importer = Local<Function>::Cast(importer_callback);
auto bridge = std::make_shared<CustomImporterBridge>(new NanCallback(importer), ctx_w->is_sync);
ctx_w->importer_bridges.push_back(bridge);

Sass_Importer_List c_importers = sass_make_importer_list(1);
c_importers[0] = sass_make_importer(sass_importer, 0, bridge.get());

sass_option_set_c_importers(sass_options, c_importers);
}
else if (importer_callback->IsArray()) {
Handle<Array> importers = Handle<Array>::Cast(importer_callback);
Sass_Importer_List c_importers = sass_make_importer_list(importers->Length());

for (size_t i = 0; i < importers->Length(); ++i) {
Local<Function> callback = Local<Function>::Cast(importers->Get(static_cast<uint32_t>(i)));

if (!callback->IsFunction()) {
NanThrowError(NanNew("options.importer must be set to a function or array of functions"));
}

auto bridge = std::make_shared<CustomImporterBridge>(new NanCallback(callback), ctx_w->is_sync);
ctx_w->importer_bridges.push_back(bridge);

c_importers[i] = sass_make_importer(sass_importer, importers->Length() - i - 1, bridge.get());
}

sass_option_set_c_importers(sass_options, c_importers);
}

Local<Value> custom_functions = options->Get(NanNew("functions"));

if (custom_functions->IsObject()) {
Local<Array> signatures = custom_functions->GetOwnPropertyNames();
Local<Object> functions = Local<Object>::Cast(custom_functions);
Local<Array> signatures = functions->GetOwnPropertyNames();
unsigned num_signatures = signatures->Length();
Sass_C_Function_List fn_list = sass_make_function_list(num_signatures);
Sass_Function_List fn_list = sass_make_function_list(num_signatures);

for (unsigned i = 0; i < num_signatures; i++) {
Local<String> signature = Local<String>::Cast(signatures->Get(NanNew(i)));
Local<Function> callback = Local<Function>::Cast(custom_functions->Get(signature));
Local<Function> callback = Local<Function>::Cast(functions->Get(signature));

if (!signature->IsString() || !callback->IsFunction()) {
NanThrowError(NanNew("options.functions must be a (signature -> function) hash"));
}

CustomFunctionBridge* bridge = new CustomFunctionBridge(new NanCallback(callback), ctx_w->is_sync);
auto bridge = std::make_shared<CustomFunctionBridge>(new NanCallback(callback), ctx_w->is_sync);
ctx_w->function_bridges.push_back(bridge);

Sass_C_Function_Callback fn = sass_make_function(create_string(signature), sass_custom_function, bridge);
Sass_Function_Entry fn = sass_make_function(create_string(signature), sass_custom_function, bridge.get());
sass_function_set_list_entry(fn_list, i, fn);
}

Expand Down Expand Up @@ -274,7 +304,8 @@ NAN_METHOD(render_file_sync) {

int result = GetResult(ctx_w, ctx, true);

sass_wrapper_dispose(ctx_w, input_path);
free(input_path);
sass_free_context_wrapper(ctx_w);

NanReturnValue(NanNew<Boolean>(result == 0));
}
Expand Down
7 changes: 3 additions & 4 deletions src/custom_function_bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
Sass_Value* CustomFunctionBridge::post_process_return_value(Handle<Value> val) const {
try {
return SassTypes::Factory::unwrap(val)->get_sass_value();
} catch (const std::invalid_argument& e) {
}
catch (const std::invalid_argument& e) {
return sass_make_error(e.what());
}
}
Expand All @@ -14,9 +15,7 @@ std::vector<Handle<Value>> CustomFunctionBridge::pre_process_args(std::vector<vo
std::vector<Handle<Value>> argv = std::vector<Handle<Value>>();

for (void* value : in) {
argv.push_back(
SassTypes::Factory::create(static_cast<Sass_Value*>(value))->get_js_object()
);
argv.push_back(SassTypes::Factory::create(static_cast<Sass_Value*>(value))->get_js_object());
}

return argv;
Expand Down
12 changes: 5 additions & 7 deletions src/custom_importer_bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "create_string.h"

SassImportList CustomImporterBridge::post_process_return_value(Handle<Value> val) const {
SassImportList imports;
SassImportList imports = 0;
NanScope();

Local<Value> returned_value = NanNew(val);
Expand Down Expand Up @@ -32,8 +32,9 @@ SassImportList CustomImporterBridge::post_process_return_value(Handle<Value> val
else {
char* path = create_string(object->Get(NanNew<String>("file")));
char* contents = create_string(object->Get(NanNew<String>("contents")));
char* srcmap = create_string(object->Get(NanNew<String>("map")));

imports[i] = sass_make_import_entry(path, (!contents || contents[0] == '\0') ? 0 : strdup(contents), 0);
imports[i] = sass_make_import_entry(path, contents, srcmap);
}
}
}
Expand All @@ -51,12 +52,9 @@ SassImportList CustomImporterBridge::post_process_return_value(Handle<Value> val
Local<Object> object = Local<Object>::Cast(returned_value);
char* path = create_string(object->Get(NanNew<String>("file")));
char* contents = create_string(object->Get(NanNew<String>("contents")));
char* srcmap = create_string(object->Get(NanNew<String>("map")));

imports[0] = sass_make_import_entry(path, (!contents || contents[0] == '\0') ? 0 : strdup(contents), 0);
}
else {
imports = sass_make_import_list(1);
imports[0] = sass_make_import_entry((char const*) this->argv[0], 0, 0);
imports[0] = sass_make_import_entry(path, contents, srcmap);
}

return imports;
Expand Down
2 changes: 1 addition & 1 deletion src/custom_importer_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

using namespace v8;

typedef Sass_Import** SassImportList;
typedef Sass_Import_List SassImportList;

class CustomImporterBridge : public CallbackBridge<SassImportList> {
public:
Expand Down
5 changes: 4 additions & 1 deletion src/libsass.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
'libsass/constants.cpp',
'libsass/context.cpp',
'libsass/contextualize.cpp',
'libsass/contextualize_eval.cpp',
'libsass/cssize.cpp',
'libsass/emitter.cpp',
'libsass/error_handling.cpp',
Expand All @@ -21,6 +22,8 @@
'libsass/functions.cpp',
'libsass/inspect.cpp',
'libsass/json.cpp',
'libsass/lexer.cpp',
'libsass/listize.cpp',
'libsass/node.cpp',
'libsass/output.cpp',
'libsass/parser.cpp',
Expand Down Expand Up @@ -72,7 +75,7 @@
'VCCLCompilerTool': {
'AdditionalOptions': [
'/GR',
'/EHsc'
'/EHs'
]
}
}
Expand Down
21 changes: 3 additions & 18 deletions src/sass_context_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ extern "C" {
return (sass_context_wrapper*)calloc(1, sizeof(sass_context_wrapper));
}

void sass_wrapper_dispose(struct sass_context_wrapper* ctx_w, char* string = 0) {
void sass_free_context_wrapper(sass_context_wrapper* ctx_w) {
if (ctx_w->dctx) {
sass_delete_data_context(ctx_w->dctx);
}
Expand All @@ -46,23 +46,8 @@ extern "C" {
free(ctx_w->source_map_root);
free(ctx_w->indent);

if (string) {
free(string);
}

if (!ctx_w->function_bridges.empty()) {
for (CustomFunctionBridge* bridge : ctx_w->function_bridges) {
delete bridge;
}
}

if (ctx_w->importer_bridge) {
delete ctx_w->importer_bridge;
}
}

void sass_free_context_wrapper(sass_context_wrapper* ctx_w) {
sass_wrapper_dispose(ctx_w);
ctx_w->importer_bridges.resize(0);
ctx_w->function_bridges.resize(0);

free(ctx_w);
}
Expand Down
6 changes: 3 additions & 3 deletions src/sass_context_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define SASS_CONTEXT_WRAPPER

#include <vector>
#include <memory>
#include <nan.h>
#include <stdlib.h>
#include <condition_variable>
Expand Down Expand Up @@ -44,12 +45,11 @@ extern "C" {
NanCallback* error_callback;
NanCallback* success_callback;

std::vector<CustomFunctionBridge*> function_bridges;
CustomImporterBridge* importer_bridge;
std::vector<std::shared_ptr<CustomFunctionBridge>> function_bridges;
std::vector<std::shared_ptr<CustomImporterBridge>> importer_bridges;
};

struct sass_context_wrapper* sass_make_context_wrapper(void);
void sass_wrapper_dispose(struct sass_context_wrapper*, char*);
void sass_free_context_wrapper(struct sass_context_wrapper*);

#ifdef __cplusplus
Expand Down
Loading

0 comments on commit 0a08d2a

Please sign in to comment.