Русский
Русский
English
Статистика
Реклама

Разработка веб-сайтов

Перевод О том, что происходит, когда в поиске Google используют слово vs

01.07.2020 16:06:10 | Автор: admin
Случалось у вас такое: ищете что-нибудь в Google и вводите после искомого слова vs, надеясь на то, что поисковик автоматически предложит вам что-то, немного похожее на то, что вам нужно?


Ввод vs после искомого слова

Со мной такое бывало.

Как оказалось, это большое дело. Это приём, который, при поиске альтернативы чему-либо, способен сэкономить массу времени.

Я вижу 3 причины, по которым этот приём отлично себя показывает в том случае, если его используют для поиска сведений о технологиях, неких разработках и концепциях, в которых хотят разобраться:

  1. Лучший способ изучить что-то новое заключается в том, чтобы выяснить, чем это, новое, похоже на то, что уже известно, или чем новое от известного отличается. Например, в списке предложений, появляющемся после vs, можно увидеть что-то такое, о чём можно сказать: А, так, оказывается, то, что я ищу, похоже на это, мне уже знакомое.
  2. Это простой приём. Для того чтобы им воспользоваться нужно, в буквальном смысле, несколько секунд.
  3. Слово vs это чёткое указание, говорящее Google о том, что пользователя интересует прямое сравнение чего-то с чем-то. Тут можно воспользоваться и словом or, но оно далеко не так сильно выражает намерение сравнить что-то с чем-то. Поэтому, если воспользоваться or, Google выдаст список предложений, в котором более вероятно появление чего-то постороннего.


Обрабатывая запрос bert or, Google выдаёт предложения, касающиеся Улицы Сезам. А запрос bert vs даёт подсказки по Google BERT

Это заставило меня задуматься. А что если взять те слова, что Google предложил после ввода vs, и поискать по ним, тоже добавляя после них vs? Что если повторить это несколько раз? Если так, можно получить симпатичный сетевой граф связанных запросов.

Например, он может выглядеть так.


Эго-граф для запроса bert с радиусом 25

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

Расскажу о том, как строить такие графы.

Автоматизация сбора vs-данных из Google


Вот ссылка, которую можно использовать для того чтобы получить от Google предложения по автозавершению запроса в формате XML. Эта возможность не выглядит как API, предназначенный для широкого использования, поэтому, вероятно, не стоит слишком сильно налегать на эту ссылку.

http://suggestqueries.google.com/complete/search?&output=toolbar&gl=us&hl=en&q=<search_term>

URL-параметр output=toolbar указывает на то, что нас интересуют результаты в формате XML, gl=us задаёт код страны, hl=en позволяет указать язык, а конструкция q=<search_term> это как раз то, для чего нужно получить результаты автозавершения.

Для параметров gl и hl используются стандартные двухбуквенные идентификаторы стран и языков.

Давайте со всем этим поэкспериментируем, начав поиск, скажем, с запроса tensorflow.

Первый шаг работы заключается в том, чтобы обратиться по указанному URL, воспользовавшись следующей конструкцией, описывающей запрос: q=tensorflow%20vs%20. Вся ссылка при этом будет выглядеть так:

http://suggestqueries.google.com/complete/search?&output=toolbar&gl=us&hl=en&q=tensorflow%20vs%20

В ответ мы получим XML-данные.

Что делать с XML?


Теперь нужно проверить полученные результаты автозавершения на соответствие некоему набору критериев. С теми, которые нам подойдут, мы продолжим работу.


Проверка полученных результатов

Я, при проверке результатов, пользовался следующими критериями:

  • Рекомендованный поисковый запрос не должен содержать текст исходного запроса (то есть tensorflow).
  • Рекомендация не должна включать в себя запросы, которые были признаны подходящими ранее (например pytorch).
  • Рекомендация не должна включать в себя несколько слов vs.
  • После того, как найдено 5 подходящих поисковых запросов, все остальные уже не рассматриваются.

Это лишь один из способов очистки полученного от Google списка рекомендаций по автозавершению поискового запроса. Я, кроме того, иногда вижу пользу в том, чтобы выбирать из списка только рекомендации, состоящие исключительно из одного слова, но использование этого приёма зависит от каждой конкретной ситуации.

Итак, используя этот набор критериев, мы получили 5 следующих результатов, каждому из которых присвоен определённый вес.


5 результатов

Следующая итерация


Затем эти 5 найденных рекомендаций подвергают такой же обработке, какой был подвергнут исходный поисковый запрос. Их передают API с использованием слова vs и опять выбирают 5 результатов автозавершения, соответствующих вышеозначенным критериям. Вот результат такой обработки вышеприведённого списка.


Поиск результатов автозавершения для уже найденных слов

Этот процесс можно продолжать, изучая ещё не исследованные слова из столбца target.

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

Эго-графы


Сетевой граф, который я вам показывал в начале статьи, это так называемый эго-граф (ego graph), построенный, в нашем случае, для запроса tensorflow. Эго-граф это такой граф, все узлы которого находятся на каком-то расстоянии от узла tensorflow. Это расстояние не должно превышать заданного расстояния.

А как определяется расстояние между узлами?

Давайте сначала посмотрим на готовый граф.


Эго-граф для запроса tensorflow с радиусом 22

Вес ребра (weight), соединяющего запрос A и B, мы уже знаем. Это ранг рекомендации из списка автозавершения, изменяющийся от 1 до 5. Для того чтобы сделать граф неориентированным, можно просто сложить веса связей между вершинами, идущими в двух направлениях (то есть от A к B, и, если такая связь есть, от B к A). Это даст нам веса рёбер в диапазоне от 1 до 10.

Длина ребра (distance), таким образом, будет вычисляться по формуле 11 вес ребра. Мы выбрали здесь число 11 из-за того, что максимальный вес ребра 10 (ребро будет иметь такой вес в том случае, если обе рекомендации появляются на самом верху списков автозавершения друг для друга). В результате минимальным расстоянием между запросами будет 1.

Размер (size) и цвет (color) вершины графа определяется количеством (count) случаев, в которых соответствующий запрос появляется в списке рекомендаций. В результате получается, что чем больше вершина тем важнее представляемая ей концепция.

Рассматриваемый эго-граф имеет радиус (radius) 22. Это означает, что добраться до каждого запроса, начиная с вершины tensorflow, можно, пройдя расстояние, не превышающее 22. Взглянем на то, что произойдёт, если увеличить радиус графа до 50.


Эго-граф для запроса tensorflow с радиусом 50

Интересно получилось! Этот граф содержит большинство базовых технологий, о которых надо знать тем, кто занимается искусственным интеллектом. При этом названия этих технологий логически сгруппированы.

И всё это построено на основе одного единственного ключевого слова.

Как рисовать подобные графы?


Я, для рисования такого графа, использовал онлайн-инструмент Flourish.

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

Как создать эго-граф с заданным радиусом?


Для создания эго-графа с заданным радиусом можно воспользоваться Python-пакетом networkx. В нём есть очень удобная функция ego_graph. Радиус графа указывают при вызове этой функции.

import networkx as nx#Формат исходных данных#nodes = [('tensorflow', {'count': 13}),# ('pytorch', {'count': 6}),# ('keras', {'count': 6}),# ('scikit', {'count': 2}),# ('opencv', {'count': 5}),# ('spark', {'count': 13}), ...]#edges = [('pytorch', 'tensorflow', {'weight': 10, 'distance': 1}),# ('keras', 'tensorflow', {'weight': 9, 'distance': 2}),# ('scikit', 'tensorflow', {'weight': 8, 'distance': 3}),# ('opencv', 'tensorflow', {'weight': 7, 'distance': 4}),# ('spark', 'tensorflow', {'weight': 1, 'distance': 10}), ...]#Построить исходный полный графG=nx.Graph()G.add_nodes_from(nodes)G.add_edges_from(edges)#Построить эго-граф для 'tensorflow'EG = nx.ego_graph(G, 'tensorflow', distance = 'distance', radius = 22)#Найти двусвязные подграфыsubgraphs = nx.algorithms.connectivity.edge_kcomponents.k_edge_subgraphs(EG, k = 3)#Получить подграф, содержащий 'tensorflow'for s in subgraphs:if 'tensorflow' in s:breakpruned_EG = EG.subgraph(s)ego_nodes = pruned_EG.nodes()ego_edges = pruned_EG.edges()

Я, кроме того, воспользовался тут ещё одной функцией k_edge_subgraphs. Она применяется для удаления некоторых результатов, которые не соответствуют нашим нуждам.

Например, storm это опенсорсный фреймворк для распределённых вычислений в реальном времени. Но это ещё и персонаж из вселенной Marvel. Как вы думаете, какие поисковые подсказки победят, если ввести в Google запрос storm vs?

Функция k_edge_subgraphs находит группы вершин, которые невозможно разделить, выполнив k или меньшее число действий. Как оказалось, тут хорошо показывают себя значения параметров k=2 и k=3. Остаются, в итоге, только те подграфы, которым принадлежит tensorflow. Это позволяет обеспечить то, что мы не слишком удаляемся от того, с чего начали поиск, и не уходим в слишком далёкие области.

Использование эго-графов в жизни


Давайте отойдём от примера с tensorflow и рассмотрим другой эго-граф. В этот раз граф, посвящённый ещё кое-чему такому, что меня интересует. Это шахматный дебют, получивший название Испанская партия (Ruy Lopez chess opening).

Исследование шахматных дебютов



Исследование Испанской партии (ruy lopez)

Наша методика позволила быстро обнаружить самые распространённые идеи дебютов, что может помочь исследователю шахмат.

Теперь давайте рассмотрим другие примеры использования эго-графов.

Здоровое питание


Капуста! Вкуснятина!

Но что если у вас возникло желание заменить прекрасную, несравненную капусту на что-то другое? Вам в этом поможет эго-граф, построенный вокруг капусты (kale).


Эго-граф для запроса kale с радиусом 25

Покупаем собаку


Собак так много, а времени так мало Мне нужна собака. Но какая? Может что-то вроде пуделя (poodle)?


Эго-граф для запроса poodle с радиусом 18

Ищем любовь


Собака и капуста ничего не меняют? Нужно найти свою вторую половину? Если так вот маленький, но весьма самодостаточный эго-граф, который может в этом помочь.


Эго-граф для запроса coffee meets bagel с радиусом 18

Что делать, если приложения для знакомств ничем не помогли?


Если приложения для знакомств оказались бесполезными, стоит, вместо того, чтобы в них зависать, посмотреть сериал, запасшись мороженым со вкусом капусты (или со вкусом недавно обнаруженной рукколы). Если вам нравится сериал The Office (безусловно, тот, что снят в Великобритании), то вам могут понравиться и некоторые другие сериалы.


Эго-граф для запроса the office с радиусом 25

Итоги


На этом я завершаю рассказ об использовании слова vs в поиске Google и об эго-графах. Надеюсь, вам всё это хотя бы немного поможет в поиске любви, хорошей собаки и здоровой еды.

Пользуетесь ли вы какими-нибудь необычными приёмами при поиске в интернете?



Подробнее..

Перевод Зачем в npm 7 оставили поддержку package-lock.json?

02.07.2020 16:18:36 | Автор: admin
Мне, с того момента, как мы объявили о том, что в npm 7 будут поддерживаться файлы yarn.lock, несколько раз задавали один и тот же вопрос. Он звучал так: Зачем тогда оставлять поддержку package-lock.json? Почему бы не использовать только yarn.lock?.



Краткий ответ на этот вопрос выглядит так: Потому что yarn.lock не полностью удовлетворяет нуждам npm. Если полагаться исключительно на него, это ухудшит возможности npm по формированию оптимальных схем установки пакетов и возможности по добавлению в проект нового функционала. Ответ более подробный представлен в данном материале.

Базовая структура файла yarn.lock


Файл yarn.lock представляет собой описание соответствия спецификаторов зависимостей пакетов и метаданных, описывающих разрешение этих зависимостей. Например:

mkdirp@1.x:version "1.0.2"resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.2.tgz#5ccd93437619ca7050b538573fc918327eba98fb"integrity sha512-N2REVrJ/X/jGPfit2d7zea2J1pf7EAR5chIUcfHffAZ7gmlam5U65sAm76+o4ntQbSRdTjYf7qZz3chuHlwXEA==

В этом фрагменте сообщается следующее: Любая зависимость от mkdirp@1.x должна разрешаться именно в то, что указано здесь. Если несколько пакетов зависят от mkdirp@1.x, то все эти зависимости будут разрешены одинаково.

В npm 7, если в проекте существует файл yarn.lock, npm будет пользоваться содержащимися в нём метаданными. Значения полей resolved сообщат npm о том, откуда ему нужно загружать пакеты, а значения полей integrity будут использоваться для проверки того, что получено, на предмет соответствия этого тому, что ожидалось получить. Если пакеты добавляются в проект или удаляются из него, соответствующим образом обновляется содержимое yarn.lock.

Npm при этом, как и прежде, создаёт файл package-lock.json. Если в проекте присутствует этот файл, он будет использоваться как авторитетный источник сведений о структуре (форме) дерева зависимостей.

Вопрос тут заключается в следующем: Если yarn.lock достаточно хорош для менеджера пакетов Yarn почему npm не может просто использовать этот файл?.

Детерминированные результаты установки зависимостей


Результаты установки пакетов с помощью Yarn гарантированно будут одними и теми же при использовании одного и того же файла yarn.lock и одной и той же версии Yarn. Применение различных версий Yarn может привести к тому, что файлы пакетов на диске будут расположены по-разному.

Файл yarn.lock гарантирует детерминированное разрешение зависимостей. Например, если foo@1.x разрешается в foo@1.2.3, то, учитывая использование одного и того же файла yarn.lock, это будет происходить всегда, во всех версиях Yarn. Но это (как минимум, само по себе) не эквивалентно гарантии детерминированности структуры дерева зависимостей!

Рассмотрим следующий граф зависимостей:

root -> (foo@1, bar@1)foo -> (baz@1)bar -> (baz@2)

Вот пара схем деревьев зависимостей, каждое из которых можно признать корректным.

Дерево 1:

root+-- foo+-- bar|  +-- baz@2+-- baz@1

Дерево 2:

+-- foo|  +-- baz@1+-- bar+-- baz@2

Файл yarn.lock не может сообщить нам о том, какое именно дерево зависимостей нужно использовать. Если в пакете root будет выполнена команда require(baz) (что некорректно, так как эта зависимость не отражена в дереве зависимостей), файл yarn.lock не гарантирует правильного выполнения этой операции. Это форма детерминизма, которую может дать файл package-lock.json, но не yarn.lock.

На практике, конечно, так как у Yarn, в файле yarn.lock, есть вся информация, необходимая для того чтобы выбрать подходящую версию зависимости, выбор является детерминированным до тех пор, пока все используют одну и ту же версию Yarn. Это означает, что выбор версии всегда делается одним и тем же образом. Код не меняется до тех пор, пока кто-нибудь его не изменит. Надо отметить, что Yarn достаточно интеллектуален для того, чтобы, при создании дерева зависимостей, не зависеть от расхождений, касающихся времени загрузки манифеста пакета. Иначе детерминированность результатов гарантировать было бы нельзя.

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

Другими словами, на то, как именно Yarn строит дерево зависимостей, влияют файл yarn.lock и реализация самого Yarn. А в npm на то, каким будет дерево зависимостей, влияет только файл package-lock.json. Благодаря этому структуру проекта, описанную в package-lock.json, становится сложнее случайно нарушить, пользуясь разными версиями npm. А если же в файл будут внесены изменения (может быть по ошибке, или намеренно), эти изменения будут хорошо заметны в файле при добавлении его изменённой версии в репозиторий проекта, в котором используется система контроля версий.

Вложенные зависимости и дедупликация зависимостей


Более того, существует целый класс ситуаций, предусматривающих работу с вложенными зависимостями и дедупликацию зависимостей, когда файл yarn.lock не способен точно отразить результат разрешения зависимостей, который будет, на практике, использоваться npm. Причём, это справедливо даже для тех случаев, когда npm использует yarn.lock в качестве источника метаданных. В то время как npm использует yarn.lock как надёжный источник информации, npm не рассматривает этот файл в роли авторитетного источника сведений об ограничениях, накладываемых на версии зависимостей.

В некоторых случаях Yarn формирует дерево зависимостей с очень высоким уровнем дублирования пакетов, а нам это ни к чему. В результате оказывается, что точное следование алгоритму Yarn в подобных случаях это далеко не идеальное решение.

Рассмотрим следующий граф зависимостей:

root -> (x@1.x, y@1.x, z@1.x)x@1.1.0 -> ()x@1.2.0 -> ()y@1.0.0 -> (x@1.1, z@2.x)z@1.0.0 -> ()z@2.0.0 -> (x@1.x)

Проект root зависит от версий 1.x пакетов x, y и z. Пакет y зависит от x@1.1 и от z@2.x. У пакета z версии 1 нет зависимостей, но этот же пакет версии 2 зависит от x@1.x.

На основе этих сведений npm формирует следующее дерево зависимостей:

root (x@1.x, y@1.x, z@1.x) <-- здесь зависимость x@1.x+-- x 1.2.0        <-- x@1.x разрешается в 1.2.0+-- y (x@1.1, z@2.x)|  +-- x 1.1.0      <-- x@1.x разрешается в 1.1.0|  +-- z 2.0.0 (x@1.x)  <-- здесь зависимость x@1.x+-- z 1.0.0

Пакет z@2.0.0 зависит от x@1.x, то же самое можно сказать и о root. Файл yarn.lock сопоставляет x@1.x c 1.2.0. Однако зависимость пакета z, где тоже указано x@1.x, вместо этого, будет разрешена в x@1.1.0.

В результате, даже хотя зависимость x@1.x описана в yarn.lock, где указано, что она должна разрешаться в версию пакета 1.2.0, имеется второй результат разрешения x@1.x в пакет версии 1.1.0.

Если запустить npm с флагом --prefer-dedupe, то система пойдёт на шаг дальше и установит лишь один экземпляр зависимости x, что приведёт к формированию следующего дерева зависимостей:

root (x@1.x, y@1.x, z@1.x)+-- x 1.1.0    <-- x@1.x для всех зависимостей разрешается в версию 1.1.0+-- y (x@1.1, z@2.x)|  +-- z 2.0.0 (x@1.x)+-- z 1.0.0

Это минимизирует дублирование зависимостей, получившееся дерево зависимостей фиксируется в файле package-lock.json.

Так как файл yarn.lock фиксирует лишь порядок разрешения зависимостей, а не результирующее дерево пакетов, Yarn сформирует такое дерево зависимостей:

root (x@1.x, y@1.x, z@1.x) <-- здесь зависимость x@1.x+-- x 1.2.0        <-- x@1.x разрешается в 1.2.0+-- y (x@1.1, z@2.x)|  +-- x 1.1.0      <-- x@1.x разрешается в 1.1.0|  +-- z 2.0.0 (x@1.x)  <-- x@1.1.0 тут бы подошёл, но...|    +-- x 1.2.0    <-- Yarn создаёт дубликат ради выполнения того, что описано в yarn.lock+-- z 1.0.0

Пакет x, при использовании Yarn, появляется в дереве зависимостей три раза. При применении npm без дополнительных настроек 2 раза. А при использовании флага --prefer-dedupe лишь один раз (хотя тогда в дереве зависимостей оказывается не самая новая и не самая лучшая версия пакета).

Все три получившихся дерева зависимостей можно признать корректными в том смысле, что каждый пакет получит те версии зависимостей, которые соответствуют заявленным требованиям. Но нам не хотелось бы создавать деревья пакетов, в которых слишком много дубликатов. Подумайте о том, что будет, если x это большой пакет, у которого есть много собственных зависимостей!

В результате имеется единственный способ, используя который, npm может оптимизировать дерево пакетов, поддерживая, в то же время, создание детерминированных и воспроизводимых деревьев зависимостей. Этот способ заключается в применении lock-файла, принцип формирования и использования которого на фундаментальном уровне отличается от yarn.lock.

Фиксация результатов реализации намерений пользователя


Как уже было сказано, в npm 7 пользователь может использовать флаг --prefer-dedupe для того чтобы был бы применён алгоритм генерирования дерева зависимостей, при выполнении которого приоритет отдаётся дедупликации зависимостей, а не стремлению всегда устанавливать самые свежие версии пакетов. Применение флага --prefer-dedupe обычно идеально подходит в ситуациях, когда дублирование пакетов нужно свести к минимуму.

Если используется этот флаг, то итоговое дерево для вышеприведённого примера будет выглядеть так:

root (x@1.x, y@1.x, z@1.x) <-- здесь зависимость x@1.x+-- x 1.1.0        <-- x@1.x разрешается в 1.1.0 во всех случаях+-- y (x@1.1, z@2.x)|  +-- z 2.0.0 (x@1.x)  <-- здесь зависимость x@1.x+-- z 1.0.0

В данном случае npm видит, что даже хотя x@1.2.0 это самая свежая версия пакета, удовлетворяющая требованию x@1.x, вместо неё вполне можно выбрать x@1.1.0. Выбор этой версии приведёт к меньшему уровню дублирования пакетов в дереве зависимостей.

Если не фиксировать структуру дерева зависимостей в lock-файле, то каждому программисту, работающему над проектом в команде, пришлось бы настраивать свою рабочую среду точно так же, как её настраивают остальные члены команды. Только это позволит ему получить тот же результат, что и остальные. Если реализация механизма построения дерева зависимостей может быть изменена подобным способом, это даёт пользователям npm серьёзные возможности по оптимизации зависимостей в расчёте на собственные специфические нужды. Но, если результаты создания дерева зависят от реализации системы, это делает невозможным создание детерминированных деревьев зависимостей. Именно к этому приводит использование файла yarn.lock.

Вот ещё несколько примеров того, как дополнительные настройки npm способны приводить к созданию отличающихся друг от друга деревьев зависимостей:

  • --legacy-peer-deps, флаг, который заставляет npm полностью игнорировать peerDependencies.
  • --legacy-bundling, флаг, говорящий npm о том, что он не должен даже пытаться сделать дерево зависимостей более плоским.
  • --global-style, флаг, благодаря которому всех транзитивные зависимости устанавливаются в виде вложенных зависимостей, в папках зависимостей более высокого уровня.

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

Фиксация же структуры готового дерева зависимостей позволяет нам давать в распоряжение пользователей подобные возможности и при этом не нарушать процесс построения детерминированных и воспроизводимых деревьев зависимостей.

Производительность и полнота данных


Файл package-lock.json приносит пользу не только тогда, когда нужно обеспечить детерминированность и воспроизводимость деревьев зависимостей. Мы, кроме того, полагаемся на этот файл для отслеживания и хранения метаданных пакетов, значительно экономя время, которое иначе, с использованием только package.json, ушло бы на работу с реестром npm. Так как возможности файла yarn.lock сильно ограничены, в нём нет метаданных, которые нам нужно постоянно загружать.

В npm 7 файл package-lock.json содержит всё, что нужно npm для полного построения дерева зависимостей проекта. В npm 6 эти данные хранятся не так удобно, поэтому, когда мы сталкиваемся со старым lock-файлом, нам приходится нагружать систему дополнительной работой, но это делается, для одного проекта, лишь один раз.

В результате, даже если в yarn.lock и были записаны сведения о структуре дерева зависимостей, нам приходится использовать другой файл для хранения дополнительных метаданных.

Будущие возможности


То, о чём мы тут говорили, может серьёзно измениться, если учитывать различные новые подходы к размещению зависимостей на дисках. Это pnpm, yarn 2/berry и PnP Yarn.

Мы, работая над npm 8, собираемся исследовать подход к формированию деревьев зависимостей, основанный на виртуальной файловой системе. Эта идея смоделирована в Tink, работоспособность концепции подтверждена в 2019 году. Мы, кроме того, обсуждаем идею перехода на что-то вроде структуры, используемой pnpm, хотя это, в некотором смысле, даже более масштабное кардинальное изменение, чем использование виртуальной файловой системы.

Если все зависимости находятся в некоем центральном хранилище, а вложенные зависимости представлены лишь символьными ссылками или виртуальной файловой системой, тогда моделирование структуры дерева зависимостей было бы для нас не таким важным вопросом. Но нам всё ещё нужно больше метаданных, чем способен предоставить файл yarn.lock. В результате больше смысла имеет обновление и рационализация существующего формата файла package-lock.json, а не полный переход на yarn.lock.

Это не статья, которую можно было бы назвать О вреде yarn.lock


Мне хотелось бы особо отметить то, что, судя по тому, что я знаю, Yarn надёжно создаёт корректные деревья зависимостей проектов. И, для определённой версии Yarn (на момент написания материала это относится ко всем свежим версиям Yarn), эти деревья являются, как и при использовании npm, полностью детерминированными.

