Какой алгоритм использует Sass для разрешения оператора @import?

Алгоритм, который Node.js использует для разрешения вызова require(), очень четко задокументирован в псевдокоде.

Мне нужно то же самое для оператора Sass @import.

Я знаю, что @import 'foo' будет пробовать различные комбинации базовых имен foo и _foo с расширениями .scss и .sass в том же каталоге, что и файл импорта, а также относительно любого из настроенных "путей загрузки"... Но в каком порядке они попробовал, т. е. что имеет приоритет, если есть несколько файлов, которые могли бы удовлетворить @import? Влияет ли запуск с ./ или ../ на попытки загрузки путей? Есть ли какие-то другие вещи, которые я не рассмотрел? А как насчет .css файлов?

В руководстве не говорится ничего, кроме "Sass умен и все сделает за вас". ." справочные документы содержат более подробные сведения, но не разъясняют шаги разрешения.

Может ли кто-нибудь предоставить точный алгоритм, который он использует, в псевдокоде?


person callum    schedule 17.12.2015    source источник
comment
Вы понимаете, что Sass является открытым исходным кодом, и что вы могли бы узнать это сами, верно?   -  person cimmanon    schedule 21.12.2015
comment
@cimmanon да. Вы не думаете, что в этом есть какая-то ценность, существующая и в псевдокоде?   -  person callum    schedule 21.12.2015


Ответы (1)


Вот упрощенный алгоритм для @import <import_arg>;. Это получено из чтения исходного кода SASS и выполнения моих собственных тестов.

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

Примечание для краткости: я использую File#dirname Ruby, File#basename, а также File#expand, что похоже на path.resolve. Я использую Ruby-подобный псевдокод, но он по-прежнему должен быть псевдокодом.

Ключевые моменты:

  • Порядок приоритета отсутствует. Вместо того, чтобы реализовать порядок приоритета, SASS сдается, когда есть несколько возможных кандидатов. Например, если вы написали @import "x" и сказали, что существуют и x.scss, и _x.scss, то sass выдаст ошибку неоднозначности. Точно так же, если существуют и x.scss, и x.sass, возникает ошибка неоднозначности.
  • Пути загрузки проверяются в порядке «слева направо». Они предоставляют корень или базу для разрешения импорта (аналогично тому, как UNIX использует $PATH для поиска исполняемых файлов). Текущий рабочий каталог всегда проверяется первым. (хотя такое поведение изменится с 3.2 на 3.4)
  • Пути загрузки всегда проверяются независимо от того, использовали ли вы ./ или ../.
  • В файле sass обычные файлы .css не могут быть импортированы

Если вам нужны подробности, я бы порекомендовал прочитать исходный код SASS:

  • метод import из sass/lib/sass/tree/import_node.rb. В строках 53-56 вы можете увидеть тот же цикл for, что и внутри функции search в нашем псевдокоде.
  • Importers::Base абстрактный класс sass/lib/sass/importors/base.rb. Комментарии к этому файлу очень удобны.
  • метод find_real_file из sass/lib/sass/importors/filesystem.rb. Строки 112-126 реализуют нашу функцию possible_files. Строка 156 проверяет наличие только одного совпадения. Если нет, то строка 167 выдает ошибку неоднозначности, иначе строка 183 выбирает один соответствующий файл.

Правка: мне не понравился мой предыдущий ответ, поэтому я переписал его, чтобы сделать его более ясным. Алгоритм теперь корректно обрабатывает символы подчеркивания в имени файла (предыдущий алгоритм этого не делал). Я также добавил некоторые ключевые моменты, касающиеся других вопросов, заданных OP.

person James Lawson    schedule 18.12.2015
comment
Строка let dirname = File.dirname(import_arg) меня смущает. Разве вам не нужно имя папки импортируемого файла, а не импортируемого файла? - person callum; 05.01.2016
comment
Кроме того, переменная dirname нигде не используется. Он передается в search(), но эта функция игнорирует его. - person callum; 05.01.2016