In a custom function, it seems there's no way to know what is the current sass file (imported) or the current main sass file. In ruby sass this is accessible from the sass options which are reachable via the current contex (this) of the function being evaluated.
Ideally we could call this.options and get access to the options and also access to other current runtime information either in the options or from some other property of this.
The reason this is important is for things like custom functions that have references to on-disk assets using relative paths to the current sass file.
Similar to #886
Yep that's what I'm after and is exactly the same use case I'm after. :smile: I'm happy for #886 to be closed if this issue more clearly describes the desired feature. As mentioned in the other issue I've come up with a temporary work around but it'd be nice to have this feature and not abuse the .contents property which leads to #894 when trying to implement something like this.
I agree this is a useful feature. I'm scheduling it for v3.next.
I hope solving sass/libsass#1160 will bring us closer to this.
Ping. I'm about to release a filesystem module for eyeglass and this limitation makes the API for using it pretty annoying. Is this on anyone's radar anytime soon?
Sorry I've been focused on getting LibSass 3.3.0 stable.
@mgreter is there any work required in LibSass for this?
@xzyfer depends on if the full stack needs to be queried or really only the current file. The later is already accessible in libsass via fb->pstate().path. The first would need quite some more work, since IMO we don't preserve the stack in the AST, so once the function call is executed it is lost. Easiest way I see to support the more complex case would be to "preserve" the import information in the AST.
My understanding of the issue is that, from within a custom function, we want to be able to expose the current file path being executed.
@xzyfer Presently, yes, the current file is needed, but in the long term, I would like access to the global environment (global vars, sass function invocation) so that custom functions have the same capabilities that native functions have. The Ruby API provides all of this, and it's been very good. Whatever API we expose should plan to give access to this information. Ideally, something like this.environment.currentSassFile. Later we can add this.environment.functions["myFunctionName"].invoke(...) and this.environment.variables["myGlobalVar"].
Maybe I'll take another swing at this, I had a patch but couldn't make it work, but a coworker helped me find my bug and so I think I can do it now.
@chriseppstein give me some days, I think I can come up with the right solution. I'm pretty sure you will get more regressions with custom importers, headers and source-maps etc (keys for style_sheets must change to abs_path). I have some tests in perl-libsass which currently fail. I can post the WIP if you like. I also want to do it correctly, since I don't want to change the C-API at a later point. Bascially we would need to refactor back-trace handling (lot of things got scatered around over time), but hadn't had the time to really touch this area (but clean-up can IMO also come after a first implementation).
I see something like this:
sass_import_stack_get_length()
sass_import_stack_get_abs_path(0)
sass_import_stack_get_import_path(0)
While the number tells how far back in the stack you want to go.
This is similar to how perl caller works.
@chriseppstein: How does ruby sass report currentSassFile with ie @import "foo"; found in include path rel? Btw. you may want to open an issue on the libsass bugtracker for the others ...
I'm currently going to expose absolute path and original import call.
@mgreter Ruby sass exposes these as runtime defined options that are put into the options hash that is passed in to the Sass engine. There is no guarantee that the files names are actually files on disk or even parseable as file paths (as they could have been imported from a database or off the internet). But basically there are several important options that are set:
filename is the file of the current sass file.css_filename is the output file being created.original_filename is the top level sass file before any imports.importer is the importer instance (in ruby sass they are objects) that imported the file. In theory, this object can be used to do other path resolutions in whatever file space it is accessing.If I were to do it over, I would not use the options for this. I would use the environment object because we spend a lot of time copying options objects, whereas the environment datastructure is a by-reference approach.
Ruby sass does not expose an API for getting at the full call stack. That is available internally, but only used during error reporting or by calls to @warn. I can't think of a great use case for knowing your full call stack except for things like deprecation warnings and error reporting. The call stack is a API that _will_ be changing as we move into 4.0, so I am hesitant to expose it or make any guarantees about what it will contain. IMO I think the custom functions should be able to issue an equivalent of an @warn statement and keep the call stack out of the public API.
@chriseppstein So what does filename (or css_filename) report for the case I've given above? As it was in the import call (meaning it would be foo for the above case)? Also how should a custom function be able to issue something like a call stack like @warn without the stack being a public API? I think I'm might missing something obvious here (just to say, I myself have absolutely no real world experience with sass).
@mgreter Sorry, it is whatever the importer says the filename is :) But for the normal filesystem importer it is the fully resolved, absolute filename. (/abs/rel/_foo.scss)
@chriseppstein thx. Maybe I can give some more info about how libsass abs_path currently works. A custom importer gets passed the previous abs_path and the given import name (foo). If it wants to handle the import (either by checking the import name or a combination with previous abs_path, ie. it should check if import name is already absolute in its context and if not combine it with the prev abs_path), it has to create/return the abs_path for that import (which is what I want to expose on the C-API). So in case the previous import was resolved to an abs_path of http://xxx/yyy.zzz and the current import name is foo, the custom importer should return http://xxx/foo (plus the content) for that import (and this is what I will expose on the C-API, plus the original import name).
As I understand, ruby sass would return just foo in case the url is handled by a custom importer?
Edit: re-read your statement and I guess libsass already does it the same as ruby sass does!
First working POC in https://github.com/sass/libsass/pull/1600
Looks like we can use the same API as with custom importers!
sass_compiler_get_import_entry
sass_import_get_imp_path
sass_import_get_abs_path
...
@xzyfer this includes quite a bit of refactoring and cleaning up in context.cpp. Do you want to include this in libsass 3.3.0 or wait for 3.3.1? https://github.com/sass/libsass/pull/1617 adds the minimal C-API changes that will be needed to support this. IMO we should at least land the API changes in 3.3.0!
Do you want to include this in libsass 3.3.0 or wait for 3.3.1?
3.3.1
we should at least land the API changes in 3.3.0!
Agreed.
/cc @saper, I expect this will effect node-sass.
renderand renderSync to custom functions as this.options. This makes it possible to know file Node Sass is compiling if the file property is being used i.e. this.options.file.However this will
data instead of file@imported fileI would appreciate a test case the desired behaviour in this issue. To be completely honestly I'm not sure what is required.
I hacked this together this implementation a few months ago: https://github.com/sass/node-sass/compare/master...also:current-file-hack
I don't have a test case, although it passed the existing tests at the time.
It changes the last argument to custom functions from the done callback to {done, currentFile}. I imagine a more comprehensive implementation would have a different API and expose more information from the compiler.
I would appreciate a test case the desired behaviour in this issue. To be completely honestly I'm not sure what is required.
@xzyfer I feel like I've given quite a bit of details here, so I don't know what else to provide. Maybe you can explain what, in particular, you are confused by. Sass functions need to know their caller context so that relative paths can be resolved and urls relative to the target output file can be determined. There are times when this information isn't available, so no code should be built in a way that expects non-null values, but there's plenty of times where it is.
I don't think that sass_compiler_get_last_import will work reliably for custom functions. I stated before that the main problem is that libsass does not preserve the information needed ATM. The problem is that we read/parse/resolve the input/import files into an AST tree that will contain a function call op. Once we evaluate/execute that function call, the context of the actual import is no longer available (we only have one ast tree). That is what https://github.com/sass/libsass/issues/1037 is all about. As I also wrote above IMO there is currently only one way to actually get that info via fb->pstate().path already. But that would be just a "crutch" for now, as the import stack should be available to custom functions (as other stuff). But nobody yet came around to actually solve/implement the missing pieces for libsass.
Just came up against this problem...
test/_images.scss.test { background-image: inline-image('./images/lol.svg'); }
If I was to then hook into a custom function using;
funcs['inline-image($file)'] = function(file, done) {
The problem here is that the function has no idea what the current directory context of Sass is... so when you reference ./images/lol.svg, it goes to /images/lol.svg, instead of test/images/lol.svg.
For example, solving this problem with Ruby Sass would involve;
real_path = File.expand_path("#{File.dirname(options[:filename])}/#{path}")
Any recommendations on any kind of hack/workaround to get this to work without forking libsass/node-sass?
Edit: I'm trying to get sass-inline-svg to work like this to rewrite url rules, and I've noticed that when it tries to open an SVG on disk that doesn't exist (because it doesn't know the path), you get an error like this from node-sass:
{
"status": 1,
"file": "/Volumes/git/workspace/packages/components/src/resourceIcon/resourceIcon.scss",
"line": 11,
"column": 31,
"message": "error in C function url: ENOENT: no such file or directory, open '/Volumes/git/resources/common/archive.svg'\n\nBacktrace:\n\tsrc/resourceIcon/resourceIcon.scss:11, in function `url`\n\tsrc/resourceIcon/resourceIcon.scss:11, in mixin `icon-style-builder`\n\tsrc/resourceIcon/resourceIcon.scss:27",
"formatted": "Error: error in C function url: ENOENT: no such file or directory, open '/Volumes/git/resources/common/archive.svg'\n\n Backtrace:\n \tsrc/resourceIcon/resourceIcon.scss:11, in function `url`\n \tsrc/resourceIcon/resourceIcon.scss:11, in mixin `icon-style-builder`\n \tsrc/resourceIcon/resourceIcon.scss:27\n on line 11 of src/resourceIcon/resourceIcon.scss\n>> background-image: url(\"../../../resources/common/#{$icon-name}.sv\n ------------------------------^\n"
}
which notably includes file, which _is_ the file path that the url rule is in, not the root file that's being compiled! So clearly this value exists somewhere in at least some contexts, but I'm not sure how to get at it (and I even considered purposely causing an exception to pull out the file path...)
@seansfkelley i had a hack here https://github.com/sass/node-sass/issues/886#issuecomment-96325318 (its not pretty!)
The best way to progress this this, or any feature is to PR test cases so
we can be sure we're building what people expect.
On 2 Aug. 2017 8:44 am, "James Newell" notifications@github.com wrote:
@seansfkelley https://github.com/seansfkelley i had a hack here #886
(comment)
https://github.com/sass/node-sass/issues/886#issuecomment-96325318
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/sass/node-sass/issues/895#issuecomment-319517945, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAjZWDgiaBj578vDfVSlVt99L1_nRONGks5sT6pDgaJpZM4EJ5E2
.
Just for the record, until there's a correct solution to the problem on node-sass level: there's a way to figure out current path from include paths.
I know it's not rock-solid but I use it for several months now and it never failed so far.
It has one basic limitation: it only works for the compilation entry point (i.e. not for files one @imports in sass), but I use it with webpack + sass-loader. I think at least some of solution seekers do the same so maybe it will be usable for you guys as well:
When there are include paths defined in node-sass config they are present in context when each compilation entry point is processed. In my use case i get them in one of functions passed to node-sass config.
Last path in include paths is the path to the compilation entry point. It doesn't matter if additional tools like eyeglass are used or not. Taking the last of include paths allows to get path of compilation entry point.
i think there is a relative module for discussion: https://github.com/fetch/node-sass-asset-functions
Most helpful comment
The reason this is important is for things like custom functions that have references to on-disk assets using relative paths to the current sass file.