Файла yarn.lock достаточно для создания детерминированных деревьев зависимостей с использованием одной и той же версии Yarn. Но мы не можем полагаться на механизмы, зависящие от реализации менеджера пакетов, учитывая использование подобных механизмов во многих инструментах. Это ещё более справедливо, если учесть то, что реализация формата файла yarn.lock нигде формально не документирована. (Это не проблема, уникальная для Yarn, в npm сложилась такая же ситуация. Документирование форматов файлов это довольно серьёзная работа.)

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

Отклонения от изначально зафиксированной структуры дерева зависимостей должны быть результатом явно выраженного желания пользователя. Такие отклонения должны сами себя документировать, внося изменения в зафиксированные ранее данные о структуре дерева зависимостей.

Только package-lock.json, или механизм, подобный этому файлу, способен дать npm такие возможности.

Каким менеджером пакетов вы пользуетесь в своих JavaScript-проектах?

Подробнее..

Перевод Стилизация контейнеров для содержимого веб-страниц

03.07.2020 16:05:03 | Автор: admin
Содержимое веб-страниц должно быть размещено в некоем элементе, ширина которого, ограничивающая ширину содержимого, позволяет пользователям удобно работать с материалами сайта. Такие элементы называют обёртками (wrapper) или контейнерами (container). Стилизовать контейнеры средствами CSS можно по-разному. Некоторые способы работы с контейнерами ставят дизайнера перед необходимостью решать достаточно сложные задачи.



В этом материале я расскажу о контейнерах для содержимого веб-страниц: о том, как они работают, о том, как ими пользоваться, и о тех случаях, когда лучше обойтись без них. Обратите внимание на то, что я буду использовать здесь термины обёртка и контейнер как равнозначные.

Общие сведения


Когда вы, при разговоре о некоем элементе веб-страницы, узнаёте о том, что речь идёт об обёртке или о контейнере, это значит, что, на самом деле, перед вами группа элементов, которая обёрнута в другой элемент или размещена внутри этого элемента. Если, настраивая веб-страницу, не пользоваться дополнительными элементами, отведя роль контейнера элементу <body>, то стилизовать этот элемент можно так:

body {max-width: 1170px;margin-left: auto;margin-right: auto;padding-left: 16px;padding-right: 16px;}

Но в современных условиях использование в качестве контейнера элемента <body> может оказаться непрактичным. Контейнер позволяет не допустить выхода дочерних элементов за его границы.


Контейнер не даёт дочерним элементам выходить за его границы

Здесь имеется боковая и основная области страницы. Обе эти области находятся внутри элемента-контейнера. Ему назначен класс .wrapper. Среди прочих свойств контейнера, естественно, задана и его ширина. Структура HTML-кода такой страницы выглядит так:

<div class="wrapper"><aside>...</aside><main>...</main></div>

Без использования элемента-обёртки дочерние элементы будут размещаться, ориентируясь на края экрана. Это может быть неудобным для пользователей. Особенно для тех, которые работают на больших экранах.


Страница без элемента-контейнера, включающего в себя её содержимое

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

О необходимости использования контейнеров для содержимого веб-страниц


Использование контейнера для содержимого веб-страниц имеет множество сильных сторон, о которых стоит знать дизайнерам и разработчикам. Вот некоторые из этих сильных сторон:

  1. Использование контейнера улучшает читабельность содержимого страницы. Без контейнера содержимое, вроде текста, может растягиваться на всю ширину экрана. На маленьких экранах подобное может давать вполне приемлемый результат. Но на больших экранах это выглядит очень плохо.
  2. Группировка элементов дизайна страницы упрощает настройку расстояния между ними.
  3. Если элементы дизайна нужно сгруппировать по столбцам, это будет сложно сделать без использования контейнера.

Настройка элемента-контейнера средствами CSS


Теперь, когда мы поговорили об основах применения контейнеров, и о том, какие преимущества даёт их использование, давайте остановимся на том, как настраивать их средствами CSS.

Настройка ширины контейнера



Элемент-контейнер с настроенной шириной

При создании контейнера первое, что нужно решить, это то, какой ширины он будет. На вопрос о желаемой ширине контейнера можно ответить, проанализировав дизайн страницы. В целом, можно сказать, что чаще всего используются контейнеры с шириной, находящейся в пределах 1000px 1300px. Например, в популярном фреймворке Bootstrap используется ширина, равная 1170px.

.wrapper {width: 1170px;}

Здесь показана установка ширины элемента с классом .wrapper в 1170px, но, на самом деле, свойство width для настройки ширины контейнеров использовать не рекомендуется. Дело в том, что это приводит к необходимости горизонтального скроллинга страницы в том случае, если ширина области окна браузера, доступной для вывода страницы, меньше 1170px. Решить эту проблему можно, воспользовавшись свойством max-width:

.wrapper {width: 1170px;max-width: 100%;}

Хотя это вполне рабочий приём, можно полностью избавиться от свойства width и, как в следующем примере, пользоваться лишь свойством max-width:

.wrapper {max-width: 1170px;}

Теперь, когда мы нашли подходящий механизм настройки ширины контейнера, давайте поговорим о том, как выровнять контейнер по центру страницы.

Выравнивание контейнера по центру страницы



Контейнер, выровненный по центру страницы

Для того чтобы выровнять контейнер по центру страницы, нужно, при настройке его внешних отступов, воспользоваться значением auto для левого и правого отступов:

.wrapper {max-width: 1170px;margin: 0 auto;}

Вот как, в соответствии со спецификацией CSS, ведут себя отступы, которым назначено значение auto:

Если margin-left и margin-right установлены в значение auto, то значения, которые будут использованы для этих отступов, будут одними и теми же. Это позволяет центрировать элемент по горизонтали относительно краёв содержащего его блока.

Если вас интересуют подробности об использовании ключевого слова auto в CSS взгляните на эту мою статью.

Я воспользовался здесь конструкцией margin: 0 auto. Она сбрасывает размеры верхнего и нижнего отступов в значение 0, а левый и правый отступы настраивает в соответствии с особенностями применения ключевого слова auto. У такого шага есть некоторые последствия, о которых я расскажу ниже. А пока же хочу отметить, что рекомендуется использовать полный вариант вышеописанной сокращённой конструкции для настройки внешних отступов:

.wrapper {max-width: 1170px;margin-left: auto;margin-right: auto;}

Настройка левого и правого внутренних отступов



Горизонтальные (левый и правый) внутренние отступы

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

.wrapper {max-width: 1170px;margin-left: auto;margin-right: auto;padding-left: 16px;padding-right: 16px;}

Настроив внутренние отступы контейнера, мы можем быть уверены в том, что края контейнера, в любом случае, будут находиться как минимум в 16px от краёв области просмотра, даже в том случае, если ширина области просмотра окажется меньше максимальной ширины контейнера. Внутренние отступы это нечто вроде защитного механизма, который не даёт границам контейнера прижиматься к границам области просмотра даже в том случае, когда область просмотра уже, чем максимальная ширина контейнера.

Использование процентных значений при настройке контейнеров


Мне, после публикации исходного варианта этого материала, написали об использовании процентных значений при настройке контейнеров. В частности, речь идёт о применении CSS-свойства max-width: 90% вместо использования свойств padding-left и padding-right.


Использование процентных значений при настройке контейнеров и ситуации, когда значение max-width: 90% приводит к приемлемым и неприемлемым результатам

Хотя этот подход оказался вполне рабочим, выяснилось, что на больших экранах 90% ширины области просмотра это слишком много. Но данную проблему можно решить, воспользовавшись медиа-запросом:

.wrapper {max-width: 90%;margin-left: auto;margin-right: auto;}/* Медиа-запрос для больших экранов */@media (min-width: 1170px) {.wrapper {max-width: 1170px;}}

В результате оказывается, что, используя процентное значение, мы усложняем CSS-код. Для того чтобы избавить себя от необходимости применения медиа-запроса, мы можем использовать фиксированное значение для ширины. Ещё одно решение, предложенное в этом твите, заключается в применении комбинации свойств width: 90% и max-width: 1170px:

.wrapper {width: 90%;max-width: 1170px;margin-left: auto;margin-right: auto;}

Это интересный подход, но я предпочёл бы настраивать внутренние отступы самостоятельно, не полагаясь на процентные значения.

Свойство display элемента-контейнера


Так как для оформления контейнеров используют теги <div>, контейнеры, по умолчанию, являются блочными элементами. Что если понадобится поменять свойство контейнера display на grid, сделав это для того чтобы разместить его дочерние элементы в сетке?

Я не рекомендую этого делать, так как это идёт вразрез с идеей разделения ответственностей. Элемент-контейнер, обёртка, это сущность, предназначение которой заключается в том, чтобы оборачивать другие элементы. Если нужно разместить дочерние элементы контейнера в сетке, тогда стоит добавить в контейнер ещё один <div>, включающий в себя другие элементы, свойство которого display установлено в значение grid. Это будет проще и чище, чем настройка сетки средствами основного контейнера. Такой подход, кроме того, позволяет говорить о том, что в будущем проект, в котором он используется, будет легче поддерживать.

Пусть имеется такой контейнер:

<div class="wrapper"><!-- Содержимое --></div>

Задавать свойство display: grid подобного элемента не рекомендуется из-за того, что такой элемент может использоваться на разных страницах. Его особые настройки могут случайно привести к нарушению размещения элементов. Вот неудачный вариант настройки контейнера, о котором идёт речь:

.wrapper {display: grid;grid-template-columns: 2fr 1fr;grid-gap: 16px;}

Лучше будет использовать такой HTML-код:

<div class="wrapper"><div class="featured-news"><!-- Элементы, которые нужно разместить в сетке --></div></div>

Элемент с классом featured-news можно стилизовать так:

.featured-news {display: grid;grid-template-columns: 2fr 1fr;grid-gap: 16px;}

Обратите внимание на то, что в этом примере мы использовали отдельный элемент <div> в качестве ещё одной обёртки для содержимого страницы. На имена классов, использованных здесь, можете внимания не обращать. Для решения подобной задачи можно подобрать более удачные имена классов, которые подойдут для многократного использования на различных страницах сайта. Однако именование CSS-сущностей выходит за рамки данного материала.

Настройка внешних отступов, разделяющих элементы-контейнеры


Помните, как выше я не рекомендовал использование сокращённого способа настройки внешних отступов для центрирования элемента-контейнера? Речь шла о такой конструкции:

.wrapper {margin: 0 auto;}

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

Я имею в виду такую схему стилизации:

.wrapper-variation {margin-top: 50px;}

Свойство margin для элемента с классом .wrapper-variation не будет применено к элементу из-за того, что его переопределяет свойство margin: 0 auto. Краткая форма настройки свойства переопределяет его полную форму. Для того чтобы подобного избежать, рекомендуется в таких случаях использовать полную форму записи свойств. То есть, при стилизации элемента с классом .wrapper нужно поступить так:

.wrapper {max-width: 1170px;margin-left: auto;margin-right: auto;padding-left: 16px;padding-right: 16px;}

Теперь поговорим о настройке внешних отступов элементов. При работе над каждым своим проектом я готовлю набор вспомогательных классов для настройки внутренних и внешних отступов. Я использую их там, где они нужны. Взгляните на следующий рисунок.


Автономный контейнер и контейнер внутри элемента <section>

Вот HTML-код:

<div class="wrapper mb-5"></div><section><div class="wrapper"></div></section><div class="wrapper"></div>

Вот стиль:

.mb-5 {margin-bottom: 3rem !important;}

При таком подходе CSS-код для элемента-обёртки остаётся в неизменном виде, а расстояния между элементами настраиваются с использованием вспомогательных CSS-классов. Тут у вас может появиться вопрос о том, зачем мне понадобилось использовать на странице несколько контейнеров, когда можно обойтись одним. Обратите внимание на то, что в вышеприведённом HTML-коде имеется элемент <section>, расположенный между двух элементов-обёрток.

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

Контейнер внутри полноэкранного элемента


В некоторых случаях бывает так, что есть элемент <section> с фоном, занимающий 100% ширины области просмотра, а внутри этого элемента имеется элемент-контейнер. Эта схема похожа на ту, которую мы рассматривали в предыдущем разделе.

HTML-структура страницы в такой ситуации может выглядеть так:

<section><div class="wrapper"></div></section><section><div class="wrapper"></div></section>

Элемент <section> занимает 100% ширины области просмотра. Этому элементу можно назначить фоновое изображение или фоновый цвет. Контейнер, находящийся внутри этого элемента, не даёт содержимому занимать всю ширину области просмотра.


Элемент <section> занимает всю ширину области просмотра, контейнер ограничивает пространство, в котором выводится содержимое страницы

На этом рисунке у элемента <section> задано фоновое изображение. Он занимает всю ширину области просмотра, а содержимое страницы, выводимое в контейнере, ограничено шириной контейнера.

Нужно ли заключать в контейнер содержимое верхнего блока страницы?


Нужен ли контейнер для оформления верхнего блока страницы, который часто называют Hero Section? Это зависит от каждой конкретной ситуации. Исследуем два самых распространённых подхода к оформлению верхних блоков страниц.

Первый подход предусматривает центровку содержимого блока и ограничение ширины содержимого.


Ширина содержимого верхнего блока страницы ограничена

Второй вариант предусматривает распределение содержимого в пределах верхнего блока.


Содержимое распределено в пределах верхнего блока страницы

Для того чтобы лучше разобраться в этих паттернах, предлагаю исследовать особенности их внутреннего устройства.

Верхний блок страницы, содержимое которого выровнено по центру


Возможно, разрабатывая верхний блок страницы, вам захочется разместить в соответствующем элементе <section> некое содержимое и выровнять его по центру без использования элемента-контейнера.

<section class="hero"><h2><font color="#3AC1EF">How to make bread at home</font></h2><p>....</p><p><a href="http://personeltest.ru/aways/habr.com/sign-up">Sign up</a></p></section>

При стилизации вышеприведённого HTML-кода выровнять его содержимое по центру можно, воспользовавшись свойством text-align:

.hero { text-align: center; }

При таком подходе всё будет выглядеть достойно до тех пор, пока не изменится ширина окна браузера. Вот разбор проблем, которые это может вызвать.

Проблема 1: содержимое раздела прижимается к краям области просмотра


Так как в данном примере не настроены правый и левый внутренние отступы, содержимое раздела будет размещено вплотную к границам этого раздела. Это создаёт неудобства для пользователей, так как им сложнее будет читать содержимое верхнего блока страницы.


Содержимое раздела прижато к его краям

Проблема 2: слишком большая длина строк текста на экранах большого размера


На экранах большого размера текст, оформленный тегом <p>, может быть очень тяжело читать из-за того, что длина абзаца окажется слишком большой. В соответствии с этим документом, рекомендованное число символов в строке составляет 45-75. Выход длины строки за пределы этого диапазона усложняет чтение.


Длина строки слишком велика

Решение проблем


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

<section class="hero"><div class="hero__wrapper"><h2><font color="#3AC1EF">How to make bread at home</font></h2><p>...</p><p><a href="http://personeltest.ru/aways/habr.com/sign-up">Sign up</a></p></div></section>

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

.hero__wrapper {max-width: 720px;margin-left: auto;margin-right: auto;padding-left: 16px;padding-right: 16px;}

Выровнять содержимое верхнего блока страницы по центру можно, используя любой удобный подход. Тут всё зависит от каждой конкретной ситуации. В данном примере для выравнивания контента достаточно воспользоваться свойством text-align: center.

Как выравнивать контейнер: по центру, или по левому краю страницы?


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

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


Выравнивание содержимого на экране ноутбука и на экране настольного компьютера

Мне нравится работать с сайтами, содержимое которых выровнено по центру. На страницах таких сайтов имеются симметричные отступы слева и справа. Но у того, что содержимое некоторых сайтов выравнивают по левому краю страницы, может быть какая-то причина, о которой я не знаю. Если вы знаете о том, почему это так, дайте мне знать.

Использование CSS-переменных для создания различных вариантов стилизации контейнеров


Редко бывает так, что все элементы-контейнеры, используемые в некоем проекте, имеют одну и ту же ширину. Ширина контейнера может меняться в зависимости от содержимого контейнера и от того, как именно он используется. Большую гибкость в работе с контейнерами даёт использование CSS-переменных. Это, кроме того, весьма современный подход к настройке контейнеров. Рассмотрим пример.

Вот HTML-код контейнера:

<div class="wrapper"></div>

Вот стиль:

.wrapper {max-width: var(--wrapper-width, 1170px);margin-left: auto;margin-right: auto;padding-left: 16px;padding-right: 16px;}

Если вы внимательно прочли CSS-код, вы могли заметить, что var() передаётся два значения: первое это переменная --wrapper-width, второе это обычное значение 1170px. Второе значение является запасным. Смысл его существования заключается в том, что оно будет использовано в том случае, если значение переменной --wrapper-width окажется неустановленным.

Что это значит? А это значит, что в наших руках оказывается инструмент для создания различных вариантов элементов-обёрток благодаря возможности переопределения значения переменной --wrapper-width. Выглядит это так:

<div class="wrapper" style="--wrapper-width: 720px"></div>

Благодаря этому подходу я смог создать особый контейнер, не прибегая при этом к следующим действиям:

  • Добавление к элементу нового класса.
  • Копирование и дублирование существующих стилей.

Такой элемент будет проще поддерживать в будущем. И, кроме того, облегчается его настройка с помощью инструментов разработчика браузера.

Если вам не нравится этот подход из-за использования в нём встроенного стиля для переопределения значения CSS-переменной, вы вполне можете решить эту задачу и с помощью добавления к элементу нового класса. Например так, как показано ниже.

Вот HTML-разметка:

<div class="wrapper wrapper--small"></div>

Так выглядит стиль:

.wrapper--small {--wrapper-width: 720px;/* благодаря этому стандартная ширина контейнера будет переопределена. */}

Здесь можно найти рабочий пример.

Использование display: contents


Для начала позвольте немного рассказать о значении contents свойства display. Каждый элемент в CSS это блок. В этом блоке что-то содержится, у него есть внутренние и внешние отступы и граница. Использование свойства display: contents приводит к тому, что блок, которому оно назначено, удаляется из потока документа. Это можно представить себе как удаление открывающего и закрывающего тегов блока.

Вот разметка:

<header class="site-header"><div class="wrapper site-header__wrapper"><!-- Содержимое заголовочной области сайта --></div></header>

Вот стиль:

.site-header__wrapper {display: flex;flex-wrap: wrap;justify-content: space-between;}


Элемент-обёртка

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

.site-header__wrapper {display: contents;}.site-header {display: flex;flex-wrap: wrap;justify-content: space-between;}

Здесь, благодаря использованию свойства display: contents, элемент-обёртка будет как бы скрыт. Теперь, когда свойство display: flex применяется к элементу с классом .site-header, дочерние элементы контейнера становятся дочерними элементами .site-header.


Заголовочная часть сайта занимает, в ширину, всё доступное пространство

Отзывчивый фон и фиксированное содержимое


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

Вот HTML-разметка:

<section><div class="wrapper"></div></section>

Вот стили:

