Статьи

Как проверить наличие зависимостей в библиотеках Sass

До сих пор я написал семь библиотек Sass. Большинство из них — просто расширенный набор функций, которые можно включать в свои проекты, чтобы дать им больше возможностей над кодом.

Например, SassyLists представляет собой набор функций для управления списками Sass. Они помогают вам перевернуть список, вставить элемент по определенному индексу, разделить список между двумя индексами и так далее.

SassyLists можно импортировать как расширение Compass , но я заметил, что иногда разработчики хотят использовать только очень специфическую функцию из SassyLists, поэтому они копируют / вставляют ее в свою базу кода. Проблема в том, что они не всегда обращают внимание на зависимости (например, другие функции).

Я решил начать работать над проверкой зависимостей. Идея довольно проста: каждая функция с зависимостями сначала будет проходить через проверку зависимостей; если последний обнаруживает, что некоторые функции отсутствуют, он предупреждает разработчика о том, что функция не сможет работать правильно.

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

1
2
3
@function missing-dependencies($functions…) {
     // Check for dependencies
   }

При этом мы используем function-exists() , введенную в Sass 3.3, которая проверяет существование данной функции в глобальной области видимости.

Примечание : в Sass 3.3 также есть mixin-exists() , variable-exists() и global-variable-exists() .

1
2
3
4
5
6
7
8
9
@function missing-dependencies($functions…) {
       @each $function in $functions {
           @if not function-exists($function) {
               @return true;
           }
       }
 
       @return false;
   }

Если по какой-либо причине функция не существует в области видимости, missing-dependencies возвращают значение true . Если все функции в порядке, то нет отсутствующих зависимостей, поэтому он возвращает false .

Поэтому вы бы использовали это так:

01
02
03
04
05
06
07
08
09
10
11
12
// @requires my-function
   // @requires my-other-function
 
   @function dummy() {
       @if missing-dependencies(my-function, my-other-function) {
           @warn «Oops! Missing some functions for `dummy`!»;
           @return null;
       }
 
       // `dummy` function’s core,
       // obviously needing `my-function` and `my-other-function` to work.
   }

Это довольно круто.

Было бы еще лучше, если бы мы могли определить, какая функция отсутствует, поэтому разработчик знает, что делать, чтобы решить проблему. Кроме того, необходимость каждый раз вводить предупреждение является раздражающим, поэтому мы можем перенести его в функцию missing-dependencies .

Идея не настолько отличается от того, что мы делали ранее. Разница в том, что теперь мы будем хранить имя отсутствующих функций, а не возвращать их напрямую. Затем, если отсутствуют функции, мы выдадим предупреждение разработчику и, наконец, вернем логическое значение, как мы делали ранее.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@function missing-dependencies ($functions…) {
       $missing-dependencies: ();
  
       @each $function in $functions {
           @if not function-exists($function) {
               $missing-dependencies: append($missing-dependencies, $function, comma);
           }
       }
  
       @if length($missing-dependencies) > 0 {
            @warn «Unmet dependencies! The following functions are required: #{$missing-dependencies}.»;
       }
  
        @return length($missing-dependencies) != 0;
   }

Как видите, он не намного сложнее, чем наша предыдущая версия. Плюс, способ использовать это еще проще:

1
2
3
4
5
6
7
8
@function dummy() {
       @if missing-dependencies(my-function, my-other-function) {
           @return null;
       }
 
       // `dummy` function’s core,
       // obviously needing `my-function` and `my-other-function` to work.
   }

Видеть? Мы можем отказаться от директивы @warn , но если отсутствует одна или несколько функций, разработчику будет предложено:

Неудовлетворенные зависимости! Требуются следующие функции: my-function, my-other-function.

Основная проблема, которую я вижу с этой функцией, заключается в том, что вам нужна функция missing-dependencies ! На этом этапе проверка зависимостей в основном является зависимостью … Вы видите иронию.

Это потому, что missing-dependencies(...) обрабатываются как строка в ситуациях, когда missing-dependencies не ссылаются на какие-либо функции, а строка всегда оценивается как true Таким образом, при выполнении @if missing-dependencies(...) вы фактически выполняете @if string , которая всегда верна, поэтому вы всегда будете выполнять это условие.

Чтобы избежать этого, есть умный обходной путь. Вместо того, чтобы просто делать @if missing-dependencies(...) , мы могли бы сделать @if missing-dependencies(...) == true . В Sass == похож на === в других языках, что означает, что он проверяет не только значение, но и тип.

1
2
3
4
5
6
7
8
@function dummy() {
       @if missing-dependencies(my-function, my-other-function) == true {
           @return null;
       }
 
       // `dummy` function’s core,
       // obviously needing `my-function` and `my-other-function` to work.
   }

Если функция не существует, то, как мы видели ранее, вызов будет рассматриваться как строка. Хотя строка оценивается как true , она не строго равна true , потому что это тип String , а не тип Bool .

Таким образом, на данный момент, если функция missing-dependencies не существует, вы не соответствуете условию, поэтому функция может работать нормально (хотя происходит сбой, если где-то в коде отсутствует отсутствующая зависимость). Что круто, потому что мы только улучшаем вещи, не ломая их.

Еще одна проблема, связанная с этой функцией, заключается в том, что она проверяет только отсутствующие функции, а не миксины или глобальные переменные. При этом, это легко выполнимо путем настройки кода проверки зависимостей.

Что если каждый аргумент, переданный функции, может быть списком из двух элементов, с типом зависимости в качестве первого аргумента (либо function , mixin или variable ) и именем зависимости в качестве второго? Например:

1
missing-dependencies(function my-function, variable my-cool-variable);

Если мы с большей вероятностью будем использовать функции в качестве зависимостей, мы могли бы сделать function по умолчанию, чтобы предыдущий вызов выглядел так:

1
missing-dependencies(my-function, variable my-cool-variable);

По сути, это все равно что просить проверить, существует ли функция my-function существует ли переменная my-cool-variable , потому что они необходимы для данной задачи. Пока ясно?

Теперь давайте перейдем к коду. Мы можем использовать функцию call() , чтобы вызвать {{ TYPE }}-exists({{ NAME }}) . Все остальное так же, как и раньше.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@function missing-dependencies ($dependencies…) {
       $missing-dependencies: ();
 
       @each $dependency in $dependencies {
           $type: «function»;
 
           @if length($dependency) == 2 {
               $type: nth($dependency, 1);
               $type: if(index(«function» «mixin» «variable», $type), $type, «function»);
               $dependency: nth($dependency, 2);
           }
 
           @if not call(«#{$type}-exists», $dependency) {
               $missing-dependencies: append($missing-dependencies, $dependency, comma);
           }
       }
 
       @if length($missing-dependencies) > 0 {
           @warn «Unmet dependencies! The following dependencies are required: #{$missing-dependencies}.»;
       }
 
       @return $missing-dependencies != 0;
   }

Довольно круто, а? Это делает код немного более сложным, поэтому, если вы, вероятно, не будете иметь различные типы зависимостей (что не в моем случае с SassyLists), я предлагаю вам придерживаться первой версии, которую мы видели.

Примечание . Возможно, вы захотите автоматически преобразовать тип variable в global-variable , чтобы вызвать global-variable-exists , поскольку вполне вероятно, что требуемая переменная является глобальной.

Это в значительной степени это люди. Очевидно, это не повседневная особенность Sass. Но я думаю, что во многих случаях, особенно при создании библиотек, фреймворков и расширений, такие советы могут пригодиться. Дайте мне знать, что вы думаете в комментариях!