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

Разработка

Перевод Flutter и настольные приложения

25.06.2020 12:16:27 | Автор: admin
Ни для кого не секрет то, что команда разработчиков Flutter стремится к тому, чтобы этот фреймворк позволял бы, пользуясь единой кодовой базой, создавать приложения для широкого разнообразия платформ. Сюда входят iOS, Android, Windows, Linux, macOS и веб. При этом такие приложения должны компилироваться в формат, специфичный для каждой из платформ, а их внешний вид не должен уступать внешнему виду хорошо сделанных игр. В Google фреймворк Flutter применяется во многих проектах: от Assistant до Stadia, от Cloud Search до Blogger. Flutter используются и другими компаниями от ByteDance до Grab, от Nubank до MGM Resorts. Всем им приносит пользу та гибкость и продуктивность разработки, которую даёт Flutter.

Многие из вас заинтересованы в развитии возможностей Flutter, касающихся настольных платформ, куда входят Windows, macOS, Linux. В опросах и на GitHub тема разработки настольных приложений относится к одной из самых заметных новых возможностей Flutter. В ближайшее время мы собираемся больше рассказать о том, чем мы занимаемся. Полагаем, стоит начать с обзора того, что сделано различными командами, отвечающими за возможности Flutter. Хотя поддержка настольных приложений пока находится на стадии ознакомительной технической версии, над соответствующими возможностями ведётся серьёзная работа.


Сборка приложений


Недавно мы выпустили профили и режимы сборки приложений для Windows и Linux в дополнение к существующей поддержке macOS. Например, если вы пользуетесь последней сборкой Flutter, Flutter-приложения можно компилировать в исполняемые файлы Windows командой flutter build windows. Для этого используется наш AOT-компилятор продакшн-уровня, который позволяет создавать машинный код для архитектуры x64, который можно выполнять на соответствующих компьютерах без установки Flutter.

Особенности настольных приложений


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

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

  • Когда вы создаёте новый проект в свежих сборках Flutter, вы увидите, что шаблон, применяемый по умолчанию, теперь включает в себя ссылку на свойство visualDensity, которое позволяет настраивать визуальную плотность расположения элементов управления на основе целевой платформы приложения. В макетах настольных приложений элементы управления упакованы плотнее, чем в мобильных приложениях. Например, это используется при настройке текстового поля (TextField), которое теперь предлагает варианты визуальной плотности compact, comfortable и standard, зависящие от параметров проекта.
  • Мы значительно улучшили поддержку мыши и клавиатуры. Сюда входит возможность работы с кодами клавиш клавиатуры в Windows, поддержка щелчка правой кнопкой мыши, поддержка изменения курсора и колёсика мыши.
  • Теперь можно, пользуясь классом Platform, узнавать о том, на какой платформе выполняется приложение. При работе приложения а Windows, macOS и Linux можно получить соответствующие результаты.
  • В самом свежем релизе мы добавили виджет NavigationRail, представляющий нечто вроде боковой панели. Этот виджет специально спроектирован для поддержки возможностей приложений, запускаемых на настольных ПК и на планшетах.

FFI


Команда Dart проделала большую работу по улучшению интерфейса внешних функций (Foreign Function Interface, FFI). Это способствует повышению скорости внедрения новых возможностей во Flutter. Например, для API, основанных на C, библиотека dart:ffi предоставляет прямой механизм для организации привязки к нативному коду. Среда выполнения Dart даёт возможность вызывать динамически связываемые библиотеки и выделять память в куче. В основе этого механизма лежат объекты Dart.

Вот фрагмент кода (здесь можно найти его полный вариант), представляющий собой простой пример, в котором показано обращение к традиционному Win32-API MessageBox(), полностью выполняемое средствами Dart-кода:

typedef MessageBoxNative = Int32 Function(IntPtr hWnd, Pointer<Utf16> lpText, Pointer<Utf16> lpCaption, Int32 uType);typedef MessageBoxDart = int Function(int hWnd, Pointer<Utf16> lpText, Pointer<Utf16> lpCaption, int uType);final user32 = DynamicLibrary.open('user32.dll');final win32MessageBox =user32.lookupFunction<MessageBoxNative, MessageBoxDart>('MessageBoxW');void showMessageBox(String message, String caption) => win32MessageBox(0,           // Нет окна-владельцаUtf16.toUtf16(message), // Текст сообщенияUtf16.toUtf16(caption), // Заголовок окна0            // Только кнопка OK);showMessageBox('Test Message', 'Window Caption'); // этот вызов выглядит как вызов обычной Dart-функции

Здесь у нас имеются typedef-псевдонимы, представляющие сигнатуры методов и для их нативного представления, и для их Dart-представления. Когда у нас есть эти псевдонимы, мы можем загрузить динамически связываемую библиотеку Windows, которая содержит реализацию функции. Для этого можно воспользоваться методом lookupFunction(), который делает мэппинг сигнатуры функции Dart на нативную функцию. И, наконец, мы, что не обязательно, описываем простую функциональную обёртку, которая упрощает работу с нативными механизмами в Dart-коде. В результате у нас получается нечто, подобное следующему рисунку.


Простой пример Windows-проекта, в котором использовано Win32-API MessageBox()

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

Обновление модели плагинов


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

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

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

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

Обратите внимание на то, что API плагинов для Windows и Linux пока ещё не устоялся, поэтому, хотя мы и советуем разработчикам знакомиться с новыми возможностями Flutter, мы пока не готовы к выпуску этих возможностей в продакшн. Мы, кроме того, работаем над добавлением на pub.dev тегов, относящихся к настольным платформам.

Запуск программ на Windows: Win32 и UWP


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


Windows даёт нам два способа создания подобного приложения. Первый это зрелая модель программирования Win32, которую можно использовать в качестве точки входа для Flutter-проектов. Этот подход предлагает максимальный уровень обратной совместимости с различными платформами, с такими, как Windows 7. Он позволяет создавать стандартные EXE-файлы, которые ожидает получить в результате работы над проектом множество программистов. В отличие от Win32, современная модель приложений UWP рекомендована для Windows 10. Эта модель предлагает заманчивые возможности по созданию Flutter-приложений для особых устройств, вроде Xbox, и для ОС Windows 10X, которая должна скоро выйти.

Мы, неофициально, работаем с различными разработчиками и исследуем разные решения. Мы с удовольствием более близко поработали бы с Microsoft ради того, чтобы повысить качество Windows-приложений, сделанных во Flutter. Так как семейство устройств Surface теперь включает в себя системы, основанные на Android и на Windows, мы думаем, что Flutter способен предложить Microsoft мощную платформу, которая позволяет создавать привлекательные нативные приложения для самых разных устройств.

Эксперименты с настольными приложениями


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

Если вы уже сейчас хотите испытать новые возможности, то вам нужно пользоваться каналом разработки. Поддержка Windows и Linux доступна в ветке master, в том месте, где идёт активная разработка Flutter. Поддержка macOS доступна в ветке dev. Там находятся более стабильные возможности, которые, правда, не рекомендуется применять в продакшне. Переключаться между каналами можно, используя команды flutter channel master или flutter channel dev. После этого нужно выполнить одну из следующих команд для того чтобы включить поддержку интересующей вас платформы:

C:\flutter> flutter config --enable-windows-desktop$ flutter config --enable-macos-desktop$ flutter config --enable-linux-desktop

Мы уже видели, как некоторые смелые разработчики начали создавать настольные приложения с помощью новых возможностей Flutter. Одно из первых таких приложений разработано для macOS. Оно называется Sharezone. Это планировщик для учеников, нацеленный на немецкий рынок образования. Сначала этот проект был выпущен в виде мобильного приложения. А недавно он вышел в настольном варианте и в варианте, предназначенном для веб.


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

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



Подробнее..

Как Тильда убивает рынок веб-фриланса

29.06.2020 12:20:00 | Автор: admin

TLDR: Тильда это рак, поглощающий здоровый рунет (и в гораздо меньших масштабах весь остальной интернет). Тильда забирает работу у разработчиков и дизайнеров и даёт её людям, которые справляются гораздо хуже. Она поспособствовала появлению целого пласта специалистов, которые ни черта не умеют кроме визуальной верстки, но позиционируют и продают себя как крутых дизайнеров/разработчиков. Она генерирует более тяжелые страницы, чем чертов вордпресс!

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

Это зло


Я прозрел, когда пришел в большой проект на Тильде. К тому моменту я успел запустить на ней несколько сайтов разной сложности и был в целом доволен платформой, той легкостью, которую она давала. В проекте почти не нужна был динамика, но была тонна статики, которую мне предстояло организовать и причесать под единый стиль. Сайтом до этого занимался человек, максимально далёкий от дизайна и вёрстки, и в первый же день я сразу всё понял и про человека, и про Тильду, и про грёбаный ужас, который получается когда эти двое встречаются. У сайта был поддомен, на котором шли вообще все продажи, то есть все деньги на сайте крутились на этом поддомене. Знаете, сколько весила его главная страница? СЕМЬДЕСЯТ ВОСЕМЬ МЕГАБАЙТ. Она грузилась ВЕЧНОСТЬ. Что-то купить на сайте можно было только через неё.

Что там так много весило? Картиночки. Вся страница состояла из десятков полноэкранных блоков, на каждом из которых было 3-5 картинок и гифок, в том числе очень тяжеловесных. Некоторые картинки занимали область порядка 300*200 пикселей, но загружались в разрешении 3000*2000. Некоторые гифки весили по 5-10 мегабайт. Это был настоящий Содом, и самое грустное в том, что мне не дали его полностью переписать, только сжать картинки и выкинуть часть бесполезных, уродских гифок.

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

А всё почему?


Потому что до WYSIWYGа такие вот сайтики клепали в основном фрилансеры (веб-студии были дороже). Они тоже не были крутыми спецами и часто писали плохо, но поддержка вообще работала по-другому. Контент набивал один человек, а дизайн правил другой, каждый был занят своим делом и все жили счастливо. А сейчас в стремлении сэкономить владелец пытается одновременно ужаться в функционал дешевого аккаунта (потому что полноценный бизнес на год стоит как разработка и поддержка нормального сайта нормальными людьми) и повесить все задачи на одного человека, как правило, менеджера. В итоге владелец либо экономит в конечном счёте на своих же продажах, либо тратит изначально больше денег, менеджер всех ненавидит и в гробу видал сайт с владельцем, а фрилансер сидит без работы и ненавидит Тильду.

Честно говоря, я вообще плохо представляю аудиторию WYSIWYG-редакторов сайтов. До Тильды большинство из них работало через задницу и что-то вменяемое делали только на таком же условно-бесплатном Wix, который обладает всеми теми же недостатками. Так вот, кому нужны визуальные редакторы? Малому бизнесу, который не может отвалить денег за хороший проект? Всяким авторам персональных страничек? Неважно, кто и с какой целью приходит, итог всегда один: сайтом так или иначе занимается один человек, и внешний вид сайта напрямую зависит от его дизайнерских навыков, как правило скудных. А вот функционал вообще от него не зависит и по сути определяется левой пяткой разработчика редактора. Для мелкой статики типа портфолио или странички автосервиса с контактами и формой это не страшно, но стоит немного подрасти и вы попадаете в ад.

Тильда это не Вордпресс


Тильду любят сравнивать с Wordpress. Она изначально выглядит выигрышнее, с её кастомизацией любых элементов, цветов фонов и возможностью в один клик редактировать текст в любом месте страницы. Но с точки зрения динамического функционала WP гораздо, гораздо лучше. Он весь пронизан огромным количеством плагинов для самых разных задумок, и всегда можно дописать новый. Например, интернет-магазины на WP в основном работают на WooCommerce, для которого есть куча тем, плагинов, кастомизации. В Тильде же полноценный интернет-магазин построить практически невозможно, несмотря на заявленный функционал. Есть корзина, есть карточки товаров, можно что-то несложное продавать (курсы, например). На всём остальном Тильда тупо ломается: нету кастомизации корзины, нету хуков вообще, нету даже полноценной базы данных, работающей из коробки! Как следствие, нет фильтрации и сортировки и много чего еще. Если же в Тильде вам чего-то не хватает, есть два пути либо попытаться заколхозить решение через HTML-вставки (соответственно, стили и скрипты подключаются через них же), либо писать в техподдержку и просить допилить.

Кстати, о техподдержке. С ней всё просто:

  1. Она всегда перегружена.
  2. Ей на тебя плевать, если ты не проплатил бизнес-аккаунт.


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

Zero blocks


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


Даже в оформлении плейсхолдера решили не запариваться с адаптивной версткой. И так сойдёт!

Надо сказать, что 99% дизайнерского дизайна на Тильде делается в зеро-блоках (потому что стандартные блоки слишком сильно урезают полёт фантазии и используются исключительно для удобства редактуры), и по моему опыту, по уровню аккуратности и внимания к деталям делаются в точности как на картинке выше.

Mobile friendly


Ещё из хронических болезней Тильды нельзя сделать кастомные меню и навбары, просто нет такого функционала. А готовые очень часто плохо переверстываются на мобильных экранах, заставляя прибегать к особо циничному костылингу одной менюшке устанавливается область отображения от 1200px ширины и больше, другой 1199 и меньше. Всё бы ничего, по сути обычное разделение по @media-запросу, но из-за невозможности адекватно настроить размеры мобильных шрифтов и отступов постепенно половина сайта переезжает на такие дублирующиеся блоки, каждый из которых надо редактирвоать отдельно и следить чтобы ничего не поехало. Отдельный кайф доставляет последовательное отображение этих блоков на странице редактирования, что увеличивает её в полтора-два раза.

Альтернативы


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

Отличный вариант для размещения статики вроде блога использование Static Site Generator (SSG) в связке с GitHub Pages. Статей и туториалов много, вот например.

С JAMstack также тесно связаны headless (или API-driven) CMS. Они дают больше гибкости, приближаясь по функционалу к традиционным CMS (вот актуальное сравнение с WP), но сохраняют легковесность и безопасность. Подходят для всего, от блога до интернет-магазина, но как правило требуют приличного количества разработки.

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

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

Мораль


Я понимаю, Хабр не самое подходящее место чтобы вещать на аудиторию Тильды. Здесь сидят не домохозяйки с менеджерами, а разработчики и даже тех же фрилансеров тут немного. Но у меня есть мнение и я хочу донести его до всех сопричастных:

  • Тильда зло.
  • WYSIWIG-редакторы сайтов зло.
  • Дизайнеры визуальной вёрстки зло.
  • Фриланс добро.
  • Разделение обязанностей обязательно.




Подробнее..

Перевод Встречаем Angular 10

29.06.2020 18:10:05 | Автор: admin
Вышел Angular 10.0.0! Это мажорный релиз, который затрагивает всю платформу, включая сам фреймворк, библиотеку компонентов Angular Material и инструменты командной строки. Размер этого релиза меньше, чем обычно. Дело в том, что с момента выхода Angular 9 прошло всего 4 месяца.

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



Поговорим о том, что нового появилось в Angular 10.

Новый компонент для выбора диапазонов дат


Angular Material теперь включает в себя новый компонент, предназначенный для выбора диапазонов дат.


Новый компонент

Для того чтобы им воспользоваться, вам понадобятся компоненты mat-date-range-input и mat-date-range-picker.

Вот пример его применения.

Здесь можно узнать подробности о нём.

Предупреждения об использовании CommonJS-импортов


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

Начиная с Angular 10 система будет выдавать предупреждения в том случае, если в сборках используются подобные бандлы. Если вы столкнётесь с такими предупреждениями, касающимися ваших зависимостей, перенастройте проект, указав, что вы предпочли бы ESM-бандл (ECMAScript Module).


Применение CommonJS- или AMD-зависимостей может приводить к необходимости принимать меры, помогающие оптимизации проекта

Особый режим, в котором применяются более строгие параметры проекта


Angular 10 предлагает разработчику более строгий вариант настроек проекта, применяемый при создании нового рабочего пространства командой ng new. Вот как это выглядит:

ng new --strict

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

  • Включается строгий режим в TypeScript.
  • Включается строгий режим при проверке типов в шаблонах.
  • Стандартные ограничения размеров различных частей бандла становятся примерно на 75% строже.
  • Включается использование правил линтинга, предотвращающих объявление сущностей типа any.
  • Приложение настраивается так, чтобы его код был бы лишён побочных эффектов, что позволяет эффективнее применять механизм tree-shaking.

Поддержка возможностей Angular в актуальном состоянии


Как это обычно делается в новых релизах Angular, в этот раз мы внесли некоторые изменения в зависимости. Это позволяет Angular использовать самые современные решения из экосистемы JavaScript.

  • Используемая версия TypeScript увеличена до 3.9.
  • Библиотека TSLib обновлена до версии 2.0.
  • Обновлён, до версии 6, линтер TSLint.

Мы, кроме того, поменяли структуру проекта. Начиная с Angular 10 вы сможете работать с файлом tsconfig.base.json. Этот дополнительный tsconfig.json-файл лучше поддерживает механизмы разрешения типов и настроек пакетов, используемые IDE и средствам для сборки проектов.

Новая стандартная конфигурация браузеров


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

Вот параметры браузеров, применяемые по умолчанию в Angular 9.


Параметры браузеров, используемые в Angular 9

Вот что используется в Angular 10.


Параметры браузеров, используемые в Angular 10

У этого шага есть побочный эффект, который заключается в том, что теперь в новых проектах, по умолчанию, отключаются ES5-сборки. Для того чтобы включить ES5-сборки и дифференциальную загрузку для браузеров, которым это нужно (для таких, как IE или UC), достаточно просто добавить в файл .browserslistrc те браузеры, которые требуется поддерживать.

Улучшение взаимодействия с сообществом любителей Angular


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

Удалённые возможности и возможности, пользоваться которыми не рекомендуется


В Angular 10 мы кое-что убрали, а кое-какие возможности отнесли к разряду запланированных к удалению.

Так, теперь в Angular Package Format больше не входят бандлы ESM5 или FESM5. Это экономит примерно 119 Мб трафика при выполнении команд yarn или npm install для пакетов и библиотек Angular. Эти форматы больше не нужны, так как преобразования, необходимые для поддержки ES5, выполняются в конце процесса сборки.

Мы, основываясь на серьёзном обсуждении с сообществом, решили отказаться от поддержки устаревших браузеров, включая IE 9, 10, а так же Internet Explorer Mobile.

Подробности о том, что удалено из Angular 10, и о том, что признано устаревшим, можно почитать здесь.

Обновление до Angular 10


Для того чтобы узнать подробности об обновлении Angular посетите ресурс update.angular.io. Для того чтобы в ходе обновления всё шло бы хорошо, мы рекомендуем, за один шаг обновления, всегда обновляться лишь до одного мажорного релиза.

Если описать процесс обновления буквально в двух словах, то выглядеть это будет так:

ng update @angular/cli @angular/core

Здесь можно найти подробные сведения об обновлении Angular до версии 10.

А вы уже обновились до Angular 10?



Подробнее..

Перевод Прости, React, но я так больше не могу

30.06.2020 16:15:52 | Автор: admin
Недавно мне попалась фраза усталость от JavaScript, JavaScript fatigue, и я мгновенно понял, что это про меня.

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



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

Но я, почти всегда, ничем таким не занимался. Дело тут было в том, что React и JavaScript, если стремиться быть в курсе всего, что в этих сферах происходит, отнимают слишком много сил и времени. Иногда я по-настоящему опасался того, что мои знания и навыки могут устареть. Всё это оставляло мне очень мало времени на то, чтобы исследовать что-то новое.

Переломный момент


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

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


Первое пересечение моей работы и химии того, чему я учился (основное состояние, в покое; акт поглощения; возбуждённое состояние и скорость отдачи)

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

Что дальше?


Я пока ещё стою на распутье, но у меня есть некоторые идеи.

Написание статей


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

Небольшие эксперименты с архитектурами, применяемыми в крупномасштабных проектах


Подобные эксперименты можно проводить, пользуясь бесплатным тарифным планом AWS. Раньше мне интересно было этим заниматься. Скажем, я брал простой список кофеен и, на базе SAM, делал из него нечто нереально раздутое. В этом проекте использовались лямбда-функции, DynamoDB и SNS.


Интересные эксперименты с переусложнённой архитектурой

Изучение Rust


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


Результаты исследования Stack Overflow 2020 года

Дело в том, что Rust, 5 лет подряд, лидирует в исследованиях Stack Overflow как самый любимый язык программирования.

Кроме того, меня сильно привлекает то, что Rust компилируется в WASM. А WebAssembly это ещё одна технология, которую мне хотелось бы освоить.

Для того чтобы облегчить себе жизнь, я могу начать с Yew. Это Rust-фреймворк, предназначенный для разработки веб-приложений, создателей которого вдохновила библиотека React. Yew это фреймворк, основанный на компонентах, в котором используются что-то вроде JSX.

Или, хотя это, вероятно, будет сложнее, я, возможно, попробую создать на Rust какой-нибудь инструмент командной строки.

Разработка мобильного приложения на Flutter


Я никогда особенно не интересовался мобильной разработкой. Именно поэтому я и собираюсь её попробовать.


Flutter-приложения пишут на Dart

Во Flutter меня привлекает то, что, используя этот фреймворк, мне не придётся думать о том, для iOS или для Android мне писать приложение. То, что сделано на Flutter, заработает и там, и там. Кроме того, интересным мне кажется язык программирования Dart.

Итоги


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

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

А вы устали от JavaScript?



Подробнее..

Перевод О том, что происходит, когда в поиске 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 это реактивный фреймворк.

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

Подробнее..

Фичи JavaScript. Часть 2

27.06.2020 10:22:34 | Автор: admin


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

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

См. Фичи JavaScript. Часть 1.

1. Частое обращение к одним и тем же элементам


Порой при написании кода приходится снова и снова обращаться к одним и тем же элементам. При работе с DOM, например, такими элементами являются document и document.body. Казалось бы, что тут такого? 8 и 13 символов, соответственно, да еще и emmet помогает. Однако, когда кода действительно много, автозавершение начинает предлагать неправильные варианты. Либо, когда работаешь не с html, а, например, с php без правильного синтаксического анализатора, многие привычные вещи приходится набирать вручную. Задумавшись о том, как решить указанную проблему, я вспомнил о canvas. Помните, с чего начинается работа с холстом? Правильно, с его определения и инициализации двумерного контекста рисования:

const C = document.querySelector('canvas')const $ = C.getContext('2d')

Также я подумал об объекте jQuery ($).

Так вот, могу предложить три варианта (один из вариантов я подглядел у разработчиков Facebook при изучении протокола Open Graph):

    // внутри функцииfunction foo() {    const D = document    const B = document.body    const div = D.createElement('div')    B.append(div)    const p = D.createElement('p')    p.textContent = 'Lorem ipsum dolor sit amet...'    div.append(p)    console.log(div)    B.removeChild(div)}foo()// снаружи функцииfunction bar(D, B) {    const div = D.createElement('div')    B.append(div)    const p = D.createElement('p')    p.textContent = 'Lorem ipsum dolor sit amet...'    div.append(p)    console.log(div)    B.removeChild(div)}bar(document, document.body)// IIFE;((D, B) => {    const div = D.createElement('div')    B.append(div)    const p = D.createElement('p')    p.textContent = 'Lorem ipsum dolor sit amet...'    div.append(p)    console.log(div)    B.removeChild(div)})(document, document.body)

Это была разминка, переходим к тренировке.

2. Генератор


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

// пример 1function* takeItem(arr) {    for (let i = 0; i < arr.length; i++) {        yield arr[i]    }}const arr = ['foo', 'bar', 'baz', 'qux']const generator = takeItem(arr)const timer = setInterval(() => {        const item = generator.next()        item.done            ? clearInterval(timer)            : console.log(item.value)    }, 1000)// пример 2async function* range(start, end) {    for (let i = start; i <= end; i++) {        yield Promise.resolve(i)    }};(async () => {    const generator = range(1, 4)    for await (const item of generator) {        console.log(item)    }})()

3. Async/await + fetch


Async/await является альтернативой промисов, позволяя обеспечить синхронность выполнения асинхронных функций. В свою очередь, fetch является альтернативой XMLHttpRequest, представляя собой интерфейс для получения ресурсов (в том числе, по сети).

const url = 'https://jsonplaceholder.typicode.com/users';(async () => {    try {        const response = await fetch(url)        const data = await response.json()        console.table(data)    } catch (er) {        console.error(er)    } finally {        console.info('потрачено')    }})()

4. For await


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

const delayedPromise = (id, ms) => new Promise(resolve => {    const timer = setTimeout(() => {        resolve(id)        clearTimeout(timer)    }, ms)})const promises = [    delayedPromise(1, 1000),    delayedPromise(2, 2000),    delayedPromise(3, 3000)]// старый стильasync function oldStyle() {    for (const promise of await Promise.all(promises)) {        console.log(promise)    }}oldStyle() // все промисы через 3 секунды// новый стильasync function newStyle() {    for await (const promise of promises) {        console.log(promise)    }}newStyle() // каждый промис в свой черед

5. Proxy


Прокси используются для объявления расширенной семантики JS объектов. Стандартная семантика реализована в движке JS, который обычно написан на низкоуровневом языке программирования, например C++. Прокси позволяют определить поведение объекта при помощи JS. Другими словами, они являются инструментом метапрограммирования.

const person = {    firstname: 'Harry',    lastname: 'Heman',    city: 'Mountain View',    company: 'Google'}const proxy = new Proxy(person, {    get(target, property) {        if (!(property in target)) {            return property                .split('_')                .map(p => target[p])                .sort()                .join(' ')        }        console.log(`получено свойство: ${property}`)        return target[property]    },    set(target, property, value) {        if (property in target) {            target[property] = value            console.log(`изменено свойство: ${property}`)        } else {            console.error('нет такого свойства')        }    },    has(target, property) {        // return property in target        return Object.entries(target)            .flat()            .includes(property)    },    deleteProperty(target, property) {        if (property in target) {            delete target[property]            console.log(`удалено свойство: ${property}`)        } else {            console.error('нет такого свойства')        }    }})console.log(proxy.company_city_firstname_lastname) // Google Harry Heman Mountain Viewproxy.firstname = 'John' // изменено свойство: firstnameproxy.surname = 'Smith' // нет такого свойстваconsole.log(proxy.city) // получено свойство: city Mountain Viewconsole.log('company' in proxy) // truedelete proxy.age // нет такого свойства// proxy + cookieconst getCookieObject = () => {    const cookies = document.cookie.split(';').reduce((cks, ck) => ({        [ck.substr(0, ck.indexOf('=')).trim()]: ck.substr(ck.indexOf('=') + 1),        ...cks    }), {})    const setCookie = (name, value) => document.cookie = `${name}=${value}`    const deleteCookie = name => document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:01 GTM;`    return new Proxy(cookies, {        set: (obj, prop, val) => (setCookie(prop, val), Reflect.set(obj, prop, val)),        deleteProperty: (obj, prop) => (deleteCookie(prop), Reflect.deleteProperty(obj, prop))    })}

6. Reduce


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

const arr = [1, 2, 3]const total = arr.reduce((sum, cur) => sum + cur)console.log(total) // 6// forEachlet total2 = 0arr.forEach(num => total2 += num)console.log(total2) // 6

Однако возможности reduce() этим далеко не исчерпываются:

const devs = [    {        name: 'John',        sex: 'm',        age: 23    },    {        name: 'Jane',        sex: 'f',        age: 24    },    {        name: 'Alice',        sex: 'f',        age: 27    },    {        name: 'Bob',        sex: 'm',        age: 28    }]const men = devs.reduce((newArr, dev) => {    if (dev.sex === 'm') newArr.push(dev.name)    return newArr}, [])console.log(men) // ["John", "Bob"]// filter + mapconst olderThan25 = devs    .filter(dev => dev.age > 25)    .map(dev => dev.name)console.log(olderThan25) // ["Alice", "Bob"]

Сформируем список имен разработчиков одной строкой:

const devsNamesList = `<ul>${devs.reduce((html, dev) => html += `<li>${dev.name}</li>`, '')}</ul>`document.body.innerHTML = devsNamesList// mapconst devsNamesList2 = `<ul>${devs.map(dev => `<li>${dev.name}</li>`).join('')}</ul>`document.body.insertAdjacentHTML('beforeend', devsNamesList2)

Поговорим о группировке:

const groupBy = (arr, criteria) =>    arr.reduce((obj, item) => {        const key = typeof criteria === 'function'            ? criteria(item)            : item[criteria]        if (!obj.hasOwnProperty(key)) obj[key] = ''        obj[key] = item        return obj    }, {})const nums = [6.1, 4.2, 2.3]console.log(groupBy(nums, Math.floor)) // {2: 2.3, 4: 4.2, 6: 6.1}// forEachconst groupBy2 = (arr, criteria, obj = {}) => {    arr.forEach(item => {        const key = typeof criteria === 'function'            ? criteria(item)            : item[criteria]                if (!obj.hasOwnProperty(key)) obj[key] = ''        obj[key] = item        return obj    })    return obj}const words = ['one', 'three', 'five']console.log(groupBy2(words, 'length')) // {3: "one", 4: "five", 5: "three"}

Сделаем выборку:

const cash = {    A: 1000,    B: 2000}const devsWithCash = devs.reduce((arr, dev) => {    const key = dev.name.substr(0,1)        if (cash[key]) {        dev.cash = cash[key]        arr.push(`${dev.name} => ${dev.cash}`)    } else dev.cash = 0    return arr}, [])console.log(devsWithCash) // ["Alice => 1000", "Bob => 2000"]// map + filterconst devsWithCash2 = devs.map(dev => {    const key = dev.name.substr(0,1)    if (cash[key]) {        dev.cash = cash[key]    } else dev.cash = 0    return dev}).filter(dev => dev.cash !== 0)console.log(devsWithCash2)

И последний пример. Помните, как мы формировали список имен разработчиков из массива объектов одной строкой? Но что если у нас имеется такой массив:

const users = [    {        john: {            name: 'John'        }    },    {        jane: {            name: 'Jane'        }    },    {        alice: {            name: 'Alice'        }    },    {        bob: {            name: 'Bob'        }    }]

Как нам сделать тоже самое?

document.body.insertAdjacentHTML('afterbegin', `<ul>${users.reduce((html, el) => html + `<li>${Object.values(el)[0].name}</li>`, '')}</ul>`) // фух!

Давайте рассмотрим что-нибудь попроще.

7. Новые методы работы со строками


// trimStart() trimEnd() trim()const start = '   foo bar'const end = 'baz qux   'console.log(`${start.trimStart()} ${end.trimEnd()}`) // foo bar baz quxconsole.log((`${start} ${end}`).trim()) // тоже самоеconst startMiddleEnd = '   foo  bar   baz  ' // три пробела в начале, два - между foo и bar, три - между bar и baz и два - в конце// при помощи регулярного выражения заменяем два и более пробела одним// затем посредством trim() удаляем пробелы в начале и концеconst stringWithoutRedundantSpaces = startMiddleEnd.replace(/\s{2,}/g, ' ').trim()console.log(stringWithoutRedundantSpaces) // foo bar baz// padStart() padEnd()let str = 'google'str = str.padStart(14, 'https://') // первый аргумент - количество символовconsole.log(str) // https://googlestr = str.padEnd(18, '.com')console.log(str) // https://google.com

8. Новые методы работы с массивами


const arr = ['a', 'b', ['c', 'd'], ['e', ['f', 'g']]]console.log(arr.flat(2)) // ["a", "b", "c", "d", "e", "f", "g"]const arr2 = ['react vue', 'angular', 'deno node']console.log(arr2.map(i => i.split(' ')))/*    [Array(2), Array(1), Array(2)]        0: (2) ["react", "vue"]        1: ["angular"]        2: (2) ["deno", "node"]*/console.log(arr2.flatMap(i => i.split(' '))) // ["react", "vue", "angular", "deno", "node"]

9. Новые методы работы с объектами


const person = {    name: 'John',    age: 30}console.log(Object.getOwnPropertyDescriptor(person, 'name')) // {value: "John", writable: true, enumerable: true, configurable: true}const arr = Object.entries(person)console.log(arr) // [["name", "John"], ["age", 30]]console.log(Object.fromEntries(arr))for (const [key, value] of Object.entries(person)) {    console.log(`${key} => ${value}`) // name => John age => 30}console.log(Object.keys(person)) // ["name", "age"]console.log(Object.values(person)) // ["John", 30]

10. Приватные переменные в классах


class Person {    // значения по умолчанию    static type = 'человек'    static #area = 'Земля'    name = 'John'    #year = 1990    get age() {        return new Date().getFullYear() - this.#year    }    set year(age) {        if (age > 0) {            this.#year = new Date().getFullYear() - age        }    }    get year() {        return this.#year    }    static area() {        return Person.#area    }}const person = new Person()console.log(person) // Person{name: "John", #year: 1990}console.log(person.age) // 30// console.log(person.#year) // errorperson.year = 28console.log(person.year) // 1992console.log(Person.type) // человек// console.log(Person.#area) // errorconsole.log(Person.area()) // Земля

11. Еще парочка нововведений


// промисыconst p1 = Promise.resolve(1)const p2 = Promise.reject('error')const p3 = Promise.resolve(3);(async () => {const result = await Promise.all([p1, p2, p3])console.log(result)})() // Uncaught (in promise) error;(async () => {const result = await Promise.allSettled([p1, p2, p3])console.log(result)})()/*    [{}, {}, {}]        0: {status: "fulfilled", value: 1}        1: {status: "rejected", reason: "error"}        2: {status: "fulfilled", value: 3}*/// приведение к null (nullish coercion)const values = {    undefined: undefined,    null: null,    false: false,    zero: 0,    empty: ''}console.log(values.undefined || 'default undefined')console.log(values.undefined ?? 'default undefined')// default undefinedconsole.log(values.null || 'default null')console.log(values.null ?? 'default null')// default nullconsole.log(values.false || 'default false') // default falseconsole.log(values.false ?? 'default false') // falseconsole.log(values.zero || 'default zero') // default zeroconsole.log(values.zero ?? 'default zero') // 0console.log(values.empty || 'default empty') // default emptyconsole.log(values.empty ?? 'default empty') // ''// опциональная цепочка (optional chaining)const obj1 = {    foo: {        bar: {            baz: {                qux: 'veryDeepInside'            }        }    }}const obj2 = {    foo: {}}// старый стильfunction getValueOld(obj) {    if (obj.foo !== undefined &&    obj.foo.bar !== undefined &&    obj.foo.bar.baz !== undefined &&    obj.foo.bar.baz.qux !== undefined) {        return obj.foo.bar.baz.qux    }}console.log(getValueOld(obj1)) // veryDeepInsideconsole.log(getValueOld(obj2)) // нет ошибки// новый стильconst getValueNew = obj => obj?.foo?.bar?.baz?.quxconsole.log(getValueNew(obj1)) // veryDeepInsideconsole.log(getValueNew(obj2)) // нет ошибки

Благодарю за потраченное время. Надеюсь, оно было потрачено не зря.

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

Перевод Что JavaScript-разработчику следует знать о Curl

28.06.2020 14:08:19 | Автор: admin


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

Представляю Вашему вниманию перевод статьи What JavaScript Developers Should Know About Curl автора Valery Karpov.

Curl это популярный инструмент командной строки, часто используемый для отправки HTTP-запросов. Curl поддерживает большое количество протоколов, однако как Node.js-разработчик вы, скорее всего, будете использовать его для отпраки http-запросов к RESTful API.

Документация curl представляет собой перечень из 383 флагов командной строки, поэтому в ней очень трудно найти то, что ищешь. В этой статье я хочу поделиться с вами некоторыми часто используемыми мной шаблонами. Для примеров будет использоваться сервис httpbin.org.

Отправка http-запроса


Для начала убедитесь в том, что у вас установлен curl, выполнив команду curl --version.

$ curl --versioncurl 7.58.0 (x86_64-pc-linux-gnu) libcurl/7.58.0 OpenSSL/1.1.1 zlib/1.2.11 libidn2/2.0.4 libpsl/0.19.1 (+libidn2/2.0.4) nghttp2/1.30.0 librtmp/2.3Release-Date: 2018-01-24Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL 

Для отправки запроса необходимо запустить curl url. Например, для отправки GET-запроса к https://httpbin.org/get?answer=42 следует запустить curl https://httpbin.org/get?answer=42.

$ curl https://httpbin.org/get?answer=42{  "args": {    "answer": "42"  },   "headers": {    "Accept": "*/*",     "Host": "httpbin.org",     "User-Agent": "curl/7.58.0",     "X-Amzn-Trace-Id": "Root=1-5ee8d737-b39c6a466892725bbb52b916"  },   "origin": "69.84.111.39",   "url": "https://httpbin.org/get?answer=42"}

После успешного завершения запроса curl возвращает тело http-ответа. Для того, чтобы заставить curl вернуть весь ответ, включая заголовки, используйте флаг -i.

$ curl -i https://httpbin.org/get?answer=42HTTP/2 200 date: Tue, 16 Jun 2020 14:30:57 GMTcontent-type: application/jsoncontent-length: 801server: istio-envoyaccess-control-allow-origin: *access-control-allow-credentials: truex-envoy-upstream-service-time: 2{  "args": {    "answer": "42"  },   "headers": {    "Accept": "*/*",     "Content-Length": "0",     "Host": "httpbin.org",     "User-Agent": "curl/7.58.0",     "X-Amzn-Trace-Id": "Root=1-5ee8d7a1-cb3954c09299eb9e0dff70a6",     "X-B3-Parentspanid": "dffc55451e64b5fc",     "X-B3-Sampled": "0",     "X-B3-Spanid": "8e233a863fb18b6c",     "X-B3-Traceid": "45bd12a9067fb5c0dffc55451e64b5fc",     "X-Envoy-External-Address": "10.100.91.201",     "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/httpbin-istio/sa/httpbin;Hash=c1ff14671b3e24ee794f9a486570abf8ccc9d622846611d3f91a322db4d480cd;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"  },   "origin": "69.84.111.39,10.100.91.201",   "url": "http://httpbin.org/get?answer=42"}

Это полный http-ответ. Заголовками ответа являются строки от date: до x-envoy-upstream-service-time:.

Загрузка файлов


Wget самый распространенный инструмент для загрузки файлов посредством командной строки. Он входит в комплект большинства диструбутивов Linux. Однако в OSX его нет.

Команда wget url аналогична команде curl -OL url. Опция это опция --remote-name, которая говорит curl сохранить тело ответа в локальном файле. Опция -L говорит curl следовать перенаправлениям.

Например, ниже представлено изображение с Unsplash, его URL https://images.unsplash.com/photo-1506812574058-fc75fa93fead.



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

$ curl -OL https://images.unsplash.com/photo-1506812574058-fc75fa93fead  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current                                 Dload  Upload   Total   Spent    Left  Speed100 12.1M  100 12.1M    0     0  3927k      0  0:00:03  0:00:03 --:--:-- 3927k

Опция -O говорит curl использовать строку после последнего / в качестве имени файла. В приведенном примере изображение будет сохранено в текущей директории с именем photo-1506812574058-fc75fa93fead. Для определения имени файла используйте опцию (строчная буква о).

$ curl -o miami-beach.jpg https://images.unsplash.com/photo-1506812574058-fc75fa93fead  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current                                 Dload  Upload   Total   Spent    Left  Speed100 12.1M  100 12.1M    0     0  6083k      0  0:00:02  0:00:02 --:--:-- 6083k$ ls -l miami-beach-jpg-rw-rw-r-- 1 val val 12788445 Jun 16 11:03 miami-beach.jpg

Отправка авторизованного запроса


Заголовок авторизации используется для включения в запрос данных для авторизации при обращении к RESTful API. Для добавления указанных данных необходимо использовать флаг -H. Например, если ваш ключ интерфейса (API key) my-secret-token, вы можете включить его в http-запрос следующим образом:

$ curl -H "Authorization: my-secret-token" https://httpbin.org/get{  "args": {},   "headers": {    "Accept": "*/*",     "Authorization": "my-secret-token",     "Host": "httpbin.org",     "User-Agent": "curl/7.58.0",     "X-Amzn-Trace-Id": "Root=1-5ee8e1a5-a3aa30e0765a7980b04ca4a0"  },   "origin": "69.84.111.39",   "url": "https://httpbin.org/get"}

Обратите внимание, что httpbin.org возвращает заголовки http-запроса в теле ответа в свойстве headers.

Curl также поддерживает авторизацию по-умолчанию посредством флага -u. В следующем примере мы отправляем запрос с именем пользователя user и паролем pass:

$ curl -i -u "user:pass" https://httpbin.org/basic-auth/user/passHTTP/2 200 date: Tue, 16 Jun 2020 15:18:45 GMTcontent-type: application/jsoncontent-length: 47server: istio-envoyaccess-control-allow-origin: *access-control-allow-credentials: truex-envoy-upstream-service-time: 1{  "authenticated": true,   "user": "user"}

Вот что происходит при отправке неправильного имени пользователя или пароля:

$ curl -i -u "user:wrongpass" https://httpbin.org/basic-auth/user/passHTTP/2 401 date: Tue, 16 Jun 2020 15:18:51 GMTcontent-length: 0server: istio-envoywww-authenticate: Basic realm="Fake Realm"access-control-allow-origin: *access-control-allow-credentials: truex-envoy-upstream-service-time: 12

Отправка POST-запроса, содержащего JSON


Флаг -X говорит curl, какой метод следует использовать: PUT, POST и т.д. По-умолчанию curl использует метод GET, поэтому писать curl -X GET не нужно.

Флаг -X часто используется совместно с флагом -d, позволяющим добавить тело запроса. В следующем примере показано как отправить POST-запрос, содержащий некоторый json:

$ curl -X POST -d '{"answer":42}' https://httpbin.org/post{  "args": {},   "data": "",   "files": {},   "form": {    "{\"answer\":42}": ""  },   "headers": {    "Accept": "*/*",     "Content-Length": "13",     "Content-Type": "application/x-www-form-urlencoded",     "Host": "httpbin.org",     "User-Agent": "curl/7.58.0",     "X-Amzn-Trace-Id": "Root=1-5ee8e3fd-8437029087be44707bd15320",     "X-B3-Parentspanid": "2a739cfc42d28236",     "X-B3-Sampled": "0",     "X-B3-Spanid": "8bdf030613bb9c8d",     "X-B3-Traceid": "75d84f317abad5232a739cfc42d28236",     "X-Envoy-External-Address": "10.100.91.201",     "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/httpbin-istio/sa/httpbin;Hash=ea8c3d70befa0d73aa0f07fdb74ec4700d42a72889a04630741193548f1a7ae1;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"  },   "json": null,   "origin": "69.84.111.39,10.100.91.201",   "url": "http://httpbin.org/post"}

Обратите внимание, что по-умолчанию значением заголовка Content-Type является application/x-www-form-urlencoded. Для json это является неверным, поэтому для определения Content-Type следует использовать флаг -H:

$ curl -X POST -d '{"answer":42}' -H "Content-Type: application/json" https://httpbin.org/post{  "args": {},   "data": "{\"answer\":42}",   "files": {},   "form": {},   "headers": {    "Accept": "*/*",     "Content-Length": "13",     "Content-Type": "application/json",     "Host": "httpbin.org",     "User-Agent": "curl/7.58.0",     "X-Amzn-Trace-Id": "Root=1-5ee8e45e-ad875af4f83efd4379b86c34",     "X-B3-Parentspanid": "5f4f33d1c5ea13aa",     "X-B3-Sampled": "0",     "X-B3-Spanid": "a062c9bf2ebfd4bd",     "X-B3-Traceid": "44aa8d62412ae34d5f4f33d1c5ea13aa",     "X-Envoy-External-Address": "10.100.86.47",     "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/httpbin-istio/sa/httpbin;Hash=2f0b3331fe4d512975b4b82583a55dd5d1196023d0dfce9e0abed246991c5b67;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"  },   "json": {    "answer": 42  },   "origin": "69.84.111.39,10.100.86.47",   "url": "http://httpbin.org/post"}

Отправка PUT-запроса, содержащего JSON-файл


Флаг -d также поддерживает отправку данных из локальных файлов.

Например, представим, что у нас есть файл data.js, содержащий такие данные:

{"answer": 42}

Для отправки PUT-запроса с этим файлом в качестве тела запроса вы можете присвоить флагу -d значение @data.json. Префикс @ говорит curl загрузить тело запроса из файла data.json:

$ curl -X PUT -d '@data.json' -H "Content-Type: application/json" https://httpbin.org/put{  "args": {},   "data": "{\"answer\":42}",   "files": {},   "form": {},   "headers": {    "Accept": "*/*",     "Content-Length": "13",     "Content-Type": "application/json",     "Host": "httpbin.org",     "User-Agent": "curl/7.58.0",     "X-Amzn-Trace-Id": "Root=1-5ee8e745-37c4ef06326b7b4354a16b94",     "X-B3-Parentspanid": "a4f8f91f4f1b051e",     "X-B3-Sampled": "0",     "X-B3-Spanid": "a018b1a3fcebdc68",     "X-B3-Traceid": "7b48b01dc3f632eea4f8f91f4f1b051e",     "X-Envoy-External-Address": "10.100.91.201",     "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/httpbin-istio/sa/httpbin;Hash=6035260d9d551af6c1907270653214e8d3195abbdd19078c1c84fd9a4106f260;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"  },   "json": {    "answer": 42  },   "origin": "69.84.111.39,10.100.91.201",   "url": "http://httpbin.org/put"}

Заключение


Резюмируя, вот опции curl, которые я нахожу самыми полезными:

  • -X определяет метод запроса, например, curl -X POST url
  • -d определяет тело запроса в виде строки в PUT и POST-запросах. Используйте @ для извлечения данных из файла
  • -H определяет заголовок запроса, например, curl -H "Authorization: my-secret-token" url
  • -u аутентификационные данные для стандартной авторизации
  • -O сохраняет тело запроса в файл
  • -i показывает полный ответ, включая заголовки

Curl полезный инструмент взаимодействия с API посредством командной строки, независимо от того, сторонний это API или API, который вы разрабатываете. Для быстрого тестирования curl подходит лучше, чем Axios в Node.js или настройка запроса в Postman, если вы знакомы с их синтаксисом.

Благодарю за потраченное время. Надеюсь, оно было потрачено не зря.
Подробнее..

Перевод Руководство по Node.js для начинающих. Часть 1. Быстрый старт

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


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

Представляю Вашему вниманию перевод этого руководства по Node.js.

Введение в Node.js


Node.js это открытая и кроссплатформенная среда выполнения JavaScript. Это отличное решение почти для любого проекта.

Node.js запускает движок JavaScript V8, ядро Google Chrome, вне браузера. Это делает Node.js очень производительным.

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

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

Это позволяет Node.js обрабатывать одновременно тысячи запросов посредством одного сервера без необходимости создания системы обеспечения согласованности потока, которая может стать источником серьезных ошибок.

Существенное преимущество Node.js состоит в том, что миллионы разработчиков, пишущих код на JavaScript для браузера, теперь имеют возможность писать серверный код в дополнение к клиентскому без необходимости изучать совершенно другой язык (программирования).

В Node.js новые ECMAScript-стандарты могут использоваться без проблем, вам не нужно ждать, пока все пользователи обновят браузеры вы сами решаете, какую версию ECMAScript использовать посредством изменения версии Node.js, вы также можете добавить экспериментальные возможности, запустив Node.js с (соответствующими) флагами.

Огромное количество библиотек

Npm с его простой структурой способствует быстрому росту экосистемы Node.js, на сегодняшний день в npm зарегистрировано свыше 1 000 000 открытых пакетов, которые вы может использовать совершенно бесплатно.

Пример Node.js-приложения

Наиболее распространенным примером использования Node.js является создание веб-сервера:

    const http = require('http')const hostname = '127.0.0.1'const port = process.env.PORT const server = http.createServer((req, res) => {    res.statusCode = 200    res.setHeader('Content-Type', 'text/plain')    res.end('Hello World!\n')})server.listen(port, hostname, () => {    console.log(`Server running at http://${hostname}:${port}/`)})

Первым делом мы подключаем модуль http.

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

Метод createServer() http создает новый HTTP-сервер и возвращает его.

Сервер устанавливается на прослушивание определенного порта и хоста. Когда сервер готов, запускается функция обратного вызова, в данном случае сообщающая о том, что сервер запущен.

При получениее нового запроса вызывается событие request, содержащее два объекта: запрос (объект http.IncomingMessage (входящее сообщение)) и ответ (объект http.ServerResponse (ответ сервера)).

Эти объекты необходимы для обработки вызова HTTP.

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

Второй объект используется для предоставления данных отправителю запроса.

В данном случае посредством

    res.statusCode = 200

мы присваиваем свойству statusCode значение 200 в качестве индикатора успешного выполнения запроса.

Мы устанавливает заголовок Content-Type (тип содержимого или контента)

    res.setHeader('Content-Type', 'text/plain')

и закрываем ответ, добавляя контент в качестве аргумента в end():

    res.end('Hello World\n')

Node.js-фреймворки и инструменты

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

Со временем многие из них стали очень популярными. Вот список некоторых из них:

  • AdonisJs: фуллстек-фреймворк с акцентом на эргономику, стабильность и надежность. Adonis один из самых быстрых веб-фреймворков для Node.js.
  • Express: предоставляет один из самых простых и одновременно мощных способов создания веб-сервера. Его ключем к успеху является минималистичный подход, простой функционал, акцент на основных возможностях сервера.
  • Fastify: веб-фреймворк, сфокусированный на обеспечении лучшего опыта разработки с минимальными накладными расходами и мощной архитектурой плагина. Fastify является одним из самых проивзодительных веб-фреймворков.
  • hapi: фреймворк с богатым функционалом для создания приложений и сервисов, позволяющий разработчикам сосредоточиться на написании переиспользуемой логики приложений вместо траты времени на построение инфраструктуры.
  • koa: создан командой Express в целях упрощения и уменьшения размера с учетом многолетнего опыта. Новый проект возник из-за необходимости внесения несовместимых изменений без разрушения сообщества, сформировавшего вокруг Express.
  • Loopback.io: облегчает создание современных приложений со сложной системой зависимостей.
  • Meteor: невероятно мощный фуллстек-фреймворк, предоставляющий изоморфный подход для создания приложений на JavaScript, разделяя код между клиентом и сервером. Входит в комлект таких библиотек, как React, Vue и Angular. Также может использоваться для создания мобильных приложений.
  • Micro: предоставляет легковесный сервер для создания асинхронных HTTP-микросервисов.
  • NestJS: основанный на TypeScript прогрессивный Node.js-фреймворк для создания корпоративных, надежных и масштабируемых серверных приложений.
  • Next.js: фреймоврк для рендеринга React-приложений на стороне сервера.
  • Nx: инструмент для фуллстек монолитной разработки посредством NestJS, Express, React, Angular и т.д. Nx помогает масштабировать разработку от одной до нескольких команд, работающих одновременно над множеством приложений.
  • Socket.io: движок для коммуникации в режиме реального времени для создания сетевых приложений.

Краткая история Node.js


Верите или нет, но Node.js всего 10 лет.

Для сравнения: JavaScript существует на протяжении 24 лет, а веб 30.

10 лет это небольшой срок для технологии, однако порой кажется, что Node.js был всегда.

Я познакомился с Node.js, когда прошло всего 2 года с момента его появления, но уже тогда, несмотря на ограниченность информации, чувствовалось, что его ждет большое будущее.

В этом разделе мы посмотрим на общую картину истории Node.js.

Немного истории

JavaScript это язык программирования, изобретенный в Netscape как скриптовый инструмент манипуляции веб-страницами в браузере Netscape Navigator.

Частью бизнес-модели Netscape являлась продажа веб-серверов, включающих среду Netscape LiveWire, способную создавать динамические страницы посредством серверного JavaScript. К сожалению, Netscape LiveWire провалился и серверный JavaScript не был популярен до появления Node.js.

Одним из ключевых факторов популярности Node.js является время (его появления). Несколькими годами ранее JavaScript был признан серьезным языком (программирования) благодаря Web 2.0-приложениям (таким как Flickr, Gmail и др.), показавших миру, как может выглядеть современный веб.

Движки JavaScript также стали значительно лучше, поскольку браузеры стремились к повышению производительности во благо пользователей. Команды разработчиком главных браузеров упорно трудились над реализацией лучшей поддержки JavaScript и его максимально быстрого выполнения. Движок, который использует Node.js, V8 (также известный как Chrome V8 открытый движок JavaScript проекта Chromium), вышел победителем из этого соревнования.

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

2009

  • Появился Node.js
  • Появился npm

2010

  • Express
  • Socket.io

2011

  • npm версии 1.0
  • Крупные компании начали внедрять Node.js: LinkedIn, Uber и др.
  • hapi

2012

  • Продолжается быстрый рост популярности Node.js

2013

  • Первая крупная блог-платформа на Node.js: Ghost
  • Koa

2014

  • Большой раскол: из Node.js выделился io.js (форк создание обособленной ветки в системе контроля версий git) ради поддержки синтаксиса ES6 и более динамичного развития

2015

  • Основание Node.js Foundation
  • IO.js вернулся в Node.js (мерж слияние веток в git)
  • В npm появились частные (приватные) модули
  • Node.js версии 4 (версий 1, 2 и 3 не было)

2016


2017

  • Повышение безопасности npm
  • Node.js 8
  • HTTP/2
  • V8 включил Node.js в комплект тестирования, официально признав Node.js движком JS в дополнение к Chrome
  • 3 млрд скачиваний npm каждую неделю

2018

  • Node.js 10
  • Экспериментальная поддержка ES-модулей с расширением .mjs

Как установить Node.js?


Node.js может быть установлен различными способами.

Дистрибутивы для основных платформ доступны на официальном сайте.

Очень удобным способом установки Node.js является использование пакетного менеджера. У каждой операционной системы он свой.

На macOS таковым является Homebrew, позволяющий легко установить Node.js с помощью командной строки:

brew install node

Список пакетных менеджеров для Linux, Windows и др. систем находится здесь.

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

Он также очень полезен при необходимости тестирования кода в старых версиях Node.js.

Для более подробной информации о nvm перейдите по этой ссылке.

Мой совет используйте официальный установщик, если только начинаете разрабатывать и ранее не пользовались Homebrew.

После установки Node.js вы получаете доступ к исполянемой программе node в командной строке.

Насколько хорошо вы должны знать JavaScript для работы с Node.js?


Будучи новичком, сложно определить свой уровень программирования.

Также сложно определить, где заканчивается JavaScript и начинается Node.js, и наоборот.

Лично я бы посоветовал хорошенько разобраться со следующими основными концепциями JavaScript перед погружением в Node.js:

  • Синтаксис или лексическая структура
  • Выражения (по сути, тот же синтаксис)
  • Типы (данных)
  • Переменные
  • Функции
  • Ключевое слово this
  • Стрелочные функции
  • Циклы
  • Область видимости
  • Массивы
  • Шаблонные или строковые литералы
  • Точка с запятой (вероятно, случаи ее обязательного использования, например, при работе с IIFE)
  • Строгий режим
  • ECMAScript 6, 2016, 2017

Освоение названных концепций начало пути профессионального фуллстек-разработчика.

Следующие концепции также являются ключом к пониманию асинхронного программирования, которое является фундаментальной частью Node.js:

  • Асинхронное программирование и функции обратного вызова (коллбэки)
  • Таймеры (счетчики)
  • Промисы (обещания)
  • Async/await
  • Замыкания
  • Цикл событий (стек вызовов)

Разница между Node.js и браузером


JavaScript может быть использован как в браузере, так и в Node.js.

Однако создание приложений для браузера сильно отличается от создания Node.js-приложений.

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

С точки зрения JavaScript-фронтендера (разработчика фронтенда клиентской части приложения), разработка приложений на Node.js имеет существенное преимущество, выражающееся в том, что везде, и на клиенте, и на сервере, используется один язык программирования JavaScript.

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

Единственное, что потребуется изучить это экосистема.

В браузере большую часть времени нам приходится иметь дело с DOM и другими веб-API, например, куки. Разумеется, их не существует в Node.js. В Node.js отсутствуют window, document и другие объекты, характерные для браузера.

С другой стороны, в браузерах отсутствуют замечательные API, предоставляемые Node.js посредством модулей, например, доступ к файловой системе.

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

Это означает, что вы можете писать код на JavaScript, поддерживаемом вашей версией Node.js.

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

Для транспиляции кода в ES5 вы можете использовать Babel, в Node.js такой необходимости не возникает.

Еще одним отличием является то, что в Node.js используется модульная система CommonJS, а в браузерах реализована поддержка ES-модулей.

На практике это означает, что в Node.js мы используем require(), а в браузере import.

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

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

Перевод Как получить размеры экрана, окна и веб-страницы в 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. Заключение


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

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

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

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

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

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)


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

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

Из чего состоит набор для разработчиков NB-IoT DevKit?

26.06.2020 12:10:59 | Автор: admin
Набор вышел в начале июня. Он поможет разобраться, в чем преимущества сети интернета вещей NB-IoT, и научит работать с ней. В комплект входит аппаратная часть, коннективити, то есть доступ к сети NB-IoT и доступ к IoT-платформам. Главная фича DevKit демонстрационная прошивка, которая позволяет на практике разобраться, как работает система. В этой статье детально рассмотрим DevKit и его возможности.



Кому это надо?


Когда мы начали разворачивать сеть NB-IoT (почитать больше о сети NB-IoT можно здесь), на нас со всех сторон посыпались различные вопросы. Крупные производители, которые много лет работают на рынке M2M устройств, стартапы, начинающие разработчики и просто любители интересовались режимами работы сети, протоколами передачи данных, даже управлением радиомодулем АТ-командами. Нас спрашивали, какие частоты (band) используются, как работает режим power save mode, как устройство и сеть согласуют соответствующие таймеры, как, используя протокол транспортного уровня UDP, добиться гарантированной доставки сообщения, как задать APN и выбрать определенный band (частотный диапазон). И множество других вопросов.

В ходе тестов мы также обратили внимание, что многие устройства не адаптированы для работы в сети NB-IoT и в результате работают некорректно. Например, использование протокола передачи данных с транспортным уровнем TCP приводит к высокому объему передаваемого трафика, а также к трудностям при работе в сложных радиоусловиях. Другая распространенная проблема использование радиомодуля в режиме по умолчанию, в котором модуль сам включает eDRX: это вводит пользователя в заблуждение, так как он не может принять данные в произвольный момент времени.

К нам стали обращаться ВУЗы, специализирующиеся на телекоммуникациях, которым интересно изучить на реальных устройствах технологии LPWAN, в том числе NB-IoT, и разработчики-любители, которые хотят поэксперементировать и создать собственное IoT-решение для умного дома или даже умного города.

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

Из чего состоит набор?


Мы сделали решение, с которым можно сразу начинать работать, ничего не приобретая дополнительно. Как уже говорилось выше, в комплект входит аппаратная часть, коннективити, то есть доступ к сети NB-IoT, и доступ к IoT-платформам.

В состав аппаратной части входит радиомодуль Ublox SARA-R410, микроконтроллер STM32L152RE, кабели, антенны и программатор. На основной плате установлены акселерометр и температурный датчик. Плата расширения в форм-факторе Arduino-shield содержит GNSS модуль.

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

Также по запросу возможна активация функционала NIDD и возможность работы через узел SCEF (Service Capability Exposure Function) сети NB-IoT МТС. Сервис снимает с разработчика необходимость идентификации и аутентификации устройств и предоставляет возможность клиентским серверам приложений (Application Server) получать данные и управлять устройствами через единый API-интерфейс. В России ни один другой оператор интернета вещей не предоставляет такую возможность.

Возможности демонстрационной прошивки


Демонстрационная прошивка содержит функциональное меню, которое позволяет новичкам сразу приступить к работе. Достаточно лишь подключить плату и получить регистрационные данные. Прошивка реализует функциональное меню, доступ к которому мы получаем, подключившись через любую терминальную программу. Можно воспользоваться знакомой многим PuTTY. Мы также рекомендуем удобную и бесплатную утилиту от Ublox: m-center



Выглядит меню так:

*** Welcome to MTS NB-IoT Development Kit service menu ***
Firmware version: 2.3 beta 2, 28.05.2020

Current settings found in EEPROM:

Target IP: 195.34.49.22
Target port: 6683
Target URL: /api/v1/devkitNIDDtest03/telemetry
NIDD APN: devkit.nidd
Use NIDD for telemetry: 1
Board mode on startup: service menu
Telemetry interval
(in logger mode): 1000 ms
GNSS privacy mode: 0

Type in a function number from a list below and press enter.

Target server setup:
1 set the URL of the resource JSON data will be transmitted to
2 set the IP address
3 set the port
4 set an APN for NIDD access or turn NIDD mode ON or OFF
System functions:
5 force send telemetry packet
6 wait for incoming NIDD data during specified timeout
7 enter direct AT-command mode
8 enter true direct mode to access the RF module
CAUTION: to exit this mode you will have to reboot the board physically
9 show ICCID, IMEI, IMSI and MCU serial number
10 show network information
11 set telemetry transmission interval
12 set GNSS privacy mode
(hide actual location data when transmitting on server)
13 set firmware startup mode (setup or logger)
14 read on-board sensors and try to acquire GNSS data
15 reboot MCU
16 reboot RF module
17 factory setup & test
(do not use this unless you really know what you want)

Первые четыре пункта меню позволяют настроить параметры соединения с платформой (URI-path, IP, порт, параметры NIDD). Пятый пункт позволяет отправить пакет, содержащий данные с датчиков на плате.

Пример того, как выглядит результат отправки пакета по протоколу CoAP:

Using IP method.
Sending data to iotsandbox.mts.ru:6683/api/v1/devkitNIDDtest03/telemetry
Telemetry:
{'interface':'telemetry', 'ICCID':'89701012417915117807', 'Tamb_degC':24, 'aX':-14, 'aY':23, 'aZ':244, 'RSSI_dBm':-81, 'latitude':55.768848, 'longitude':37.715088, 'GNSS_data_valid':1}
(184 bytes)
Raw CoAP data (225 bytes):
5102000000B36170690276310D036465766B69744E4944447465737430330974656C656D65747279FF7B27696E74657266616365273A2774656C656D65747279272C20274943434944273A273839373031303132343137393135313137383037272C202754616D625F64656743273A32342C20276158273A2D31342C20276159273A32332C2027615A273A3234342C2027525353495F64426D273A2D38312C20276C61746974756465273A35352E3736383834382C20276C6F6E676974756465273A33372E3731353038382C2027474E53535F646174615F76616C6964273A317D
Server response code: 2.03, response time = 664 ms.
Server response dump:
5143067000

Переданные данные мы видим на платформе:



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

On-board sensors data:
temperature: 23

accelerometer: X = -3 Y = 4 Z = 249
Accelerometer signature value is correct.

Testing GNSS option
GNSS string:
$GNGLL,5546.12120,N,03742.89412,E,133220.00,A,D*75
Parsed data:
latitude = 55.768687
longitude = 37.714902

Можно прочитать идентификаторы радиомодуля и микроконтроллера, используя пункт 7 меню. В таком случае ответ будет:

Board identification data
SIM card ICCID: 89701012417915117807
AT+CCID

+CCID: 89701012417915117807

OK
RF module IMEI: 359215103260015
AT+CGSN

359215103260015

OK
IMSI:
AT+CIMI

250011791511780

OK
MCU serial number:
0x393533307473832200

Используя пункт 10 меню можно считать сетевые параметры NB-IoT, вот пример вывода:

RSSI = -75 dBm (valid range is -111 to -51 dBm)
SNR = 22 dB
RSRP = -83 dBm (valid range is -141 to -44 dBm)
Cell ID = 753621
EARFCN = 1711

Также можно перейти в режим прямого управления радиомодулем посредством АТ-команд, используя пункт меню 8. Эта функция переводит Development kit в режим отладочной платы радиомодуля. Ниже пример прямого управления радиомодулем с командами обмена данными между устройством и сервером в режиме NIDD:

Entering true direct mode.
From now on everything you type into a terminal will be transferred to the RF module as is
(and similarly in reverse direction).
NOTICE: No special commands supported here, for nothing is between you and the RF module.
YOU NEED TO REBOOT THE BOARD PHYSICALLY TO EXIT THIS MODE.

at+cereg?
+CEREG: 0,1
OK

AT+COPS?
+COPS: 0,0,MTS RUS MTS RUS,9
OK

at+cgdcont?
+CGDCONT: 1,NONIP,devkit.nidd,0.0.0.0,0,0,0,0
OK

AT+CRTDCP=1
OK

AT+CSODCP=1,16,"{'test':'hello'}"
OK

+CRTDCP:1,5,Hi hi

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

Для продвинутых пользователей открыты исходные коды прошивки, можно оценить логику работы устройства на уровне кода, внести изменения и экспериментировать. Со стороны облачной платформы доступны инструменты для обработки данных: виджеты, панели управления, инструменты Интеграции, позволяющие принять данные в любом формате в протоколах UDP, TCP и Non-IP. Инструмент Конвертер данных позволяют привести получаемый поток данных к виду понятному платформе.

DevKit и Ардуино


Несмотря на критическое отношение части профессионалов к Ардуино, которые указывают на неоптимальный код и на то, что библиотеки написаны не профессиональными разработчиками, мы изначально хотели сделать DevKit совместимым с Ардуино. Arduino IDE пользуется популярностью среди разработчиковлюбителей, а также может применяться в учебных программах ВУЗами.

DevKit полностью совместим с Ардуино R3 по своему духу, архитектуре, и разьемам. Для устройства были разработаны все сопутствующие файлы поддержки, так что оно устанавливается в Arduino IDE штатным образом.



В состав пакета для Arduino IDE включены необходимые инструменты и библиотеки, а также ряд примеров с исходными текстами. Программная совместимость с другими платами Arduino на основе STM32 позволяет использовать написанные для них библиотеки, а также загружать рабочие скетчи практически без изменений. Для программирования DevKit из Arduino IDE необходимо установить ПО STMCubeProgrammer, которое можно скачать с сайта компании STM. Наши разработчики продолжают наполнять пакет для Arduino новыми библиотеками и примерами.

В качестве простейшего примера работы с Arduino IDE рассмотрим загрузку примера Blink (мигание светодиодом). На изображении ниже можно увидеть результат загрузки и компиляции данного скетча из меню Файл Примеры 01.Basics





Поддерживаемые интегрированные среды разработки


Разработка демонстрационной прошивки производилась в интегрированной среде Em::Bitz. Программный код примера рассчитан на компиляцию с помощью GCC. Для разработки пользовательского кода пригодны любые IDE, поддерживающие семейство микроконтроллеров STM32L15x. Например, Arduino IDE, IAR Embedded Workbench, Keil uVision MDK ARM и другие.

Расширение функциональности


Функциональность можно расширять как с помощью шилдов, так и подключая любые дискретные датчики.



Установка GNSS-шилда превращает DevKit в GPS/GLONASS трекер.



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



Можно подключить специальный шилд и добавить функциональность Ethernet и карту памяти.



Подключая датчики скорости и направления ветра получаем NB-IoT метеостанцию



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



В следующем материале покажем на практике, как собрать на основе NB-IoT DevKit устройство интернета вещей. Заказать NB-IoT Development Kit можно уже сейчас на нашем сайте.

Авторы:
старший менеджер группы планирования сети IoT Виталий Бачук
ведущий эксперт направления МТС Виктор Лучанский
руководитель группы планирования сети IoT Андрей Плавич
Подробнее..

15 распространённых мифов о программировании

30.06.2020 16:15:52 | Автор: admin

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




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

1. Хорошие разработчики работают круглосуточно


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

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

2. Чем больше людей проверит код, тем меньше багов


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

3. Математические навыки определяют навыки разработки


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

4. Разработчики гении


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

5. Достаточно освоить что-то одно


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

6. Язык Х лучше


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

7. Овладеть языком можно за несколько недель


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

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

8. Выход проудкта в продакшн = конец работы над проектом


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

9. Программирование это просто написание кода


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

10. Программирование не творческое занятие


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

11. Разработчики обладают низкими социальными навыками


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

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

12. Молодые разработчики считают себя лучшими специалистами


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

13. Программирование это скучно


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

14. После 35 карьера разработчика заканчивается


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

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

15. Разработчики это товар


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

Заключение


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

Перевод Разбираемся в моделях кода архитектуры x64

01.07.2020 18:14:54 | Автор: admin
Какой моделью кода мне воспользоваться? часто возникающий, но нечасто разбираемый вопрос при написании кода для архитектуры х64. Тем не менее, это довольно интересная проблема, и для понимания генерируемого компиляторами машинного кода х64 полезно иметь представление о моделях кода. Кроме того, для тех, кто беспокоится о производительности вплоть до мельчайших команд, выбор модели кода влияет и на оптимизацию.

Информация по этой теме в сети, или где бы то ни было еще, встречается редко. Самым важным из доступных ресурсов является официальный х64 ABI, скачать его можно по ссылке (далее по тексту он будет упоминаться как ABI). Часть информации также можно найти на man-страницах gcc. Задача данной статьи предоставить доступные рекомендации по теме, обсудить связанные с ней вопросы, а так же хорошими примерами через используемый в работе код продемонстрировать некоторые концепты.

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



Также смотрите нашу предыдущую публикацию на схожую тему: Как x86_x64 адресует память



Модели кода. Мотивационная часть


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

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

Таким образом, компромиссом становятся модели кода. [1] Модель кода это формальное соглашение между программистом и компилятором, в котором программист указывает свои намерения относительно размера ожидаемой программы (или программ), в которую попадет компилируемый в данный момент объектный модуль. [2] Модели кода нужны для того чтобы программист мог сказать компилятору: не волнуйся, этот объектный модуль пойдет только в небольшие программы, так что можно пользоваться быстрыми RIP-относительными режимами адресации. С другой стороны, он может сказать компилятору следующее: мы собираемся компоновать этот модуль в большие программы, так что пожалуйста используй неторопливые и безопасные абсолютные режимы адресации с полным 64-битным сдвигом.

О чем расскажет эта статья


Мы поговорим о двух описанных выше сценариях, малой модели кода и большой модели кода: первая модель говорит компилятору, что 32-битного относительного сдвига должно хватить для всех ссылок на код и данные в объектном модуле; вторая настаивает на использовании компилятором абсолютных 64-битных режимов адресации. Кроме того, существует еще и промежуточный вариант, так называемая средняя модель кода.

Каждая из этих моделей кода представлена в независящих друг от друга PIC и не-PIC вариациях, и мы поговорим про каждую из шести.

Исходный пример на С


Для демонстрации обсуждаемых в этой статье концептов я воспользуюсь представленной ниже программой на С и скомпилирую ее с различными моделями кода. Как можно видеть, функция main получает доступ к четырем разным глобальным массивам и одной глобальной функции. Массивы отличаются двумя параметрами: размером и видимостью. Размер важен для объяснения средней модели кода и не понадобится в работе с малой и большой моделями. Видимость важна для работы PIC-моделей кода и бывает либо статичной (видимость только в исходном файле), либо глобальной (видимость всем скомпонованным в программу объектам).

int global_arr[100] = {2, 3};static int static_arr[100] = {9, 7};int global_arr_big[50000] = {5, 6};static int static_arr_big[50000] = {10, 20};int global_func(int param){    return param * 10;}int main(int argc, const char* argv[]){    int t = global_func(argc);    t += global_arr[7];    t += static_arr[7];    t += global_arr_big[7];    t += static_arr_big[7];    return t;}

gcc использует модель кода как значение опции -mcmodel. Кроме того, флагом -fpic можно задать PIC компиляцию.

Пример компиляции в объектный модуль через большую модель кода с использованием PIC:

> gcc -g -O0 -c codemodel1.c -fpic -mcmodel=large -o codemodel1_large_pic.o

Малая модель кода


Перевод цитаты из man gcc на тему малой модели кода:

-mcmodel=small
Генерация кода для малой модели: программа и ее символы должны быть скомпонованы в нижних двух гигабайтах адресного пространства. Размер указателей 64 бит. Программы могут быть скомпонованы и статически, и динамически. Это основная модель кода.


Другими словами, компилятор может спокойно считать, что код и данные доступны через 32-битный RIP-относительный сдвиг из любой команды в коде. Давайте взглянем на дизассемблированный пример программы на С, которую мы скомпилировали через не-PIC малую модель кода:

> objdump -dS codemodel1_small.o[...]int main(int argc, const char* argv[]){  15: 55                      push   %rbp  16: 48 89 e5                mov    %rsp,%rbp  19: 48 83 ec 20             sub    $0x20,%rsp  1d: 89 7d ec                mov    %edi,-0x14(%rbp)  20: 48 89 75 e0             mov    %rsi,-0x20(%rbp)    int t = global_func(argc);  24: 8b 45 ec                mov    -0x14(%rbp),%eax  27: 89 c7                   mov    %eax,%edi  29: b8 00 00 00 00          mov    $0x0,%eax  2e: e8 00 00 00 00          callq  33 <main+0x1e>  33: 89 45 fc                mov    %eax,-0x4(%rbp)    t += global_arr[7];  36: 8b 05 00 00 00 00       mov    0x0(%rip),%eax  3c: 01 45 fc                add    %eax,-0x4(%rbp)    t += static_arr[7];  3f: 8b 05 00 00 00 00       mov    0x0(%rip),%eax  45: 01 45 fc                add    %eax,-0x4(%rbp)    t += global_arr_big[7];  48: 8b 05 00 00 00 00       mov    0x0(%rip),%eax  4e: 01 45 fc                add    %eax,-0x4(%rbp)    t += static_arr_big[7];  51: 8b 05 00 00 00 00       mov    0x0(%rip),%eax  57: 01 45 fc                add    %eax,-0x4(%rbp)    return t;  5a: 8b 45 fc                mov    -0x4(%rbp),%eax}  5d: c9                      leaveq  5e: c3                      retq

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

> readelf -r codemodel1_small.oRelocation section '.rela.text' at offset 0x62bd8 contains 5 entries:  Offset          Info           Type           Sym. Value    Sym. Name + Addend00000000002f  001500000002 R_X86_64_PC32     0000000000000000 global_func - 4000000000038  001100000002 R_X86_64_PC32     0000000000000000 global_arr + 18000000000041  000300000002 R_X86_64_PC32     0000000000000000 .data + 1b800000000004a  001200000002 R_X86_64_PC32     0000000000000340 global_arr_big + 18000000000053  000300000002 R_X86_64_PC32     0000000000000000 .data + 31098

Давайте в качестве примера полностью декодируем доступ к global_arr. Интересующий нас дизассемблированный сегмент:

  t += global_arr[7];36:       8b 05 00 00 00 00       mov    0x0(%rip),%eax3c:       01 45 fc                add    %eax,-0x4(%rbp)

RIP-относительная адресация относительна очередной команде, таким образом сдвиг необходимо запатчить в команду mov таким образом, чтобы он соответствовал 0х3с. Нас интересует вторая релокация, R_X86_64_PC32, она указывает на операнд mov по адресу 0x38 и означает следующее: берем значение символа, добавляем слагаемое и вычитаем указываемый релокацией сдвиг. Если вы все корректно посчитали, вы увидите как результат разместит относительный сдвиг между очередной командой и global_arr, плюс 0х1с. Поскольку 0х1с означает седьмой int в массиве (в архитектуре х64 размер каждого int составляет 4 байта), то этот относительный сдвиг нам и нужен. Таким образом, используя RIP-относительную адресацию, команда корректно ссылается на global_arr[7].

Также интересно отметить следующее: пусть команды доступа к static_arr здесь и схожи, его переадресация использует другой символ, тем самым вместо конкретного символа указывая в секцию .data. Виной тому действия компоновщика, он размещает статический массив в известном месте секции, и таким образом массив нельзя использовать совместно с другими общими библиотеками. В итоге компоновщик урегулирует ситуацию с этой релокацией. С другой стороны, поскольку global_arr может быть использован (или перезаписан) другой общей библиотекой, уже динамический загрузчик должен будет разобраться со ссылкой к global_arr. [3]

Наконец, давайте взглянем на отсылку к global_func:

  int t = global_func(argc);24:       8b 45 ec                mov    -0x14(%rbp),%eax27:       89 c7                   mov    %eax,%edi29:       b8 00 00 00 00          mov    $0x0,%eax2e:       e8 00 00 00 00          callq  33 <main+0x1e>33:       89 45 fc                mov    %eax,-0x4(%rbp)

Поскольку операнд callq тоже RIP-относителен, релокация R_X86_64_PC32 работает здесь аналогично размещению фактического относительного сдвига к global_func в операнд.

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

Большая модель кода


Перевод цитаты из man gcc на тему большой модели кода:

-mcmodel=large
Генерация кода для большой модели: Эта модель не делает предположений относительно адресов и размеров секций.

Пример дизассемблированного кода main, скомпилированного при помощи не-PIC большой модели:

int main(int argc, const char* argv[]){  15: 55                      push   %rbp  16: 48 89 e5                mov    %rsp,%rbp  19: 48 83 ec 20             sub    $0x20,%rsp  1d: 89 7d ec                mov    %edi,-0x14(%rbp)  20: 48 89 75 e0             mov    %rsi,-0x20(%rbp)    int t = global_func(argc);  24: 8b 45 ec                mov    -0x14(%rbp),%eax  27: 89 c7                   mov    %eax,%edi  29: b8 00 00 00 00          mov    $0x0,%eax  2e: 48 ba 00 00 00 00 00    movabs $0x0,%rdx  35: 00 00 00  38: ff d2                   callq  *%rdx  3a: 89 45 fc                mov    %eax,-0x4(%rbp)    t += global_arr[7];  3d: 48 b8 00 00 00 00 00    movabs $0x0,%rax  44: 00 00 00  47: 8b 40 1c                mov    0x1c(%rax),%eax  4a: 01 45 fc                add    %eax,-0x4(%rbp)    t += static_arr[7];  4d: 48 b8 00 00 00 00 00    movabs $0x0,%rax  54: 00 00 00  57: 8b 40 1c                mov    0x1c(%rax),%eax  5a: 01 45 fc                add    %eax,-0x4(%rbp)    t += global_arr_big[7];  5d: 48 b8 00 00 00 00 00    movabs $0x0,%rax  64: 00 00 00  67: 8b 40 1c                mov    0x1c(%rax),%eax  6a: 01 45 fc                add    %eax,-0x4(%rbp)    t += static_arr_big[7];  6d: 48 b8 00 00 00 00 00    movabs $0x0,%rax  74: 00 00 00  77: 8b 40 1c                mov    0x1c(%rax),%eax  7a: 01 45 fc                add    %eax,-0x4(%rbp)    return t;  7d: 8b 45 fc                mov    -0x4(%rbp),%eax}  80: c9                      leaveq  81: c3                      retq

И вновь полезно взглянуть на релокации:

Relocation section '.rela.text' at offset 0x62c18 contains 5 entries:  Offset          Info           Type           Sym. Value    Sym. Name + Addend000000000030  001500000001 R_X86_64_64       0000000000000000 global_func + 000000000003f  001100000001 R_X86_64_64       0000000000000000 global_arr + 000000000004f  000300000001 R_X86_64_64       0000000000000000 .data + 1a000000000005f  001200000001 R_X86_64_64       0000000000000340 global_arr_big + 000000000006f  000300000001 R_X86_64_64       0000000000000000 .data + 31080

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

  t += global_arr[7];3d:       48 b8 00 00 00 00 00    movabs $0x0,%rax44:       00 00 0047:       8b 40 1c                mov    0x1c(%rax),%eax4a:       01 45 fc                add    %eax,-0x4(%rbp)

Двум командам необходимо получить желаемое значение из массива. Первая команда размещает абсолютный 64-битный адрес в rax, который, как мы скоро увидим, окажется адресом global_arr, тогда как вторая команда загружает слово из (rax) + 0х1с в eax.

Так что давайте сфокусируемся на команде по адресу 0x3d, movabs, абсолютной 64-битной версии mov в архитектуре х64. Она может забросить полную 64-битную константу прямо в регистр, и так как в нашем дизассемблированном коде значение этой константы равно нулю, за ответом нам придется обратиться к таблице релокаций. В ней мы найдем абсолютную релокацию R_X86_64_64 для операнда по адресу 0x3f, со следующим значением: размещение значения символа плюс слагаемого обратно в сдвиг. Другими словами, rax будет содержать абсолютный адрес global_arr.

А что насчет функции вызова?

  int t = global_func(argc);24:       8b 45 ec                mov    -0x14(%rbp),%eax27:       89 c7                   mov    %eax,%edi29:       b8 00 00 00 00          mov    $0x0,%eax2e:       48 ba 00 00 00 00 00    movabs $0x0,%rdx35:       00 00 0038:       ff d2                   callq  *%rdx3a:       89 45 fc                mov    %eax,-0x4(%rbp)

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

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

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

Средняя модель кода


Как и ранее, давайте взглянем на перевод цитаты из man gcc:

-mcmodel=medium
Генерация кода для средней модели: Программа скомпонована в нижних двух гигабайтах адресного пространства. Здесь же расположены и малые символы. Символы размера большего, чем задано через -mlarge-data-threshold, попадают в больше данные или секции bss и могут находиться выше двух гигабайт. Программы могут быть скомпонованы и статически, и динамически.

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

Также важно отметить, что при работе со средней моделью кода для больших данных по аналогии с секциями .data и .bss создаются специальные секции: .ldata и .lbss. Это не так важно в призме темы текущей статьи, однако я собираюсь немного от нее отклониться. Детальнее с данным вопросом можно ознакомиться в ABI.

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

int main(int argc, const char* argv[]){  15: 55                      push   %rbp  16: 48 89 e5                mov    %rsp,%rbp  19: 48 83 ec 20             sub    $0x20,%rsp  1d: 89 7d ec                mov    %edi,-0x14(%rbp)  20: 48 89 75 e0             mov    %rsi,-0x20(%rbp)    int t = global_func(argc);  24: 8b 45 ec                mov    -0x14(%rbp),%eax  27: 89 c7                   mov    %eax,%edi  29: b8 00 00 00 00          mov    $0x0,%eax  2e: e8 00 00 00 00          callq  33 <main+0x1e>  33: 89 45 fc                mov    %eax,-0x4(%rbp)    t += global_arr[7];  36: 8b 05 00 00 00 00       mov    0x0(%rip),%eax  3c: 01 45 fc                add    %eax,-0x4(%rbp)    t += static_arr[7];  3f: 8b 05 00 00 00 00       mov    0x0(%rip),%eax  45: 01 45 fc                add    %eax,-0x4(%rbp)    t += global_arr_big[7];  48: 48 b8 00 00 00 00 00    movabs $0x0,%rax  4f: 00 00 00  52: 8b 40 1c                mov    0x1c(%rax),%eax  55: 01 45 fc                add    %eax,-0x4(%rbp)    t += static_arr_big[7];  58: 48 b8 00 00 00 00 00    movabs $0x0,%rax  5f: 00 00 00  62: 8b 40 1c                mov    0x1c(%rax),%eax  65: 01 45 fc                add    %eax,-0x4(%rbp)    return t;  68: 8b 45 fc                mov    -0x4(%rbp),%eax}  6b: c9                      leaveq  6c: c3                      retq

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

Средняя модель кода это умелый компромисс между большой и малой моделями. Навряд ли код программы окажется слишком велик [4], так что подвинуть его сверх лимита в два гигабайта могут разве что статически скомпонованные в него большие куски данных, возможно как часть некоего объемного табличного поиска. Так как средняя модель кода отсеивает такие объемные куски данных и особым образом их обрабатывает, то вызовы кодом функций и малых символов будут так же эффективны, как и в малой модели кода. Только обращения к большим символам, по аналогии с большой моделью, потребуют от кода воспользоваться полным 64-битным методом большой модели.

Малая PIC-модель кода


Теперь давайте посмотрим на PIC варианты моделей кода, и как и раньше мы начнем с малой модели. [5] Ниже можно видеть пример кода, скомпилированного через малую PIC-модель:

int main(int argc, const char* argv[]){  15:   55                      push   %rbp  16:   48 89 e5                mov    %rsp,%rbp  19:   48 83 ec 20             sub    $0x20,%rsp  1d:   89 7d ec                mov    %edi,-0x14(%rbp)  20:   48 89 75 e0             mov    %rsi,-0x20(%rbp)    int t = global_func(argc);  24:   8b 45 ec                mov    -0x14(%rbp),%eax  27:   89 c7                   mov    %eax,%edi  29:   b8 00 00 00 00          mov    $0x0,%eax  2e:   e8 00 00 00 00          callq  33 <main+0x1e>  33:   89 45 fc                mov    %eax,-0x4(%rbp)    t += global_arr[7];  36:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax  3d:   8b 40 1c                mov    0x1c(%rax),%eax  40:   01 45 fc                add    %eax,-0x4(%rbp)    t += static_arr[7];  43:   8b 05 00 00 00 00       mov    0x0(%rip),%eax  49:   01 45 fc                add    %eax,-0x4(%rbp)    t += global_arr_big[7];  4c:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax  53:   8b 40 1c                mov    0x1c(%rax),%eax  56:   01 45 fc                add    %eax,-0x4(%rbp)    t += static_arr_big[7];  59:   8b 05 00 00 00 00       mov    0x0(%rip),%eax  5f:   01 45 fc                add    %eax,-0x4(%rbp)    return t;  62:   8b 45 fc                mov    -0x4(%rbp),%eax}  65:   c9                      leaveq  66:   c3                      retq

Релокации:

Relocation section '.rela.text' at offset 0x62ce8 contains 5 entries:  Offset          Info           Type           Sym. Value    Sym. Name + Addend00000000002f  001600000004 R_X86_64_PLT32    0000000000000000 global_func - 4000000000039  001100000009 R_X86_64_GOTPCREL 0000000000000000 global_arr - 4000000000045  000300000002 R_X86_64_PC32     0000000000000000 .data + 1b800000000004f  001200000009 R_X86_64_GOTPCREL 0000000000000340 global_arr_big - 400000000005b  000300000002 R_X86_64_PC32     0000000000000000 .data + 31098

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

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

Интересно обратить внимание на глобальные массивы: стоит напомнить, что в PIC глобальные данные должны проходить через GOT, поскольку в какой-то момент их могут хранить, или пользоваться ими, общие библиотеки [6]. Ниже можно видеть код для доступа к global_arr:

  t += global_arr[7];36:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax3d:   8b 40 1c                mov    0x1c(%rax),%eax40:   01 45 fc                add    %eax,-0x4(%rbp)

Интересующая нас релокация это R_X86_64_GOTPCREL: позиция входа символа в GOT плюс слагаемое, минус сдвиг за применение релокации. Другими словами, в команду патчится относительный сдвиг между RIP (следующей инструкции) и зарезервированного для global_arr в GOT слота. Таким образом, в rax в команду по адресу 0x36 размещается фактический адрес global_arr. Следом за этим шагом идет сброс ссылки на адрес global_arr плюс сдвиг на его седьмой элемент в eax.

Теперь давайте взглянем на вызов функции:

  int t = global_func(argc);24:   8b 45 ec                mov    -0x14(%rbp),%eax27:   89 c7                   mov    %eax,%edi29:   b8 00 00 00 00          mov    $0x0,%eax2e:   e8 00 00 00 00          callq  33 <main+0x1e>33:   89 45 fc                mov    %eax,-0x4(%rbp)

В ней есть релокация операнда callq по адресу 0x2e, R_X86_64_PLT32: адрес PLT входа для символа плюс слагаемое, минус сдвиг за применение релокации. Другими словами, callq должен корректно вызывать PLT трамплин для global_func.

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

Большая PIC-модель кода


Дизассемблирование:

int main(int argc, const char* argv[]){  15: 55                      push   %rbp  16: 48 89 e5                mov    %rsp,%rbp  19: 53                      push   %rbx  1a: 48 83 ec 28             sub    $0x28,%rsp  1e: 48 8d 1d f9 ff ff ff    lea    -0x7(%rip),%rbx  25: 49 bb 00 00 00 00 00    movabs $0x0,%r11  2c: 00 00 00  2f: 4c 01 db                add    %r11,%rbx  32: 89 7d dc                mov    %edi,-0x24(%rbp)  35: 48 89 75 d0             mov    %rsi,-0x30(%rbp)    int t = global_func(argc);  39: 8b 45 dc                mov    -0x24(%rbp),%eax  3c: 89 c7                   mov    %eax,%edi  3e: b8 00 00 00 00          mov    $0x0,%eax  43: 48 ba 00 00 00 00 00    movabs $0x0,%rdx  4a: 00 00 00  4d: 48 01 da                add    %rbx,%rdx  50: ff d2                   callq  *%rdx  52: 89 45 ec                mov    %eax,-0x14(%rbp)    t += global_arr[7];  55: 48 b8 00 00 00 00 00    movabs $0x0,%rax  5c: 00 00 00  5f: 48 8b 04 03             mov    (%rbx,%rax,1),%rax  63: 8b 40 1c                mov    0x1c(%rax),%eax  66: 01 45 ec                add    %eax,-0x14(%rbp)    t += static_arr[7];  69: 48 b8 00 00 00 00 00    movabs $0x0,%rax  70: 00 00 00  73: 8b 44 03 1c             mov    0x1c(%rbx,%rax,1),%eax  77: 01 45 ec                add    %eax,-0x14(%rbp)    t += global_arr_big[7];  7a: 48 b8 00 00 00 00 00    movabs $0x0,%rax  81: 00 00 00  84: 48 8b 04 03             mov    (%rbx,%rax,1),%rax  88: 8b 40 1c                mov    0x1c(%rax),%eax  8b: 01 45 ec                add    %eax,-0x14(%rbp)    t += static_arr_big[7];  8e: 48 b8 00 00 00 00 00    movabs $0x0,%rax  95: 00 00 00  98: 8b 44 03 1c             mov    0x1c(%rbx,%rax,1),%eax  9c: 01 45 ec                add    %eax,-0x14(%rbp)    return t;  9f: 8b 45 ec                mov    -0x14(%rbp),%eax}  a2: 48 83 c4 28             add    $0x28,%rsp  a6: 5b                      pop    %rbx  a7: c9                      leaveq  a8: c3                      retq

Релокации:

Relocation section '.rela.text' at offset 0x62c70 contains 6 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000027 00150000001d R_X86_64_GOTPC64 0000000000000000 _GLOBAL_OFFSET_TABLE_ + 9
000000000045 00160000001f R_X86_64_PLTOFF64 0000000000000000 global_func + 0
000000000057 00110000001b R_X86_64_GOT64 0000000000000000 global_arr + 0
00000000006b 000800000019 R_X86_64_GOTOFF64 00000000000001a0 static_arr + 0
00000000007c 00120000001b R_X86_64_GOT64 0000000000000340 global_arr_big + 0
000000000090 000900000019 R_X86_64_GOTOFF64 0000000000031080 static_arr_big + 0

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

1e: 48 8d 1d f9 ff ff ff    lea    -0x7(%rip),%rbx25: 49 bb 00 00 00 00 00    movabs $0x0,%r112c: 00 00 002f: 4c 01 db                add    %r11,%rbx

Ниже можно прочесть перевод связанной с этим цитаты из ABI:

В малой модели кода ко всем адресам (включая GOT) можно получить доступ через предоставленную архитектурой AMD64 IP-относительную адресацию. Именно поэтому нет нужды в явном указателе GOT и таким образом нет нужды в устанавливающем его прологе в функции. В большой и средней моделях кода необходимо определить регистр для хранения адреса GOT в независимых от расположения объектах, поскольку AMD64 ISA не поддерживает мгновенное перемещение размером больше чем 32 бит.

Давайте посмотрим на то, как описанный выше пролог вычисляет адрес GOT. Во-первых, команда по адресу 0x1e загружает свой собственный адрес в rbx. Затем совместно с релокацией R_X86_64_GOTPC64 совершается абсолютный 64-битный шаг в r11. Эта релокация означает следующее: берем адрес GOT, вычитаем перемещаемый сдвиг и добавляем слагаемое. Наконец, команда по адресу 0x2f складывает оба результата вместе. Итогом становится абсолютный адрес GOT в rbx. [7]

Зачем же мучиться с вычислением адреса GOT? Во-первых, как отмечено в цитате, в большой модели кода мы не можем предполагать, что 32-битного RIP-относительного сдвига будет достаточно для адресации GOT, из-за чего нам и требуется полный 64-битный адрес. Во-вторых, мы все еще хотим работать с PIC-вариацией, так что мы не можем попросту поместить абсолютный адрес в регистр. Скорее сам адрес должен быть вычислен относительно RIP. Для этого и нужен пролог: он совершает 64-битное RIP-относительное вычисление.

В любом случае, раз у нас в rbx теперь есть адрес GOT, давайте посмотрим на то как получить доступ к static_arr:

  t += static_arr[7];69:       48 b8 00 00 00 00 00    movabs $0x0,%rax70:       00 00 0073:       8b 44 03 1c             mov    0x1c(%rbx,%rax,1),%eax77:       01 45 ec                add    %eax,-0x14(%rbp)

Релокация первой команды это R_X86_64_GOTOFF64: символ плюс слагаемое минус GOT. В нашем случае это относительный сдвиг между адресом static_arr и адресом GOT. Следующая инструкция добавляет результат в rbx (абсолютный адрес GOT) и сбрасывает ссылку со сдвигом по 0x1c. Для простоты визуализации такого вычисления ниже можно ознакомиться с псевдо-C примером:

// char* static_arr// char* GOTrax = static_arr + 0 - GOT;  // rax now contains an offseteax = *(rbx + rax + 0x1c);   // rbx == GOT, so eax now contains                             // *(GOT + static_arr - GOT + 0x1c) or                             // *(static_arr + 0x1c)

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

Но что насчет global_arr?

  t += global_arr[7];55:       48 b8 00 00 00 00 00    movabs $0x0,%rax5c:       00 00 005f:       48 8b 04 03             mov    (%rbx,%rax,1),%rax63:       8b 40 1c                mov    0x1c(%rax),%eax66:       01 45 ec                add    %eax,-0x14(%rbp)

Этот код несколько длиннее, а релокация отличается от обычной. По сути, GOT используется здесь более традиционным образом: релокация R_X86_64_GOT64 для movabs лишь говорит функции разместить сдвиг в GOT, там где в rax расположен адрес global_arr. Команда по адресу 0x5f берет адрес global_arr из GOT и помещает его в rax. Следующая команда сбрасывает ссылку на global_arr[7] и помещает значение в eax.

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

  int t = global_func(argc);39: 8b 45 dc                mov    -0x24(%rbp),%eax3c: 89 c7                   mov    %eax,%edi3e: b8 00 00 00 00          mov    $0x0,%eax43: 48 ba 00 00 00 00 00    movabs $0x0,%rdx4a: 00 00 004d: 48 01 da                add    %rbx,%rdx50: ff d2                   callq  *%rdx52: 89 45 ec                mov    %eax,-0x14(%rbp)

Интересующая нас релокация это R_X86_64_PLTOFF64: адрес PLT входа для global_func минус адрес GOT. Результат размещается в rdx, куда затем помещается rbx (абсолютный адрес GOT). В итоге мы получаем адрес PLT входа для global_func в rdx.

Обратите внимание что вновь GOT используется как привязка, на этот раз для обеспечения независящей от адреса отсылки к сдвигу PLT входа.

Средняя PIC-модель кода


Наконец, мы разберем сгенерированный для средней PIC-модели код:

int main(int argc, const char* argv[]){  15:   55                      push   %rbp  16:   48 89 e5                mov    %rsp,%rbp  19:   53                      push   %rbx  1a:   48 83 ec 28             sub    $0x28,%rsp  1e:   48 8d 1d 00 00 00 00    lea    0x0(%rip),%rbx  25:   89 7d dc                mov    %edi,-0x24(%rbp)  28:   48 89 75 d0             mov    %rsi,-0x30(%rbp)    int t = global_func(argc);  2c:   8b 45 dc                mov    -0x24(%rbp),%eax  2f:   89 c7                   mov    %eax,%edi  31:   b8 00 00 00 00          mov    $0x0,%eax  36:   e8 00 00 00 00          callq  3b <main+0x26>  3b:   89 45 ec                mov    %eax,-0x14(%rbp)    t += global_arr[7];  3e:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax  45:   8b 40 1c                mov    0x1c(%rax),%eax  48:   01 45 ec                add    %eax,-0x14(%rbp)    t += static_arr[7];  4b:   8b 05 00 00 00 00       mov    0x0(%rip),%eax  51:   01 45 ec                add    %eax,-0x14(%rbp)    t += global_arr_big[7];  54:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax  5b:   8b 40 1c                mov    0x1c(%rax),%eax  5e:   01 45 ec                add    %eax,-0x14(%rbp)    t += static_arr_big[7];  61:   48 b8 00 00 00 00 00    movabs $0x0,%rax  68:   00 00 00  6b:   8b 44 03 1c             mov    0x1c(%rbx,%rax,1),%eax  6f:   01 45 ec                add    %eax,-0x14(%rbp)    return t;  72:   8b 45 ec                mov    -0x14(%rbp),%eax}  75:   48 83 c4 28             add    $0x28,%rsp  79:   5b                      pop    %rbx  7a:   c9                      leaveq  7b:   c3                      retq

Релокации:

Relocation section '.rela.text' at offset 0x62d60 contains 6 entries:  Offset          Info           Type           Sym. Value    Sym. Name + Addend000000000021  00160000001a R_X86_64_GOTPC32  0000000000000000 _GLOBAL_OFFSET_TABLE_ - 4000000000037  001700000004 R_X86_64_PLT32    0000000000000000 global_func - 4000000000041  001200000009 R_X86_64_GOTPCREL 0000000000000000 global_arr - 400000000004d  000300000002 R_X86_64_PC32     0000000000000000 .data + 1b8000000000057  001300000009 R_X86_64_GOTPCREL 0000000000000000 global_arr_big - 4000000000063  000a00000019 R_X86_64_GOTOFF64 0000000000030d40 static_arr_big + 0

Для начала давайте уберем вызов функции. Аналогично малой модели, в средней модели мы предполагаем, что ссылки на код не превышают пределов 32-битного RIP сдвига, следовательно, код для вызова global_func полностью аналогичен такому же коду в малой PIC-модели, равно как и для случаев массивов малых данных static_arr и global_arr. Поэтому мы сфокусируемся на массивах больших данных, но сначала поговорим о прологе: здесь он отличается от пролога большой модели данных.

1e:   48 8d 1d 00 00 00 00    lea    0x0(%rip),%rbx

Это весь пролог: чтобы при помощи релокации R_X86_64_GOTPC32 разместить GOT адрес в rbx, потребовалась всего одна команда (по сравнению с тремя в большой модели). В чем же разница? Дело в том, что так как в средней модели GOT не является частью секций больших данных, мы предполагаем его доступность в рамках 32-битного сдвига. В большой модели мы не могли совершать подобные предположения, и были вынуждены пользоваться полным 64-битным сдвигом.

Вызывает интерес тот факт, что код для доступа к global_arr_big похож на такой же код в малой PIC-модели. Это происходит по той же причине, почему пролог средней модели короче пролога большой модели: мы полагаем доступность GOT в рамках 32-битной RIP-относительной адресации. Действительно, к самому global_arr_big нельзя получить такой доступ, но этот случай все равно покрывает GOT, так как фактически global_arr_big в нем и находится, причем в виде полного 64-битного адреса.

Ситуация, тем не менее, отличается для static_arr_big:

  t += static_arr_big[7];61:   48 b8 00 00 00 00 00    movabs $0x0,%rax68:   00 00 006b:   8b 44 03 1c             mov    0x1c(%rbx,%rax,1),%eax6f:   01 45 ec                add    %eax,-0x14(%rbp)

Этот случай похож на большую PIC-модель кода, поскольку тут мы все же получаем абсолютный адрес символа, которого нет в самом GOT. Так как это большой символ, о котором нельзя предположить его нахождение в нижних двух гигабайтах, нам, как и в большой модели, требуется 64-битный PIC сдвиг.

Примечания:


[1] Не стоит путать модели кода с 64-битными моделями данных и моделями памяти Intel, все это разные темы.

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

[3] Если что-то осталось непонятным, ознакомьтесь со следующей статьей.

[4] Тем не менее, объемы постепенно растут. Когда я в последний раз проверял Debug+Asserts билд Clang, он почти достигал одного гигабайта, за что во многом спасибо автогенерируемому коду.

[5] Если вы еще не знаете как работает PIC (как в целом, так и в частности для архитектуры x64), самое время ознакомиться со следующими статьями по теме: раз и два.

[6] Таким образом, компоновщик не может самостоятельно разрешить ссылки, и вынужден переложить обработку GOT на динамический загрузчик.

[7] 0x25 0x7 + GOT 0x27 + 0x9 = GOT



Подробнее..

MLOps Cook book, chapter 1

03.07.2020 10:08:54 | Автор: admin


Всем привет! Я CV-разработчик в КРОК. Уже 3 года мы реализуем проекты в области CV. За это время чего мы только не делали, например: мониторили водителей, чтобы во время движения они не пили, не курили, по телефону не разговаривали, смотрели на дорогу, а не сны или в облака; фиксировали любителей ездить по выделенным полосам и занимать несколько мест на парковке; следили за тем, чтобы работники носили каски, перчатки и т.п.; идентифицировали сотрудника, который хочет пройти на объект; подсчитывали всё, что только можно.


Я все это к чему?


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


Моделируем ситуацию


Представим, что мы устроились в молодую компанию N, деятельность которой связана с ML. Работаем мы над ML (DL, CV) проектом, потом по каким-либо причинам переключаемся на другую работу, в общем делаем перерыв, и возвращаемся к своей или чужой нейроночке.


  1. Наступает момент истины, нужно как-то вспомнить на чем ты остановился, какие гиперпараметры пробовал и, самое главное, к каким результатам они привели. Может быть множество вариантов, кто как хранил информацию по всем запускам: в голове, конфигах, блокноте, в рабочей среде в облаке. Мне довелось видеть вариант, когда гиперпараметры хранились в виде закомментированных строк в коде, в общем полет фантазии. А теперь представьте, что вы вернулись не к своему проекту, а к проекту человека, который покинул компанию и в наследство вам достался код и модель под названием model_1.pb. Для полноты картины и передачи всей боли, представим, что вы еще и начинающий специалист.
  2. Идем дальше. Для запуска кода нам и всем кто будет с ним работать необходимо создать окружение. Часто бывает, что и его нам в наследство также по каким-то причинам не оставили. Это тоже может стать нетривиальной задачей. На этот шаг не хочется тратить время, не так ли?
  3. Тренируем модель (например, детектор автомобилей). Доходим до момента, когда она становится очень даже ничего самое время сохранить результат. Назовем ее car_detection_v1.pb. Потом тренируем еще одну car_detection_v2.pb. Некоторое время спустя наши коллеги или мы сами обучаем ещё и ещё, используя различные архитектуры. В итоге формируется куча артефактов, информацию о которых нужно кропотливо собирать (но, делать мы это будем позже, у нас ведь пока есть более приоритетные дела).
  4. Ну вот и всё! У нас есть модель! Мы можем приступать к обучению следующей модели, к разработке архитектуры для решения новой задачи или можем пойти попить чай? А деплоить кто будет?

Выявляем проблемы


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



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


  • удобно хранить результаты работы;
  • сделать простым процесс вовлечения новых сотрудников;
  • упростить процесс развертывания среды разработки;
  • настроить процесс версионирования моделей;
  • иметь удобный способ валидации моделей;
  • найти инструмент управления состоянием моделей;
  • найти способ доставки моделей в production.

Видимо необходимо придумать workflow, который бы позволял легко и удобно управлять этим жизненным циклом? У такой практики есть название MLOps


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


Можете почитать, что обо всем этом думают ребята из Google. Из статьи понятно, что MLOps, довольно, объемная штука.



Далее в своей статье я опишу лишь часть процесса. Для реализации я воспользуюсь инструментом MLflow, т.к. это open-source проект, для подключения необходимо небольшое количество кода и есть интеграция с популярными ml-фреймворками. Вы можете поискать на просторах интернета другие инструменты, например Kubeflow, SageMaker, Trains и т.д., и возможно, подобрать тот, который лучше подходит под ваши нужды.


"Cтроим" MLOps на примере использования инструмента MLFlow


MLFlow это платформа с открытым исходным кодом для управления жизненным циклом ml моделей (https://mlflow.org/).


MLflow включает четыре компонента:


  • MLflow Tracking закрывает вопросы фиксации результатов и параметров, которые к этому результату привели;
  • MLflow Project позволяет упаковывать код и воспроизвести его на любой платформе;
  • MLflow Models отвечает за деплой моделей в прод;
  • MLflow Registry позволяет хранить модели и управлять их состоянием в централизованном хранилище.

MLflow оперирует двумя сущностями:


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

Все шаги примера реализованы на операционной системе Ubuntu 18.04.


1. Разворачиваем сервер


Для того, чтобы мы могли легко управлять нашим проектом и получать всю необходимую информацию, развернем сервер. MLflow tracking server имеет два основных компонента:


  • backend store отвечает за хранение информации о зарегистрированных моделях (поддерживает 4 СУБД: mysql, mssql, sqlite, and postgresql);
  • artifact store отвечает за хранение артефактов (поддерживает 7 вариантов хранения: Amazon S3, Azure Blob Storage, Google Cloud Storage, FTP server, SFTP Server, NFS, HDFS).

В качестве artifact store для простоты возьмем sftp сервер.


  • создаем группу
    $ sudo groupadd sftpg
    
  • добавляем пользователя и устанавливаем ему пароль
    $ sudo useradd -g sftpg mlflowsftp$ sudo passwd mlflowsftp 
    
  • корректируем пару настроек доступа
    $ sudo mkdir -p /data/mlflowsftp/upload$ sudo chown -R root.sftpg /data/mlflowsftp$ sudo chown -R mlflowsftp.sftpg /data/mlflowsftp/upload
    
  • добавляем несколько строк в /etc/ssh/sshd_config
    Match Group sftpg ChrootDirectory /data/%u ForceCommand internal-sftp
    
  • перезапускаем службу
    $ sudo systemctl restart sshd
    

В качестве backend store возьмем postgresql.


$ sudo apt update$ sudo apt-get install -y postgresql postgresql-contrib postgresql-server-dev-all$ sudo apt install gcc$ pip install psycopg2$ sudo -u postgres -i# Create new user: mlflow_user[postgres@user_name~]$ createuser --interactive -PEnter name of role to add: mlflow_userEnter password for new role: mlflowEnter it again: mlflowShall the new role be a superuser? (y/n) nShall the new role be allowed to create databases? (y/n) nShall the new role be allowed to create more new roles? (y/n) n# Create database mlflow_bd owned by mlflow_user$ createdb -O mlflow_user mlflow_db

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


pip install mlflowpip install pysftp

Запускаем наш сервер


$ mlflow server  \                 --backend-store-uri postgresql://mlflow_user:mlflow@localhost/mlflow_db \                 --default-artifact-root sftp://mlflowsftp:mlflow@sftp_host/upload  \                --host server_host \                --port server_port

2. Добавляем трекинг


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


Для примера я создал небольшой проект на github на Keras по сегментации всего, что есть в COCO датасете. Для добавления трекинга я создал файл mlflow_training.py.


Вот строки, в которых происходит самое интересное:


def run(self, epochs, lr, experiment_name):        # getting the id of the experiment, creating an experiment in its absence        remote_experiment_id = self.remote_server.get_experiment_id(name=experiment_name)        # creating a "run" and getting its id        remote_run_id = self.remote_server.get_run_id(remote_experiment_id)        # indicate that we want to save the results on a remote server        mlflow.set_tracking_uri(self.tracking_uri)        mlflow.set_experiment(experiment_name)        with mlflow.start_run(run_id=remote_run_id, nested=False):            mlflow.keras.autolog()            self.train_pipeline.train(lr=lr, epochs=epochs)        try:            self.log_tags_and_params(remote_run_id)        except mlflow.exceptions.RestException as e:            print(e)

Здесь self.remote_server это небольшая обвязка над методами mlflow.tracking. MlflowClient (я сделал для удобства), с помощью которых я создаю эксперимент и запуск на сервере. Далее указываю куда должны сливаться результаты запуска (mlflow.set_tracking_uri(self.tracking_uri)). Подключаю автоматическое логирование mlflow.keras.autolog(). На данный момент MLflow Tracking поддерживает автоматическое логирование для TensorFlow, Keras, Gluon XGBoost, LightGBM, Spark. Если вы не нашли своего фреймворка или библиотеки, то вы всегда можете логировать в явном виде. Запускаем обучение. Регистрируем теги и входные параметры на удаленном сервере.


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


3. Оформляем проект


Теперь сделаем так, чтобы запустить проект было проще простого. Для этого добавим в корень проекта файл MLproject и conda.yaml.
MLproject


name: flow_segmentationconda_env: conda.yamlentry_points:  main:    parameters:        categories: {help: 'list of categories from coco dataset'}        epochs: {type: int, help: 'number of epochs in training'}        lr: {type: float, default: 0.001, help: 'learning rate'}        batch_size: {type: int, default: 8}        model_name: {type: str, default: 'Unet', help: 'Unet, PSPNet, Linknet, FPN'}        backbone_name: {type: str, default: 'resnet18', help: 'exampe resnet18, resnet50, mobilenetv2 ...'}        tracking_uri: {type: str, help: 'the server address'}        experiment_name: {type: str, default: 'My_experiment', help: 'remote and local experiment name'}    command: "python mlflow_training.py \            --epochs={epochs}            --categories={categories}            --lr={lr}            --tracking_uri={tracking_uri}            --model_name={model_name}            --backbone_name={backbone_name}            --batch_size={batch_size}            --experiment_name={experiment_name}"

MLflow Project имеет несколько свойств:


  • Name имя вашего проекта;
  • Environment в моем случае conda_env указывает на то, что для запуска используется Anaconda и описание зависимостей находится в файле conda.yaml;
  • Entry Points указывает какие файлы и с какими параметрами мы можем запустить (все параметры при запуске обучения автоматически логируются)

conda.yaml


name: flow_segmentationchannels:  - defaults  - anacondadependencies:  - python==3.7  - pip:    - mlflow==1.8.0    - pysftp==0.2.9    - Cython==0.29.19    - numpy==1.18.4    - pycocotools==2.0.0    - requests==2.23.0    - matplotlib==3.2.1    - segmentation-models==1.0.1    - Keras==2.3.1    - imgaug==0.4.0    - tqdm==4.46.0    - tensorflow-gpu==1.14.0

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


4. Запускаем обучение


Клонируем проект и переходим в директорию проекта:


git clone https://github.com/simbakot/mlflow_example.gitcd mlflow_example/

Для запуска вам необходимо установить библиотеки


pip install mlflowpip install pysftp

Т.к. в примере я использую conda_env на вашем компьютере должна быть установлена Anaconda (но и это можно обойти установив все необходимые пакеты самостоятельно и поигравшись с параметрами запуска).


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


$ mlflow run -P epochs=10 -P categories=cat,dog -P tracking_uri=http://server_host:server_port .

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


5. Оцениваем результаты обучения


После окончания обучения мы можем перейти в браузере по адресу нашего сервера http://server_host:server_port



Здесь мы видим список всех экспериментов (слева вверху), а также информацию по запускам (посередине). Мы можем посмотреть более подробную информацию (параметры, метрики, артефакты и какую-то доп. информацию) по каждому запуску.



По каждой метрике мы можем наблюдать историю изменения



Т.е. на данный момент мы можем анализировать результаты в "ручном" режиме, также вы можете настроить и автоматическую валидацию при помощи MLflow API.


6. Регистрируем модель


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



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



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



У нас также имеется удобный доступ ко всем моделям



и их версиям



Как и в предыдущем пункте все операции можно сделать при помощи API.


7. Деплоим модель


На данном этапе у нас уже есть натренированная (keras) модель. Пример, как можно её использовать:


class SegmentationModel:    def __init__(self, tracking_uri, model_name):        self.registry = RemoteRegistry(tracking_uri=tracking_uri)        self.model_name = model_name        self.model = self.build_model(model_name)    def get_latest_model(self, model_name):        registered_models = self.registry.get_registered_model(model_name)        last_model = self.registry.get_last_model(registered_models)        local_path = self.registry.download_artifact(last_model.run_id, 'model', './')        return local_path    def build_model(self, model_name):        local_path = self.get_latest_model(model_name)        return mlflow.keras.load_model(local_path)    def predict(self, image):        image = self.preprocess(image)        result = self.model.predict(image)        return self.postprocess(result)    def preprocess(self, image):        image = cv2.resize(image, (256, 256))        image = image / 255.        image = np.expand_dims(image, 0)        return image    def postprocess(self, result):        return result

Здесь self.registry это опять небольшая обвязка над mlflow.tracking.MlflowClient, для удобства. Суть в том, что я обращаюсь к удаленному серверу и ищу там модель с указанным именем, причем, самую последнюю production версию. Далее скачиваю артефакт локально в папку ./model и собираю модель из этой директории mlflow.keras.load_model(local_path). Всё теперь мы можем использовать нашу модель. CV (ML) разработчики могут спокойно заниматься улучшением модели и публиковать новые версии.


В заключение


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


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

Данный пример является игрушечным и служит точкой старта для выстраивания вашей собственной системы, которая, возможно, будет включать в себя автоматизацию оценки результатов и регистрации моделей (п.5 и п.6 соответственно) или вы добавите версионирование датасетов, или может ещё что-то? Я пытался донести мысль, что вам нужен MLOps в целом, MLflow лишь средство достижения цели.


Напишите какие проблемы, с которыми вы сталкивались, я не отобразил?
Что бы вы добавили в систему, чтобы она закрывала ваши потребности?
Какие инструменты и подходы используете вы, чтобы закрыть все или часть проблем?


P.S. Оставлю пару ссылок:
github проект https://github.com/simbakot/mlflow_example
MLflow https://mlflow.org/
Моя рабочая почта, для вопросов ikryakin@croc.ru


У нас в компании периодически проводятся различные мероприятия для ИТ-специалистов, например: 8-го июля в 19:00 по МСК будет проходить митап по CV в онлайн-формате, если интересно, то можете принять участие, регистрация здесь .

Подробнее..

Из песочницы Редактор кода на Android часть 1

02.07.2020 20:17:59 | Автор: admin

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

Вступление


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

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

Для начала составим список того, что наш редактор должен уметь:

  • Подсвечивать синтаксис
  • Отображать нумерацию строк
  • Показывать варианты автодополнения (расскажу во второй части)
  • Подсвечивать синтаксические ошибки (расскажу во второй части)

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

MVP простой текстовый редактор


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

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

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


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

Очевидно, чтобы контролировать весь процесс реагировать на ввод, отрисовывать номера строк, нам придется писать CustomView наследуясь от EditText. Накидываем TextWatcher чтобы слушать изменения в тексте и переопределяем метод afterTextChanged, в котором и будем вызывать метод отвечающий за подсветку:

class TextProcessor @JvmOverloads constructor(    context: Context,    attrs: AttributeSet? = null,    defStyleAttr: Int = R.attr.editTextStyle) : EditText(context, attrs, defStyleAttr) {    private val textWatcher = object : TextWatcher {        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}        override fun afterTextChanged(s: Editable?) {            syntaxHighlight()        }    }    private fun syntaxHighlight() {        // Тут будем подсвечивать текст    }}

Вопрос: Почему мы используем TextWatcher как переменную, ведь можно реализовать интерфейс прямо в классе?

Ответ: Так уж получилось, что у TextWatcher есть метод который конфликтует c уже существующим методом у TextView:

// Метод TextWatcherfun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int)// Метод TextViewfun onTextChanged(text: CharSequence?, start: Int, lengthBefore: Int, lengthAfter: Int)

Оба этих метода имеют одинаковое название и одинаковые аргументы, да и смысл вроде у них тот же, но проблема в том что метод onTextChanged у TextView вызовется вместе с onTextChanged у TextWatcher. Если проставить логи в тело метода, то увидим что onTextChanged вызовется дважды:


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

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

fun processText(newText: String) {    removeTextChangedListener(textWatcher)    // undoStack.clear()    // redoStack.clear()    setText(newText)    addTextChangedListener(textWatcher)}

Но вернёмся к подсветке.

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

Сейчас нам важно знать только две вещи:

  1. Pattern определяет что конкретно нам нужно найти в тексте
  2. Matcher будет пробегать по всему тексту в попытках найти то, что мы указали в Pattern

Может не совсем корректно описал, но принцип работы такой.

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

private val KEYWORDS = Pattern.compile(    "\\b(function|var|this|if|else|break|case|try|catch|while|return|switch)\\b")

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

Далее с помощью Matcher мы пройдёмся по всему тексту и установим спаны:

private fun syntaxHighlight() {    val matcher = KEYWORDS.matcher(text)    matcher.region(0, text.length)    while (matcher.find()) {        text.setSpan(            ForegroundColorSpan(Color.parseColor("#7F0055")),            matcher.start(),            matcher.end(),            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE        )    }}

Поясню: мы получаем объект Matcher у Pattern, и указываем ему область для поиска в символах (Соответственно с 0 по text.length это весь текст). Далее вызов matcher.find() вернёт true если в тексте было найдено совпадение, а с помощью вызовов matcher.start() и matcher.end() мы получим позиции начала и конца совпадения в тексте. Зная эти данные, мы можем использовать метод setSpan для раскраски определённых участков текста.

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

Итак, запускаем!


Результат соответствует ожиданиям ровно до того момента, пока мы не начнём редактировать большой файл (на скриншоте файл в ~1000 строк)

Дело в том что метод setSpan работает медленно, сильно нагружая UI Thread, а учитывая что метод afterTextChanged вызывается после каждого введенного символа, писать код становится одним мучением.

Поиск решения


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

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

Точно! Так и сделаем! Вот только как?

Оптимизация


Хоть я и упомянул что нас заботит только производительность метода setSpan, всё же рекомендую выносить работу RegEx в фоновой поток чтобы добиться максимальной плавности.

Нам нужен класс, который будет в фоне обрабатывать весь текст и возвращать список спанов.
Конкретной реализации приводить не буду, но если кому интересно то я использую AsyncTask работающий на ThreadPoolExecutor. (Да-да, AsyncTask в 2020)

Нам главное, чтобы выполнялась такая логика:

  1. В beforeTextChanged останавливаем Task который парсит текст
  2. В afterTextChanged запускаем Task который парсит текст
  3. По окончанию своей работы, Task должен вернуть список спанов в TextProcessor, который в свою очередь подсветит только видимую часть

И да, спаны тоже будем писать свои собственные:

data class SyntaxHighlightSpan(    private val color: Int,    val start: Int,    val end: Int) : CharacterStyle() {    // можно заморочиться и добавить italic, например, только для комментариев    override fun updateDrawState(textPaint: TextPaint?) {        textPaint?.color = color    }}

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

Много кода
class TextProcessor @JvmOverloads constructor(    context: Context,    attrs: AttributeSet? = null,    defStyleAttr: Int = R.attr.editTextStyle) : EditText(context, attrs, defStyleAttr) {    private val textWatcher = object : TextWatcher {        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {            cancelSyntaxHighlighting()        }        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}        override fun afterTextChanged(s: Editable?) {            syntaxHighlight()        }    }    private var syntaxHighlightSpans: List<SyntaxHighlightSpan> = emptyList()    private var javaScriptStyler: JavaScriptStyler? = null    fun processText(newText: String) {        removeTextChangedListener(textWatcher)        // undoStack.clear()        // redoStack.clear()        setText(newText)        addTextChangedListener(textWatcher)        // syntaxHighlight()    }    private fun syntaxHighlight() {        javaScriptStyler = JavaScriptStyler()        javaScriptStyler?.setSpansCallback { spans ->            syntaxHighlightSpans = spans            updateSyntaxHighlighting()        }        javaScriptStyler?.runTask(text.toString())    }    private fun cancelSyntaxHighlighting() {        javaScriptStyler?.cancelTask()    }    private fun updateSyntaxHighlighting() {        // подсветка видимой части будет тут    }}


Т.к конкретной реализации обработки в фоне я не показал, представим что мы написали некий JavaScriptStyler, который в фоне будет делать всё тоже самое что мы делали до этого в UI Thread пробегать по всему тексту в поисках совпадений и заполнять список спанов, а в конце своей работы вернёт результат в setSpansCallback. В этот момент запустится метод updateSyntaxHighlighting, который пройдётся по списку спанов и отобразит только те, что видны в данный момент на экране.

Как понять, какой текст попадает в видимую область?


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

val topVisibleLine = scrollY / lineHeightval bottomVisibleLine = topVisibleLine + height / lineHeight + 1 // height - высота Viewval lineStart = layout.getLineStart(topVisibleLine)val lineEnd = layout.getLineEnd(bottomVisibleLine)

И он работает! Теперь вынесем topVisibleLine и bottomVisibleLine в отдельные методы и добавим пару дополнительных проверок, на случай если что-то пойдёт не так:

Новые методы
private fun getTopVisibleLine(): Int {    if (lineHeight == 0) {        return 0    }    val line = scrollY / lineHeight    if (line < 0) {        return 0    }    return if (line >= lineCount) {        lineCount - 1    } else line}private fun getBottomVisibleLine(): Int {    if (lineHeight == 0) {        return 0    }    val line = getTopVisibleLine() + height / lineHeight + 1    if (line < 0) {        return 0    }    return if (line >= lineCount) {        lineCount - 1    } else line}


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

for (span in syntaxHighlightSpans) {    val isInText = span.start >= 0 && span.end <= text.length    val isValid = span.start <= span.end    val isVisible = span.start in lineStart..lineEnd            || span.start <= lineEnd && span.end >= lineStart    if (isInText && isValid && isVisible)) {        text.setSpan(            span,            if (span.start < lineStart) lineStart else span.start,            if (span.end > lineEnd) lineEnd else span.end,            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE        )    }}

Не пугайтесь страшного if'а, он всего лишь проверяет попадает ли спан из списка в видимую область.

Ну что, работает?


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

// Примечание: метод getSpans из библиотеки core-ktxval textSpans = text.getSpans<SyntaxHighlightSpan>(0, text.length)for (span in textSpans) {    text.removeSpan(span)}

Ещё один косяк после закрытия клавиатуры кусок текста остаётся неподсвеченным, исправляем:

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {    super.onSizeChanged(w, h, oldw, oldh)    updateSyntaxHighlighting()}

Главное не забыть указать adjustResize в манифесте.

Скроллинг


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

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

Достаточно вызывать метод отвечающий за обновление подсветки:

override fun onScrollChanged(horiz: Int, vert: Int, oldHoriz: Int, oldVert: Int) {    super.onScrollChanged(horiz, vert, oldHoriz, oldVert)    updateSyntaxHighlighting()}

Нумерация строк


Если мы добавим в разметку ещё один TextView то будет проблематично их между собой связать (например, синхронно обновлять размер текста), да и если у нас большой файл то придется полностью обновлять текст с номерами после каждой введенной буквы, что не очень круто. Поэтому будем использовать стандартные средства любой CustomView рисование на Canvas в onDraw, это и быстро, и не сложно.

Для начала определим что будем рисовать:

  • Номера строк
  • Вертикальную линию, отделяющую поле ввода от номеров строк

Предварительно необходимо вычислить и установить padding слева от редактора, чтобы не рисовать поверх написанного кода.

Для этого напишем функцию, которая будет обновлять отступ перед отрисовкой:

Обновление отступа
private var gutterWidth = 0private var gutterDigitCount = 0private var gutterMargin = 4.dpToPx() // отступ от разделителя в пикселях...private fun updateGutter() {    var count = 3    var widestNumber = 0    var widestWidth = 0f    gutterDigitCount = lineCount.toString().length    for (i in 0..9) {        val width = paint.measureText(i.toString())        if (width > widestWidth) {            widestNumber = i            widestWidth = width        }    }    if (gutterDigitCount >= count) {        count = gutterDigitCount    }    val builder = StringBuilder()    for (i in 0 until count) {        builder.append(widestNumber.toString())    }    gutterWidth = paint.measureText(builder.toString()).toInt()    gutterWidth += gutterMargin    if (paddingLeft != gutterWidth + gutterMargin) {        setPadding(gutterWidth + gutterMargin, gutterMargin, paddingRight, 0)    }}


Пояснение:

Для начала мы узнаем кол-во строк в EditText (не путать с кол-вом "\n" в тексте), и берем кол-во символов от этого числа. Например, если у нас 100 строк, то переменная gutterDigitCount будет равна 3, потому что в числе 100 ровно 3 символа. Но допустим, у нас всего 1 строка а значит отступ в 1 символ будет визуально казаться маленьким, и для этого мы используем переменную count, чтобы задать минимально отображаемый отступ в 3 символа, даже если у нас меньше 100 строк кода.

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

Далее вычисляем и устанавливаем отступ на основе имеющихся widestNumber и widestWidth.

Приступим к рисованию


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

setHorizontallyScrolling(true)

Ну а теперь можно приступать к рисованию, объявим переменные с типом Paint:

private val gutterTextPaint = Paint() // Нумерация строкprivate val gutterDividerPaint = Paint() // Отделяющая линия

Где-нибудь в init блоке установим цвет текста и цвет разделителя. Важно помнить, что если вы поменяйте шрифт текста, то шрифт Paint'а придется применять вручную, для этого советую переопределить метод setTypeface. Аналогично и с размером текста.

После чего переопределяем метод onDraw:

override fun onDraw(canvas: Canvas?) {    updateGutter()    super.onDraw(canvas)    var topVisibleLine = getTopVisibleLine()    val bottomVisibleLine = getBottomVisibleLine()    val textRight = (gutterWidth - gutterMargin / 2) + scrollX    while (topVisibleLine <= bottomVisibleLine) {        canvas?.drawText(            (topVisibleLine + 1).toString(),            textRight.toFloat(),            (layout.getLineBaseline(topVisibleLine) + paddingTop).toFloat(),            gutterTextPaint        )        topVisibleLine++    }    canvas?.drawLine(        (gutterWidth + scrollX).toFloat(),        scrollY.toFloat(),        (gutterWidth + scrollX).toFloat(),        (scrollY + height).toFloat(),        gutterDividerPaint    )}

Смотрим на результат




Выглядит круто.

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

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

Заключение


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

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

Задавайте вопросы и предлагайте темы для обсуждения, ведь я вполне мог что-то упустить.

Спасибо!
Подробнее..

Категории

Последние комментарии

© 2006-2020, personeltest.ru