section {background-color: #ccc;}.wrapper {max-width: 1170px;margin-left: auto;margin-right: auto;padding-left: 16px;padding-right: 16px;}

Здесь значения свойств margin-left: auto и margin-right: auto вычисляются путём взятия половины ширины области просмотра и вычитания из неё ширины содержимого. Того же самого можно добиться с использованием внутренних отступов.


Внутренние отступы

section {padding: 1rem calc(50% - 585px);}

Но дело пока ещё не сделано. На мобильных устройствах содержимое будет прижато к краям области просмотра. Решить эту проблему можно, например, так:

section {padding: 1rem;}@media (min-width: 1170px) {section {padding: 1rem calc(50% - 585px);}}

В качестве альтернативного решения можно предложить применение новой CSS-функции max(). Используя её, мы задаём минимальный размер внутреннего отступа, равный 1rem, а в качестве второго значения, передаваемого ей, указываем выражение 50% 585px.

section {padding: 1rem max(1rem, (50% - 585px));}

Если вам интересны подробности о CSS-функциях min(), max() и clamp() вот мой материал на эту тему.

Как вы стилизуете элементы-контейнеры?

Подробнее..

Перевод Обзор технологий скроллинга

04.07.2020 16:09:21 | Автор: admin
Анимации, имеющие отношение к скроллингу веб-страниц, существуют уже многие годы. В последнее время подобные анимации стали распространённее. Возможно, дело тут отчасти в том, что устройства, используемые для работы в интернете, стали мощнее. Эти устройства способны нормально обрабатывать и выводить больше визуальных эффектов, чем раньше.



Существует множество технологий, связанных со скроллингом. Цель этой статьи заключается в том, чтобы дать обзор таких технологий и инструментов, которые помогут подобрать и создать то, что нужно в каждой конкретной ситуации. Я разделил бы технологии скроллинга на две широкие категории. В первую входят технологии для реализации специфических механизмов скроллинга, во вторую технологии скроллинга общего назначения.

Технологии для реализации специфических механизмов скроллинга


В CSS существует несколько простых стандартных эффектов скроллинга, поддерживаемых современными браузерами. В некоторых случаях их применения может быть достаточно для того чтобы обеспечить особенные нужды некоего проекта.

CSS-свойство position: sticky


Если вам нужно, чтобы некий элемент не прокручивался бы вместе с остальным содержимым страницы, то при стилизации этого элемента достаточно применить свойство position: sticky. Это простой и понятный приём, его поддержка встроена в современные браузеры. Но для того, чтобы это работало бы в IE и в некоторых мобильных браузерах, понадобится полифилл. Если вам интересна эта тема взгляните на данный материал.


Синий элемент упирается в верхнюю часть контейнера и не прокручивается вместе с остальными элементами

Вот демонстрация такого скроллинга.

Эффект параллакса


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


Эффект параллакса: элементы движутся с разной скоростью.

Вот демонстрация эффекта параллакса.

Прокрутка с привязкой к определённым точкам


Использование скроллинга с точками привязки позволяет браузеру автоматически настраивать положение элементов, перемещая их в определённую позицию после того, как пользователь завершил обычную операцию скроллинга. Это может оказаться полезным в случаях, когда нужно, чтобы после завершения прокрутки некий элемент находился бы целиком в поле зрения пользователя. Однако соответствующий API пока ещё нестабилен, поэтому постарайтесь пользоваться самыми свежими его реализациями и с осторожностью относитесь к применению этого подхода к скроллингу в продакшне. Вот хорошая статья на эту тему.


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

Вот демонстрация работы скроллинга с точками привязки.

Плавная прокрутка


Плавный скроллинг поддерживается средствами браузера при прокрутке страницы до определённого раздела с использованием метода window.scrollTo() в JavaScript, или даже с применением CSS-свойства scroll-behavior. В настоящее время для реализации плавного скроллинга со сглаживанием движений колеса мыши требуются специальные JavaScript-библиотеки. Но при применении таких библиотек нужно обеспечить их нормальное взаимодействие с другими технологиями скроллинга. Кроме того, использование плавного скроллинга это далеко не всегда хорошая идея.

Технологии скроллинга общего назначения


В настоящее время нет способа, применяя лишь CSS, запускать какие-либо анимации скроллинга общего назначения, основываясь на позиции прокрутки (хотя имеется предложение, в соответствии с которым в отдалённом будущем в нашем распоряжении могут появиться некие анимации, основанные на технологиях скроллинга общего назначения). В результате, если вы хотите анимировать элементы при скроллинге, вам нужно, как минимум, использовать некоторый объём JavaScript-кода для достижения требуемого эффекта. Существуют два метода применения JavaScript для анимирования элементов при скроллинге. Первый заключается в использовании API Intersection Observer, второй в обработке события scroll.

Использование API Intersection Observer


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

Использование события scroll


Перед тем, кто, для реализации анимации при скроллинге, использует событие scroll, открываются очень большие возможности. Этот приём позволяет, при прокрутке, воздействовать на элемент при любом положении элемента относительно границ области просмотра. Используя событие scroll, можно очень точно, в соответствии с нуждами проекта, задавать позиции начала и окончания анимации.

Учитывая это, нужно отметить, что данный подход к анимации скроллинга может создать немалую нагрузку на систему. Так происходит в том случае, если разработчик не заботится о производительности и не ограничивает частоту обработки события scroll. Кроме того, в распоряжении программиста, который решит пользоваться событием scroll, не будет удобного API, позволяющего реализовывать различные сценарии скроллинга. Именно поэтому часто, вместо того, чтобы реализовывать механизмы обработки события scroll самостоятельно, есть смысл применить специализированную библиотеку, авторы которой уже позаботились и о влиянии обработки события scroll на производительность, и об API. Некоторые из этих библиотек даже способны помочь разработчику при возникновении проблем с размерами элементов

Инструменты для создания механизмов скроллинга общего назначения


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

ScrollMagic


Библиотека ScrollMagic даёт нам сравнительно простой API, позволяющий создавать различные эффекты при скроллинге. Эта библиотека может работать совместно с различными библиотеками для анимации, наподобие GSAP и Velocity.js. Правда, в последние несколько лет эта библиотека недостаточно хорошо поддерживается. Это привело к тому, что была создана библиотека ScrollScene.

ScrollScene


ScrollScene это, в сущности, обёртка, которая направлена на то, чтобы повысить удобство работы с библиотекой ScrollMagic и (или) с API IntersectionObserver. Здесь используется собственная версия ScrollMagic, которая отличается лучшей поддержкой, чем обычный вариант библиотеки. Тут имеются и дополнительные возможности, наподобие проигрывания видео и поддержки контрольных точек, влияющих на анимацию. Кроме того, эта библиотека использует GSAP.

ScrollTrigger


Библиотека ScrollTrigger это официальный GreenSock-плагин для GSAP. Эта библиотека отличается большим набором возможностей, её API кажется мне самым простым из API существующих библиотек для скроллинга. Используя эту библиотеку, вы полностью контролируете то, где именно начинается и заканчивается анимация скроллинга, вы можете анимировать при прокрутке всё что угодно (WebGL, canvas, SVG, DOM), можете закреплять элементы на время выполнения анимации. Этим возможности данной библиотеки не ограничиваются. Кроме того, эту библиотеку поддерживает GreenSock, получить помощь по её использованию можно на форумах GreenSock.

Библиотека, достойная упоминания: Locomotive Scroll


Библиотека Locomotive Scroll не стремится к реализации столь же широкого набора возможностей, как другие библиотеки, о которых мы говорили. Её основная цель реализация плавной прокрутки. Используя её, кроме того, можно анимировать некоторые свойства DOM-объектов, используя атрибуты data-*, или пользоваться обработчиком onscroll для анимирования объектов других видов.

Сравнение технологий и инструментов


Вот сравнение технологий скроллинга.

API Intersection Observer Плавная прокрутка Точки привязки в CSS CSS-эффект параллакса CSS-свойство position: sticky
Закрепление элементов - - - - +
Эффект параллакса - - - + -
Управление динамикой анимации - - -
Использование контрольных точек - + - -
Динамическая пакетная обработка элементов + - - - -
Поддержка эффектов горизонтального скроллинга + + + + +
Подходит для продакшна (хорошая браузерная поддержка) +
Полная свобода в анимировании - - - - -
Поддержка разработчиком n/a n/a n/a n/a n/a
Работа с DOM, Canvas, WebGl, SVG + - - - -
Поддержка изменения размеров элементов + + + + +
Ограничивает анимацию только релевантным разделом + + + - +
Различает направления скроллинга - - - -
Технология, встроенная в браузер + + + + +

Вот сравнение рассмотренных библиотек.

ScrollTrigger Locomotive Scroll ScrollScene ScrollMagic
Закрепление элементов + + +
Эффект параллакса + + + +
Управление динамикой анимации +
Использование контрольных точек +
Динамическая пакетная обработка элементов + - + -
Поддержка эффектов горизонтального скроллинга + + + +
Подходит для продакшна (хорошая браузерная поддержка) + + +
Полная свобода в анимировании + + +
Поддержка разработчиком + + + -
Работает с DOM, Canvas, WebGl, SVG + + +
Поддержка изменения размеров элементов + + +
Ограничивает анимацию только релевантным разделом + -
Различает направления скроллинга + + +
Технология, встроенная в браузер - - - -

Итоги


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

Я обычно, для настройки скроллинга, рекомендую использовать библиотеку ScrollTrigger. Она позволяет достичь всего, на что способен чистый CSS, а так же многого другого. Эта библиотека берёт на себя заботу о браузерной поддержке тех или иных технологий, облегчает выполнение вычислений, что позволяет тому, кто её использует, просто заниматься своими делами.

Какие технологии вы используете при настройке скроллинга в своих проектах?

Подробнее..

Перевод О появлении поддержки CUDA в WSL 2

05.07.2020 16:17:42 | Автор: admin
Компания Microsoft, откликаясь на многочисленные просьбы пользователей, представила в мае 2020 года на конференции Build новую возможность подсистемы Windows для Linux 2 (Windows Subsystem for Linux 2, WSL 2) поддержку видеоускорителей. Это позволит запускать в WSL 2 приложения, занимающиеся специализированными вычислениями. Поддержка GPU откроет дорогу профессиональным инструментам, поможет решать в WSL 2 задачи, которые в настоящее время можно решать только в Linux. Теперь подобные задачи можно будет решать и в Windows, пользуясь возможностями GPU.

Крайне важно тут и то, что в WSL приходит поддержка программно-аппаратной архитектуры параллельных вычислений NVIDIA CUDA.

Материал, перевод которого мы публикуем, подготовлен специалистами NVIDIA. Здесь речь пойдёт о том, чего можно ожидать от CUDA в Public Preview-версии WSL 2.


Запуск AI-фреймворков, используемых в Linux, в WSL 2-контейнерах

Что такое WSL?


WSL это возможность Windows 10, которая позволяет использовать инструменты командной строки Linux непосредственно в Windows без необходимости сталкиваться со сложностями применения конфигурации двойной загрузки. WSL представляет собой контейнеризованное окружение, которое тесно интегрировано с ОС Microsoft Windows. Это позволяет запускать Linux-приложения вместе с традиционными Windows-приложения и с современными приложениями, распространяемыми через Microsoft Store.

WSL это, преимущественно, инструмент для разработчиков. Если вы работаете над некими проектами в контейнерах Linux, это значит, что вы можете заниматься теми же делами локально, на Windows-компьютере, используя привычные инструменты Linux. Обычно, чтобы запустить подобные приложения на Windows, нужно потратить много времени на настройку системы, нужны какие-то сторонние фреймворки, библиотеки. Теперь, с выходом WSL 2, всё изменилось. Благодаря WSL 2 в мир Windows пришла полная поддержка ядра Linux.

WSL 2 и технология паравиртуализации GPU (GPU Paravirtualization, GPU-PV) позволили Microsoft вывести поддержку Linux в Windows на новый уровень, сделав возможным запуск вычислительных нагрузок, рассчитанных на GPU. Ниже мы подробнее поговорим о том, как выглядит использование GPU в WSL 2.

Если вас интересует тема поддержки видеоускорителей в WSL 2 взгляните на этот материал и на этот репозиторий.

CUDA в WSL


Для того чтобы воспользоваться возможностями GPU в WSL 2, необходимо, чтобы на компьютере был бы установлен видеодрайвер, поддерживающий Microsoft WDDM. Подобные драйверы создают производители видеокарт такие, как NVIDIA.

Технология CUDA позволяет заниматься разработкой программ для видеоускорителей NVIDIA. Эта технология поддерживается в WDDM, в Windows, уже многие годы. Новый контейнер WSL 2 от Microsoft даёт возможности по GPU-ускорению вычислений, которыми может воспользоваться технология CUDA, что позволяет выполнять в среде WSL программы, рассчитанные на CUDA. Подробности об этом можно узнать в руководстве пользователя по работе с CUDA в WSL.

Поддержка CUDA в WSL включена в драйверы NVIDIA, рассчитанные на WDDM 2.9. Эти драйверы достаточно просто установить в Windows. Драйверы пользовательского режима CUDA в WSL (libcuda.so) автоматически становятся доступными внутри контейнера, их может обнаружить загрузчик.

Команда NVIDIA, занимающаяся разработкой драйверов, добавила в драйвер CUDA поддержку WDDM и GPU-PV. Сделано это для того чтобы эти драйверы могли бы работать в среде Linux, запущенной на Windows. Эти драйверы всё ещё находятся в статусе Preview, их релиз состоится только тогда, кода состоится официальный релиз WSL с поддержкой GPU. Подробности о выпуске драйверов можно найти здесь.

На следующем рисунке показана схема подключения драйвера CUDA к WDDM внутри гостевой системы Linux.


WDDM-драйвер пользовательского режима с поддержкой CUDA, выполняющийся в гостевой системе Linux

Предположим, вы разработчик, который установил дистрибутив WSL на последнюю сборку Windows из Fast Ring (сборка 20149 или старше) Microsoft Windows Insider Program (WIP). Если вы переключились на WSL 2 и у вас есть GPU NVIDIA, вы можете испытать драйвер и запустить свой код, выполняющий GPU-вычисления, в WSL 2. Для этого достаточно установить драйвер в хост-системе Windows и открыть WSL-контейнер. Здесь вам, без дополнительных усилий, будет доступна возможность работы с приложениями, использующими CUDA. На следующем рисунке показано, как в WSL 2-контейнере выполняется TensorFlow-приложение, использующее возможности CUDA.


TensorFlow-контейнер, выполняющийся в WSL 2

То, что в WSL теперь доступна технология CUDA, позволяет выполнять в WSL приложения, которые раньше можно было выполнять только в обычном Linux-окружении.

NVIDIA всё ещё активно работает над этим проектом и вносит в него улучшения. Мы, кроме прочего, работаем над добавлением в WDDM API, которые раньше были рассчитаны исключительно на Linux. Это приведёт к тому, что в WSL, без дополнительных усилий со стороны пользователя, сможет работать всё больше и больше приложений.

Ещё один интересующий нас вопрос это производительность. Как уже было сказано, поддержка GPU в WSL 2 серьёзно использует технологию GPU-PV. Это может плохо повлиять на скорость выполнения небольших задач на GPU, в ситуациях, когда не будет использоваться конвейеризация. Прямо сейчас мы работаем в направлении как можно более сильного сокращения подобных эффектов.

NVML


В исходный пакет драйвера не включена технология NVML, мы стремимся это исправить, планируя добавить в WSL поддержку NVML и поддержку других библиотек.

Мы начали работу с основного драйвера CUDA, что позволит пользователям запускать большую часть существующих CUDA-приложений даже на ранней стадии появления поддержки CUDA в WSL. Но, как оказалось, некоторые контейнеры и приложения используют NVML для получения информации о GPU даже до загрузки CUDA. Именно поэтому добавление поддержки NVML в WSL это одна из наших первоочередных задач. Вполне возможно то, что скоро мы сможем поделиться хорошими новостями по поводу решения этой задачи.

GPU-контейнеры в WSL


В дополнение к поддержке в WSL 2 DirectX и CUDA, NVIDIA работает над добавлением в WSL 2 поддержки NVIDIA Container Toolkit (раньше эта технология называлась nvidia-docker2). Контейнеризованные GPU-приложения, которые дата-сайентисты создают в расчёте на запуск в локальной или облачной среде Linux, теперь могут, без внесения в них каких-либо изменений, запускаться в WSL 2, на компьютерах, работающих под управлением Windows.

Каких-то особых пакетов WSL для этого не требуется. Библиотека времени выполнения NVIDIA (libnvidia-container) может динамически обнаруживать библиотеку libdxcore и пользоваться ей в ситуации, когда код выполняется в WSL 2-среде с поддержкой GPU-ускорения. Это происходит автоматически, после установки пакетов Docker и NVIDIA Container Toolkit, так же, как и на Linux. Это позволяет, без дополнительных усилий, запускать в WSL 2 контейнеры, в которых используются возможности GPU.

Мы настоятельно рекомендуем тем, кто хочет пользоваться опцией --gpus, установить последнюю версию инструментов Docker (19.03 или свежее). Для того чтобы включить поддержку WSL 2, следуйте инструкциям для вашего дистрибутива Linux и установите самую свежую из доступных версий nvidia-container-toolkit.

Как это работает? Все задачи, характерные для WSL 2, решаются средствами библиотеки libnvidia-container. Теперь эта библиотека может, во время выполнения, обнаруживать присутствие libdxcore.so и использовать эту библиотеку для обнаружения всех GPU, видимых этому интерфейсу.

Если эти GPU нужно использовать в контейнере, то, с помощью libdxcore.so, выполняется обращение к месту хранения драйверов, к папке, которая содержит все библиотеки драйверов для хост-системы Windows и WSL 2. Библиотека libnvidia-container.so отвечает за настройку контейнера таким образом, чтобы можно было бы корректно обратиться к хранилищу драйверов. Эта же библиотека отвечает за настройку базовых библиотек, поддерживаемых WSL 2. Схема этого показана на следующем рисунке.


Схема обнаружения и отображения в контейнер хранилища драйверов, используемая libnvidia-container.so в WSL 2

Кроме того, это отличается от логики, используемой за пределами WSL. Этот процесс полностью абстрагирован с помощью libnvidia-container.so и он, для конечного пользователя, должен быть как можно прозрачнее. Одно из ограничений этой ранней версии заключается в невозможности выбора GPU в окружениях, в которых имеется несколько GPU. В контейнере всегда видны все GPU.

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

Сейчас мы расскажем о том, как запускать в WSL 2 контейнеры TensorFlow и N-body, рассчитанные на использование GPU NVIDIA для ускорения вычислений.

Запуск контейнера N-body


Установим Docker, воспользовавшись скриптом установки:

user@PCName:/mnt/c$ curl https://get.docker.com | sh

Установим NVIDIA Container Toolkit. Поддержка WSL 2 доступна, начиная с nvidia-docker2 v2.3 и с библиотеки времени выполнения libnvidia-container 1.2.0-rc.1.

Настроим репозитории stable и experimental и GPG-ключ. Изменения в коде времени выполнения, рассчитанные на поддержку WSL 2, доступны в экспериментальном репозитории.

user@PCName:/mnt/c$ distribution=$(. /etc/os-release;echo $ID$VERSION_ID)user@PCName:/mnt/c$ curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -user@PCName:/mnt/c$ curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.listuser@PCName:/mnt/c$ curl -s -L https://nvidia.github.io/libnvidia-container/experimental/$distribution/libnvidia-container-experimental.list | sudo tee /etc/apt/sources.list.d/libnvidia-container-experimental.list

Установим пакеты времени выполнения NVIDIA и их зависимости:

user@PCName:/mnt/c$ sudo apt-get updateuser@PCName:/mnt/c$ sudo apt-get install -y nvidia-docker2

Откроем WSL-контейнер и запустим в нём демон Docker. Если всё сделано правильно после этого можно будет увидеть служебные сообщения dockerd.

user@PCName:/mnt/c$ sudo dockerd


Запуск демона Docker

В другом окне WSL загрузим и запустим контейнер симуляции N-body. Нужно, чтобы у пользователя, выполняющего эту задачу, было бы достаточно полномочий для загрузки контейнера. Следующие команды может потребоваться запустить с использованием sudo. В выводимых данных можно увидеть сведения о GPU.

user@PCName:/mnt/c$ docker run --gpus all nvcr.io/nvidia/k8s/cuda-sample:nbody nbody -gpu -benchmark


Запуск контейнера N-body

Запуск контейнера TensorFlow


Испытаем в Docker, в среде WSL 2, ещё один популярный контейнер TensorFlow.

Загрузим Docker-образ TensorFlow. Для того чтобы избежать проблем с подключением к Docker, выполним следующую команду в режиме sudo:

user@PCName:/mnt/c$ docker pull tensorflow/tensorflow:latest-gpu-py3

Сохраним немного изменённую версию кода из 15 урока руководства по TensorFlow, посвящённого использованию GPU, на диск C хост-системы. Этот диск, по умолчанию, монтируется в контейнере WSL 2 как /mnt/c.

user@PCName:/mnt/c$ vi ./matmul.pyimport sysimport numpy as npimport tensorflow as tffrom datetime import datetimedevice_name = sys.argv[1] # Choose device from cmd line. Options: gpu or cpushape = (int(sys.argv[2]), int(sys.argv[2]))if device_name == "gpu":device_name = "/gpu:0"else:device_name = "/cpu:0"tf.compat.v1.disable_eager_execution()with tf.device(device_name):random_matrix = tf.random.uniform(shape=shape, minval=0, maxval=1)dot_operation = tf.matmul(random_matrix, tf.transpose(random_matrix))sum_operation = tf.reduce_sum(dot_operation)startTime = datetime.now()with tf.compat.v1.Session(config=tf.compat.v1.ConfigProto(log_device_placement=True)) as session:result = session.run(sum_operation)print(result)# Вывод результатовprint("Shape:", shape, "Device:", device_name)print("Time taken:", datetime.now() - startTime)

Ниже показаны результаты выполнения этого скрипта, запущенного со смонтированного в контейнере диска C. Скрипт выполнялся, сначала, с использованием GPU, а потом с использованием CPU. Для удобства объём представленных здесь выходных данных сокращён.

user@PCName:/mnt/c$ docker run --runtime=nvidia --rm -ti -v "${PWD}:/mnt/c" tensorflow/tensorflow:latest-gpu-jupyter python /mnt/c/matmul.py gpu 20000


Результаты выполнения скрипта matmul.py

При использовании GPU в WSL 2-контейнере наблюдается значительное ускорение выполнения кода в сравнении с его выполнением на CPU.

Проведём ещё один эксперимент, рассчитанный на исследование производительности GPU-вычислений. Речь идёт о коде из руководства по Jupyter Notebook. После запуска контейнера вы должны увидеть ссылку на сервер Jupyter Notebook.

user@PCName:/mnt/c$ docker run -it --gpus all -p 8888:8888 tensorflow/tensorflow:latest-gpu-py3-jupyter


Запуск Jupyter Notebook

Теперь у вас должна появиться возможность запускать демонстрационные примеры в среде Jupyter Notebook. Обратите внимание на то, то, что для подключения к Jupyter Notebook с использованием браузера Microsoft Edge, нужно, вместо 127.0.0.1, использовать localhost.

Перейдите в tensorflow-tutorials и запустите блокнот classification.ipynb.

Для того чтобы увидеть результаты ускорения вычислений с помощью GPU, перейдите в меню Cell, выберите Run All и посмотрите журнал в WSL 2-контейнере Jupyter Notebook.


Журнал Jupyter Notebook

Этот демонстрационный пример, да и некоторые другие в данном контейнере, позволяют увидеть проблемы со слоем виртуализации, относящиеся к неоправданно высокой дополнительной нагрузке на систему при решении небольших задач. Выше мы уже говорили об этом. Так как мы запускаем тут очень маленькие учебные модели, время их выполнения на GPU меньше времени, необходимого на решение задач синхронизации. При решении таких вот игрушечных задач в WSL 2, CPU может оказаться эффективнее GPU. Мы занимаемся решением этой проблемы, стремясь ограничить её проявления лишь совсем небольшими рабочими нагрузками, к которым не применяется конвейеризация.

Обзор WSL


Для того чтобы понять то, как поддержка GPU была добавлена в WSL 2, сейчас мы поговорим о том, что собой представляет запуск Linux на Windows, и о том, как контейнеры видят аппаратное обеспечение.

Компания Microsoft представила технологию WSL на конференции Build в 2016 году. Эта технология быстро нашла широкое применение и стала популярной в среде Linux-разработчиков, которым нужно было запускать Windows-приложения, вроде Office, вместе с инструментами разработки для Linux и соответствующими программами.

Система WSL 1 позволяла запускать немодифицированные исполняемые файлы Linux. Однако здесь использовался слой эмуляции ядра Linux, который был реализован в виде подсистемы ядра NT. Эта подсистема обрабатывала вызовы, поступающие от Linux-приложений, перенаправляя их соответствующим механизмам Windows 10.

Система WSL 1 была полезным инструментом, но она не была совместима со всеми Linux-приложениям, так как ей требовалось эмулировать абсолютно все системные вызовы Linux. Кроме того, медленными были операции по работе с файловой системой, что приводило к недопустимо низкой производительности некоторых приложений.

Учитывая это, Microsoft решила пойти другим путём и выпустила WSL 2 новую версию WSL. Контейнеры WSL 2 выполняют полные Linux-дистрибутивы в виртуализованном окружении, но при этом используют все полезные возможности новой системы контейнеризации Windows 10.

В то время как WSL 2 использует Hyper-V-сервисы Windows 10, это не традиционная виртуальная машина, а, скорее, легковесный вспомогательный механизм виртуализации. Этот механизм отвечает за управление виртуальной памятью, связанной с физической памятью, позволяя WSL 2-контейнерам динамически выделять память, обращаясь к хост-системе Windows.

Среди основных целей создания WSL 2 можно отметить увеличение производительности работы с файловой системой и обеспечение совместимости со всеми системными вызовами. Кроме того, WSL 2 создавали, стремясь улучшить уровень интеграции WSL и Windows. Это позволяет удобно работать с Linux-системой, выполняемой в контейнере, пользуясь средствами командной строки Windows. Это, кроме того, повышает удобство работы с файловой системой хоста, автоматически монтируемой в выбранные директории файловой системы контейнера.

WSL 2 была представлена в Windows Insider Program в виде Preview-возможности и была выпущена в самом свежем обновлении Windows 10, в версии 2004.

В WSL 2 из самой свежей версии Windows внесено ещё больше улучшений, которые затрагивают много всего от сетевых стеков, до базовых VHD-механизмов системы хранения данных. Описание всех новых возможностей WSL 2 выходит за рамки этого материала. Подробнее о них можно узнать на этой странице, где приводится сравнение WSL 2 и WSL 1.

Linux-ядро WSL 2


Ядро Linux, применяемое в WSL 2, собрано Microsoft на основе самой свежей стабильной ветки, с использованием исходного кода, доступного на kernel.org. Это ядро было специально настроено для WSL 2, оптимизировано с точки зрения размеров и производительности с целью обеспечения работы Linux в среде Windows. Ядро поддерживается через механизм Windows Update. Это значит, что пользователю не нужно заботиться о том, чтобы загружать последние обновления безопасности и улучшения ядра. Всё это делается автоматически.

Microsoft поддерживает в WSL несколько дистрибутивов Linux. Компания, следуя правилам опенсорс-сообщества, опубликовала в GitHub-репозитории WSL2-Linux-Kernel исходный код ядра WSL 2 с модификациями, необходимыми для интеграции с Windows 10.

Поддержка GPU в WSL


Разработчики Microsoft добавили в WSL 2-контейнеры поддержку реальных GPU с использованием технологии GPU-PV. Здесь графическое ядро операционной системы (dxgkrnl) маршалирует драйверу режима ядра, который находится на хосте, вызовы от компонентов пользовательского режима, выполняемых в гостевой виртуальной машине.

Компания Microsoft разработала эту технологию в виде возможности WDDM, с момента её появления вышло уже несколько релизов Windows. Эта работа была проведена с привлечением независимых производителей аппаратного обеспечения (Independent Hardware Vendor, IHV). Графические драйверы NVIDIA поддерживали GPU-PV начиная с ранних дней появления этой технологии в Preview-версиях продуктов, доступных в Windows Insider Program. Все GPU NVIDIA, поддерживаемые в настоящий момент, могут быть доступны ОС Windows, выполняемой в гостевом режиме, в виртуальной машине, использующей Hyper-V.

Для того чтобы в WSL 2 можно было бы пользоваться возможностями GPU-PV, Microsoft пришлось создать базу своего графического фреймворка для гостевой системы Linux: WDDM с поддержкой протокола GPU-PV. Новый драйвер Microsoft находится за dxgkrnl, за системой, отвечающей за поддержку WDDM в Linux. Код драйвера можно найти в репозитории WSL2-Linux-Kernel.

Ожидается, что dxgkrnl обеспечит поддержку GPU-ускорения в контейнерах WSL 2 в WDDM 2.9. Microsoft говорит о том, что dxgkrnl это GPU-драйвер Linux, основанный на протоколе GPU-PV, и о том, что у него нет ничего общего с Windows-драйвером, имеющим похожее имя.

В настоящее время вы можете загрузить Preview-версию драйвера NVIDIA WDDM 2.9. В ближайшие несколько месяцев этот драйвер будет распространяться через Windows Update в WIP-версии Windows, что делает ненужными ручную загрузку и установку драйвера.

Основные сведения о GPU-PV


Драйвер dxgkrnl делает доступным, в пользовательском режиме гостевой системы Linux, новое устройство /dev/dxg. Сервисный слой ядра D3DKMT, который был доступен в Windows, тоже был портирован, как часть библиотеки dxcore, на Linux. Он взаимодействует с dxgkrnl, используя набор частных IOCTL-вызовов.

Гостевая Linux-версия dxgkrnl подключаются к ядру dxg на Windows-хосте, используя несколько каналов шины VM. Ядро dxg на хосте обрабатывает то, что ему приходит от Linux-процесса, так же, как то, что приходит от обычных Windows-приложений, использующих WDDM. А именно, ядро dxg отправляет то, что получило, KMD (Kernel Mode Driver, драйверу режима ядра, уникальному для каждого HIV). Драйвер режима ядра подготавливает то, что получил, для отправки аппаратному графическому ускорителю. На следующем рисунке показана упрощённая схема взаимодействия Linux-устройства /dev/dxg и KMD.


Упрощённая схема, иллюстрирующая то, как компоненты Windows-хоста обеспечивают работу устройства dxg в гостевой системе Linux

Если говорить об обеспечении подобной схемы работы в гостевых системах Windows, то можно сказать, что драйверы NVIDIA поддерживают GPU-PV в Windows 10 уже довольно давно. GPU NVIDIA могут быть использованы для ускорения вычислений и вывода графики во всех Windows 10-приложениях, использующих слой виртуализации Microsoft. Использование GPU-PV позволяет и работать с vGPU. Вот несколько примеров подобных приложений:


Вот как выглядит запуск DirectX-приложения в контейнере Windows Sandbox с применением видеоускорителя NVIDIA GeForce GTX 1070.


В контейнере Windows Sandbox ускорение графики выполняется средствами NVIDIA GeForce GTX 1070

Поддержка пользовательского режима


Для того чтобы добавить в WSL поддержку вывода графики, соответствующая команда разработчиков из Microsoft, кроме того, портировала на Linux компонент пользовательского режима dxcore.

Библиотека dxcore предоставляет API, который позволяет получать сведения об имеющихся в системе графических адаптерах, совместимых с WDDM. Эту библиотеку задумывали как кросс-платформенную низкоуровневую замену для средства работы с DXGI-адаптерами в Windows и Linux. Библиотека, кроме того, абстрагирует доступ к сервисам dxgkrnl (IOCTL-вызовы в Linux и GDI-вызовы в Windows), используя слой API D3DKMT, который используется CUDA и другими компонентами пользовательского режима, полагающимися на поддержку WDDM в WSL.

По сведениям Microsoft, библиотека dxcore (libdxcore.so) будет доступна и в Windows, и в Linux. NVIDIA планирует добавить в драйвер поддержку DirectX 12 и API CUDA. Эти дополнения нацелены на новые возможности WSL, доступные благодаря WDDM 2.9. Обе библиотеки, представляющие API, будут подключены к dxcore для того чтобы они могли бы давать dxg указания по поводу маршалирования их запросов к KMD на хост-системе.

Попробуйте новые возможности WSL 2


Хотите использовать свой Windows-компьютер для решения настоящих задач из сфер машинного обучения и искусственного интеллекта, и при этом пользоваться всеми удобствами Linux-окружения? Если так, то поддержка CUDA в WSL даёт вам отличную возможность это сделать. Среда WSL это то место, где Docker-контейнеры CUDA показали себя как самое популярное среди дата-сайентистов вычислительное окружение.

  • Для того чтобы получить доступ к Preview-версии WSL 2 с поддержкой GPU-ускорения, вы можете присоединиться к Windows Insider Program.
  • Загрузите свежие драйверы NVIDIA, установите их и попробуйте запустить в WSL 2 какой-нибудь CUDA-контейнер.

Здесь можно узнать подробности о применении технологии CUDA в WSL. Здесь, на форуме, посвящённом CUDA и WSL, вы можете поделиться с нами вашими впечатлениями, наблюдениями и идеями об этих технологиях.

А вы уже пробовали CUDA в WSL 2?

Подробнее..

Перевод Vue.js для начинающих, урок 1 экземпляр Vue

06.07.2020 18:16:50 | Автор: admin
Сегодня мы предлагаем вашему вниманию перевод первого урока учебного курса по Vue.js для начинающих. Освоив этот урок, вы узнаете о том, что такое экземпляр Vue, и о том, как приступить к разработке собственных Vue-приложений.



Предварительные требования


Предполагается, что тот, кто решит освоить этот курс, обладает знаниями в области базовых веб-технологий: HTML, CSS и JavaScript.

В этом курсе мы займёмся разработкой страницы, представляющей собой карточку товара.

image

Страница, разработкой которой мы будем заниматься

Цель урока


В этом уроке мы разберёмся с тем, как использовать Vue для вывода данных на веб-странице.

Начальный вариант кода


Мы начнём работу с очень простого HTML- и JavaScript-кода, расположенного в двух файлах.

Файл index.html:

<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width, initial-scale=1"><title>Product App</title></head><body><div id="app"><h1>Product Name</h1></div><script src="main.js"></script></body></html>

Файл main.js:

var product = "Socks";

В этом курсе в качестве среды, в которой предлагается выполнять домашние задания, используется платформа codepen.io. Соответствующие заготовки оформлены в виде CodePen-проектов. Тем, кто проходит этот курс, рекомендуется самостоятельно запускать весь код, который они здесь встречают.

В интерфейсе CodePen есть три области для кода. Это, соответственно, поля HTML, CSS и JS. Код, введённый в полях CSS и JS, автоматически подключается к веб-странице, описанной в поле HTML. То есть для того чтобы воссоздать в среде CodePen вышеприведённый пример нужно ввести в область HTML код, содержащийся в теге <body> файла index.html без последней строчки, подключающей main.js, а в область JS код main.js.


Начало экспериментов в CodePen

Использовать CodePen для запуска кода примеров необязательно. Вы вполне можете использовать какой-нибудь другой онлайн-сервис, или можете обойтись локальной средой разработки, воссоздав у себя описываемые здесь файлы.

Задача


Нам нужен механизм, который позволяет взять значение, имеющееся в JavaScript-коде, например то, что записано сейчас в переменную product, и поместить его в код веб-страницы, в тег h1.

Решить эту задачу нам поможет фреймворк Vue.js. Вот официальное русскоязычное руководство по нему.

Первым шагом нашей работы с Vue будет подключение фреймворка к странице. Для этого внесём изменения в файл index.html, добавив в него, прямо над кодом подключения файла main.js, следующее:

<script src="http://personeltest.ru/aways/unpkg.com/vue"></script>

Далее, в main.js, вводим следующий код, убрав из него объявление переменной product:

var app = new Vue({el: '#app',data: {product: "Socks"}})

Теперь нужно связать DOM с данными экземпляра Vue. Делается это с использованием особой HTML-конструкции, с помощью синтаксиса Mustache, при применении которого используются двойные фигурные скобки:

<div id="app"><h1>{{ product }}</h1></div>

JavaScript-выражение в фигурных скобках будет заменено на значение свойства product объекта data.

Вот как будет выглядеть проект в CodePen в том случае, если всё работает так, как нужно.


Данные перенесены из JavaScript на HTML-страницу

Как видите, нам удалось перенести данные из JavaScript-кода на HTML-страницу. А теперь давайте разберёмся в том, что мы только что сделали.

Экземпляр Vue


Вот схема кода, с помощью которого создают экземпляр Vue:

var app = new Vue({options})

Экземпляр Vue это корневая сущность приложения. Его создают, передавая конструктору Vue объект с опциями. Этот объект содержит различные свойства и методы, которые дают экземпляру Vue возможность хранить данные и выполнять какие-то действия.

Подключение экземпляра Vue к элементу веб-страницы


Обратите внимание на следующее свойство объекта с опциями, использованного при создании экземпляра Vue:

el: '#app'

С помощью этого свойства мы подключаем экземпляр Vue к элементу нашей страницы. Благодаря этому мы создаём связь между экземпляром Vue и соответствующей частью DOM. Другими словами, мы активируем Vue в элементе <div> с идентификатором app, записывая '#app' в свойство el объекта с опциями, который был использован при создании экземпляра Vue.

Размещение данных в экземпляре Vue


В экземпляре Vue имеется место для хранения данных. Эти данные описывают с помощью свойства data объекта с опциями:

data: {product: "Socks"}

К данным, хранящимся в экземпляре Vue, можно обратиться из элемента веб-страницы, к которому подключён экземпляр Vue.

Использование JavaScript-выражений в HTML-коде


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

<h1>{{ product }}</h1>

Фактически, речь идёт о том, что в двойных фигурных скобках находится JavaScript-выражение, результаты вычисления которого фреймворк подставляет в тег <h1> в качестве текста.

Важный термин: выражение


Выражения позволяют использовать значения, хранящиеся в экземпляре Vue, а так же JavaScript-конструкции, применение которых позволяет создавать какие-то новые значения.

Когда Vue видит выражение {{ product }}, он понимает, что мы ссылаемся на данные, связанные с экземпляром Vue, используя ключ product. Фреймворк заменяет имя ключа на соответствующее ему значение. В данном случае это Socks.

Примеры выражений


Как уже было сказано, в двойных фигурных скобках можно использовать различные JavaScript-конструкции. Вот несколько примеров:

{{ product + '?' }}{{ firstName + ' ' + lastName }}{{ message.split('').reverse().join('') }}

Знакомство с реактивностью


Причина, по которой Vue сразу же после загрузки страницы выводит в теге <h1> значение, соответствующее свойству product, заключается в том, что Vue это реактивный фреймворк. Другими словами, данные экземпляра Vue связаны со всеми местами веб-страницы, в которых есть ссылки на эти данные. В результате Vue может не только вывести данные в некоем месте страницы, но и обновить соответствующий HTML-код в том случае, если данные, на которые он ссылается, будут изменены.

Для того чтобы это доказать, давайте откроем консоль инструментов разработчика браузера и изменим значение, записанное в свойство product объекта app. Когда мы это сделаем, например, введя в консоли app.product = 'Coat', изменится и текст, выводимый на странице.

image

Изменение значения свойства product приводит к изменению текста, выводимого на веб-странице

Видите, как легко это делается?

Практикум


Добавьте к уже имеющимся в экземпляре Vue данным ключ description, содержащий текст A pair of warm, fuzzy socks. Затем выведите значение этого ключа в элементе <p>, который должен находиться ниже элемента <h1>.

Вот заготовка, которую вы можете использовать для решения этой задачи.

Вот решение задачи.

Итоги


Поговорим о том, что мы сегодня изучили:

  • Мы узнали о том, как начать разработку Vue-приложения, создав экземпляр Vue, и о том, как выводить данные на веб-страницу.
  • Экземпляр Vue является корнем каждого Vue-приложения.
  • Экземпляр Vue подключается к выбранному при его создании элементу DOM.
  • Данные, хранящиеся в экземпляре Vue, можно выводить на страницу, используя синтаксис Mustache, в котором используются двойные фигурные скобки, {{ }}, содержащие JavaScript-выражения.
  • Vue это реактивный фреймворк.

Планируете ли вы пройти этот курс?

Подробнее..

Дайджест свежих материалов из мира фронтенда за последнюю неделю 422 (29 июня 5 июля 2020)

06.07.2020 00:12:26 | Автор: admin
Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.


Веб-разработка


habr Обзор технологий скроллинга
habr Хватит это верстать, ударим автоматизацией по макетам
Google запустили бета-версию плагина для публикации AMP-сториз в WordPress
en Полное руководство по темному режиму в вебе
en Темные времена веб-разработки
en Выделение фрагмента текста: как сделать ссылку на конкретный фрагмент текста на веб-странице и подсветить его
en Прогрессивные веб-приложения: руководство по практическому использованию






CSS


habr TailwindCSS очередной фреймворк или новый шаг эволюции?
habr Упрощаем фоновые рисунки c помощью конических градиентов
habr Стилизация контейнеров для содержимого веб-страниц
Создание простой страницы лендинга за 5 минут используя готовые CSS блоки tailwind.
en Accordion Rows в CSS Grid
en Производительность CSS Painting vs. CSS Houdini Paint API
en Ссылки нестандартной формы с помощью Subgrid
en Получение значений CSS Translate с помощью JavaScript
en Выравнивание изображений логотипа в CSS
en Динамический импорт CSS
en Необычные свойства CSS
en Адаптирующиеся изображения в изменчивых пропорциях контейнера
en Когда строка не разрывается. О вариантах реализации принудительных переносов в списках
en Новое в Chrome: CSSOverview
en Когда Sass и новые функции CSS сталкиваются
en Полное руководство по медиа-запросам CSS


JavaScript


habr Устройство ленивой загрузки в популярных фронтенд-фреймворках
habr Как получить размеры экрана, окна и веб-страницы в JavaScript
habr Избушка на обратно-совместимых ножках компилируем JS для нужных браузеров
Кортежи в JS/ES и TypeScript в 2020
video en Почему подход vanilla JavaScript first может быть НЕ лучшим выбором
en Техническое руководство по SEO с Gatsby.js
en Вашему блогу не нужен JavaScript фреймворк
en Изучение регулярных выражений: руководство для начинающих









Браузеры


Релиз Firefox 78
Firefox78, технические подробности для разработчиков
Новый Edge на базе Chromium распространяется вместе с принудительным обновлением ОС и навязчиво ведёт себя
Google Chrome побил собственный рекорд, а Windows 10 продолжает расти

Просим прощения за возможные опечатки или неработающие/дублирующиеся ссылки. Если вы заметили проблему напишите пожалуйста в личку, мы стараемся оперативно их исправлять.



Дайджест за прошлую неделю.
Материал подготовили dersmoll и alekskorovin.
Подробнее..

Перевод Motion Path введение в современные анимации

07.07.2020 00:22:55 | Автор: admin


Вам никогда не хотелось создать красивую анимированную главную страницу для вашего сайта? А представить флагманский продукт в лучшем виде с помощью тщательно проработанных анимаций? А как насчет того, чтобы привлечь внимание пользователей к ссылкам меню, движущимся по замысловатой траектории?


Для того чтобы выполнить подобные пожелания, актуальные для современного мира веб-разработки, CSS-модуль Motion Path Module Level 1 дает возможность использовать абсолютно новый вид анимаций и позволяет перемещать HTML-элементы по заданной траектории.


Поддержка браузерами


Начнем с неприятных моментов. Придется огорчить поклонников Safari (OS X и iOS) и Internet Explorer (если таковые остались): свойства модуля Motion Path пока не поддерживаются всеми браузерами.


Однако на момент написания данной статьи около 75% посетителей вашего сайта имеют возможность насладиться этой современной технологией.


Обратите внимание, что данный тип анимаций лишь приятное дополнение. Он не является необходимым с точки зрения восприятия контента и опыта взаимодействия пользователя с вашими веб-интерфейсами.

Data on support for the css-motion-paths feature across the major browsers from caniuse.com

Свойства модуля Motion Path


При реализации анимаций задействуются три механизма:


  • определение траектории движения элемента (за это отвечает свойство offset-path),
  • определение положения элемента на траектории (свойство offset-distance)
  • и ориентация элемента во время движения (свойство offset-rotate).

offset-path


Это наиболее значимое свойство из перечисленных. Оно может принимать несколько значений: path(), ray(), url(), circle(), polygon(), inset(), и none.


На сегодняшний день большинство браузеров поддерживают только одно значение path(), поэтому в данной статье внимание будет сосредоточено именно на нем.

Синтаксис значений offset-path заимствован из языка разметки SVG. Поэтому чтобы освоить анимации, рассматриваемые в данной статье, нужны базовые знания SVG или опыт работы с редакторами векторной графики.


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


Вот пример квадратной траектории, по которой будет двигаться div.


div {  offset-path: path('M10 10 H 180 V 180 H 10 Z'); /* квадратная траектория */}   

Цель данной статьи не изучение SVG, а чтобы подробнее узнать об используемом здесь синтаксисе можно прочитать замечательный перевод статьи с СSS-Tricks на сайте la Cascade (перевод на русский прим. пер.) или подробное руководство на MDN.

offset-distance


Что касается свойства offset-distance, оно определяет положение элемента на заданной траектории. Его значение может быть выражено в любых стандартных единицах (пикселях, rem, процентах и т.д.). Свойство представляет особый интерес, когда требуется реализовать анимацию движения объекта по определенному пути.


div {  animation: move 1s; /* анимация "move" будет длиться 1s */}@keyframes move {  0% {     offset-distance: 0%; /* стартуем в начале траектории */  }  100% {     offset-distance: 100%; /* и прекращаем движение в конце траектории */  }}

Смотрите демо на CodePen:





offset-rotate


И наконец, третье свойство модуля, offset-rotate, определяющее ориентацию объекта на изогнутых участках пути.


Принимаемые значения:


  • auto (значение по умолчанию): ориентация объекта совпадает с направлением участка траектории, на котором он находится. Чаще всего требуется именно это значение.
  • reverse: объект также будет менять ориентацию в зависимости от направления, но при этом будет развернут на 180 вокруг своей оси, то есть все время будет двигаться задом наперед.
  • auto Xdeg (или reverse Xdeg): ориентация объекта будет отклоняться от кривой движения на угол X.

Смотрите демо:





Визуализация траектории


Вы, конечно, заметили, что траектория движения объекта не отображается на мониторе, и это не есть хорошо, тем более что такая опция, судя по всему, не предусмотрена спецификацией.


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


path() или пропал


Вспомним квадратную траекторию, по которой движется div:


div {  offset-path: path('M10 10 H 180 V 180 H 10 Z'); /* квадратная траектория */}

Наша задача в точности воспроизвести этот же путь в SVG, потому что только так он станет видимым.


В языке разметки SVG путь можно отрисовать с помощью элемента svg со вложенным path, который в свою очередь имеет атрибут d


Таким образом, в SVG у нас получается следующий код:


<svg ...>    <path d="M10 10 H 180 V 180 H 10 Z" fill="none" stroke="gray" /></svg>

Обратите внимание, что большинство значений атрибутов SVG меняются с помощью CSS, так что можно сократить код:


<svg class="svg-path" width="200" height="200" xmlns="http://personeltest.ru/away/www.w3.org/2000/svg">  <path d="M10 10 H 180 V 180 H 10 Z"></svg>

А в таблице стилей будет следующее:


.svg-path {  stroke: gray;  stroke-width: 4;  fill: none;}

Накладываем видимый путь на траекторию


На втором этапе необходимо спозиционировать относительно друг друга все необходимые элементы. А именно нам понадобится:


  • немного CSS-кода, чтобы отобразить путь с помощью path (это мы уже сделали),
  • объект, перемещающийся по своей траектории с помощью свойства offset-path (это тоже готово),
  • контейнер-обертка, относительно которого можно спозиционировать элементы с position: absolute.

А если конкретно, нам нужен следующий код:


<div class="motion-container">  <svg class="svg-path">    <path>  </svg>  <div class="motion-object"></div></div>

И, соответственно, такой CSS:


/* обертка для .svg-path */.motion-container {  position: relative;}.svg-path {  position: absolute;  left: 0;  top: 0;}




Усложняем анимации


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


Единственное, что вас может в этом ограничить, это ваше воображение. Ну и, конечно, желание рисовать SVG. C последним вам помогут различные SVG-редакторы. Например, значительно облегчить жизнь могут Sketch или Inkscape.


Также с этой задачей отлично справится онлайн SVG-редактор Method Draw.


Предлагаю посмотреть видео, в котором показано, как можно воспользоваться на практике инструментарием, рассмотренным в данной статье.



Смотрите демо на CodePen:





И немного к вопросу о доступности веб-контента


У вас могли зародиться сомнения по поводу анимаций, которые воспроизводятся, несмотря на то что такая настройка браузера не включена, или, хуже того, намеренно отключена пользователем. Для посетителей это может представлять проблему.


Рассмотрим существующие подходы к данному вопросу.


  1. Согласно одному из стандартов RGAA 4 длительность анимации не должна превышать пяти секунд, либо необходимо предоставить возможность включить анимацию и поставить ее на паузу или развернуть/свернуть блок с контентом. В ином случае контент должен быть неподвижным. (По поводу стандартов WC3 по обеспечению доступности веб-контента см. следующий обзор и его перевод. Прим.пер.)
  2. Медиа-запрос prefers-reduce-motion позволяет учитывать предпочтения пользователей в вопросе анимаций, принимая в расчет индивидуальные настройки.

Несколько демо и заключение


Чтобы вызвать у вас желание поэкспериментировать с модулем Motion Path, я подготовил на CodePen подборку демо, которые у меня получились лучше всего:



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


Пожалуйста, поделитесь в комментариях своими наработками по теме статьи или понравившимися примерами из интернета.

Подробнее..

Что делать, если брать фронтенд-фреймворк это излишество

07.07.2020 08:21:43 | Автор: admin

Пример @@include


Современные фронтенд-фреймворки дают удивительные возможности. React, Vue, Angular и другие созданы делать то, что раньше было невозможно, веб-приложения. В 2020 скачивать и устанавливать приложения уже необязательно. Зачем, если всё можно сделать на сайте?


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


В этом вопросе я поддерживаю "консерваторов". Не нужно писать лендинги и многостранички на Create-React-App, для этого пойдет и обычная статика.


Однако я также прекрасно понимаю тех, кто делает иначе. Современные фронтенд-фреймворки дают не только множество нового функционала, они очень удобны в использовании. А если мне нужно сделать простой сайт с парой страниц, у меня оказывается не очень то много выбора.


Что делать? Писать простыню HTML-разметки в одном файле? Хранить данные во view? Это не сделать шаг назад, это сорваться и упасть в пропасть. Это не просто неудобно, это идет вразрез с современной парадигмой фронтенд-разработки.


Во-первых, data-driven. Данные во главе угла, а внешний вид лишь их отображение. Пользователь делает действие, данные меняются, вслед меняется отображение. Так работают все современные фреймворки.


Еще один элемент современного подхода компонентность. Делим приложение на мелкие самодостаточные переиспользуемые куски. Больше переиспользуемости меньше кода. Меньше кода меньше багов.


До этого мы уже обсуждали, как реализовать data-driven минимальными усилиями. Мой выбор Alpine.js. Что же делать с компонентностью? Для простых статических сайтов я предлагаю самый простой вариант gulp-file-include.


Столько говорил о современности и парадигмах, а закончилось всё библиотекой, которой уже лет 100? Ну, на самом деле, она не такая уж и старая. 4 года с версии 1.0.0, столько же, сколько первой стабильной версии React (15). Да и зачем заново изобретать велосипед, если уже всего готово.


У библиотеки всего шестьсот звезд на Github и 6,5 тысяч скачиваний в неделю на npm, но она дает нам всё, что нужно, чтобы быстро разделить простыню HTML на небольшие удобные компоненты. И при этом не добавив ни байта дополнительного кода в конечный результат.


Единственная преграда знакомство с Gulp. Ну, на самом деле, писать даже простой лендинг без подобного инструмента сегодня не очень правильно. Поэтому, если вы не знаете Gulp, узнайте. Вот для этого статья на Хабре, вот ссылка на перевод документации. Мы на этом останавливаться не будем.


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


Итак, что будем делать? Вот это


Что мы будем делать


Выглядит просто. Посмотрим, как это выглядит в HTML.


Как это выглядит в HTML
<section class="text-gray-700 body-font">  <div class="container px-5 py-24 mx-auto">    <h1 class="mb-20 text-2xl font-medium text-center text-gray-900 sm:text-3xl title-font">      Проблемы, которые я решаю    </h1>    <div class="flex flex-wrap -mx-4 -mt-4 -mb-10 sm:-m-4 md:mb-10">      <div        class="flex flex-col items-center p-4 mb-6 sm:flex-row lg:w-1/3 md:mb-0 sm:items-stretch"      >        <div          class="inline-flex items-center justify-center flex-shrink-0 w-12 h-12 mb-4 text-indigo-500 bg-indigo-100 rounded-full"        >          <svg            class="w-6 h-6"            fill="none"            stroke="currentColor"            stroke-linecap="round"            stroke-linejoin="round"            stroke-width="2"            viewBox="0 0 24 24"          >            <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>          </svg>        </div>        <div class="flex-grow pl-6">          <h2 class="mb-2 text-xl font-medium text-gray-900 title-font">Оптимизация скорости</h2>          <p class="text-lg leading-relaxed">            Увеличим быстродействие системы при загрузке, уменьшим нагрузку на процессор и оперативную память, исключим из автозагрузки требовательные к ресурсам устройства программы.          </p>        </div>      </div>      <div        class="flex flex-col items-center p-4 mb-6 lg:w-1/3 md:mb-0 sm:flex-row sm:items-stretch"      >        <div          class="inline-flex items-center justify-center flex-shrink-0 w-12 h-12 mb-4 text-indigo-500 bg-indigo-100 rounded-full"        >          <svg            class="w-6 h-6"            fill="none"            stroke="currentColor"            stroke-linecap="round"            stroke-linejoin="round"            stroke-width="2"            viewBox="0 0 24 24"          >            <circle cx="6" cy="6" r="3"></circle>            <circle cx="6" cy="18" r="3"></circle>            <path d="M20 4L8.12 15.88M14.47 14.48L20 20M8.12 8.12L12 12"></path>          </svg>        </div>        <div class="flex-grow pl-6">          <h2 class="mb-2 text-xl font-medium text-gray-900 title-font">            Восстановление системных файлов          </h2>          <p class="text-lg leading-relaxed">            В случае некорректной работы системы и устройств, проведём анализ системных файлов и восстановим их, если они повреждены.          </p>        </div>      </div>      <div        class="flex flex-col items-center p-4 mb-6 lg:w-1/3 md:mb-0 sm:flex-row sm:items-stretch"      >        <div          class="inline-flex items-center justify-center flex-shrink-0 w-12 h-12 mb-4 text-indigo-500 bg-indigo-100 rounded-full"        >          <svg            class="w-6 h-6"            fill="none"            stroke="currentColor"            stroke-linecap="round"            stroke-linejoin="round"            stroke-width="2"            viewBox="0 0 24 24"          >            <path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"></path>            <circle cx="12" cy="7" r="4"></circle>          </svg>        </div>        <div class="flex-grow pl-6">          <h2 class="mb-2 text-xl font-medium text-gray-900 title-font">            Установка и обновление драйверов устройств          </h2>          <p class="text-lg leading-relaxed">            При неработоспособности какого-либо из устройств или проблемах, связанных с их некорректной работой, произведём установку, обновление или откат драйверов.          </p>        </div>      </div>    </div>  </div></section>

Воу! Не так уж и просто, как хотелось бы. Безусловно, TailwindCSS, который здесь используется, тоже даёт о себе знать. Если бы мне пришлось разбираться в этом коде, я бы первым дизлайкнул статью, заявляющую, что TailwindCSS это новый шаг эволюции. Но нет, я её написал. А всё потому, что этот код можно красиво поделить на компоненты, где эта гора классов превратится в осмысленную картину, которая не только ухудшит, а даже улучшить наш developer-experience.


Но вернемся к плагину. Начнем с небольшой теории. Чтобы с помощью gulp-file-include вставить один HTML в другой, нам нужно написать команду @@include(<путь до файла>, <объект с переменными>).


При этом в gulpfile можно многое настроить под себя. У меня настроено примерно так:


function html() {  return src('src/*.html')    .pipe(fileinclude({ basepath: './src/partials' }))    .pipe(dest('dist'));}

Берем все HTML-файлы из src, прогоняем через наш плагин и кладем в папку dist. При этом в настройки плагина можно передать ряд опций. Быстро пройдемся по ним.


  • prefix можно изменить префикс с @@ на любой другой.
  • suffix можно добавить суффикс.
  • basepath можно настроить, как просчитываются пути в директивах. По умолчанию '@file' от HTML-файла. Есть еще '@root' от корня, или любой другой путь. В нашем случае, я создал специальную папку в src partials, где и будут лежать все наши компоненты. Важно правильно настроить Gulp, чтобы эти компоненты не попали в окончальную сборку. В примере выше, Gulp берет только файлы из корня src, не заглядывая в папки. Это то, что нам нужно.
  • filters позволяет задавать функции, которые потом можно будет запускать из разметки. Смотрите примеры в документации.
  • context "глобальные" переменные для условий @@if.

Также есть ряд директив:


  • @@include вставка HTML-файла в другой HTML.
  • @@if условия; переменные берутся из "глобального" context и/или из объекта переменных использованного @@include.
  • @@for обычный цикл по массиву из context/переменных @@include.
  • @@loop проходимся по массиву объектов, используя данные из них как переменные, и вставляем для каждого компонент. Может использовать JSON.

Можно было бы разобрать каждый из них по отдельности, но это избыточно. Проще просто перейти в документацию и посмотреть примеры для каждого случая. Там всё более-менее понятно.


В нашем случае нас больше всего интересует @@loop. Мы перенесем все данные из карточек в JSON-файл, создадим компонент и передадим ему его данные, как пропсы.


Что у нас входит в данные? Правильный ответ: заголовок карточки, текст и SVG. В переменные мы можем передавать как просто текст, так и HTML как строку. Он просто подставит её, куда мы скажем.


Вот как выглядит JSON (data.json).


[  {    "title": "Оптимизация скорости",    "text": "Увеличим быстродействие системы при загрузке, уменьшим нагрузку на процессор и оперативную память, исключим из автозагрузки требовательные к ресурсам устройства программы.",    "svg": "<path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"></path>"  },  {    "title": "Восстановление системных файлов",    "text": "В случае некорректной работы системы и устройств, проведём анализ системных файлов и восстановим их, если они повреждены.",    "svg": "<circle cx=\"6\" cy=\"6\" r=\"3\"></circle><circle cx=\"6\" cy=\"18\" r=\"3\"></circle><path d=\"M20 4L8.12 15.88M14.47 14.48L20 20M8.12 8.12L12 12\"></path>"  },  {    "title": "Установка и обновление драйверов устройств",    "text": "При неработоспособности какого-либо из устройств или проблемах, связанных с их некорректной работой, произведём установку, обновление или откат драйверов.",    "svg": "<path d=\"M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2\"></path><circle cx=\"12\" cy=\"7\" r=\"4\"></circle>"  }]

Теперь создадим компонент одной карточки (card.html). Переменные вставляем как @@<имя переменной>.


<div  class="flex flex-col items-center p-4 mb-6 sm:flex-row lg:w-1/3 md:mb-10 sm:items-stretch">  <div    class="inline-flex items-center justify-center flex-shrink-0 w-12 h-12 mb-4 text-indigo-500 bg-indigo-100 rounded-full"  >    <svg      class="w-6 h-6"      fill="none"      stroke="currentColor"      stroke-linecap="round"      stroke-linejoin="round"      stroke-width="2"      viewBox="0 0 24 24"    >      @@svg    </svg>  </div>  <div class="flex-grow pl-6">    <h2 class="mb-2 text-xl font-medium text-gray-900 title-font">@@title</h2>    <p class="text-lg leading-relaxed">@@text</p>  </div></div>

Осталось только создать файл секции (index.html).


<section class="text-gray-700 body-font">  <div class="container px-5 py-24 mx-auto">    <h1 class="mb-20 text-2xl font-medium text-center text-gray-900 sm:text-3xl title-font">      Проблемы, которые я решаю    </h1>    <div class="flex flex-wrap -mx-4 -mt-4 -mb-10 sm:-m-4">      @@loop('problems/card.html', 'partials/problems/data.json')    </div>  </div></section>

Первым параметром в @@loop передаем путь до компонента (от настроенного ранее basepath), вторым путь до JSON-файла (от src).


Структура файлов выглядит вот так:


src   index.html   main.csspartials      problems          index.html          card.html          data.json...

Теперь сам index.html я могу вставить с помощью @@include в файл основной страницы.


<!DOCTYPE html><html lang="ru">  <head>    ...  </head>  <body>    ...    @@include('problems/index.html')    ...  </body></html>

Минимальными усилиями мы получили полноценное компонентное деление. При этом, это никак не отразится на результате, будет такой же HTML, как и раньше. Конечно, не могу не заметить, что это подкрепляет также тезис про TailwindCSS, он уже не кажется таким неудобным, как прежде, не так ли? Дальше дело за малым повторить всё выше перечисленное для всех секций сайта.


На этом закончим знакомство с данным плагином. Цель данной статьи было показать, как легко можно в небольших проектах избежать использование тяжеловесных современных фреймворков, и при этом сохранить те идеи, которые они с собой несут. Конечно, это не единственный вариант, как это сделать. Возможно, вы знаете другие альтернативы, интересно будет услышать о них в комментариях.


Напоследок прошу ответить на вопрос:

Подробнее..

Перевод Как получить размеры экрана, окна и веб-страницы в JavaScript

02.07.2020 16:18:36 | Автор: admin


Доброго времени суток, друзья!

Представляю Вашему вниманию перевод небольшой заметки How to Get the Screen, Window, and Web Page Sizes in JavaScript автора Dmitri Pavlutin.

Для определения ориентации окна браузера (ландшафтной или портретной) можно сравнить его ширину и высоту.

Однако во всевозможных доступных размерах легко запутаться: существуют размеры экрана, окна, веб-страницы и т.д.

Что означают эти размеры и, главное, как их получить? Именно об этом я и собираюсь рассказать.

1. Экран


1.1. Размер экрана

Размер экрана это ширина и высота всего экрана: монитора или мобильного дисплея.



Получить информацию о размере экрана можно с помощью свойства screen объекта window:

const screenWidth = window.screen.widthconst screenHeight = window.screen.height

1.2. Доступный размер экрана

Доступный размер экрана это ширина и высота активного экрана без панели инструментов операционной системы.



Для получения доступного размера экрана снова обращаемся к window.screen:

const availableScreenWidth = window.screen.availWidthconst availableScreenHeight = window.screen.availHeight

2. Окно


2.1. Размер внешнего окна (или внешний размер окна)

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



Получить информацию о размере внешнего окна можно с помощью свойств outerWidth и outerHeight объекта window:

const windowOuterWidth = window.outerWidthconst windowOuterHeight = window.outerHeight

2.2. Внутренний размер окна (или размер внутреннего окна)

Внутренний размер окна это ширина и высота области просмотра (вьюпорта).



Объект window предоставляет свойства innerWidth и innerHeight:

const windowInnerWidth = window.innerWidthconst windowInnerHeight = window.innerHeight

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

const windowInnerWidth = document.documentElement.clientWidthconst windowInnerHeight = document.documentElement.clientHeight

3. Размер веб-страницы


Размер веб-страницы это ширина и высота отображаемого содержимого (отрендеренного контента).



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

const pageWidth = document.documentElement.scrollWidthconst pageHeight = document.documentElement.scrollHeight

Если pageHeight больше, чем внутренняя высота окна, значит, присутствует вертикальная полоса прокрутки.

4. Заключение


Надеюсь, теперь Вы понимаете, как получать различные размеры.

Размер экрана это размер монитора (или дисплея), а доступный размер экрана это размер экрана без панелей инструментов ОС.

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

Наконец, размер веб-страницы это размер контента.

Благодарю за внимание, друзья!
Подробнее..

Перевод Устройство ленивой загрузки в популярных фронтенд-фреймворках

03.07.2020 12:17:33 | Автор: admin
Snail steampunk by Avi-li

Команда Mail.ru Cloud Solutions перевела статью о том, что означает ленивая загрузка в трех фронтенд-фреймворках: Angular, React и Vue.js. Далее текст от лица автора.

Один из моих любимых терминов на сегодняшний день ленивая загрузка. Честно говоря, когда несколько лет назад я его услышал, то улыбнулся. В этой статье рассмотрю, что именно означает этот термин применительно к трем наиболее часто используемым фронтенд-фреймворкам: Angular, React и Vue.js.

Нетерпеливая загрузка против ленивой


Нетерпеливая загрузка означает загрузку каждого компонента конкретного приложения, что создает потенциальные узкие места в производительности. Ленивая загрузка гарантирует, что данный компонент загружается только тогда, когда это необходимо, и ни секундой ранее.

Теперь вы можете подумать: это здорово, приложение станет более быстрым, будет загружаться быстрее. Однако если в вашем приложении есть модуль или компонент, выполнение и загрузка которого занимает значительное время, это все равно означает замедление работы приложения. По этой причине вы можете использовать предварительную загрузку компонента в фоновом режиме. Эта техника требует отдельной статьи здесь я не буду вдаваться в детали, а просто познакомлю вас с этой концепцией ближе к концу.

О типовых проектах для примера


Примеры приложений, созданных во всех трех фреймворках, очень похожи. Каждый из них показывает следующие две вещи:

  • как использовать ленивую загрузку компонента внутри страницы;
  • как использовать ленивую загрузку компонента с помощью маршрутизации.

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

Чтобы получить доступ к проекту, пожалуйста, посетите репозиторий GitHub.

Angular


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

Компонент Фибоначчи


Вот как наш компонент выглядит в Angular:


Поскольку это отдельный компонент и он уже принадлежит корневому компоненту Angular, мы можем загрузить его на страницу.

Загрузка компонента на странице


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


Вместе со следующим TypeScript-кодом:


Что действительно интересно в этой ситуации, так это то, что компонент Fibonacci будет загружаться только в том случае, если значение showFibonacci равно true. Это означает, что управлять ленивой загрузкой можно только с помощью директивы ngIf. Это происходит потому, что Angular не просто показывает или скрывает компонент в DOM он добавляет или удаляет его на основе указанного условия.

Ленивая загрузка или роутинг


Во время обсуждения ленивой загрузки через маршрутизацию в Angular мы установили, что необходимо создать функциональный модуль (больше про функциональные модули Angular).

Создать функциональный модуль в нашем приложении вместе со вторым компонентом можно с помощью Angular CLI: ng g m fibonacci && ng g c --module=fibonacci fibonacci.

После создания модуля мы можем назначить ему компонент, а затем добавить его в основной модуль маршрутизации (app-routing.module.ts):


Обратите внимание, что мы используем loadChildren()и импортируем модуль как часть определения маршрута. Это означает, что модуль будет загружен только тогда, когда будет активен соответствующий маршрут.

Сравните код выше с этим:


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

Тут можно прочитать больше про ленивую загрузку в Angular.

Vue


Теперь давайте рассмотрим, как добиться ленивой загрузки при разработке с помощью фреймворка Vue.js. Давайте создадим Vue-приложение с помощью интерфейса командной строки Vue CLI и добавим новый компонент. Взгляните на то, как будет выглядеть часть компонента <script>:


Обратите внимание: причина, по которой нам нужно выполнить вычисление вне блока export default {}, в том, что иначе мы не сможем имитировать операцию блокировки. Естественно, Vue.js имеет как свойство mounted, так и свойство method, доступные для компонентов, что позволит вызывать код только при создании компонента.

Ленивая загрузка одиночного компонента


В Vue.js мы можем использовать директиву v-if для добавления или удаления элемента из DOM, и так лениво загружать компонент. Однако есть еще много вещей, которые нам нужно сделать, когда речь заходит о сравнении Vue.js и Angular. Взгляните на следующий код:


Это может показаться логичным способом сделать ленивую загрузку, однако при открытии страницы становится очевидным, что начальное время загрузки действительно велико. Это происходит потому, что компонент загружается сразу независимо от условия v-if. Другими словами, мы говорим Vue загрузить все компоненты независимо от их добавления в DOM.

Производительность загрузки существенно изменится, если мы внесем следующие изменения в элемент <script>:


Добавляя выражение import в инлайн-стиле, как часть свойства компонента, мы включаем ленивую загрузку для компонента Fibonacci. Теперь обновление приложения будет означать, что главная страница загружается действительно быстро. Только когда компонент Fibonacci отображается на главной странице, есть некоторая задержка.

Ленивая загрузка компонентов или роутинг


Ленивая загрузка компонентов в Vue.js следует аналогичной схеме, которую мы обсуждали ранее. Посмотрите на роутер:


Такой маршрутизатор вы, возможно, использовали или видели раньше в приложениях Vue. Несмотря на то что он функциональный, вы можете наблюдать следующую проблему. Если у нас есть блокирующая операция в компоненте Fibonacci, она будет блокировать загрузку компонента Home.

Чтобы устранить эту проблему, мы можем прибегнуть к привычному паттерну и импортировать компонент в определение маршрута:


Теперь загрузка главной страницы не будет заблокирована, а компонент Fibonacci загружается только тогда, когда пользователь выбирает нужный маршрут.

Тут больше информации про ленивую загрузку в Vue.js.

React


И последнее, но не менее важное: давайте рассмотрим, как добиться ленивой загрузки в React. Приложение было создано с помощью CLI create-react-app и, как и в предыдущих примерах, у нас есть компонент с некоторой блокирующей операцией:


Ленивая загрузка одиночного компонента


По умолчанию, как и в предыдущих примерах с использованием других фреймворков, импорт компонентов будет означать нетерпеливую загрузку:


В приведенном выше примере, даже если компонент Fibonacci не отображается, загрузка главной страницы приложения все равно занимает много времени. Чтобы исправить это, нужно сказать React о ленивой загрузке компонента после знака вопроса. В React есть несколько вспомогательных инструментов, таких как компонент Suspense для отображения плейсхолдера во время загрузки компонента и метод lazy (), который загружает компонент лениво:


Внесение этих изменений в приложение означает, что компонент Home будет загружаться быстро, а компонент Fibonacci будет загружаться только тогда, когда пользователь попросит об этом.

Ленивая загрузка или роутинг


Тот же подход применим и к ленивой загрузке компонента с помощью маршрутизации, включая использование Suspense и lazy():


Учитывая вышеприведенный маршрутизатор, в сочетании с оператором import это означает, что компонент Fibonacci будет загружен сразу. Теперь, надеюсь, понятно, почему это не идеально. Чтобы включить ленивую загрузку компонентов через маршрутизацию, нужно изменить код, чтобы использовать вышеупомянутый компонент Suspense и метод lazy ():


Тут больше о ленивой загрузке в React.

Проверки через Инструменты разработчика


Чтобы увидеть, что происходит под капотом, мы можем использовать панель DevTools браузера.

То, что обсуждается в этом разделе, справедливо для всех фреймворков, которые рассматриваются в этой статье.

В первую очередь мы можем проверить, что когда наше приложение использует нетерпеливую загрузку, весь JavaScript загружается и выполняется браузером. Как это можно увидеть в DevTools? Нажатие на ссылку Фибоначчи не загружает дополнительный JavaScript.

Обновление кода для использования ленивой загрузки будет означать, что для начала будет загружено меньше JavaScript. Когда компонент загружается, появляется новый запрос JavaScript это тот самый фрагмент, который мы только что запросили.

Взгляните на скриншоты ниже, чтобы увидеть состояние до и после. Я также рекомендую вам запустить эти образцы самостоятельно и поиграть с ними в DevTools.







Еще одна вещь


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

Существуют определенные стратегии, которые помогут преодолеть эту проблему. Со всеми фреймворками мы можем использовать волшебные комментарии через Webpack для динамического добавления prefetch (или preload) через тег <link rel="prefetch" /> на страницу. Просто поместите волшебные комментарии перед именем компонента, внутри импорта:


Это добавит в DOM тег <link rel="prefetch" as="script" href="http://personeltest.ru/aways/habr.com/static/js/fibonacci.chunk.js">.

Больше о волшебных комментариях и параметрах preload/prefetch в Webpack.

В заключение


Ленивая загрузка принцип, который позволяет выбрать, какие компоненты будут загружены позже в приложении, чтобы обеспечить лучшую производительность. Это стратегия, которую можно выбрать вместо нетерпеливой загрузки, когда все компоненты загружаются одновременно, вызывая потенциальные проблемы с производительностью.

Что еще почитать по теме:

  1. Переиспользуемые компоненты React: как перестать писать одно и то же.
  2. Как избежать ошибок при разработке на React.
  3. Наш телеграм-канал с новостями о цифровой трансформации.
Подробнее..

50200 вопросов по JavaScript

06.07.2020 10:05:56 | Автор: admin


Доброго времени суток, друзья!

Предлагаю Вашему вниманию небольшой интерактив своего рода викторину по JavaScript, на данный момент состоящую из 50 вопросов.

На мой взгляд, решение подобных задач лучший способ определить свой уровень мастерства.

Предисловие


Данная часть основана на этом репозитории. Его автор, Lydia Hallie, позиционирует свой проект как список продвинутых вопросов и, действительно, среди них есть такие, которые, как мне кажется, даже опытному JavaScript-разработчику покажутся непростыми. Однако среди этих вопросов есть и такие, для ответа на которые достаточно владеть базовыми знаниями. В репозитории имеется русский перевод, но, мягко говоря, он оставляет желать лучшего, поэтому большую часть ответов (объяснений) пришлось переводить заново.

Следует отметить, что приводимые пояснения (ответы) не всегда в полной мере раскрывают суть проблемы. Это объясняется формой проекта он представляет собой чеклист, а не учебник. Ответы, скорее, являются подсказкой для дальнейших поисков на MDN или Javascript.ru. Впрочем, многие из объяснений содержат исчерпывающие ответы.

Несмотря на то, что код неоднократно протестирован, никто не застрахован от ошибок, разумеется, кроме тех, кто ничего не делает. Поэтому при обнаружении ошибок, опечаток, неточностей, некорректности формулировок и т.п., а также при желании улучшить перевод, прошу писать в личку, буду признателен (активность на GitHub также приветствуется).

Собственно, это все, что я хотел сказать в качестве предисловия.

Правила


Правила простые: 50 вопросов, 3-4 варианта ответа, рейтинг: количество правильных и неправильных ответов, прогресс: номер и количество вопросов.

По результатам определяется процент правильных ответов и делается вывод об уровне владения JavaScript: больше 80% отлично, больше 50% неплохо, меньше 50% ну, Вы понимаете.

К каждому вопросу прилагается пояснение. При неправильном ответе данное пояснение раскрывается.

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

Но довольно слов, пора переходить к делу.

Викторина



Код проекта находится здесь.

Механика


Несколько слов о том, как реализована викторина для тех, кому интересно.

Разметка выглядит так:

<head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>200+ вопросов по JavaScript</title>    <!-- шрифт -->    <link href="http://personeltest.ru/aways/fonts.googleapis.com/css2?family=Ubuntu&display=swap" rel="stylesheet">    <!-- стили -->    <link rel="stylesheet" href="style.css">    <!-- основной скрипт с типом "модуль" -->    <script type="module" src="script.js"></script></head><body></body>

Добавляем минимальные стили.
CSS:
* {    margin: 0;    padding: 0;    box-sizing: border-box;    font-family: Ubuntu, sans-serif;    font-size: 1em;    text-align: center;    letter-spacing: 1.05px;    line-height: 1.5em;    color: #111;    user-select: none;}@media (max-width: 512px) {    * {        font-size: .95em;    }}html {    position: relative;}body {    padding: 1em;    min-height: 100vh;    background: radial-gradient(circle, skyblue, steelblue);    display: flex;    flex-direction: column;    justify-content: start;    align-items: center;}h1 {    margin: .5em;    font-size: 1.05em;}output {    margin: .5em;    display: block;}.score {    font-size: 1.25em;}form {    text-align: left;}form p {    text-align: left;    white-space: pre;}form button {    position: relative;    left: 50%;    transform: translateX(-50%);}button {    margin: 2em 0;    padding: .4em .8em;    outline: none;    border: none;    background: linear-gradient(lightgreen, darkgreen);    border-radius: 6px;    box-shadow: 0 1px 2px rgba(0, 0, 0, .4);    font-size: .95em;    cursor: pointer;    transition: .2s;}button:hover {    color: #eee;}label {    cursor: pointer;}input {    margin: 0 10px 0 2em;    cursor: pointer;}details {    font-size: .95em;    position: absolute;    bottom: 0;    left: 50%;    transform: translateX(-50%);    width: 90%;    background: #eee;    border-radius: 4px;    cursor: pointer;}details h3 {    margin: .5em;}details p {    margin: .5em 1.5em;    text-align: justify;    text-indent: 1.5em;}.right {    color: green;}.wrong {    color: red;}


Исходники (assets) представляют собой массив объектов, где каждый объект имеет свойства question (вопрос), answers (ответы), rightAnswer (правильный ответ) и explanation (объяснение):

[{    question: `        function sayHi() {            console.log(name);            console.log(age);            var name = "Lydia";            let age = 21;        }        sayHi();    `,    answers: `        A: Lydia и undefined        B: Lydia и ReferenceError        C: ReferenceError и 21        D: undefined и ReferenceError    `,    rightAnswer: `D`,    explanation: `        Внутри функции мы сначала определяем переменную name с помощью ключевого слова var. Это означает, что name поднимется в начало функции. Name будет иметь значение undefined до тех пор, пока выполнение кода не дойдет до строки, где ей присваивается значение Lydia. Мы не определили значение name, когда пытаемся вывести ее в консоль, поэтому будет выведено undefined. Переменные, определенные с помощью let (и const), также поднимаются, но в отличие от var, не инициализируются. Доступ к ним до инициализации невозможен. Это называется "временной мертвой зоной". Когда мы пытаемся обратиться к переменным до их определения, JavaScript выбрасывает исключение ReferenceError.    `},...]

Основной скрипт.
JavaScript
// импортируем массив объектов - исходникиimport assets from './assets.js'// IIFE;((D, B) => {    // заголовок - вопрос    const title = D.createElement('h1')    B.append(title)    // рейтинг: количество правильных и неправильных ответов    const score = D.createElement('output')    score.className = 'score'    B.append(score)    // прогресс: порядковый номер вопроса    const progress = D.createElement('output')    progress.className = 'progress'    B.append(progress)    // контейнер для вопроса, вариантов ответа и кнопки для отправки формы    const div = D.createElement('div')    B.append(div)    // получаем значения правильных и неправильных ответов из локального хранилища    // или присваиваем переменным 0    let rightAnswers = +localStorage.getItem('rightAnswers') || 0    let wrongAnswers = +localStorage.getItem('wrongAnswers') || 0    // получаем значение счетчика из локального хранилища    // или присваиваем ему 0    let i = +localStorage.getItem('i') || 0    // рендерим вопрос    showQuestion()    // обновляем рейтинг и прогресс    updateScoreAndProgress()    function showQuestion() {        // если значение счетчика равняется количеству вопросов        // значит, игра окончена,        // показываем результат        if (i === assets.length) {            return showResult()        }        // заголовок-вопрос зависит от значения счетчика - номера вопроса        switch (i) {            case 4:                title.textContent = `Что не является валидным?`                break;            case 9:                title.textContent = `Что произойдет?`                break;            case 12:                title.textContent = `Назовите три фазы распространения событий`                break;            case 13:                title.textContent = `Все ли объекты имеют прототипы?`                break;            case 14:                title.textContent = `Каким будет результат?`                break;            case 20:                title.textContent = `Чему равно sum?`                break;            case 21:                title.textContent = `Как долго будет доступен cool_secret?`                break;            case 23:                title.textContent = `Каким будет результат?`                break;            case 25:                title.textContent = `Глобальный контекст исполнения создает две вещи: глобальный объект и this`                break;            case 27:                title.textContent = `Каким будет результат?`                break;            case 29:                title.textContent = `Каким будет результат?`                break;            case 30:                title.textContent = `Что будет в event.target после нажатия на кнопку?`                break;            case 33:                title.textContent = `Каким будет результат?`                break;            case 34:                title.textContent = `Какие из значений являются "ложными"?`                break;            case 38:                title.textContent = `Все в JavaScript это`                break;            case 39:                title.textContent = `Каким будет результат?`                break;            case 40:                title.textContent = `Каким будет результат?`                break;            case 41:                title.textContent = `Что возвращает setInterval?`                break;            case 42:                title.textContent = `Каким будет результат?`                break;            case 42:                title.textContent = `Каково значение num?`                break;            case 49:                title.textContent = `Каким будет результат?`                break;            default:                title.textContent = `Что будет выведено в консоль?`                break;        }        // поскольку каждый элемент массива - это объект,        // мы можем его деструктурировать, получив вопрос, правильный ответ и объяснение        const {            question,            rightAnswer,            explanation        } = assets[i]        // поскольку варианты ответа - это input type="radio",        // строку необходимо преобразовать в массив (критерием является перенос строки - \n)        // первый и последний элементы - пустые строки,        // избавляемся от них с помощью slice(1, -1),        // также удаляем пробелы        const answers = assets[i].answers            .split('\n')            .slice(1, -1)            .map(i => i.trim())        // HTML-шаблон        const template = `        <form action="#">            <p><em>Вопрос:</em><br> ${question}</p>            <p><em>Варианты ответов:</em></p><br>            ${answers.reduce((html, item) => html += `<label><input type="radio" name="answer" value="${item}">${item}</label><br>`, '')}            <button type="submit">Ответить</button>        </form>        <details>            <summary>Показать правильный ответ</summary>            <section>                <h3>Правильный ответ: ${rightAnswer}</h3>                <p>${explanation}</p>            </section>        </details>`        // помещаем шаблон в контейнер        div.innerHTML = template        // находим форму        const form = div.querySelector('form')        // выбираем первый инпут        form.querySelector('input').setAttribute('checked', '')        // обрабатываем отправку формы        form.addEventListener('submit', ev => {            // предотвращаем перезагрузку страницы            ev.preventDefault()            // определяем выбранный вариант ответа            const chosenAnswer = form.querySelector('input:checked').value.substr(0, 1)            // проверяем ответ            checkAnswer(chosenAnswer, rightAnswer)        })    }    function checkAnswer(chosenAnswer, rightAnswer) {        // индикатор правильного ответа        let isRight = true        // если выбранный ответ совпадает с правильным,        // увеличиваем количество правильных ответов,        // записываем количество правильных ответов в локальное хранилище,        // иначе увеличиваем количество неправильных ответов,        // записываем количество неправильных ответов в локальное хранилище        // и присваиваем индикатору false        if (chosenAnswer === rightAnswer) {            rightAnswers++            localStorage.setItem('rightAnswers', rightAnswers)        } else {            wrongAnswers++            localStorage.setItem('wrongAnswers', wrongAnswers)            isRight = false        }        // находим кнопку        const button = div.querySelector('button')        // если ответ был правильным        if (isRight) {            // сообщаем об этом            title.innerHTML = `<h1 class="right">Верно!</h1>`            // выключаем кнопку            button.disabled = true            // через секунду вызываем функции            // обновления рейтинга и прогресса и рендеринга следующего вопроса            // отключаем таймер            const timer = setTimeout(() => {                updateScoreAndProgress()                showQuestion()                clearTimeout(timer)            }, 1000)            // если ответ был неправильным        } else {            // сообщаем об этом            title.innerHTML = `<h1 class="wrong">Неверно!</h1>`            // выключаем инпуты            div.querySelectorAll('input').forEach(input => input.disabled = true)            // раскрываем объяснение            div.querySelector('details').setAttribute('open', '')            // меняем текст кнопки            button.textContent = 'Понятно'            // по клику на кнопке вызываем функции            // обновления рейтинга и прогресса и рендеринга следующего вопроса            // удаляем обработчик            button.addEventListener('click', () => {                updateScoreAndProgress()                showQuestion()            }, {                once: true            })        }        // увеличиваем значение счетчика        i++        // записываем значение счетчика в локальное хранилище        localStorage.setItem('i', i)    }    function updateScoreAndProgress() {        // обновляем рейтинг        score.innerHTML = `<span class="right">${rightAnswers}</span> - <span class="wrong">${wrongAnswers}</span>`        // обновляем прогресс        progress.innerHTML = `${i + 1} / ${assets.length}`    }    function showResult() {        // определяем процент правильных ответов        const percent = (rightAnswers / assets.length * 100).toFixed()        // объявляем переменную для результата        let result        // в зависимости от процента правильных ответов        // присваиваем result соответствующее значение        if (percent >= 80) {            result = `Отличный результат! Вы прекрасно знаете JavaScript.`        } else if (percent > 50) {            result = `Неплохой результат, но есть к чему стремиться.`        } else {            result = `Вероятно, вы только начали изучать JavaScript.`        }        // рендерим результаты        B.innerHTML = `        <h1>Ваш результат</h1>        <div>            <p>Правильных ответов: <span class="right">${rightAnswers}</span></p>            <p>Неправильных ответов: <span class="wrong">${wrongAnswers}</span></p>            <p>Процент правильных ответов: ${percent}</p>            <p>${result}</p>            <button>Заново</button>        </div>        `        // при нажатии на кнопку        // очищаем хранилище        // и перезагружаем страницу,        // удаляем обработчик        B.querySelector('button').addEventListener('click', () => {            localStorage.clear()            location.reload()        }, {            once: true        })    }})(document, document.body)


Благодарю за внимание, друзья.

Продолжение следует
Подробнее..

Интеграция ЭЦП НУЦ РК в информационные системы на базе веб технологий

07.07.2020 14:13:48 | Автор: admin

Я расскажу о тонкостях внедрения электронной цифровой подписи (ЭЦП) в информационные системы (ИС) на базе веб технологий в контексте Национального Удостоверяющего Центра Республики Казахстан (НУЦ РК).


В центре внимания будет формирование ЭЦП под электронными документами и, соответственно, NCALayer предоставляемое НУЦ РК криптографическое программное обеспечение. В частности уделю внимание вопросам связанным с UX и объемом поддерживаемого функционала NCALayer.


Процесс разделю на следующие этапы:


  • формирование неизменного представления подписываемого документа (под подписываемым документом я буду подразумевать любые данные которые необходимо подписать, такие как: договор, бланк заказа, форма аутентификации и т.п.);
  • подписание документа в веб интерфейсе с помощью NCALayer;
  • проверка подписи на стороне сервера;
  • (при необходимости) подготовка подписи к долгосрочному хранению.

Формирование неизменного представления подписываемого документа


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


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


  • извлечь все поля записи, привести их к строкам и соединить в одну строку;
  • сформировать XML или JSON представление;
  • сформировать PDF документ на базе шаблона с каким-то оформлением содержащий данные из записи;
  • и т.п.

Далее возможны два сценария каждый из которых имеет свои подводные камни:


  • ИС не хранит сформированного представления и каждый раз для проверки подписи под документом (в частности проверки неизменности данных) формирует его;
  • ИС хранит сформированное представление и использует его для проверки подписи.

В том случае, если ИС не хранит сформированного представления, разработчикам необходимо гарантировать что в будущем формируемые представления будут бинарно идентичны тем, которые были сформированы в первый раз не смотря на:


  • изменения схемы базы данных;
  • обновления механизма формирования представления (которое может так же зависеть от внешних библиотек которые, скорее всего, не стремятся к обеспечению бинарной идентичности).

В том случае, если на каком-то этапе бинарная идентичность перестанет соблюдаться, то проверки цифровых подписей перестанут выполняться и юридическая значимость документов в ИС будет утеряна.


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


Подписание документа в веб интерфейсе с помощью NCALayer


Для формирования цифровых подписей на стороне клиента НУЦ РК предоставляет единственный инструмент сертифицированное программное обеспечение NCALayer которое представляет из себя WebSocket сервер, запускаемый на 127.0.0.1, которому можно отправлять запросы на выполнение криптографических (а так же некоторых смежных) операций. При выполнении некоторых операций NCALayer отображает диалоговые окна то есть берет часть работы по получению пользовательского ввода на себя.


Описание API NCALayer доступно в составе комплекта разработчика. Для того, чтобы поэкспериментировать со взаимодействием с NCALayer по WebSocket можно воспользоваться страницей интерактивной документации KAZTOKEN mobile (KAZTOKEN mobile повторяет API NCALayer).


Взаимодействовать с NCALayer из браузера можно напрямую с помощью класса WebSocket, либо можно воспользоваться библиотекой ncalayer-js-client которая оборачивает отправку команд и получение ответов в современные async вызовы.


Замечу что весь основной функционал NCALayer доступен в модуле kz.gov.pki.knca.commonUtils, использовать модуль kz.gov.pki.knca.applet.Applet (наследие Java аплета) не рекомендую, так как, на мой взгляд, это не даст никаких преимуществ, но шансов выстрелить себе в ногу с ним больше к примеру можно случайно разработать интерфейс который не будет поддерживать аппаратных носителей (токенов или смарт-карт) с несколькими ключевыми парами.


Модуль kz.gov.pki.knca.commonUtils берет на себя взаимодействие с пользователем связанное с выбором конкретного хранилища, которое нужно использовать для выполнения операции (так же он берет на себя выбор конкретного сертификата и соответствующего ключа, а так же ввод пароля или ПИН кода), но ему необходимо указать какой тип хранилищ нужно использовать. Типы хранилищ стоит разделить на два класса:


  • файловые, поддерживается единственный тип заданный константой 'PKCS12',
  • аппаратные (токены и смарт-карты), для перечисления тех типов, экземпляры которых в данный момент подключены в ПК пользователя, следует использовать запрос getActiveTokens.

Таким образом для того, чтобы предоставить пользователю возможность работать с любым поддерживаемым NCALayer хранилищем, можно воспользоваться одним из следующих подходов:


  • гибкий отправить запрос getActiveTokens, в ответ получить массив имен доступных а данный момент типов хранилищ, добавить в него 'PKCS12' и спросить у пользователя каким он хотел бы в данный момент воспользоваться;
  • простой отправить запрос getActiveTokens, в том случае, если он вернул массив несколькими именами, попросить пользователя отключить лишнее, если в массиве один элемент, использовать его, если массив пустой, использовать 'PKCS12'.

Для подписания данных рекомендую использовать следующие запросы (опять же чтобы снизить вероятность выстрела в ногу):


  • createCAdESFromBase64 вычислить подпись под данными и сформировать CMS (CAdES);
  • createCMSSignatureFromBase64 вычислить подпись под данными, получить на подпись метку времени (TSP) и сформировать CMS (CAdES) с внедренной меткой времени;
  • signXml вычислить подпись под XML документом, сформированную подпись добавить в результирующий документ (XMLDSIG);
  • signXmls аналогично signXml, но позволяет за один раз подписать несколько XML документов.

В качестве параметров каждому из них нужно будет передать тип хранилища, тип сертификата, подписываемые данные и дополнительные модификаторы.


Модуль kz.gov.pki.knca.commonUtils поддерживает следующие типы сертификатов:


  • 'AUTHENTICATION' сертификаты для выполнения аутентификации;
  • 'SIGNATURE' сертификаты для подписания данных.

NCLayer предоставит пользователю выбирать только из тех сертификатов, которые соответствуют указанному типу.


Упрощенный пример подписания произвольного блока данных с использованием ncalayer-js-client:


async function connectAndSign(base64EncodedData) {  const ncalayerClient = new NCALayerClient();  try {    await ncalayerClient.connect();  } catch (error) {    alert(`Не удалось подключиться к NCALayer: ${error.toString()}`);    return;  }  let activeTokens;  try {    activeTokens = await ncalayerClient.getActiveTokens();  } catch (error) {    alert(error.toString());    return;  }  const storageType = activeTokens[0] || NCALayerClient.fileStorageType;  let base64EncodedSignature;  try {    base64EncodedSignature = await ncalayerClient.createCAdESFromBase64(storageType, base64EncodedData);  } catch (error) {    alert(error.toString());    return;  }  return base64EncodedSignature;}

Проверка подписи на стороне сервера


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


С технической точки зрения проверка цифровой подписи на стороне сервера в первую очередь гарантирует целостность полученного документа, во вторую авторство, а так же неотказуемость. То есть даже в том случае, если ИС получает подписанный документ не напрямую от пользователя, а через какие-то дополнительные слои программного обеспечения, уверенность в том, что было получено именно то, что отправлял пользователь сохраняется. Поэтому в ситуации когда подписание на стороне клиента реализовано, а возможность проверки подписи на стороне сервера отсутствует, внедрение цифровой подписи теряет смысл.


С юридической точки зрения ориентироваться следует на Приказ Министра по инвестициям и развитию Республики Казахстан Об утверждении Правил проверки подлинности электронной цифровой подписи. В Приказе перечислены необходимые проверки которые должны выполнять информационные системы для обеспечения юридической значимости подписанных электронной цифровой подписью документов. Анализу этого документа, а так же некоторым техническим вопросам посвящена заметка Проверка цифровой подписи.


Выполнять проверки необходимо с применением сертифицированных средств, к примеру с помощью библиотек входящих в состав комплекта разработчика НУЦ РК, либо можно воспользоваться готовым решением SIGEX.


Подготовка подписи к долгосрочному хранению


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


Для фиксации момента подписания принято использовать метки времени TSP. Метку времени на подпись можно получить либо на клиенте (запрос createCMSSignatureFromBase64 интегрирует метку времени в CMS), либо на сервере. Метка времени позволит удостовериться в том, что момент подписания попадает в срок действия сертификата.


Для того, чтобы удостовериться в том, что сертификат не был отозван в момент подписания, следует использовать CRL или OCSP ответ. Этот нюанс и рекомендации по реализации описаны в разделе APPENDIX B Placing a Signature At a Particular Point in Time документа RFC 3161.

Подробнее..

TailwindCSS очередной фреймворк или новый шаг эволюции?

01.07.2020 14:11:33 | Автор: admin

Лид-изображение


Очевидно, я бы не стал писать эту статью, если бы считал, что TailwindCSS просто очередной фреймворк. Я считаю, что он кардинально отличается от всех других фреймворков и создает отдельную парадигму web-стилизации. И при этом выполняет все поставленные перед ним задачи, делая это лучше и удобнее других.


Тех, кто еще не знаком с TailwindCSS, я постараюсь завербовать в ряды его поклонников. Тех, кто против него, я постараюсь заставить в этом усомнится и пересмотреть своё мнение.


Я также хотел узнать ваше мнение по этому поводу. TailwindCSS это шаг вперед, назад или просто топтание на месте? Свой ответ вы можете оставить в опросе в конце статьи. А если вам есть, что добавить по теме, пожалуйста, сделайте это в комментариях.


Кто не в курсе, TailwindCSS это CSS-библиотека, которая упрощает стилизацию HTML, тем же путем, как это делает Bootstrap, добавляя огромное количество разнообразных классов. Но, в отличие от Bootstrap, который добавляет уже готовые к употреблению компоненты, такие как кнопки, алерты и навбары, классы TailwindCSS нацелены на конкретное свойство. В TailwindCSS нет заранее написанной кнопки, её ты должен сделать сам.


По факту, вы пишете свой CSS как HTML-классы в формате похожем на популярный плагин Emmet. Ерунда? Как бы не так. Всё дело как всегда в деталях и окружении.


Я прекрасно понимаю людей, которые морщатся при виде такого формата записи. И я понимаю почему. Но мне кажется, что это просто плохая привычка из "программистского детства".


Мы попробуем избавится от неё и понять все плюсы работы с TailwindCSS. Для этого сначала перечислим все минусы, которые якобы свойственны таким CSS-фреймворкам. А затем разберемся, есть ли они в TailwindCSS.


Проблемы


  1. Как бы много не было классов, мы всё равно ограничены данным нам набором. Мы загоняем своё оформление в придуманные за нас рамки, что приводит к тому, что все сайты, написанные на TailwindCSS похожи друг на друга.
  2. У нас будет куча лишних стилей, которые мы не используем.
  3. Если нам нужно сделать две одинаковых кнопки, нам придется еще раз писать те же самые классы в другом месте.
  4. Засорять HTML стилями это строго против правил. Всех верстальщиков "с пелёнок" учат не писать стили inline, а TailwindCSS нарушает эту аксиому.

Если я что-то пропустил, жду ваши предложения в комментариях. Обязательно добавим и обсудим.

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


1. TailwindCSS это не Bootstrap


Вроде бы небольшое отличие TailwindCSS от всех других CSS-фреймворков, во главе которых Bootstrap, а именно классы как свойства, а не классы как компоненты, меняет примерно всё. И если внешне TailwindCSS на них похож, по своей сути, он нечто совсем другое новый подход к написанию CSS.


Если вы считаете, что TailwindCSS лишает сайты оригинальности, значит вы эту разницу пока не видите. Во-первых, претензия к одинаковости касается именно Bootstrap и подобных фреймворков с классами как компонентами. Во-вторых, даже там при желании всё можно кастомизировать до неузнаваемости, переписав классы под свои нужды и разбавив с классами из чистого CSS.


В чем основная сила Bootstrap он хорошо подходит для proof of concept. Быстро нарисовать более-менее красивый интерфейс, когда нужно показать функционал, это Bootstrap. Нам не столь важен внешний вид, главное, чтобы быстро и глаза не резало (например, в админпанели), это тоже Bootstrap.


TailwindCSS в таких случаях не подходит по определению. У вас нет готовых компонентов, вам всё нужно писать с нуля (TailwindUI не в счет).


Конечно, TailwindCSS изначально тоже вас ограничивает. Например, есть margin со значением 2.5rem, есть 3rem, но нет 2.75rem. Ограничение. Однако оно легко решается настройками в файле tailwind.config.js, где вы можете добавить любые значения, нужные вам в проекте.


Вы можете добавлять, удалять и переписывать любые классы. Если вдруг вас не устраивает цвет bg-red-500, вы можете его изменить одной строчкой кода. В других фреймворках вам бы пришлось менять компоненты. Подробнее о кастомизации читайте в документации.


2. Никаких лишних стилей


Проблема с переизбытком лишних классов также решается с помощью tailwind.config.js. Если раньше вам нужно было добавлять PurgeCSS или другую библиотеку для удаления лишних классов, теперь TailwindCSS сделает это за вас. Всё, что вам нужно сделать, указать папку, где хранятся ваши файлы с разметкой. TailwindCSS сам пройдется по ним, найдет все использованные классы, их оставит, а остальные удалит.


Если какие-то классы добавляются через JavaScript, укажите их в whitelist, TailwindCSS также их сохранит. Подробнее в документации.


3. Переиспользуемость


Если писать CSS в inline-стиле, для одинаковых или почти одинаковых кнопок каждый раз придется писать почти один и тот же код. Как эту проблему можно решить в TailwindCSS? It depends.


Если вы пишете код без фронтенд-фреймворка, ваш выбор очевиден это директива @apply.


У вас есть кнопка


<button class="px-4 py-2 font-bold text-white bg-blue-500 rounded">Отправить</button>

И тут вам нужно использовать её где-то еще. Можно просто скопировать эту кнопку и вставить HTML-код в другое место. А можно открыть main.css файл и скопировать TailwindCSS-классы туда в отдельный CSS-класс.


@tailwind base;@tailwind components;.btn {  @apply bg-blue-500 text-white font-bold py-2 px-4 rounded;}@tailwind utilities;

Теперь мы можем просто добавить btn как обычный CSS-класс в HTML и переиспользовать сколько угодно раз. При этом мы можем как-угодно кастомизировать нашу кнопку, просто добавляя TailwindCSS-классы.


<button class="btn">Отправить</button><button class="btn px-6 py-4 bg-red-500">Отправить</button>

Кнопки


По-моему, такой подход смотрится значительно интереснее того же самого БЭМ. Мы пишем основные свойства кнопки, а все модификации пишем не в отдельный класс, а просто поверх.


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


Если вы используете любой фронтенд-фреймворк, у вас вообще нет данной проблемы. Вам не нужна директива @apply. Вы просто делаете отдельный компонент для любого повторяющегося кода. Легко и удобно.


Для примера воспользуемся самым простым из возможных вариантов Alpine.js.


<div x-data="cards()" class="space-y-12">  <template x-for="card in cards">    <div>      <img class="rounded" :src="card.img" :alt="card.imgAlt" />      <div class="mt-2">        <div x-text="card.eyebrow" class="text-xs font-bold text-gray-600 uppercase"></div>        <div class="font-bold leading-snug text-gray-700">          <a x-text="card.title" :href="card.url" class="hover:underline"></a>        </div>      </div>    </div>  </template></div><script>  // ...</script>

Карточки


Даже если вы не планировали использовать фреймворк, 7 килобайт, которые вы потратите на Alpine.js, быстро окупятся. Подробнее об Alpine.js в других моих статьях.


А если вы вместо Alpine.js используете полноценный компонентный фреймворк (React, Vue и др.), это становится еще проще. Вы просто добавляете <MyComponent>, где внутри скрыта вся логика.


4. Писать стили в HTML это не плохо


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


Большую часть истории Web писать стили в HTML-коде было моветоном. Использовать тег style было жутко неудобно, всё превращалось в жуткую колбасу, которую было невозможно разобрать. К тому же, некоторый новый функционал, например media-запросы (в TailwindCSS они есть как приставки sm:, md:), в нем просто не поддерживались. Отформатированный CSS было элементарно удобнее.


Когда появился Bootstrap, весь предыдущий негативный опыт отразился на отношении сообщества к такому подходу. И он сохранился до сих пор, хотя объективных причин для этого нет.


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


Заключение


Так какое же место занимает TailwindCSS в истории CSS?


Он удобнее ванильного CSS и не создает проблем со вложенностью и коллизией имен классов.


Он отлично работает со всеми препроцессорами. Поэтому, если вам нужно построить базу стилей с переменными, прогнать цикл и т.п., TailwindCSS этому никак не мешает.


TailwindCSS расширяет работу по БЭМ. Вам не нужно делать модификатор для каждого случая, просто сделайте базовый стиль и перезапишите сверху, что нужно. Если модифицированный блок используется не в одном месте, а в множестве, можно сделать модификатор. TailwindCSS вас в этом не ограничивает.


С TailwindCSS пограничные случаи не будут занимать отдельный класс. Можно вообще весь код писать без самописных классов. Так решается проблема, для которой были придуманы CSS-модули.


Мне нравится формат работы со стилями, который мне предлагает этот фреймворк. Но каждый должен попробовать и решить это для себя самого. Мне рекомендовали TailwindCSS со словами: "Попробуй, потом не захочешь делать по другому". Я попробовал, и теперь мне не хочется делать по другому. Попробуйте и вы.


Напоследок один небольшой лайфхак, который показывает еще одну удобную функцию TailwindCSS: с ним стили можно очень удобно править прямо в Chrome DevTools.

DevTools

Выбираем элемент, в панели нажимаем .cls, пишем классы, а затем просто копируем их к себе в верстку.
Подробнее..

Как написать и поместить на сайт фотобанк на gt 100 000 картин

05.07.2020 20:11:11 | Автор: admin
Допустим, у вас есть >100'000 изображений, которые надо рассортировать и удобно выложить в веб для массового просмотра. Это может быть что угодно галерея всего созданного человечеством искусства (в задаче которую я делал), или исторический фотоархив города Москвы, или кадры из кинофильмов, или общий архив фотографий с отдыха от крупного турагентства, или веб-сайт стоковых иллюстраций и фотографий, или архив изображений при крупном СМИ за много лет в котором надо навести порядок, организовать навигацию и доступ сотрудников из внутренней сети.

Я расскажу, как это целесообразно запрограммировать.

Ключевые слова и их наследование

Современный подход, используемый всеми фотобанками и галереями снабжение иллюстраций набором тегов (ключевых слов). Я развил этот подход в двух направлениях: (1) теги могут иметь наследование (пользователь искал ягоды нашел картину с тегом черешня), и (2) теги могут крепиться на уровне не одиночных иллюстраций, а каталогов.

Недостаток такого подхода с тегами вы осуществляете поиск на основе ключевых слов, игнорируя сюжет картины. Дракон, убитый девушкой, и дракон, убивший девушку это два разных сюжета, но один и тот же для список слов: Дракон, Девушка, Смерть, и Победитель (если имела место битва). Подход на основе ключевых слов не позволит получить выборку по запросу Мёртвый дракон, в которую не входило бы изображение дракона победителя над убитым врагом.

Основные теги те, что видны пользователю в алфавитном справочнике. Дополнительные теги те, что доступны пользователю только по вводу им вручную названий этих тегов в тексте поисковой строки. Оптимальным считаю количество тэгов: основных = 1/75, дополнительных = 1/195, от количества изображений.

Множественное число (всадники, горы и т.д.) тегов обозначайте в именах файлов как <имя тега>! (т.е. восклицательный знак). Вам потребуется словарь, как теги могут называться множественное число, женский/мужской род, слова-синонимы, неверные написания.

Словарь тегов держите в 4х файлах: Marks.csv основные теги, Other.csv дополнительные теги, Wrong.csv неверные написания, синонимы, названия тегов во множественном числе, Artists.csv авторы. В файлах Marks.csv и Other.csv после идентификатора тегов и основного названия на русском языке, следует перечисление родительских тегов (т.е. наследование).

Marks.csv
Arwen;Арвен (Властелин Колец);Person,Girl,Elf,LordOfTheRingsThorinOakenshield;Торин Дубощит;Person,Male,Beard,LordOfTheRings

Здесь написано, что Арвен персона, девушка, эльф, персонаж Властелин колец; Торин Дубощит персона, мужчина, носит бороду, персонаж Властелин колец. Соответственно, при поиске пользователем по запросу Властелин колец, все изображения Арвен и Торина будут найдены. При поиске борода в числе прочего, будет Торин. При поиске Торин он тоже будет найден, так как это сокращённое написание есть во Wrong.csv.

Структура папок
Если на 100 000 изображений наложить выборку показать девушек или показать солнце, то количество результатов будет чрезмерно велико. Но такого не произойдёт, если изображения будут разбиты на папки. Например, в корневой директории есть папка Драконы, внутри неё папка Жёлтые, внутри неё папка Девушки (т.е. изображения, на которых есть девушки), и внутри неё (по всем подпапкам) 200 изображений. В этом случае, в результаты поиска выйдет не эти 200 изображений, а папка, их содержащая. Это лучше и пользователю.

Здесь, правда, существует проблема близкородственных связей. Практически всегда короли на изображениях имеют короны, но не во всех случаях. Допустим, есть папка Короли, и в ней 3000 изображений, из них 2500 в коронах. Здесь, в отношении короны простой подход показать папку не работает.

Оптимальным считаю количество папок = 1/28 от количества изображений

Как вы понимаете, если файл уже лежит в папке Драконы/Желтые/Девушки, то именно эти теги в имя файла добавлять не надо, в имя файла добавляйте только те идентификаторы тегов, которые не следуют из места его хранения.



Мультиязычность, иконки, тексты, виртуальные подпапки
Внутри каждой папки создан файл _.jpg размером 200 (ширина) * 280 (высота) это иконка папки при её просмотре (текст выводится поверх неё), как при находжении пользователя в вышестоящей папке, так и при просморте пользователем результатов поиска (если найдена эта папка). Такое же разрешение и у иконок ключевых слов.

Также, во многих папках создан файл _.txt, состоящий из таких строк:

Artefact\_.txt (фрагмент)
Миелофон=MielofonМьёльнир=MjolnirПалантир=PalantirПерчатка Таноса=ThanosGloveСклянки=Glass-Potionby-DavisonCarvalho=*TheWitcher/Wolf-Head-Logo|Амулет ВедьмакаDisneyPrincess/Moana/HeartOfTeFiti|Сердце Те ФитиSuperHeroes/Hellraiser/HellraiserBox|Шкатулка Лемаршана-m|Artefact

Здесь мы видим виды записей:
1) Склянки=Glass-Potion псевдонимы для подпапок. На иллюстрации, приведённой выше, видим что для папки Japan псевдоним не был записан, и при просмотре папки она без перевода на русский. Два тега Glass и Potion (Стекло и Зелье) переведены одним словом.
2) by-DavisonCarvalho=* псевдоним не требуется
3) SuperHeroes/Hellraiser/HellraiserBox|Шкатулка Лемаршана виртуальная подпапка. Находящаяся в другом каталоге подпапка, также, будет отображаться здесь, под заданным именем.
4) -m|Artefact папка олицетворяет тег Артефакт. Если к этому тегу привязан текст, он будет написан под иллюстрациями.

Размер на диске
Сейчас 111'000 изображений занимают на диске 65GB. И это при том, что во многих случаях из них приходится делать более тяжёлый png формат:
Если изображение с полями (и никакой штрих или предмет изображения не заходит на них), поля надо убрать в paint-е.
Если изображение с чужими вотермарками фотогалерей а-ля пикабу, вотермарки убираются в фотошопе.
Если оно в формате .webp, приходится просто пересохранять в .png, иначе моя программа не сможет сделать миниатюры (да, я знаю, можно было дописать код).
Если формат не .png, .jpg, .gif. Я против излишнего разнообразия форматов.

Структура сайта файлы и папки
index.php запущенный без параметров, выводит корневую папку галереи, алфавит и поисковую строку. По клику на подпапку в корневой папке переходит на неё. По клику на букве алфавита переходит на основные теги, начинающиеся на эту букву. При вводе в поисковую строку текста, переходит на тег, опознанный по этому тексту.
i.php инструмент просмотра одного выбранного изображения. Позволяет перейти к тегам из списка, которым это изображение соответствует.
img корневая папка веб-галереи
m папка со сгенерированными миниатюрами всех изображений. Миниатюры имеют высоту 200, ширина в соответствии с пропорциями изображения. Структура папки m повторяет структуру папки img. Папка m создаётся программно перед выкладыванием каждой версии галереи.
Tags для каждого ключевого слова, содержит файл с результатом его поиска в каталогах.
Marks виды файлов:
1) Для каждого ключевого слова, содержит файл его миниатюры
2) Для большинства ключевых слов, содержит файл с их текстовым описанием или тематическую историю, анекдот
3) Для некоторых ключевых слов, содержит один или несколько html-текстовых тематических рассказов
4) Также, в этой папке хранятся файлы типа <код буквы>.txt упорядоченные по алфавиту списки ключевых слов по каждой букве русского алфавита

Порядок выгрузки новой версии галереи на сайт
Специально написанная (используя Delphi и библиотеку Graphics32) программа делает следующее:
1) Проверка папки галереи проверяется отсутствие лишних символов, правильность и отсутствие избыточности в тегах файлов (в т.ч. с учётом их иерархии), отсутствие среди имён файлов синонимов и ошибочных написаний ключевых слов (используя Wrong.csv), корректность файлов _.txt, наличие миниатюр для папок, отсутствие файлов с неверными именами.
2) Пересоздание миниатюр для всех изображений. На этом этапе часто выясняется, что некоторые файлы имеют неверное расширение: .jpg вместо .png и т.д.
3) Генерация результатов поиска по каждому ключевому слову. Проверка наличия миниатюр для основных ключевых слов. Особый порядок для ключевых слов исключений, по которым надо выдавать специально прописанную подборку.
4) Генерация списков ключевых слов для букв русского алфавита.

Затем, и папка галереи, и эти материалы выкладываются на сервер.
Движок веб-галереи не использует СУБД.

Хостинг
Хостинг я использую Avahost, 100GB на диске стоят 500 рублей в месяц. Как несложно заметить, при размере коллекции 65GB, + миниатюры и прочее, и размере хостинга 100GB, обновление не бывает бесшовным. Не достаточно места чтобы сначала полностью новую версию выгрузить и на неё затем перейти бесшовно, возникает неизбежный интервал неработоспособности сайта длиной в несколько часов. Обновления я сейчас делаю раз в месяц.

