#docker #python
Именно такую фразу мне сказал старший товарищ при одной из бесед, но тогда я его не понял. Был ли он прав? Разбираемся с примерами.
Спустя несколько лет работы с docker, я пришёл к выводу, что не имеет смысла выбирать между pipenv, poetry или conda, поскольку их использование внутри docker является затруднительным и требует плодить костыли, вместо того чтобы просто установить зависимости через pip.
Основная проблема заключается в том, что внутри контейнера вам никакое виртуальное окружение не нужно, а значит - мы затягиваем лишнюю зависимость, которая увеличит время сборки, зато локальная разработка без докера становится удобнее и приятнее. Только я, хоть убей, не пойму - какой смысл разрабатывать локально приложения на python без докера. Пожалуй, есть только один небольшой минус - нужно вписать новую зависимость в requirements.txt вручную. Но и этого можно избежать, если вы просто зайдёте в терминал контейнера, установите зависимость там и сделаете pip freeze в нужный файлик (разумеется, при условии, что зависимости попадают в volume с приложением).
Полная версия у меня в блоге.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍2❤🔥1
"был в сети 15 минут назад" — учимся форматировать даты на фронтенде
#frontend #intl
Идея этого поста пришла ко мне во время кодревью, в процессе которого я вспомнил про такую штуку, как dayjs. Библиотека, которая на мой взгляд, в принципе не нужна, потому что существует Intl API (которым, к сожалению, мало кто пользуется). Причём про это даже в саркастичном ключе написал автор канала ExtremeCode. И ещё тогда я пересылал друзьям и писал, что смешно, что такое решение существует. Но пошутили и забыли, а тут я увидел его в MR и сразу забраковал. И дело вовсе не в саркастичных постах.
Если говорить кратко, dayjs это более современная замена moment.js. Только весит moment в распакованном виде 4.35 Мб, а dayjs всего 664 Кб. Впрочем, это не значит, что это решение в чём-то действительно лучше, поскольку, ни то, ни другое не использует браузерные API, а предоставляет вам свой собственный велосипед. И он действительно может быть неплох в каких-то моментах, но вряд ли вы будете спорить с тем, что решение, интегрированное в браузер будет менее оптимальным выбором.
В нашем случае, dayjs был втянут в проект только чтобы отрисовывать текст типа: "30 минут назад", вместо конкретной даты и времени. Именно это мне и не понравилось, что ради одного красивого вывода мы тянем целую отдельную библиотеку. В качестве альтернативы, я накидал небольшой пример для решения задачи, которую мой сотрудник пытался решить с помощью dayjs:
Константные переменные можно вынести в общий конфиг решения, но в остальном оно не использует никаких велосипедов и костылей, но предоставляет возможность опираясь на силы браузера реализовать решение простой задачи, не устанавливая лишних зависимостей.
#frontend #intl
Идея этого поста пришла ко мне во время кодревью, в процессе которого я вспомнил про такую штуку, как dayjs. Библиотека, которая на мой взгляд, в принципе не нужна, потому что существует Intl API (которым, к сожалению, мало кто пользуется). Причём про это даже в саркастичном ключе написал автор канала ExtremeCode. И ещё тогда я пересылал друзьям и писал, что смешно, что такое решение существует. Но пошутили и забыли, а тут я увидел его в MR и сразу забраковал. И дело вовсе не в саркастичных постах.
Если говорить кратко, dayjs это более современная замена moment.js. Только весит moment в распакованном виде 4.35 Мб, а dayjs всего 664 Кб. Впрочем, это не значит, что это решение в чём-то действительно лучше, поскольку, ни то, ни другое не использует браузерные API, а предоставляет вам свой собственный велосипед. И он действительно может быть неплох в каких-то моментах, но вряд ли вы будете спорить с тем, что решение, интегрированное в браузер будет менее оптимальным выбором.
В нашем случае, dayjs был втянут в проект только чтобы отрисовывать текст типа: "30 минут назад", вместо конкретной даты и времени. Именно это мне и не понравилось, что ради одного красивого вывода мы тянем целую отдельную библиотеку. В качестве альтернативы, я накидал небольшой пример для решения задачи, которую мой сотрудник пытался решить с помощью dayjs:
function countTimedeltaFromToday(date: Date) {
const currDate = new Date();
const delta = currDate.getTime() - date.getTime();
const ONE_MILLISECOND = 1000;
const HOUR_IN_SECONDS = 60 * 60;
const DAY_IN_HOURS = 24;
const MONTH_IN_DAYS = 30;
const YEAR_IN_MONTH = 12;
const daysDelta = delta / ONE_MILLISECOND / HOUR_IN_SECONDS / DAY_IN_HOURS;
const monthsDelta = daysDelta / MONTH_IN_DAYS;
const yearsDelta = monthsDelta / YEAR_IN_MONTH;
return {
delta,
daysDelta,
monthsDelta,
yearsDelta,
};
}
function formatDate(intl: Intl.RelativeTimeFormat, date: Date) {
const MAX_DAYS_DELTA = 15;
const MAX_MONTHS_DELTA = 11;
const delta = countTimedeltaFromToday(date);
const { daysDelta, monthsDelta, yearsDelta } = delta;
if (daysDelta < MAX_DAYS_DELTA) {
return intl.format(Math.floor(-daysDelta), 'day');
}
if (monthsDelta < MAX_MONTHS_DELTA) {
return intl.format(Math.floor(-monthsDelta), 'month');
}
return intl.format(Math.floor(-yearsDelta), 'year');
}
const intl = new Intl.RelativeTimeFormat('ru', { style: 'long', numeric: 'auto' });
Константные переменные можно вынести в общий конфиг решения, но в остальном оно не использует никаких велосипедов и костылей, но предоставляет возможность опираясь на силы браузера реализовать решение простой задачи, не устанавливая лишних зависимостей.
👍8⚡3❤🔥1
#frontend #браузеры
Пару лет назад, я писал про предложение о добавлении элемента, который стал бы нативным попапом. Судя по комментарию в топике этого предложения, идея элемента popup была перенесена в новую концепцию — Popover API. К моему удивлению, в данный момент это API поддерживается большинством современных браузеров, кроме Firefox. Подробности с примерами можно почитать, как в переводе на хабре, так и в оригинале. К своему стыду, я упустил новость о внедрении этого API в браузеры, хотя это произошло уже почти год назад. Постараюсь найти время и собрать под это дело интерактивную демку.
Внедрение различных новых фич в браузеры -- это всегда повод для радости. В декабре я писал про свойство
field-sizing
(на момент публикации, в интернете об этом почти не было постов, я в числе первых, кто написал об этом на русском языке), сегодня хочу поделиться с вами новостью про добавление нового элемента <dialog>
.Как этим пользоваться?
Я собрал небольшую демку на codepen, где используется элемент
<dialog>
для отрисовки модального окна. Есть минимальная стилизация. Давайте разберём эту демку и попробуем как-то её усложнить.<style>
::backdrop {
background: #000;
opacity: 0.75;
}
dialog {
border: unset;
border-radius: 10px;
}
dialog form {
display: flex;
justify-content: end;
}
</style>
<button onclick="dialog.showModal()">Открыть модалку</button>
<dialog id="dialog">
<p>Это нативное модальное окно</p>
<form method="dialog">
<button>ОК</button>
</form>
</dialog>
Начнём со стилизации, псевдокласс
::backdrop
позволяет отрисовать затемнение общего фона страницы, чтобы отделить модальное окно от всего остального интерфейса. Стилизация самого элемента и формы не так интересна, её можно пропустить.Дальше по коду идёт кнопка, с обработчиком onclick, в котором обозначен вызов метода
showModal()
на элементе с id dialog
. Внутри тела самого модального окна есть форма, которая отвечает за различные действия, которые можно совершить. По умолчанию, нажатие на кнопку будет закрывать модальное окно.Логично, что модальные окна могут быть не только информационными, одного закрытия по кнопке маловато. Изменить это можно следующим образом:
<button onclick="dialog.showModal()">Открыть модалку</button>
<dialog id="dialog">
<p>Хотите продолжить?</p>
<form method="dialog">
<button type="submit" value="no">Нет</button>
<button type="submit" value="yes">Да</button>
</form>
</dialog>
<script>
dialog.addEventListener('close', (event) => {
if (dialog.returnValue === 'yes') { /* ... */ }
});
</script>
Таким образом, мы получаем полнофункциональное модальное окно, которое можно и нужно использовать вместо самописных велосипедов. Доступность у такого элемента точно будет выше, чем у написанного руками. Бывают случаи, когда фремйворки соблюдают рекомендации WAI ARIA, как например, Bootstrap. Но если есть возможность заменить ненативный элемент нативным, это всегда большой плюс.
Расширенная версия поста доступна у меня в блоге.
Please open Telegram to view this post
VIEW IN TELEGRAM
Teletype
Нативные модалки
Пару лет назад, я писал про предложение о добавлении элемента, который стал бы нативным попапом. Судя по комментарию в топике этого...
👍5🔥2❤🔥1
#firefox #chrome #браузеры
К такой мысли можно случайно прийти, если открыть логотип браузера, который выводится на странице открытия новой вкладки, поскольку он открывается по адресу:
chrome://branding/content/about-logo.png
Пользователь reddit решил пойти дальше простых и очевидных догадок и обратился к сообществу с вопросом.
Самый популярный комментарий с ответом:
Chrome refers to "user interface chrome", which is the borders and widgets that frame the content part of a browser window.
Google Chrome (the web browser) is named after this element of GUI.
Перевожу на русский:
Chrome, в данном случае, относится к термину "хром пользовательского интерфейса", который представляет собой рамки и виджеты, находящиеся вокруг контентной части окна браузера.
Браузер Google Chrome был назван так позже этого элемента пользовательского интерфейса.
Если спускаться по ветке комментариев ниже, можно также узнать, что термин появился задолго до первых браузеров. Дальнейшее исследование темы привело меня на stackoverflow (кто бы сомневался).
В ответе говорится следующее:
It is a euphemism for the graphical framework and elements surrounding the content, and thus means different things depending on the context.
То есть:
Это эвфемизм для графического фреймворка и элементов, находящихся вокруг контента, значение термина может разниться, поскольку зависит от контекста.
Please open Telegram to view this post
VIEW IN TELEGRAM
Reddit
From the firefox community on Reddit: Why is this chrome:// ?
Explore this post and more from the firefox community
👍3🤔3❤🔥1
#frontend
Проблема, с которой мы недавно столкнулись — это redux в зависимостях у react-beautiful-dnd. Есть какое-то внутреннее сопротивление тому факту, что кто-то втягивает целый стейт-менеджер в библиотеку, которая должна давать возможность перетащить карточку из одной колонки в другую. Разумеется, я несколько утрирую ситуацию, но в данном случае решение кажется необоснованным.
В моём понимании, библиотека для drag-n-drop, в целом, не нуждается в том, чтобы иметь свой собственный стетйт-менеджер, возможностей локального стейта компоненты для такого решения более, чем достаточно.
В случае, если стор всё-таки требуется, я предлагаю следующее решение: "стор должен поступать внешней зависимостью, согласно типизированного библиотекой интерфейса". То есть, сделать возможность передать стор внутрь библиотеки, создав кастомную обёртку, подходящую для конкретной библиотеки. Понимаю, что от этого сложность может возрасти, но почти наверняка такой подход позволит уменьшить итоговый бандл, хотя и увеличит время и сложность разработки. С другой стороны, это решение разработанное инженерами Atlassian, вряд ли они недостаточно компетентны, хотя по работе в Jira этого иногда и не скажешь.
Возможно, если у меня дойдут руки, то мы соберём отдельное собственное решение для drag-n-drop. В случае, если это произойдёт — я обязательно поделюсь ссылкой на решение.
В целом, ситуация, является лишним доказательством слов про то, что решение, разработанное большой компанией призвано решать проблемы этой самой большой компании, а не ваши.
Please open Telegram to view this post
VIEW IN TELEGRAM
npm
npm: react-beautiful-dnd
Beautiful and accessible drag and drop for lists with React. Latest version: 13.1.1, last published: 3 years ago. Start using react-beautiful-dnd in your project by running `npm i react-beautiful-dnd`. There are 2136 other projects in the npm registry using…
🤔3👍1
#python #django #backend
Сегодня хочу рассказать про методы
select_related
и prefetch_related
: показать разницу между ними, привести примеры использования. Полная версия с примерами доступна у меня в блоге.В чём разница?
Если говорить совсем кратко, то:
select_related
— это JOIN
, то есть происходит 1 запрос, в рамках которого связываются указанные сущности и приходят в python уже связанными, в то время, как prefetch_related
— делает отдельные запросы по каждой указанной связанной сущности и производит связывание на уровне python. В документации django сказано, что
select_related
стоит применять в тех случаях, когда вы используете ForeignKey
, если вы используете ManyToManyField
, ваш выбор — это prefetch_related
.Подробности и подводные камни
Предположим, что наши модели выглядят следующим образом:
from django.db import models
class Genre(models.Model):
name = models.CharField(max_length=512)
class City(models.Model):
...
class Person(models.Model):
hometown = models.ForeignKey(
City,
)
class Book(models.Model):
name = models.CharField(max_length=512)
author = models.ForeignKey(Person, on_delete=models.CASCADE)
genres = models.ManyToManyField(Genre)
Инвалидация кэша
⚠️ При использовании
prefetch_related
, важно помнить о такой вещи, как инвалидация кэша на уровне Queryset
(инвалидация может быть вызвана любым дополнительным методом в цепочке, который подразумевает другой запрос к базе данных). Например, при использовании фильтрации в следующем примере, будут выполнены отдельные запросы для каждого объекта Book
:for book in Book.objects.prefetch_related("genres"):
print(book.name, ":")
for genre in Book.genres.filter(age_rating="18+"):
print(" ", genre.name)
Объект Prefetch
Также, стоит упомянуть и о существовании объекта
Prefetch
, который позволяет использовать подготовленный Queryset
вместе с prefetch_related
. Например, мы хотим отобрать заранее только те жанры, которые имеют возрастной рейтинг "18+" и посчитать количество книг по каждому такому жанру.Код для решения этой задачи выглядит следующим образом:
genres = Genre.objects.filter(age_rating="18+")
.annotate(books_count=Count("book_set"))
queryset = Book.objects.all().prefetch_related(
Prefetch("genres", queryset=genres)
)
Полная версия с примерами доступна у меня в блоге.
📖 Источники:
1) Статья с хабра, в которой всё доходчиво объясняют: https://habr.com/ru/articles/752574/
2) Документация Django: https://docs.djangoproject.com/en/5.0/ref/models/querysets/
Please open Telegram to view this post
VIEW IN TELEGRAM
Teletype
Оптимизация запросов в Django ORM
Сегодня хочу рассказать про методы select_related и prefetch_related: показать разницу между ними, привести примеры использования.
👍7
📆 Правильное форматирование дат в JS
#frontend
Буквально месяц назад писал свои мысли по поводу таких решений, как
И вот, моя мечта сбылась. На неделе я сидел и листал тематические каналы в поисках чего-то нового и интересного и наткнулся на пост про библиотеку tempo. В основе это решение использует браузерный объект
#frontend
Буквально месяц назад писал свои мысли по поводу таких решений, как
moment
/day.js
и ругал их за то, что они не используют нативные браузерные API. И вот, моя мечта сбылась. На неделе я сидел и листал тематические каналы в поисках чего-то нового и интересного и наткнулся на пост про библиотеку tempo. В основе это решение использует браузерный объект
Date
и Intl API
. При этом, API самой библиотеки достаточно простое и имеет довольно подробную документацию.Formkit
Tempo • Dates by FormKit
An open-source library for handling complex date operations across timezones. The easiest way to work with dates in JavaScript.
👍5❤2
#vue #frontend
Несколько месяцев назад, в подборке от reddit мне попался довольно странный вопрос в сабреддите vue.js: "Почему я должен использовать Pinia вместо глобальных реактивных переменных?".
Странным он показался мне, в первую очередь, из-за того, что я никогда об этом всерьёз не задумывался и выбирал использовать какой-либо стейт-менеджер, вместо переменных, потому что так заведено. Как будто, это тема, в которой и так всё ясно, дополнительных исследований не требуется: хочешь реактивность и надёжность — бери стейт-менеджер, он это предоставляет.
В тексте своего сообщения автор опирается на раздел документации Vue, в котором рассказывается о том, как управление состоянием устроено в компонентах. По мнению пользователя reddit, у использования глобальных реактивных переменных есть такие плюсы, как:
1) простота;
2) нативная поддержка фреймворка;
3) нет зависимостей, что означает, не появится ситуации, как с Vuex (который прекратили развивать), в которой придётся переписывать половину кодовой базы;
4) Composition API выглядит более зрело и стабильно и вряд ли изменится в ближайшем будущем (в сравнении с переходом с Vue 2 на Vue 3);
5) возможность использования всей мощи реактивных API, вместо урезанной реактивной обёртки Pinia;
6) разница в эффективности при миллионе операций записи довольна внушительна:
Ref time: 36ms
Pinia ref time: 628ms
Reactive time: 501ms
Pinia reactive time: 809ms
В комментариях в качестве основных плюсов использования Pinia вместо глобальных реактивных переменных, приводили аргументы только про возможность использования SSR и DevTools.
Эта дискуссия действительно заставила меня задуматься, в следующем своём проекте на Vue, хочу попытаться применить подобную концепцию на практике.
P. S.
Оказалось, автор является частью русскоязычного сообщества @vuejs_ru и поддерживает и развивает ресурс Vue FAQ. Я бегло по нему прошёлся и не могу не выразить искреннее восхищение проделанной работой. Прошу обратить своё внимание на этот ресурс, в нём собрано довольно много информации по фреймворку.
Please open Telegram to view this post
VIEW IN TELEGRAM
Reddit
From the vuejs community on Reddit
Explore this post and more from the vuejs community
👍7👏3
Как менялось моё рабочее место 2021-2024
Делитесь своими фотографиями в комментариях👇
Делитесь своими фотографиями в комментариях👇
🔥16👍5
#python #backend
Полная версия с разбором решения доступна у меня в блоге.
Сегодня на работе столкнулся с задачей, когда надо было посчитать дату оплаты счёта, с учётом праздников и выходных (чтобы выводилась дата по условию "не позднее, чем через 5 рабочих дней"). Ранее подобных вещей я не делал, поэтому пошёл гуглить.
Ищем готовое решение на python
На этапе ресёрча по задаче нашёл такое решение:
import datetime
import numpy as np
start = datetime.date(2022, 2, 15)
end = datetime.date(2022, 3, 16)
# include holidays in a list
days = np.busday_count(start, end, holidays=['2022-02-21'])
print('Number of business days is:', days)
На том же сайте было упоминание библиотеки holidays, чтобы проставить сразу корректный список всех праздников, относительно определённой страны. Принцип работы простой:
from datetime import date
import holidays
ru_holidays = holidays.RU()
date(2024, 3, 8) in ru_holidays # True
date(2024, 3, 7) in ru_holidays # False
К сожалению, нельзя сказать, что это решение покрывает все кейсы. К примеру, в 2024м году майские праздники в России по производственному календарю продлятся с 9 по 12 мая. Библиотека же, считает не так:
>>> date(2024, 5, 9) in ru_holidays
True
>>> date(2024, 5, 10) in ru_holidays
False
В этом примере, 9 мая ещё входит в список праздников, а вот 10 мая уже нет. Возможно, что numpy будет определять корректно?
>>> import datetime
>>> import numpy as np
>>> start = datetime.date(2024, 5, 6)
>>> end = datetime.date(2024, 5, 12)
>>> days = np.busday_count(start, end)
>>> days
5
Как видим, без параметра
holidays
, numpy считает, что вся неделя с 6 по 12 рабочая, кроме 2 выходных.Ищем открытое API
Следующее, на что я наткнулся, было открытое API производственного календаря для России и Казахстана. Работа с ним максимально простая. К примеру, чтобы получить список всех "отклонений" от обычного календаря для России, достаточно выполнить GET-запрос по адресу: https://production-calendar.ru/get/ru/2024/json?compact=1. Результат полностью совпадает с производственным календарём на сайте КонсультантПлюс.
Мастерим решение
С учётом всех вводных реализуем решение (полная версия с его разбором у меня в блоге):
import datetime
import numpy as np
# глобальные константы откуда-то из settings/django constance
HOLIDAYS = ['2024-03-08', ...]
BUSINESS_DAYS_DELTA = 5
def found_business_days_date(step = 5, _start_date = None):
start_date = _start_date or datetime.datetime.now().date()
end_date = start_date + datetime.timedelta(days=step)
days = np.busday_count(start_date, end_date, holidays=HOLIDAYS)
if days == BUSINESS_DAYS_DELTA:
return end_date
return found_business_days_date(step + 1, start_date)
Результат работы этой функции для сегодняшней даты (04.03.2024): 2024-03-12. Соответственно, функция работает корректно.
Заключение
Решение подобных базовых задач очень позитивно складывается на опыте начинающих и продолжающих разработчиков.
Полная версия с разбором решения доступна у меня в блоге.
💬 Поделитесь, сталкивались ли вы с подобными задачами? Какое решение в итоге находили?
Please open Telegram to view this post
VIEW IN TELEGRAM
Teletype
Вычисляем дату с учётом рабочих дней на python
Сегодня на работе столкнулся с задачей, когда надо было посчитать дату оплаты счёта, с учётом праздников и выходных (чтобы выводилась...
👍5❤2🔥1
#docker #backend
В прошлом году столкнулся с задачей по настройке VPN-подключения внутри docker для обеспечения доступа к API, находящемуся во внутренней сети предприятия. Недавно с подобной проблемой столкнулся и мой друг, но ситуации у нас несколько разные: я настраивал связку для
openfortivpn
, а ему нужно было настроить для openvpn
.Основная проблема, которая возникает при настройке VPN-соединения для нескольких контейнеров заключается в необходимости настройки сетевых параметров и особенностях работы docker. Без контейнеризации всё было бы намного проще: вы запускаете VPN-подключение и все ваши запросы к соответствующим хостам внутри VPN-сети проходят через это подключение.
Как работает сеть в docker и docker-compose
По умолчанию, все наши сервисы объединяются в рамках сети
default
. При необходимости мы можем выделять отдельные сети для разных сервисов, чтобы изолировать их друг от друга. Кроме прочего, мы можем использовать внешнюю docker-сеть, чтобы объединить сервисы из разных приложений (отделённых своими compose-файлами) между собой.Нетрудно догадаться, что для обеспечения VPN-подключения внутри docker-сети, нам потребуется уметь чуть больше, чем просто создание docker-сети. Тут на помощь приходят сетевые драйверы и
network_mode
.Сетевые драйверы
В официальной документации описано целых 7 различных сетевых драйверов. Начнём с того, что сам по себе сетевой драйвер нужен для расширения возможностей сети. Рассмотрим какие сетевые драйверы существуют (адаптированная информация из документации):
- bridge — драйвер по умолчанию, необходим для того, чтобы сервисы могли общаться друг с другом внутри docker-сети;
- host — драйвер, который позволяет использовать сеть хоста напрямую, убирая изоляцию контейнера от хоста;
- overlay — драйвер, позволяющий соединить несколько хостов, где находятся docker-контейнеры вместе, чтобы обеспечить взаимодействие между ними (такой вариант отлично подходит для обеспечения коммуникации между нодами в swarm mode);
- ipvlan — драйвер, предоставляющий полный контроль над адресацией IPv4 и IPv6;
- macvlan — драйвер, позволяющий назначить MAC-адрес для контейнера, делая его доступным в виде физического устройства внутри вашей сети;
- none — драйвер, позволяющий изолировать сеть контейнера от хостовой сети и сети других контейнеров;
- network plugins — возможность использования в качестве сетевого драйвера любого внешнего решения.
network_mode
network_mode
это атрибут сервиса внутри конфигурации docker-compose, необходимый для более тонкой настройки. Существует несколько значений у атрибута newtork_mode
:-
bridge
— дефолтный способ для связи между контейнерами, работает по умолчанию;-
host
— использование единого сетевого пространства между выбранным сервисом и хостом;-
none
— отсутствие сетевого взаимодействия для выбранного сервиса;-
service:[service_name]
— использование сетевого пространства выбранного сервиса;-
container:[container_name/id]
— использование сетевого пространства выбранного контейнера (отличается от прошлого необходимость указания именно container_name/container_id, а не service_name).К сожалению, в официальной документации compose, подробно ничего не сказано об этом атрибуте, кроме ссылки на то, что значения могут быть те же самые, что и у парамерта
--network
, но также добавляется специальная форма service:[service name]
. Возможно, это сделано так именно потому, что все значения по смыслу похожи.Пример compose-файла
Разницы для конфигурации между
openfortivpn
и openvpn
особо нет, поэтому рассмотрим на примере openfortivpn
:version: '3'
services:
vpn:
container_name: openfortivpn
restart: unless-stopped
build: .
privileged: true
volumes:
- ./config:/etc/openfortivpn/config.conf
command: ["openfortivpn", "--config=/etc/openfortivpn/config.conf"]
some-service:
image: your-container-image
network_mode: service:vpn
Please open Telegram to view this post
VIEW IN TELEGRAM
🐳6👍2
Маска для номера телефона
#frontend #браузеры #стандартизация
Проблема стандартизации форматирования и валидации поля ввода номера телефона абсолютно не новая. Даже в MDN говорят о том, что
Решение, которое мы можем без труда соорудить стандартными средствами браузера, не используя дополнительных библиотек выглядит примерно так:
С таким решением мы будем строго принимать один обозначенный формат (пример сильно упрощён, более комплексный вариант регулярного выражения можно подглядеть тут) с общей подсказкой для пользователя, без маски для ввода. Как давно вы в последний раз видели поле ввода для номера телефона без маски?
Можно долго дискутировать о том, нужна ли маска для ввода в данном случае, но пользователи уже к этому привыкли и вы явно их не отучите, только добавите неудобств.
Вернёмся к основной теме. Без добавления JS-кода маску мы не получим. Можно поискать готовое решение, но с этим тоже есть проблема. Решения, которые сразу будут хорошо работать из коробки найти сложно. Довольно часто, многие из этих решений уже содержат в себе стилизацию, которую не всегда легко переделать под нужный дизайн.
На мой взгляд, всё, что должно быть в хорошем решении подобной задачи:
1) возможность управления паттерном форматирования, валидации;
2) отсутствие лишних зависимостей и любой стилизации, она должна поступать извне, либо легко кастомизироваться;
3) интернационализация.
Почти идеальным решением, на мой взгляд, является imask. Он предоставляет только функциональную часть с очень простыми API. К примеру, для российского номера телефона код будет выглядеть так:
Кроме прочего, решение имеет адаптацию под популярные веб-фреймворки (react, vue, angular, svelte) и даже неплохой задел на интернационализацию за счёт использования динамических масок. Пожалуй, единственным минусом этого решения является необходимость написания большого количества кода для более тонкой настройки. С другой стороны, это даёт возможность широкой кастомизации и позволяет менять паттерны и маски так, как вам требуется, не отнимая возможности стилизовать свои поля ввода так, как вы считаете нужным.
#frontend #браузеры #стандартизация
Проблема стандартизации форматирования и валидации поля ввода номера телефона абсолютно не новая. Даже в MDN говорят о том, что
<input type="tel">
в отличие от <input type="email">
не имеет никакой автоматической валидации, потому что "форматы телефонных номеров сильно раличаются по всему миру".Решение, которое мы можем без труда соорудить стандартными средствами браузера, не используя дополнительных библиотек выглядит примерно так:
<div>
<input
type="tel"
pattern="(^8|7|\+7)([0-9]{10})"
required
>
</div>
<div>
<small>Формат: +79999999999</small>
</div>
С таким решением мы будем строго принимать один обозначенный формат (пример сильно упрощён, более комплексный вариант регулярного выражения можно подглядеть тут) с общей подсказкой для пользователя, без маски для ввода. Как давно вы в последний раз видели поле ввода для номера телефона без маски?
Можно долго дискутировать о том, нужна ли маска для ввода в данном случае, но пользователи уже к этому привыкли и вы явно их не отучите, только добавите неудобств.
Вернёмся к основной теме. Без добавления JS-кода маску мы не получим. Можно поискать готовое решение, но с этим тоже есть проблема. Решения, которые сразу будут хорошо работать из коробки найти сложно. Довольно часто, многие из этих решений уже содержат в себе стилизацию, которую не всегда легко переделать под нужный дизайн.
На мой взгляд, всё, что должно быть в хорошем решении подобной задачи:
1) возможность управления паттерном форматирования, валидации;
2) отсутствие лишних зависимостей и любой стилизации, она должна поступать извне, либо легко кастомизироваться;
3) интернационализация.
Почти идеальным решением, на мой взгляд, является imask. Он предоставляет только функциональную часть с очень простыми API. К примеру, для российского номера телефона код будет выглядеть так:
IMask(
document.getElementById('phone-mask'),
{
mask: '+{7}(000)000-00-00'
}
)
Кроме прочего, решение имеет адаптацию под популярные веб-фреймворки (react, vue, angular, svelte) и даже неплохой задел на интернационализацию за счёт использования динамических масок. Пожалуй, единственным минусом этого решения является необходимость написания большого количества кода для более тонкой настройки. С другой стороны, это даёт возможность широкой кастомизации и позволяет менять паттерны и маски так, как вам требуется, не отнимая возможности стилизовать свои поля ввода так, как вы считаете нужным.
👍9
Давайте знакомиться. Что связывает вас с IT?
Anonymous Poll
25%
Работаю фронтендером
24%
Работаю бэкендером
15%
Работаю фуллстаком
5%
Работаю девопсом
1%
Работаю сис. админом
12%
Работаю по другой специальности
4%
Преподаю (в университете/школе/колледже)
6%
Не связан с IT, но интересуюсь
36%
Учусь по специальности, связанной с IT
4%
Свой вариант (в комментариях)
#frontend #vue #производительность
На reddit наткнулся на пост в комьюнити Vue.JS с описанием проблемы с производительностью у вычисляемых свойств (
computed
). Заключается проблема в том, что судя по тестам использованием watch
+ ref
на 50% более эффективно, чем использованием computed
.Добавим немного контекста. Тест проводился на дропдауне с 1 000 000 элементов. Задача теста состояла в том, чтобы написать функцию, которая будет конвертировать объекты (или строки) с типом
selectOption
в объект selectOptionsObject
.Типы выглядят так:
export type selectOption = selectOptionObject | string;
export type selectOptionObject = {
id: string | number;
render: string;
raw?: any;
};
Код функции
normaliseOptions
для конвертации:const normaliseOptions = (
options?: selectOption[]
): normalisedOptionObject[] => {
if (!options) return [];
// We will use a straight for loop for performance
const normalisedOptions = [];
for (let i = 0; i < options.length; i++) {
const option = options[i];
if (typeof option === "string") {
normalisedOptions.push({
id: option,
render: option,
});
continue;
}
normalisedOptions.push({
id: option.id.toString(),
render: option.render,
disabled: option.disabled || false,
raw: option.raw,
});
}
return normalisedOptions;
};
Код, который использовался в тесте:
1) С использованием computed:
const normalisedOptions = computed(() => {
return normaliseOptions(props.options);
});
2) С использованием watch + ref:
// Using the pattern below rather than a computed value gives us a 2x performance improvement
const normalisedOptions = ref(normaliseOptions(props.options));
const recomputeOptions = () => {
normalisedOptions.value = normaliseOptions(props.options);
};
watch(
() => props.options,
() => {
recomputeOptions();
}
);
Самые умные и опытные уже догадались, в чём может заключаться проблема конкретно в данном случае (а я не успел самостоятельно подумать и наткнулся на ответ в комментариях). Проблема заключается в том, что
computed
отслеживает каждую вложенную зависимость. Поэтому он будет работать медленнее, если props.options
не является плоской структурой, состоящей из примитивов, потому что требуется провести кратно больше вычислений. Таким образом, корректный код для данного случая выглядит так:const recomputedOptions = computed(() => normaliseOptions(toRaw(props.options)));
Метод
toRaw
извлекает необработанный объект из proxy-объекта, созданного Vue, давая нам возможность поработать с оригинальным объектом. Подробнее про него можно почитать в документации.Please open Telegram to view this post
VIEW IN TELEGRAM
Reddit
From the vuejs community on Reddit
Explore this post and more from the vuejs community
👍8🤔1