【问题标题】:What is the algorithm that Sass uses to resolve an @import statement?Sass 用来解析 @import 语句的算法是什么?
【发布时间】:2023-03-26 01:20:01
【问题描述】:

Node.js 用来解析require() 调用的算法非常清楚documented in pseudocode

对于 Sass 的 @import 声明,我需要同样的东西。

我知道@import 'foo' 将尝试在与导入文件相同的目录中以及相对于任何已配置的“加载路径”...但是这些尝试的顺序是什么,即如果有多个文件可以满足@import,则优先顺序是什么?以./../ 开头是否会影响它是否尝试加载路径?还有其他我没有涉及的事情吗? .css 文件呢?

guide 除了“Sass 很聪明,会为你解决问题”之外,并没有说太多。 reference docs 更详细,但仍然没有说明解决步骤。

谁能用伪代码提供它使用的确切算法?

【问题讨论】:

  • 您确实意识到 Sass 是开源的,您可以自己去发现它,对吧?
  • @cimmanon 是的。你不认为它在伪代码中也存在任何价值吗?

标签: sass libsass node-sass


【解决方案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

注意简洁,我使用的是 Ruby 的 File#dirnameFile#basename 以及 File#expand,就像 Node.js 的 path.resolve。我正在使用类似 Ruby 的伪代码,但它仍然是伪代码。

关键点:

  • 没有优先顺序。 SASS 没有实施优先顺序,而是在有多个可能的候选者时放弃。例如,如果你写了@import "x" 并说x.scss_x.scss 都存在,那么sass 会抛出一个歧义错误。同样,如果x.scssx.sass 都存在,则会引发歧义错误。
  • 按“从左到右”的顺序尝试加载路径。它们提供了一个根或基础来解析导入(类似于 UNIX 使用 $PATH 查找可执行文件的方式)。当前工作目录总是首先尝试。 (虽然这种行为 will change 从 3.2 到 3.4)
  • 无论您使用的是./ 还是../,总是会尝试加载路径
  • 在 sass 文件中,常规 .css 文件 cannot be imported

如果您想了解更多细节,我建议您阅读 SASS 的源代码:

  • sass/lib/sass/tree/import_node.rbimport 方法。在第 53-56 行,您可以看到与伪代码中 search 函数内部的 for 循环相同。
  • Importers::Base 抽象类sass/lib/sass/importors/base.rb。这个文件的 cmets 非常好用。
  • sass/lib/sass/importors/filesystem.rbfind_real_file 方法。第 112-126 行实现了我们的 possible_files 函数。第 156 行检查只有一个匹配项。如果没有,则第 167 行会引发歧义错误,否则第 183 行会选择一个匹配的文件。

编辑:我对之前的答案不满意,因此我将其重写为更清晰一些。算法现在可以正确处理文件名中的下划线(以前的算法没有)。我还添加了一些关键点来解决 OP 提出的其他问题。

【讨论】:

  • let dirname = File.dirname(import_arg) 这行让我很困惑。您不想要导入文件的目录名,而不是正在导入的文件吗?
  • 另外,dirname 变量似乎没有在任何地方使用。它被传递到search(),但该函数忽略它。
猜你喜欢
  • 1970-01-01
  • 2016-03-21
  • 2019-10-17
  • 2014-03-30
  • 1970-01-01
  • 2012-12-02
  • 1970-01-01
  • 2011-11-08
  • 1970-01-01
相关资源
最近更新 更多