Файлы отправляются на хостинг в виде архивов. Использующаяся сейчас на всех хостингах система cPanel умеет распаковывать только архивы zip. Желательно использовать файлы длиной до 2.5GB, иначе после завершения выгрузки файла в папку через веб-интерфейс cPanel, полоска прогресса выгрузки (изначальный цвет синий) может стать не зелёной, а красной. В чём разница я не понял (файл вроде бы даже в этом случае выгружается нормально), но я в этом случае перевыкладываю. Для некоторых папок это приводит к тому, что папки приходится делить на несколько отдельных архивов zip.

Ранее я пытался создать хостинг дома, купил на авито б/у нетбук за 2000 рублей. Настроил, всё работает. Проходит пара суток не работает. Перезагружаю без толку. Потом, опять заработало, потом опять нет. Сменил нетбук (купил другой, более мощный, тоже на Авито, за 3000 рублей) и стал использовать другой софт то же самое. Сменил трёх провайдеров (Севен скай > Акадо > МГТС) то же самое. Короче, оборудование стоящее у провайдеров обрубает видимо домашний хостинг, причём сами провайдеры об этом и не знают. Или какие другие причины. Идите к хостерам, не делайте дома хостинга. Инди хостинг отстой. Даже примитивный маршрутизатор для взаимодействия сетевых игр лучше сваять на php и положить на хостинг чем держать дома или в офисе, и ждать что сломается без разумных причин.

