Skip to content
This repository has been archived by the owner on Jul 24, 2024. It is now read-only.

Can I provide an 'import hook' to load files in a custom way? #470

Closed
callumlocke opened this issue Oct 18, 2014 · 9 comments
Closed

Can I provide an 'import hook' to load files in a custom way? #470

callumlocke opened this issue Oct 18, 2014 · 9 comments

Comments

@callumlocke
Copy link

node-sass already allows me to provide an SCSS string (as data) instead of a file path. But if there are @import statements in that string, these get loaded from disk (using whatever loadPaths I provide).

I want to be able to provide some kind of 'importer' hook in the options - a custom function for loading file contents. This function would be called whenever libsass encounters an @import statement (so node-sass/libsass never actually touches the disk directly), and its job is to call back with the contents of the requested file.

sass.render({
  data: "@import '_var.scss';\nbody {background-color: $bg;}",
  importer: function (file, cb) {
    if (file === '_var.scss')
      cb(null, "$bg: red;");
    else cb(new Error('unknown partial: ' + file));
  },
  success: function () {}
});

This would make it much more efficient to use node-sass in a multi-step preprocessing sequence - eg with .scss.hbs files, where the first step renders Handlebars templates to Sass, and then the second step renders the in-memory Sass to CSS. At the moment the only way I can do this is using a .tmp directory.

Is this possible?

@michaek
Copy link

michaek commented Oct 22, 2014

This would require an unwelcome amount of juggling in node-sass, because the Sass compilation is done with https://github.com/sass/libsass, so the import hooks would need to be implemented in libsass and made available via the interface. The functionality you're looking for might be a better fit for rework or postcss (run before scss compilation), and a stream-based workflow (via gulp, perhaps) would do away with temporary files.

@michaek michaek closed this as completed Oct 22, 2014
@callumlocke
Copy link
Author

I don't think you follow. Doing preprocessing (eg with rework) before SCSS compilation is exactly why I'm requesting this feature, because it's currently not possible to do it, except with 'entry' SCSS files. For imported partials, there's no way to preprocess them because libsass wants to read them directly from disk. There's no way to feed it the partials dynamically from my own stream.

Or am I missing something? How would rework help me?

If node-sass lets me feed in an entry file like main.scss as data, it seems weird that I can't also feed in _partial.scss, that it instead has to go and read these from the local hard disk, by design. This prevents me from doing earlier preprocessing on the partials, and prevents libsass being used as a 'pure' transform in a gulp pipeline (without using hacks and tmp directories).

I'm happy to request this on the libsass project and see where it gets me, but it sounds like you don't agree it's a good idea... if it got implemented in libsass would you be willing to expose it through node-sass?

@michaek
Copy link

michaek commented Oct 22, 2014

I did miss your point regarding how rework might help, but I think it makes sense to think of this as a libsass (rather than node-sass) feature request. Perhaps libsass could take a map with include paths as keys and the file contents as data?

@callumlocke
Copy link
Author

cool I agree it's a libsass request (now that I know more about it). just wanted to check you were potentially on board, if I ever manage to get the libsass authors to do this :)

regarding passing in a map... I would rather be able to provide my own dynamic 'importer' so that I can decide when/whether to load (and prepreprocess) partials on-demand, rather than have to prepreprocess the whole project and pass the whole lot to libsass in one go. If I could provide my own import hook, this would allow me to put something very efficient together

I think Ruby Sass has a feature like this (ie you can bring your own Importer class), so I'll read up on that and then pitch it to libsass...

thanks

@alexdpunkt
Copy link

I came across this posting because I would need this too... I want to be able to create the following behavior:

  1. set a global project variable with content 'xyz'
  2. if this variable is set, @include 'file' would include file.xyz.scss if it exists, otherwise include file.scss

Is this somehow possible?

@callumlocke
Copy link
Author

@adick no, not with node-sass/libsass.

Ruby Sass lets you provide your own import hook... it has a default Importer class that loads files from the filesystem according to the usual rules, but you can provide your own Importer by extending this Base class. Then Sass would never touch the filesystem itself (except maybe its own .sass-cache files), and instead would always go through your Importer. So you can do whatever you want – eg look for non-standard file extensions, maybe preprocess things first to inject some config from environment variables, whatever.. This is probably how the Rails asset pipeline lets you do things like thing.scss.erb.

@alexdpunkt
Copy link

Hi @callumlocke - thanks for the heads-up!

I already managed to get it work by overriding the default Filesystem importer:

module Sass
  module Importers
    class Filesystem
      private
      def _find(dir, name, options)

        full_filename, syntax = Sass::Util.destructure(find_real_file(dir, name, options))
        return unless full_filename && File.readable?(full_filename)

        # detect environment from ENV file
        if File.readable?('ENV')
            env = File.read('ENV')
        else
            env = ""
        end

        if env
            file_ext = File.extname(full_filename)
            filename_without_ext = full_filename.chomp(file_ext)
            filename_with_suffix = filename_without_ext + "." + env + file_ext
            if File.readable?(filename_with_suffix)
                full_filename = filename_with_suffix
            end
        end

        options[:syntax] = syntax
        options[:filename] = full_filename
        options[:importer] = self
        Sass::Engine.new(File.read(full_filename), options)
      end
    end
  end
end

I had to override the default Filesystem importer because in my compass version the reference to the importer class is still hardcoded, so providing my own importer (extended from the Base class) had no effect.

@callumlocke
Copy link
Author

Ah cool, mine was just theory till now, hadn't seen a working example yet.
Your code will be useful, thanks
Le mer. 5 nov. 2014 à 01:04, adick notifications@github.com a écrit :

Hi @callumlocke https://github.com/callumlocke - thanks for the
heads-up!

I already managed to get it work by overriding the default Filesystem
importer:

module Sass
module Importers
class Filesystem
private
def _find(dir, name, options)

    full_filename, syntax = Sass::Util.destructure(find_real_file(dir, name, options))
    return unless full_filename && File.readable?(full_filename)

    # detect environment from ENV file
    if File.readable?('ENV')
        env = File.read('ENV')
    else
        env = ""
    end

    if env
        file_ext = File.extname(full_filename)
        filename_without_ext = full_filename.chomp(file_ext)
        filename_with_suffix = filename_without_ext + "." + env + file_ext
        if File.readable?(filename_with_suffix)
            full_filename = filename_with_suffix
        end
    end

    options[:syntax] = syntax
    options[:filename] = full_filename
    options[:importer] = self
    Sass::Engine.new(File.read(full_filename), options)
  end
end

end
end

I had to override the default Filesystem importer because in my compass
version the reference to the importer class is still hardcoded, so
providing my own importer had no effect.


Reply to this email directly or view it on GitHub
#470 (comment).

@callumlocke
Copy link
Author

this is now being picked up in #530

jiongle1 pushed a commit to scantist-ossops-m2/node-sass that referenced this issue Apr 7, 2024
s/Nathan/Natalie in Readme
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants