The algorithm that Node.js uses to resolve a require()
call is very clearly documented in pseudocode.
I need the same thing for Sass's @import
statement.
I know that @import 'foo'
will try various combinations of the basenames foo
and _foo
, with extensions .scss
and .sass
, in the same directory as the importing file as well as relative to any of the configured 'load paths'... But what order these are tried in, i.e. what takes precedence if there are multiple files that could satisfy the @import
? Does starting with a ./
or ../
affect whether it tries the load paths? Are there any other things it will try that I haven't covered? What about .css
files?
The guide doesn't say much beyond "Sass is smart and will figure it out for you." The reference docs go into more detail, but still don't spell out the resolution steps.
Can anyone provide the exact algorithm it uses, in pseudocode?
Here is a simplified algorithm for @import <import_arg>;
.
This is derived from reading the source code for SASS and from running my own tests.
def main(import_arg)
let dirname = File.dirname(import_arg)
let basename = File.basename(import_arg)
if import_arg is absolute ... # omitted as this is a rare case
else return search(dirname, basename)
end
# try resolving import argument relative to each path in load_paths
# 1. If we encounter an unambiguous match, we finish
# 2. If we encounter an ambiguous match, we give up
# see: https://stackoverflow.com/a/33588202/3649209
def search(dirname, basename)
let cwd = operating system current working directory
let load_paths = paths specified via SASS_PATH env variable and via --load-path options
let search_paths = [cwd].concat(load_paths)
for path in search_paths
let file = find_match(File.expand_path(basename, path), basename)
if (file != false) return file
end
throw "File to import not found or unreadable"
end
def find_match(directory, basename)
let candidates = possible_files(directory, basename)
if candiates.length == 0
# not a single match found ... don't give up yet
return false
else if candidates.length > 1
# several matching files, ambiguity! ... give up
# throw ambiguity error
throw "It's not clear which file to import"
else
# success! exactly one match found
return candidates[0]
end
end
# NB: this is a bit tricky to express in code
# which is why I settled for a high-level description
def possible_files(directory, basename)
# if `basename` is of the form shown on the LHS
# then check the filesystem for existence of
# any of the files shown on the RHS within
# directory `directory`. Return the list all the files
# which do indeed exist (or [] if none exist).
# LHS RHS
# x.sass -> _x.sass, x.sass
# x.scss -> _x.scss, x.scss
# x -> x.scss, _x.scss, x.sass, _x.sass
# _x -> _x.scss, _x.sass
end
Note for brevity, I am using Ruby's File#dirname
, File#basename
as well as File#expand
which is like Node.js's path.resolve
. I'm using a Ruby-like pseudocode but it is still meant to be pseudocode.
Key Points:
@import "x"
and say both x.scss
and _x.scss
exist, then sass will throw an ambiguity error. Similarly, if both x.scss
and x.sass
exist then an ambiguity error is thrown. ./
or ../
If you want more detail, I would recommend reading SASS's source code:
import
method of sass/lib/sass/tree/import_node.rb
. On lines 53-56 you can see the same for loop as the one inside the search
function in our pseudocode.Importers::Base
abstract class sass/lib/sass/importors/base.rb
. The comments of this file are quite handy.find_real_file
method of sass/lib/sass/importors/filesystem.rb
. Lines 112-126 implements our possible_files
function. Line 156 checks there is only one match. If there isn't then line 167 throws an ambiguity error, else line 183 picks the one matching file.Edit: I wasn't happy with my previous answer so I've rewritten it to be a bit clearer. Algorithm now correctly handles underscores in filename (previous algorithm didn't). I also added some key points that address the other questions OP asked.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With