Хозяйке на заметку (про хостинг)
Кроме техно-характеристик (из которых действительно нужно только одна количество гигабайт, всё остальное числа у всех по своей шкале, я пришёл к тому что характеристики лучше у авахост), есть такой параметр абуз-устойчивость. abuse жалоба. Причём причина для жалобы может возникнуть на ровном месте, например у студии Артемия Лебедева. Поэтому нормальный хостинг имеет параметр абузоустойчивость, устойчивость к жалобам. (Не путать со специальными хостингами, где можно размещать вообще что угодно, хоть фишинг страницу сбербанка с приглашением войти в личный кабинет это отдельные конторы, я них не разбираюсь).

Монетизация
Допустим, что вы крупное СМИ, и решили значительную часть имеющих у вас (накопленных за десятки лет) фотографий сделать публичными. Например, используя описанную выше технологию. Как можно с этого зарабатывать (кроме брендирования наложением вотемарков на фотографии, а также их продажи)? Ну, если вы СМИ то вы знаете, расскажу для остальных.

Большинство схем монетизации даёт вам по 10 копеек со среднего посетителя сайта в день (включая как посетивших сайт один раз, так и посетивших несколько раз в день). Аналогично даёт автору сайта и РСЯ (рекламная сеть Яндекса). Чтобы зарабатывать больше, нужно вовлекать людей в религиозные секты или продавать чудо-талисманы, я такого не делаю. Агрегаторы подобных реклам легко найти в сети, они платят за достижение результатов (человек купил пылесос Кирби или стал членом секты). Причём, что обидно: я этого не делаю, зато Яндекс то и дело гонит через мой сайт подобное. В итоге людям всё равно подчас продают туфту задорого (через Яндекс), только я с этого получаю в 6 10 раз меньше.

У многих моих знакомых людей по умолчанию стоит адблок или что-то подобное и реклама Яндекса не видна. Причём сами они этого не ставили. Почему так не знаю.

Яндекс позволяет вывести сумму по достижении 3000 рублей.

Также, владелец сайта может зарегистрироваться в miralinks.ru и размещать статьи. Адрес статьи и ссылки на неё надо размещать вечно, т.е. предусмотрите заранее чтобы их размещение не было слишком токсично. Допустимо, чтобы новые статьи вытесняли прежние в следующие страницы истории.

Можно продавать размещение баннеров, и иное сообразно смыслу ресурса.

Где можно увидеть данную технологию в действии (какой проект я делаю)?

Я делаю сайт corchaosis.ru некий аналог вики для графики.

Почему пока не получилось его раскрутить (как я думаю):
Людям нужно только средство совершения достижений.
Если даже люди идут в картинную галерею смотреть картины, им всё равно важно материальное достижение. Я посетил Третьяковскую галерею. Я посмотрел Лебединое озеро.
Если веб-ресурс не приближает человека к материальным достижениям, то на него не заходят.
Сами люди могут думать другое, что им нравятся картины. Это не важно. Если мы что-то делаем в отношении людей, мы должны быть сложнее людей. Понимать и осознавать больше. Если лиса ест кур и мышей, то лиса должна быть и совершеннее, чем курицы. Из уровня представлений курицы, нельзя достичь результатов лисы.
Людям нужно интерактивное.
ВЕБ 1.0 мертв.
Если вы не можете предложить интерактивности, то вы никому не нужны.
Вас не смотрят. Это снова о достижении результатов. Кобвой не идёт в джунгли ради туризма, он идёт в джунгли, чтобы основать собственное ранчо. Пока у сайта нет инструментов чтобы создать своё ранчо (портфолио и т.д.), джунгли ковбоям неинтересны.

Где взять готовый движок
В принципе я описал всё необходимое, чтобы его сделать. Можно написать мне.
Локальный exe файл написан на Delphi+Graphics32, серверная часть это два файла .php.
Подробнее..

Анонс онлайн-митапа по тестированию три доклада про плохие процессы в команде, хотфиксы и первые шаги в автоматизации

07.07.2020 10:10:24 | Автор: admin

image


Наши тестировщики из Новосибирска соскучились по встречам с единомышленниками и приготовили онлайн-митап, который нельзя пропустить. Катя Синько порассуждает о том, как занять проактивную позицию и улучшить выстроенные процессы в команде. Инна Шундеева расскажет, как стать автоматизатором и не отступать перед трудностями. А Люда Малеева из Miro поделится советами, как организовать релизы без багов и что правильно делать, если на боевой их всё-таки нашли.


Когда: 9 июля в 16:00 (Мск)
Где: Ютуб-канал Контура


Менять процессы нельзя страдать Катя Синько, Контур


Многие ежедневно сталкиваются проблемами типа усталости от монотонной работы или повторяющихся багов на боевой. С этим можно смириться и ждать, пока менеджер разберётся, а можно взять ситуацию в свои руки, найти причину проблемы и полечить её для себя и для всей команды.


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


Качество релизов ответственность команды Люда Малеева, Miro


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


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


5 открытий: дневники автоматизатора Инна Шундеева, Контур


Всё больше тестировщиков руками смотрят в сторону автоматизации. Но этот путь кажется начинающим сложным и тернистым. А как стать автоматизатором, который смог?


В докладе я расскажу о проблемах, с которыми столкнулась сама, какие уроки вынесла и как не сошла с намеченного пути.


До встречи! Жмите колокольчик на Ютубе, готовьте прохладительные напитки, подключайтесь и смотрите онлайн-митап Kontur Tech Talks по тестированию.

Подробнее..

Из песочницы Расширенная настройка web сервера (Nginx Apache2)

01.07.2020 18:14:54 | Автор: admin

В этом руководстве мы рассмотрим процедуру установки и настройки работы двух web-серверов с целью использования преимуществ каждого из них, где Nginx как frontend и Apache как backend.
В этой статье будет идти речь идти речь о настройки сервера с использованием следующих технологиях: Apache2, Nginx, ngx_pagespeed, PHP, PHP-FPM, MariaDB и MemCached.


Nginx


HTTP-сервер и обратный прокси-сервер, почтовый прокси-сервер, а также TCP/UDP прокси-сервер общего назначения.


Установка


Установите пакеты, необходимые для подключения apt-репозитория:


sudo apt install curl gnupg2 ca-certificates lsb-release

Для подключения apt-репозитория для стабильной версии nginx, выполните следующую команду:


echo "deb http://nginx.org/packages/debian `lsb_release -cs` nginx" \    | sudo tee /etc/apt/sources.list.d/nginx.list

Теперь нужно импортировать официальный ключ, используемый apt для проверки подлинности пакетов:


curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add -

Проверьте, верный ли ключ был импортирован:


sudo apt-key fingerprint ABF5BD827BD9BF62

Вывод команды должен содержать полный отпечаток ключа 573B FD6B 3D8F BC64 1079 A6AB ABF5 BD82 7BD9 BF62:


pub   rsa2048 2011-08-19 [SC] [expires: 2024-06-14]      573B FD6B 3D8F BC64 1079  A6AB ABF5 BD82 7BD9 BF62uid   [ unknown] nginx signing key

Чтобы установить nginx, выполните следующие команды:


sudo apt update && sudo apt install nginx

Настройка


Запускаем nginx


systemctl enable nginx && systemctl start nginx

Проверяем, что пользователь nginx user www-data:


vi /etc/nginx/nginx.conf

Проверим работу веб-сервера. Открываем браузер и вводим в адресной строке http://IP-адрес сервера.


В итоге мы должны увидеть заголовок Welcome to nginx!.


PHP-FPM


FastCGI протоколу взаимодействия веб-сервера с программами. FPM расшифровывается как Fastcgi Process Manager.


Установка


Устанавливаем PHP-FPM:


apt-get install php-fpm

Настройка


Разрешаем автозапуск php-fpm и запускаем его:


systemctl enable php7.3-fpm && systemctl start php7.3-fpm

Обратите внимание, что мы запустили php-fpm версии 7.2. Но установлена может быть и другая версия ее можно узнать по версии php командой php -v.


ngx_pagespeed


ngx_pagespeed (или просто pagespeed) это модуль Nginx, предназначенный для автоматической оптимизации работы сайта путём сокращения времени загрузки сайта в браузере. Дополнительную информацию о модуле можно найти на официальном сайте.


Установка


Устанавливаем необходимые пакеты:


sudo apt-get install unzip gcc make g++ libpcre3-dev zlib1g-dev build-essential libpcre3 uuid-dev

Настройка


Создаем и переходим в папку, в которой будем собирать ngx_pagespeed:


mkdir /etc/nginx/buildcd /etc/nginx/build

Узнаем текущую версию nginx:


nginx -v

Скачиваем необходимую версию:


wget -qO - http://nginx.org/download/nginx-1.18.0.tar.gz | tar zxfv -

В нашем случае это nginx 1.18


Скачиваем репозиторий с ngx_pagespeed:


git clone https://github.com/pagespeed/ngx_pagespeed.gitcd ngx_pagespeed/git checkout tags/latest-stablecat PSOL_BINARY_URL

Скачиваем папку psol:


psol_url=https://dl.google.com/dl/page-speed/psol/${NPS_VERSION}.tar.gz [ -e scripts/format_binary_url.sh ] && psol_url=$(scripts/format_binary_url.sh PSOL_BINARY_URL)wget ${psol_url}tar zxf 1.13.35.2-x64.tar.gz

Собираем файл ngx_pagespeed.so:


cd ../nginx-1.18.0/./configure --add-dynamic-module=../ngx_pagespeed --with-compatmakels objs/*so

Копируем файл ngx_pagespeed.so:


cd objscp ngx_pagespeed.so /etc/nginx/modules

Apache2


Для поддержки файла .htaccess, который используется многими сайтами, необходимо установить и настроить веб-сервер Apache.


Установка


Устанавливаем apache и модуль для php:


apt-get install apache2 libapache2-mod-php

Настройка


Заходим в настройки портов:


vi /etc/apache2/ports.conf

И редактируем следующее:


Listen 127.0.0.1:8080# <IfModule ssl_module>#    Listen 443# </IfModule># <IfModule mod_gnutls.c>#    Listen 443# </IfModule>

мы настроили прослушивание на порту 8080, так как на 80 уже работает NGINX. Также мы закомментировали прослушивание по 443, так как и он будет слушаться NGINX.


Запрещаем mpm_event:


a2dismod mpm_event

по умолчанию, apache2 может быть установлен с модулем мультипроцессовой обработки mpm_event. Данный модуль не поддерживает php 7 и выше.


Разрешаем модуль мультипроцессовой обработки mpm_prefork:


a2enmod mpm_prefork

Разрешаем модуль php:


a2enmod php7.3

Разрешаем модуль rewrite:


a2enmod rewrite

Разрешаем модуль setenvif:


a2enmod setenvif

Разрешаем автозапуск и запускаем службу:


systemctl enable apache2 && systemctl start apache2

Открываем браузер и вводим в адресную строку http://IP-адрес сервера:8080. Мы должны увидеть привычную страницу.


в разделе Server API мы должны увидеть Apache.


Apache2 Real IP


Запросы на apache приходят от NGINX, и они воспринимаются первым как от IP-адреса 127.0.0.1. На практике, это может привести к проблемам, так как некоторым сайтам необходимы реальные адреса посетителей. Для решения проблемы будем использовать модуль remoteip.


Установка


Создаем конфигурационный файл со следующим содержимым:


vi /etc/apache2/mods-available/remoteip.conf

Настройка


Записываем:


<IfModule remoteip_module>  RemoteIPHeader X-Forwarded-For  RemoteIPTrustedProxy 127.0.0.1/8</IfModule>

Активируем модуль:


a2enmod remoteip

Перезапускаем apache:


systemctl restart apache2

Для проверки настройки открываем браузер и вводим в адресную строку http://IP-адрес сервера, где откроется наша страница phpinfo.


В разделе Apache Environment мы должны увидеть внешний адрес компьютера, с которого обращаемся к серверу в опции REMOTE_ADDR.


PHP


Устанавливаем необходимые библиотеки для PHP и PHP-FPM:


apt install php-xml php-intl php-gd php-curl php-zip php-mbstring php-bcmath php-bz2 php-cgi php-cli php-common php-dba php-dev php-enchant php-gmp php-imap php-interbase php-json php-ldap php-odbc php-opcache php-phpdbg php-pspell php-readline php-recode php-sybase php-tidy php-xmlrpc php-xsl

Mysql (Mariadb)


Установка


Установим MariaDB:


apt-get install mariadb-server php-mysql php-mysqli

Настройка


Разрешаем автозапуск и запускаем СУБД:


systemctl enable mariadbsystemctl start mariadb

Зададим пароль для пользователя root:


mysqladmin -u root password

Перезапускаем apache2:


systemctl restart apache2

Создаем и настраиваем пользователя:


mysql -uroot -pmysql> GRANT ALL PRIVILEGES ON *.* TO 'dbuser'@'localhost' IDENTIFIED BY 'password' WITH GRANT OPTION;# ALL PRIVILEGES: предоставляет полные права на использование данных.# *.* : права предоставляются на все базы и все таблицы.# dbuser: имя учетной записи.# localhost: доступ для учетной записи будет предоставлен только с локального компьютера.# password: пароль, который будет задан пользователю.# WITH GRANT OPTION: будут предоставлены дополнительные права на изменение структуры баз и таблиц.

Настраиваем возможность входа в adminer.php


> update user set plugin='' where User='root';> flush privileges;> exit

Перезапускаем:


sudo systemctl restart mariadb.service

Memcached


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


Установка


Для начала, выполняем установку пакетов:


apt-get install memcached php-memcached

Настройка


После разрешаем автозапуск и запускаем сервис кэширования:


systemctl enable memcached && systemctl start memcached

Перезапускаем apache2:


systemctl restart apache2

Для проверки, что модуль memcached появился в PHP, открываем наш сайт в браузере в phpinfo должна появиться новая секция Memcached.


Доступы и настройка находится в файле memcached.conf:


vi /etc/memcached.conf

Проверяем работу:


netstat -tap | grep memcached

Настройка пользователя


Создаем пользователя:


adduser dev

Добавляем пользователя в группу www-data:


adduser dev www-data

Даем права sudo пользователю:


usermod -aG sudo dev

Настройка сайта


Создаем каталог для сайта


Создаем каталог:


mkdir -p /var/www/example.com/{www,tmp}mkdir -p /var/www/example.com/log/{nginx,apache}

Задаем права на папки:


chown -R www-data:www-data /var/www/example.com/wwwchmod -R 775 /var/www/example.com/www

Создаем индексный файл:


vi /var/www/example.com/www/index.php

С содержанием:


<?php phpinfo(); ?>

Настройка сайта


Nginx http


server {    listen       80;    server_name  example.com;    set $root_path /var/www/example.com/www;    access_log /var/www/example.com/log/nginx/access_log;    error_log /var/www/example.com/log/nginx/error_log;    root   $root_path;    gzip  on;    gzip_disable "msie6";    gzip_min_length 1000;    gzip_vary on;    gzip_proxied    expired no-cache no-store private auth;    gzip_types      text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss;    location / {        location ~ [^/]\.ph(p\d*|tml)$ {            try_files /does_not_exists @fallback;        }        location ~* ^.+\.(jpg|jpeg|gif|png|css|zip|tgz|gz|rar|bz2|doc|docx|xls|xlsx|exe|pdf|ppt|tar|wav|bmp|rtf|js)$ {            try_files $uri $uri/ @fallback;        }        location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ {            expires 7d;            access_log off;        }        location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff2?)$ {            add_header Access-Control-Allow-Origin "*";            expires 7d;            access_log off;        }        location = /favicon.ico {            log_not_found off;            access_log off;        }        location = /robots.txt {            log_not_found off;            access_log off;        }        location / {            try_files /does_not_exists @fallback;        }    }    # Если используется PHP    location @fallback {        proxy_pass http://127.0.0.1:8080;        proxy_redirect http://127.0.0.1:8080 /;        proxy_set_header Host $host;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;        proxy_set_header X-Forwarded-Proto $scheme;        access_log off;    }    # Если используется PHP-FPM    location @fallback {        index index.php index.html index.htm;         fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;        fastcgi_index index.php;        fastcgi_param SCRIPT_FILENAME $root_path$fastcgi_script_name;        include fastcgi_params;        fastcgi_param DOCUMENT_ROOT $root_path;    }}

Создаем ярлык:


ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/example.com

Все запросы будут переводится на локальный сервер по порту 8080, на котором работает apache, кроме обращений к статическим файлам (jpg, png, css и так далее).


apache2


Создаем файл:


vi /etc/apache2/sites-available/example.com.conf

<VirtualHost 127.0.0.1:8080>    Define root_domain example.com    Define root_path /var/www/example.com    ServerName ${root_domain}    ServerAlias www.${root_domain}    DocumentRoot ${root_path}/www    ErrorLog ${root_path}/log/apache/error_log    TransferLog  ${root_path}/log/apache/access_log    <IfModule mod_dir.c>        DirectoryIndex index.php index.html index.htm    </IfModule>    <Directory /var/www/example.com/www>        AllowOverride All        Options Indexes ExecCGI FollowSymLinks        Require all granted    </Directory>    <IfModule setenvif_module>        SetEnvIf X-Forwarded-Proto https HTTPS=on    </IfModule>    <IfModule php7_module>        php_admin_value upload_tmp_dir ${root_path}/tmp        php_admin_value doc_root ${root_path}        php_value open_basedir    ${root_path}:/usr/local/share/smarty:/usr/local/share/pear        php_value post_max_size 512M        php_value upload_max_filesize 512M        php_flag short_open_tag On    </IfModule></VirtualHost>

Создаем ярлык:


ln -s /etc/apache2/sites-available/example.com.conf /etc/apache2/sites-enabled/example.com.conf

Проверяем


Проверяем корректность настроек конфигурационных файлов:


nginx -tapachectl configtest

Перезапускаем веб-сервер:


systemctl reload nginxsystemctl reload apache2

https (Существующий Сертификат)


Создаем файл:


vi /etc/nginx/conf.d/example.com.conf

# Устанавливается только на главный домен, чтобы шел редирект с ip на главный домен.server {    listen 80;    server_name Ваш_ip;    return 301 https://example.com$request_uri;}server {    listen       443 ssl;    ssl on;    ssl_certificate /etc/nginx/ssl/cert.pem;    ssl_certificate_key /etc/nginx/ssl/cert.key;    server_name example.com;    set $root_path /var/www/example.com/www;    access_log /var/www/example.com/log/nginx/access_log;    error_log /var/www/example.com/log/nginx/error_log;    gzip  on;    gzip_disable "msie6";    gzip_min_length 1000;    gzip_vary on;    gzip_proxied    expired no-cache no-store private auth;    gzip_types      text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss    root   $root_path;    location / {        location ~ [^/]\.ph(p\d*|tml)$ {            try_files /does_not_exists @fallback;        }        location ~* ^.+\.(jpg|jpeg|gif|png|css|zip|tgz|gz|rar|bz2|doc|docx|xls|xlsx|exe|pdf|ppt|tar|wav|bmp|rtf|js)$ {            try_files $uri $uri/ @fallback;        }        location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ {            expires 7d;            access_log off;        }        location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff2?)$ {            add_header Access-Control-Allow-Origin "*";            expires 7d;            access_log off;        }        location = /favicon.ico {            log_not_found off;            access_log off;        }        location = /robots.txt {            log_not_found off;            access_log off;        }        location / {            try_files /does_not_exists @fallback;        }    }    # Если используется PHP    location @fallback {        proxy_pass http://127.0.0.1:8080;        proxy_redirect http://127.0.0.1:8080 /;        proxy_set_header Host $host;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;        proxy_set_header X-Forwarded-Proto $scheme;        access_log off;    }    # Если используется PHP-FPM    location @fallback {        index index.php index.html index.htm;         fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;        fastcgi_index index.php;        fastcgi_param SCRIPT_FILENAME $root_path$fastcgi_script_name;        include fastcgi_params;        fastcgi_param DOCUMENT_ROOT $root_path;    }}

Все запросы будут переводится на локальный сервер по порту 8080, на котором работает apache, кроме обращений к статическим файлам (jpg, png, css и так далее).


Apache2


Создаем файл:


vi /etc/apache2/sites-available/example.com.conf

<VirtualHost 127.0.0.1:8080>    Define root_domain example.com    Define root_path /var/www/example.com    ServerName ${root_domain}    ServerAlias www.${root_domain}    DocumentRoot ${root_path}/www    ErrorLog ${root_path}/log/apache/error_log    TransferLog  ${root_path}/log/apache/access_log    <IfModule mod_dir.c>        DirectoryIndex index.php index.html index.htm    </IfModule>    <Directory /var/www/example.com/www>        AllowOverride All        Options Indexes ExecCGI FollowSymLinks        Require all granted    </Directory>    <IfModule setenvif_module>        SetEnvIf X-Forwarded-Proto https HTTPS=on    </IfModule>    <IfModule php7_module>        php_admin_value upload_tmp_dir ${root_path}/tmp        php_admin_value doc_root ${root_path}        php_value open_basedir    ${root_path}:/usr/local/share/smarty:/usr/local/share/pear        php_value post_max_size 512M        php_value upload_max_filesize 512M        php_flag short_open_tag On    </IfModule></VirtualHost>

Создаем ярлык:


ln -s /etc/apache2/sites-available/example.com.conf /etc/apache2/sites-enabled/example.com.conf

Проверяем


Проверяем корректность настроек конфигурационных файлов:


nginx -tapachectl configtest

Перезапускаем веб-сервер:


systemctl reload nginxsystemctl reload apache2

ngx_pagespeed on


Загрузка динамического модуля PageSpeed


Откройте файл nginx.conf:


vi /etc/nginx/nginx.conf

Добавляем в начало:


load_module modules/ngx_pagespeed.so;

Настраивается PageSpeed в http контексте, поэтому поместите эти директивы в новый файл конфигурации под названием example.com.conf в файле /etc/nginx/conf.d каталог.


# Максимальный размер кэшаpagespeed MessageBufferSize 10240;# Путь к каталогу кэшаpagespeed FileCachePath /var/cache/nginx_pagespeed;server {    listen       80;    server_name example.com;    set $root_path /var/www/example.com/www;    # запуск pagespeed    pagespeed on;    root   $root_path;    # Адрес и директория сайта    pagespeed LoadFromFile "http://www.example.com" "/var/www/example.com/www";    access_log /var/www/example.com/log/nginx/access_log;    error_log /var/www/example.com/log/nginx/error_log;    # Настройки фильтров    pagespeed RewriteLevel CoreFilters;    pagespeed EnableFilters collapse_whitespace,remove_comments;    pagespeed DisableFilters rewrite_images;    gzip  on;    gzip_disable "msie6";    gzip_min_length 1000;    gzip_vary on;    gzip_proxied    expired no-cache no-store private auth;    gzip_types      text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss;    location / {        location ~ [^/]\.ph(p\d*|tml)$ {            try_files /does_not_exists @fallback;        }        location ~* ^.+\.(jpg|jpeg|gif|png|css|zip|tgz|gz|rar|bz2|doc|docx|xls|xlsx|exe|pdf|ppt|tar|wav|bmp|rtf|js)$ {            try_files $uri $uri/ @fallback;        }        location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ {            expires 7d;            access_log off;        }        location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff2?)$ {            add_header Access-Control-Allow-Origin "*";            expires 7d;            access_log off;        }        location = /favicon.ico {            log_not_found off;            access_log off;        }        location = /robots.txt {            log_not_found off;            access_log off;        }        location / {            try_files /does_not_exists @fallback;        }        # правила обработки адресов        location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" {            add_header "" "";        }        location ~ "^/pagespeed_static/" { }        location ~ "^/ngx_pagespeed_beacon$" { }    }    # Если используется PHP        location @fallback {            proxy_pass http://127.0.0.1:8080;            proxy_redirect http://127.0.0.1:8080 /;            proxy_set_header Host $host;            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;            proxy_set_header X-Forwarded-Proto $scheme;            access_log off;       }    # Если используется PHP-FPM        location @fallback {            index index.php index.html index.htm;             fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;            fastcgi_index index.php;            fastcgi_param SCRIPT_FILENAME $root_path$fastcgi_script_name;            include fastcgi_params;            fastcgi_param DOCUMENT_ROOT $root_path;       }}

Создаем папку для хранения кэша:


mkdir /var/cache/nginx_pagespeed/chown www-data:www-data /var/cache/nginx_pagespeed/

Проверяем конфигурацию Nginx и применяем настройки:


nginx -tnginx -s reload
Подробнее..

Ещё один велосипед пишем свой автозагрузчик классов для Битрикс

03.07.2020 20:16:17 | Автор: admin
Кто бы что ни говорил, но я считаю, что изобретение велосипедов штука полезная. Использование готовых библиотек и фреймворков, конечно, хорошо, но порой стоит их отложить и создать что-то своё. Так мы поддерживаем мозг в тонусе и реализуем свой творческий потенциал.

Статья обещает быть длинной, поэтому устраивайтесь поудобнее, я начинаю.

Итак, Битрикс, а точнее, Bitrix Framework. Несмотря на наличие богатого API, периодически возникает необходимость в создании своих классов/библиотек, а также подключении сторонних. Поэтому для начала рассмотрим уже имеющиеся способы автозагрузки.

Старый добрый include/require. Я его добавил сугубо для исторической справки. Хотя на заре своего программерского пути я складывал нужные классы и библиотеки в отдельную папку, создавал отдельный файл, куда инклудил все эти классы и уже потом инклудил файл с инклудами (прошу прощения за тавтологию).

Composer. Позволяет подключать как собственные классы, так и сторонние библиотеки. Однако при добавлении новых классов требует ручного обновления. Кроме того, сами классы, файлы и неймспейсы также нужно писать вручную. О том, как подружить Битрикс с композером, можно почитать тут

Битриксовый лоадер. Используется как для подключения модулей, так и для автозагрузки классов. Однако прежде, чем подключить нужные классы, придётся сформировать массив, где ключами будут выступать имена классов, а значениями пути к ним. И выглядеть это всё будет примерно так:

$classes = [    'Namespace\\Package\\ClassName' => '/path/to/class.php'];Loader::registerAutloadClasses(null, $classes);


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

  • Помимо написания классов нужно также прописать процедуру установки и удаления модуля. Там есть ряд обязательных параметров и методов, без которых модуль, возможно, работать не будет (хотя не знаю, не проверял)
  • Без подключения модуля классы работать не будут
  • Не всегда имеет смысл выносить класс в отдельный модуль

Тем не менее, если вы написали свой локальный модуль и потом решили добавить в него ещё пару классов, то для их использования вам уже не потребуется переустановка модуля просто вызываете нужные методы в нужном месте, и всё!

Ну а теперь, собственно, сам велосипед...


Проанализировав все вышеописанные методы, я задумался о том, что бы такое придумать, чтобы достаточно было просто складывать новые классы в определённое место, а они бы подгружались потом автоматически, без добавления новых элементов в массив неймспейсов и путей к файлам.

В итоге было решено написать специальный модуль как бы странно это ни звучало, но мне эта идея показалась более удачной, чем добавлять несколько функций в init.php который будет автоматически подгружать все классы из необходимой директории.

Опущу процесс написания установки/удаления модуля кому надо, посмотрят в исходниках, а сразу перейду к основному функционалу.

Т.к. изначально неизвестно количество уровней вложенности папок, то нужно, чтобы методы были рекурсивными. Также мы будем использовать класс Bitrix\Main\Loader, который и будет грузить классы.

Представим, что мы решили складировать все наши классы в директорию /local/php_interface/lib:

image

Также у нас могут быть файлы, которые не содержат классов и, соответственно, не должны быть включены в автозагрузчик, поэтому нужно также учесть и этот момент.

Итак, поехали.

namespace Ramapriya\LoadManager;use Bitrix\Main\Loader;class Autoload{}


Первым делом нам нужно получить всё содержимое нашей папки. Для этого напишем метод scanDirectory:

    public static function scanDirectory(string $dir) : array    {        $result = [];        $scanner = scandir($dir); // сканируем содержимое директории        foreach ($scanner as $scan) {            switch ($scan) {                // пропускаем                case '.':                 case '..':                    break;                default:// получаем путь вложенной папки или файла                                        $item = $dir . '/' . $scan;                     $SplFileInfo = new \SplFileInfo($item);                        if($SplFileInfo->isFile()) {// если элемент является файлом, кладём в возвращаемый массив                            $result[] = $scan;                                             } elseif ($SplFileInfo->isDir()) {// если элемент является директорией, вызываем текущую функцию и результаты кладём в возвращаемый массив                                                $result[$scan] = self::scanDirectory($item, $result[$scan]);                         }            }        }            return $result;    }


На выходе должно получиться следующее:



Как мы видим, структура файлов соблюдена, поэтому можно приступать к формированию массива для автозагрузки:

/* Тут уже добавляется переменная $defaultNamespace, которая и будет являться основой для имени класса. Также добавим массив php-файлов, которые не должны попасть в автозагрузчик*/    public static function prepareAutoloadClassesArray(string $directory, string $defaultNamespace, array $excludeFiles) : array    {        $result = [];// вызываем предыдущий метод        $scanner = self::scanDirectory($directory);             foreach ($scanner as $key => $value) {                $sep = '\\';                        switch(gettype($key)) {                                case 'string':// если тип ключа является строкой, скорее всего это директория                    $SplFileInfo = new \SplFileInfo($directory . '/' . $key);                    $classNamespace = $defaultNamespace . $sep . $key;                        if($SplFileInfo->isDir()) {// ещё раз проверяем, является ли ключ директорией, и если является, то вызываем текущую функцию, передавая в качестве аргументов полученные значения                        $tempResult = self::prepareAutoloadClassesArray($directory . '/' . $key, $classNamespace, $excludeFiles);                        foreach($tempResult as $class => $file) {// делаем прогон массива и записываем полученные данные в результат                            $result[$class] = $file;                         }                    }                        break;                    case 'integer':// если тип ключа - число, то с большой долей вероятности значением является файл                    $SplFileInfo = new \SplFileInfo($directory . '/' . $value);// получаем название класса из файла (поэтому я рекомендую именовать файлы и папки с соблюдением того же регистра, что и в классах)                    $classNamespace = $defaultNamespace . $sep . str_ireplace('.php', '', $SplFileInfo->getBasename()); // далее проверяем является ли значение php-файлом                    if(                        $SplFileInfo->isFile() &&                        $SplFileInfo->getExtension() === 'php'                    ) { // прогоняем массив исключаемых файлов и проверяем, нет ли их среди наших значений                        foreach($excludeFiles as $excludeFile) {                            if($SplFileInfo->getBasename() !== $excludeFile) {// записываем в массив относительный путь файла с классом                                $result[$classNamespace] = str_ireplace($_SERVER['DOCUMENT_ROOT'], '', $directory . '/' . $value);                             }                        }                                                                    }                        break;                                }            }            return $result;    }


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



Для проверки работоспособности добавим в папку с исключениями файл MainException.php, содержащий следующий класс:

<?phpnamespace Ramapriya\Exceptions;class MainException extends \Exception{    public function __construct($message = null, $code = 0, Exception $previous = null)    {        parent::__construct($message, $code, $previous);    }}


Как мы видим, наш файл подгрузился в массив классов:



Забегая вперёд, попробуем вызвать наше новое исключение:

throw new Ramapriya\Exceptions\MainException('test exception');


В результате увидим:

[Ramapriya\Exceptions\MainException]
test exception (0)


Итак, нам осталось реализовать метод автозагрузки полученного массива. Для этой цели напишем метод с самым что ни на есть банальным названием loadClasses, куда передадим полученный массив:

    public static function loadClasses(array $classes, $moduleId = null)    {        Loader::registerAutoloadClasses($moduleId, $classes);    }


Данный метод использует битриксовый лоадер, который и регистрирует массив с нашими классами.

Теперь осталось совсем немного сформировать массив с классами и загрузить их с помощью написанного нами класса. Для этого в нашей папке lib создадим файл include.php:

<?phpuse Bitrix\Main\Loader;use Bitrix\Main\Application;use Ramapriya\LoadManager\Autoload;// загружаем наш модуль - обязательно нужно перед этим его установить, иначе ничего не будет работатьLoader::includeModule('ramapriya.loadmanager');$defaultNamespace = 'Ramapriya';$excludeFiles = ['include.php'];$libDir = Application::getDocumentRoot() . '/local/php_interface/lib';$autoloadClasses = Autoload::prepareAutoloadClassesArray($libDir, $defaultNamespace, $excludeFiles);Autoload::loadClasses($autoloadClasses);

Далее подключим данный файл в init.php:

// init.php$includeFile = $_SERVER['DOCUMENT_ROOT'] . '/local/php_interface/lib/include.php';if(file_exists($includeFile)) {    require_once $includeFile;}

Вместо заключения


Ну что же, поздравляю, наш велосипед готов и со своей функцией прекрасно справляется.
Исходники, как всегда, на гитхабе.

Спасибо за внимание.
Подробнее..

Из песочницы Как определить функционал MVP и влюбить клиента в пилотную версию продукта

04.07.2020 00:19:48 | Автор: admin

Итак, MVP. Достаточно заезженная тема, на мой взгляд. Каждый, кто хоть как-то связывал себя с разработкой программного обеспечения за последние 5 лет, с 99% вероятностью слышал эти 3 буквы. Но даже несмотря на обилие информации, народ все равно наступает на грабли идеального продукта при создании проектов.


Эта статья не претендует на то, чтобы быть истиной в последней инстанции. Она не про важность и необходимость MVP. И не про его роль в бережливом запуске стартапов. Я просто порассуждаю о том, каким должен быть минимально жизнеспособный продукт на момент пилотного выхода на рынок.


Начну с вирусной зарисовки пути развития стартапа по принципу MVP, которая гуляет по интернету и которую вы наверняка встречали.


image


90% авторов используют иллюстрацию в своих публикациях в буквальном смысле, что вводит в заблуждение читателей.


Разберем?


Верхняя строка ничего общего с MVP не имеет. Это понятно. На ней изображен классический цикл разработки. Автор использовал его как преувеличение ради контекста в примере ниже. Разумеется, ни о каком минимально жизнеспособном продукте на первом этапе с колесом речи и быть не может. Продукт прошел 3 стадии разработки (колеса, кузов, двигатель), прежде чем смог выполнить свое предназначение ехать.


Но и вторая строка, на мой взгляд, также не совсем корректно отображает принцип MVP.


Почему мое внутреннее понимание минимально жизнеспособного продукта не сходится с такой концепцией? Если предположить, что она отражает путь разработки какого-то продукта, становится очевидно, что он претерпел 4 коренных перестройки. В итоге его создатели получили три группы товарно-родовых конкурентов: 1 скейт-самокат-велосипед; 2 мотоцикл; 3 автомобиль.


Да, покупатель, как мы знаем, покупает не продукт, а решение своей проблемы. И скейт вроде бы решает базовую потребность (передвигаться быстрее, чем пешком). Но ведь скейт и автомобиль это абсолютно разные средства передвижения, нацеленные на разную аудиторию и цель езды для каждого из них имеет совершенно разную специфику. Продукты по-разному удовлетворяют запросы к комфорту, цене, сложности техобслуживания, скорости и т.д. В результате, у каждого своя целевая аудитория.


У автомобиля более широкий охват: пожилые и молодые люди, те, кто путешествует в одиночку или в компании, кто ездит на работу или мотается в другой город им нужна скорость, комфортное размещение и езда при любых погодных условиях. Скейт предназначен исключительно для одного человека, чаще ребенка или подростка, да и цель езды носит больше развлекательный характер. Аудитория скейта воспримет в штыки перспективу трансформации продукта в машину, которой нужно топливо, дорогостоящее ТО и возраст 17+, не говоря уже о цене самого автомобиля. А те, кому нужна машина, изначально не обратят на ваш самокат никакого внимания. Да, каждый из этих продуктов может иметь MVP, но они не являются MVP друг для друга.


Хенрик Книберг, автор этой картинки, не раз писал о том, что его изображение не всегда интерпретируется в правильном контексте. Дело в том, что он вкладывал в нее метафорический смысл, при котором цель разработки не построить автомобиль, а решить задачу добраться из пункта А в пункт Б. И самым простым жизнеспособным вариантом решения этой задачи является скейт. То есть на ней абстрактно и очень упрощенно передан некий концепт поиска продукта. Скейт или самокат не являются MVP для машины. Это факт. На картинке лишь изображена идея того, что в рамках создания продукта можно сделать несколько пивотов и в итоге найти MVP.


А эта переосмысленная версия MVP мне больше пришлась по душе:


image


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


Что завернуть в MVP, чтобы его захотели попробовать?


С концепцией MVP разобрались. Теперь поговорим о том, как определить начинку пилотного продукта, приоритезировать функционал и сделать первую версию продукта востребованной.


Первое, что необходимо сделать, это выяснить, какие инструменты, фичи и возможности станут для вашего продукта must have.


image


Принцип Парето


20% усилий дают 80% результата, а остальные 80% усилий лишь 20% результата. Это эмпирический принцип, а значит многие явления в мире, в том числе и разработка, подчиняются такому соотношению. 80% ваших пользователей будут использовать только 20% функционала. Поэтому не нужно тратить месяцы на то, чтобы отполировать до кристального сияния свой продукт перед запуском. Пропишите полный перечень функций будущего приложения и определите те 20%, которые закроют 80% потребностей пользователей.


MVP = обязательно + просто


Слышали про матрицу приоритетов? Популярная техника приоритезации задач, которая распределяет фичи по шкалам обязательно и просто. Изобразите две оси, где горизонтальная будет отображать увеличение сложности реализации конкретной функции, а вертикальная путь от желательного к обязательному. Раскидайте весь запланированный функционал на этой схеме, отвечая на 2 вопроса:


  1. Сложно это или легко для реализации?
  2. Желательно это или обязательно для пользователя?

image


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


Формула Rice Score


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


  • Охват скольким пользователям вы улучшите жизнь?
    (Оцените в течение определенного периода времени и берите цифры из метрик, а не с потолка)
  • Влияние насколько вы улучшите жизнь пользователям?
    (Очень сильно = 3х, сильно = 2х, хорошо = 1х, слабо = 0,5х, совсем немного = 0,25х)
  • Уверенность насколько вы уверены, что гипотеза окажется верна и продукт выстрелит?
    (100% = высокая уверенность, подтвержденная опросами или исследованиями, 80% = средняя уверенность, 50% = слабая уверенность, 50% и ниже = много сомнений)
  • Усилия сколько человеко-часов, человеко-дней или просто дней уйдет на реализацию?
    (1 человеко-день = это день работы одного разработчика)

Рассчитайте оценку для каждой фичи, согласно формуле:


image


Вместо итога


MVP подход играет роль подушки безопасности и позволяет адекватно прогнозировать коммерческий и технический потенциал продукта. Поэтому, когда мы брифуем заказчиков, то настаиваем на том, что стартовать разработку лучше с него. И, разумеется, помогаем определиться с ключевым функционалом. Если вы планируете создание минимально жизнеспособного продукта, то лучший вариант построить его на NoCode технологии. Сегодня платформы для разработки приложений без кода пользуются бешеным спросом. Они позволяют быстрее и дешевле создавать web-приложения любой сложности, а существующие биржы NoCode фриланса помогают подобрать подходящую под запросы бизнеса технологию и проверенного специалиста. Осмелюсь предположить, что тенденция к упрощению и удешевлению разработки будет только усиливаться. И сейчас самое время задействовать ее потенциал.

Подробнее..

Laravel-Дайджест (29 июня 5 июля 2020)

05.07.2020 18:06:24 | Автор: admin

Подборка новых статей по фреймворку Laravel. Разберемся как работает шифрование во фреймворке. Аутентифицируем пользователя по отпечатку пальца. Развернём приложение по методу zero-downtime. Спарсим данные с сайтов и выведем в удобной для себя форме. И устроим видео-стриминг.


Laravel Дайджест


В твиттере Тейлора 80% проголосовали за модельки в папке app/Models. В Laravel 4 модельки хранились в папке app/models, затем, начиная с пятой версии их, по дефолту, кидали в корень папки app. И, наконец, в 8-ой версии они снова получают свою собственную папку :)


Samuel Stancl продавал pdf-версию Laravel Clean Code Tactics за 10$ с возможностью выбрать варианты: light/dark/markdown/printable-markdown/web. Сейчас он разрешил скачивать её бесплатно: gumroad.com/l/laravel-clean-code/free


Судя по печеньке laravel_session, сайт Электронного голосования по поправкам в Конституцию РФ написан на Laravel.


На русском языке



На английском


Релизы



Уроки



Pusher



Самые свежие уроки смотрите в наших каналах: Телеграм, Вконтакте и Твиттер.

Подробнее..

Категории

Последние комментарии

© 2006-2020, personeltest.ru