Веб-платформа – Telegram
Веб-платформа
2.03K subscribers
4 photos
2 videos
153 links
📍 О том, как всё устроено в веб-платформе и что происходит в индустрии фронтенда

⭐️ Новости, полезные выжимки, находки и напоминания

👨‍💻 Вопросы и предложения @web_platform_support

🔗 webplatform.tech
Download Telegram
Зачем нужен Symbol?

#JS #Лаборатория_веб_платформы

В JS есть тип данных — Symbol. Название не говорящее само за себя сходу, и непонятно зачем он нужен. Давайте разбираться.

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

const mySymbol = Symbol();


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

Symbol появился в стандарте ES2015, когда язык сильно менялся и в нём появлялись новые методы. При появлении новых фич нужно было не «сломать» старые.

В частности нужна была возможность добавлять в объекты новые свойства, но чтобы:

1) все методы для «перебора», например, for-in или Object.keys(), продолжили работать как раньше;
2) не было риска «пересечения» названий новых и уже существующих свойств.

Про перебор

Например, у объекта есть условно три свойства:

const myObject = {first: '1', second: '2', third: '3'};


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

А вот если создать новое свойство объекта как символ, то оно не появится в списке всех свойств при итерации.


const coolNewProperty = Symbol();

myObject[coolNewProperty] = 'coolNewValue';

for (let property in myObject) {
console.log(property);
// Выведется first second third
}

console.log(myObject[coolNewProperty]);
// Выведется coolNewValue


Про «пересечение» имён

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



const reverse = Symbol();

Array.prototype[reverse] = () => console.log('custom reverse');

console.log(myArray.reverse());
// Выведется [3, 2, 1]

console.log(myArray[reverse]());
// Выведется custom reverse
1
Метрики Web Vitals

#перфоманс #Лаборатория_веб_платформы

На ранжирование страниц в в Гугл-поиске влияет не только качество контента, но и хороший UX. По мнению Гугла его можно оценить по трём метрикам:

- время загрузки сайта;
- время до возможности взаимодействовать с интерактивнымм элементами на сайте;
- визуальная «стабильность» интерфейса сайта.

Эти метрики объединили под общим названием Web Vitals. Теперь про каждую подробнее.

Время загрузки сайта (LCP, Largest Contentful Paint)

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

Хорошо, когда это время 2,5 секунды или меньше; 2,5-4 секунды — норм, но надо улучшать; больше 4 секунд — плохо, пользователь не дождётся и уйдёт.

На эту метрику влияют:

➡️ Размер загружаемых ресурсов

- нужно сжимать картинки и остальную статику саму по себе (минификация) и на сервере (gzip, brotli);
- нужно уменьшать размер блочащих рендер ресурсов — JS и CSS — разделять их на мелкие бандлы по страницам, а реиспользуемый код выносить в небольшие отдельные модули.

➡️ Способ загрузки ресурсов

- картинки нужно загружать «лениво», то есть только в момент, когда они становятся непосредственно видимы;
- сторонние скрипты (аналитика, реклама, виджеты) нужно загружать асинхронно, вне основного потока.

Время до интерактивности (FID, First Input Delay)

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

Если задержки меньше 100 милисекунд, то человеком это воспринимается нормально; 100-300мс — может казаться тормознутым; больше 300мс — явно тормозит, плохо.

На эту метрику влияет всё, что может «забивать» основной поток работы браузерного движка:

- долгие таски в JS-коде;
- неоптимальное использование операций, вызывающие перерасчёт лейаута страницы (список операций);
- большое количество DOM-нод на странице.

Визуальная «стабильность» интерфейса (CLS, Cumulative Layout Shift)

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

➡️ Чаще всего сдвиги могут вызывать подгружающиеся картинки, видео, реклама. Фиксится заданием фиксированных размеров картинкам и контейнерам, в которые вставляет реклама.

➡️ Если сдвиги происходят после отрабатывания скриптов, то тоже можно поставить «подпорки» в CSS, которые не допустят сдвигов.

➡️ Если контент сдвигается при подгрузке и применении кастомного шрифта, то можно изменить способ показа шрифта на font-display: optional, то есть скрывать текст и задерживать «мигание» до 100мс, пока не загрузится шрифт. Также можно использовать формат шрифтов woff2, вырезать ненужные символы и предзагружать шрифты.

⚡️ Как измерять в продакшене

В сервисе PageSpeed Insight или webpagetest.

💻 Как измерять при разработке

Локально можно воспользоваться сервисом Lighthouse или расширением Web Vitals для Хрома. Также посдсветка Web Vitals появилась во вкладке "Performance" в Dev Tools 88-й версии Хрома.

Так как метрика First Input Delay (FID) не может быть достоверно измерена локально, то её можно «проксировать» другой метрикой — Total Blocking Time (TBT). Это время с начала отрисовки страницы до момента её полной готовности к взаимодействию. Улучшение TBT приведёт к улучшению реальной пользовательской FID.
1
Скришоты в Dev Tools

#инструменты #Лаборатория_веб_платформы

Из средств разработчика DevTools в Chrome и Firefox можно делать скришоты.

📸 Отдельная DOM-нода страницы

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

- в Chrome — вкладка Elements, в меню на ноде пункт Capture node screenshot;
- в Firefox — вкладка Inspector, в меню на ноде пункт Screenshot Node.

📸 Выделенная область или весь сайт

Для этого нужно перейти в режим «адаптивности» и оттуда уже сделать скриншот:

- в Chrome нужно нажать кнопку Toggle device toolbar в левом верхнем углу окошка средств разработчика (или нажать Cmd/Ctrl + Shift + M), затем в появившейся верхней панели в её правом углу нажать три точки, чтобы выпало меню, и там выбрать Capture screenshot (для снимка видимой области) или Capture full size screenshot (для снимка всего экрана). Также эти команды можно вызвать текстом из командного меню по Cmd/Ctrl + Shift + P;

- в Firefox нужно перейти в режим Responsive Design Mode кнопкой в правом верхнем углу окошка средств разработчика (или нажать Cmd/Ctrl + Opt/Alt + M), затем в появившейся верхней панели нажать на иконку с фотоаппаратом. Чтобы заскриншотить всю страницу, то сначала нужно включить эту фичу в настройках DevTools: открыть настройки и там поставиьт галочку Take a screenshot of the entire page. После этого рядом с иконкой Responsive Design Mode появится иконка с фотоаппаратом, которая делает полноразмерный скриншот страницы.

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

💡 Заодно в настройках FF нашёл встроенную в DevTools линейку.
1
Hot Module Replacement на нативных ES-модулях, часть 1

#инструменты #JS #Лаборатория_веб_платформы

Представьте, что вы начинаете разрабатывать JS-приложение. При разработке вы используете локальный сервер, запускаете проект, далее собирается JS-бандл и приложение открывается в браузере. В вашем проекте пока что немного модулей, и приложение «собирается» быстро. Правда в процессе разработки вам приходится полностью обновлять страницу, чтобы увидеть, как работают ваши написанные скрипты, и состояние приложения теряется при перезагрузке. Со временем также модулей в приложении становится много и полная «пересборка» теперь длится долго.

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

Ок, сделали. Перебилд теперь проихоходит быстрее, но страницу всё ещё приходится полностью перезагружать для обновления. Теперь задача в том, чтобы точечно обновить загруженный код в вашем браузере с запущенным сервером. Как это сделать «на горячую», без перезагрузки всей страницы целиком? Для этого придуман подход Hot Module Replacement (HMR).

HMR — это способ точечного обновления в браузере только изменившихся JS-модулей приложения вместо полной перезагрузки страницы. Дальше я рассмотрю, как HMR реализован в сборщиках на нативных ES-модулях — например, в Snowpack или Vite.

В HMR-движке на нативных ES-модулях есть две части: серверная и клиентская.

На сервере из всех модулей и их зависимостей (import и export) строится дерево: в корне — «главный» модуль (что-то типа app.js), от него расходится древовидная цепочка подключаемых JS-модулей, от которых модуль зависит.

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

Клиент подключается к серверу по WebSocket. Когда изменяется один из файлов JS-модулей, от клиента на сервер отправляется сообщение, какой именно модуль изменился.

Когда сервер получает сообщение от клиента, он начинает анализировать зависимости этого модуля. Сервер идёт вверх по цепочке родительских зависимостей и инвалидирует их до тех пор, пока не дойдёт до «граничного» модуля — это последний элемент в цепочке, который явно помечен как принимающий HMR-обновления. Именно этот файл и все его зависимости сервер «перебилдит», а клиент запросит на обновление. В случае, если этим «граничным» модулем окажется корневой модуль app.js, то приложение перезагрузится целиком.

Так как в движке используется механизм нативных ES-модулей найти «граничный» родительский модуль и заменить его — достаточно. Все дочерние зависимости модуля уже обработаются и загрузятся автоматически самим браузером.

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

Как же именно «подменяется» модуль со старого на новый?

В случае, если используются нативные ES-модули, выполняется динамический import интересующего модуля. А если преварительно изменившиеся файлы ещё нужно «сбилдить», то перед импортом ещё выполняется этот этап билда.

Чтобы импортировать свежий файл (не кешированную браузером версию), то к импорту можно добавить уникальную метку, например, текущий timestamp:


import(muduleName + `?time=${updateID}`)


Остаётся решить вопрос, как при такой «подмене» передать в новый модуль состояние из старого модуля.

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

Продолжение 👇
Hot Module Replacement на нативных ES-модулях, часть 2

#инструменты #JS #Лаборатория_веб_платформы

Посмотрим детали реализации.

Всю служебную инфу о модуле будем записывать в специальном объекте import.meta.

Самое первое, что нужно, это пометка, участвует ли модуль в HMR-процессе. Это объект import.meta.hot:


let test = 1;

//...

if (import.meta.hot) {
// в этот условии записывается вся начинка HMR
// нужная только для режима разработки
}


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

Внутри import.meta.hot есть несколько методов, которые будут вызываться на этапах «жизни» HMR-модуля и свойств.

import.meta.hot.accept — этот метод, который в старом модуле принимает подменённый новый модуль.



import.meta.hot.accept(({ module }) => { // module – новый импортированный модуль
value = module.value;
});


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

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



import.meta.hot.dispose(() => {
document.head.removeChild(style);
});


import.meta.hot.decline — это метод для того, чтобы модуль мог безусловно отклонить HMR-обновление. Этот метод триггерит полную перезагрузку страницы. Он нужнен, например, в случае, если модуль вносит в состояние приложения какие-то непоправимые сайд-эффекты.


import.meta.hot.decline();


import.meta.hot.invalidate — это метод для того, чтобы по условию пометить модуль как нуждающийся в обновлении и стриггерить перезагрузку страницы.


import.meta.hot.accept(({ module }) => {
if (something) {
import.meta.hot.invalidate();
}
});


import.meta.hot.data — «буфер» для передачи данных между обновлением модулей (к нему можно обращаться между dispose() и accept()).



if (import.meta.hot) {
// Приём данных от прошлого dispose
import.meta.hot.accept(({ module }) => {
value = import.meta.hot.data.value || module.value;
});

// Отправка данных будущему accept
import.meta.hot.dispose(() => {
import.meta.hot.data = { value };
});
}


Важное дополнение про ES-модули. Так как ES-модули не поддерживают неотносительные пути при подключении — import {init} from 'module', то на первом этапе «сборки» нужно для дев-режима автоматически пройтись по всем файлам и заменить пути импортов на относительные import {init} from '/node_modules/module'.
Event loop и рендер в браузере, часть 1

#перфоманс #инструменты #Лаборатория_веб_платформы

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

К вам сразу же клиенты начинают звонить по телефону. Ваша задача:

1) ответить им, пообщаться, понять их вопрос;

2) затем внести клиента в ваш список, найти ответ на вопрос и сообщить клиенту ответ, завершить разговор;

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

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

Event Loop

А теперь представьте, что в браузере есть тоже некий «исполнитель», который на сайте постоянно ждёт задачи на исполнение — такой бесконечный цикл, который называется Event Loop. Он делает все задачи по исполнению JS-кода (Task) и отрисовке сайта (Render). Этот исполнитель один, и работает он в одном «потоке», так как задачи по исполнению JS-кода и отрисовке DOM взаимосвязаны и их проблематично разделять. Event loop связан с обновлением экрана — в нём рассчитываются кадры страницы.

Все JS-задачи, которые поступают на исполнение, ставятся в очередь — TaskQueue. Задачи по рендеру тоже записываются в очередь — RenderQueue. Вспомните пример со звонками и почтой. Исполнитель делает поступающие JS-задачи (это очередь телефонных звонков), а когда освобождается от них, переходит к задачам по отрисовке интерфейса (отправка почты).

В приоритете — работа с JS-задачами, а когда приходит необходимость отрисовать следующий кадр для обновления экрана, Event Loop переходит к задачам отрисовки.

Как определяется эта необходимость? Современные устройства работают с частотой отрисовки 60 кадров в секунду (FPS). Это значит, что каждые 16.6мс (1/60 секунды) браузеру нужно перерисовывать следующий кадр.

В случае с общением нашего менеджера с клиентами по телефону, длинные звонки «задерживают» отправку почты. Так же и длинные JS-задачи (>16.6мс) заставляют браузер откладывать отрисовку. Поэтому в момент, когда отрисовка уже была нужна, браузер может «пропустить» кадр. То есть частота обновления кадров станет уже <60FPS. Визуально интерфейс начнёт «подтормаживать» или вообще «фризиться».

Аналогично и со случаем, когда много писем в очереди могут помешать менеджеру вовремя отвечать на звонки. Если в RenderQueue задач по отрисовке много или они сложные, то исполнение JS-задач в TaskQueue тоже может задерживаться, что проявляется в «тормозах».

Микротаски

Есть ещё одна разновидность задач — микротаски (MicroTask). Это такие особенные JS-задачи — колбеки для Promise или mutationObserver. Микротаски собираются в отдельную очередь — MicroTaskQueue. Чем микротаски отличаются от обычных тасков? Они исполняются сразу же, когда дорабатывает текущий таск или микротаск, то есть вне очереди TaskQueue и RenderQueue. Эта особенность может быть критична в случае, когда микротаски циклично вызывают следующие микротаски, откладывая рендер и обычные JS-таски, так как до них просто не доходит очередь.
1
Event loop и рендер в браузере, часть 2

#перфоманс #инструменты #Лаборатория_веб_платформы

Что происходит в RenderQueue?

Вот в такой последовательности исполняется рендер:

JS (RequestAnimationFrame) > Style > Layout > Paint > Composite

Этап JS (RequestAnimationFrame)

В момент, когда браузер освобождается от JS-тасок и микротасок, он готов выполнять рендер. С помощью колбека requestAnimationFrame можно «подписаться на свободный момент» в общей очереди исполнения задач. Так как браузер гарантированно будет «не занят» чем-то другим, то это удобный момент, чтобы отрисовать анимацию, посчитать что-то или изменить кадр до его отрисовки.

Этап Style

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

Этап Layout

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

Этап Paint

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

Этап Composite

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

Особенные CSS-свойства для этого этапа — transform и will-change. Для элементов с заданным transform браузер задействует процесс composition. В случае с will-change — это подсказка для браузера, что для элемента нужно использовать composition-процесс.
1
Event loop и рендер в браузере, часть 3

#перфоманс #инструменты #Лаборатория_веб_платформы

Как оптимизировать Render

Чем меньше операций производится браузером, тем лучше производительность. При первоначальной загрузке сайта браузер выполяет все этапы RequestAnimationFrame > Style > Layout > Paint > Composite.

Минимизировать количество layout

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

Например, если изменить что-то из свойств, затрагивающих лейаут страницы (размеры, отступы, позиционирование) или считать их в JS, то сработает layout, а за ним нужно будет снова перерисовать элементы в paint и сложить в слои в composition.

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

А если, к примеру, для перепозиционирования элементов изменять свойство transform, то браузеру не требуется перестраивать сетку страницы и делать перерисовку элементов, и поэтому этап layout и paint не запускается повторно. Работа идёт только на этапе composite, поэтому операций становится существенно меньше, и они уже не такие тяжёлые.

Также «тяжёлый» этап layout пропускается, если изменяетя только внешний вид элемента, например, фоновое изображение, цвет текста или тень, а не его размеры. В этом случае выполняется только paint и composite.

Использовать requestAnimationFrame

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

Использовать CSS-анимации

Анимации, сделанные на JS, вдобавок к исполнению JS-таски будут в лучшем случае прождать процесс composition (в худшем ещё и style, layout, paint). Поэтому если делать анимации в CSS, количество тасок для Event Loop будет меньше, так как мы не создаём JS-таски.

Кроме того, все CSS-анимации и процесс composition браузер выполняет с задействованием GPU, а не только с помощью CPU. Если браузер выясняет, что изменяемые свойства не влияют на layout и paint, то он отрисовывает слои в картинки (делает ещё раз repaint) и вместе с информацией о слоях передаёт их в GPU. Процессинг в GPU не зависит от CPU, и поэтому разгружает основной поток исполнения задач. Преимущетсво именно в параллельности, так как когда, например, слоёв мало или нет драйвера для видеокарты, то браузер не включает GPU, а запускает composition в отдельном CPU-треде и получается прирост произовдительности.

Также отдельные composition-слои могут создаваться неявно, если анимируемый элемент находится «под» другими слоями. Так для каждого вышележащего слоя будет создаваться отдельный composition-слой.

У CSS-анимаций есть ограничения.

Чем больше composition-слоёв формируются и чем они больше по размеру, тем дольше происходит передача слоёв с CPU на GPU и может появиться «мигание».

Кроме того, каждый слой в GPU занимает место в памяти видеокарты. Размер памяти тоже ограничен, как и вычислительное время процессора.

Использовать will-change

Использование этого свойства может подсказать браузеру сделать необходимую подготовку, например, заранее создать отдельный слой для процесса composition. Когда дело дойдёт до изменения указанных в will-change свойств, браузеру не нужно будет делать подготовку, так как он её произвёл заранее, в свободный момент.

Отдельный composition-слой будет создаваться браузером, если задавать will-change для opacity, transform, top, left, bottom, right.

Но задавать will-change для всего сразу тоже вредно, так как это может создать нагрузку на GPU:

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

Поэтому will-change лучше применять точечно для улучшение плохой ситуации с перфомансом непосредственно перед грядущей перерисовкой, и по возможности его выключать после отрисовки. Помимо количества слоёв, можно снижать размеры слоёв. Чем меньше, тем лучше.
1
Когда overflow: hidden не работает

#CSS #Лаборатория_веб_платформы

Есть несколько причин, по которым дочерние боксы элементов, могут «вываливаться» из родительских.

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

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

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

И известный способ ликвидировать эффект «вываливания» — применить свойство overflow: hidden. В таком случае части бокса, вышедшие за пределы родительского элемента обрежутся без возможности доскроллить до них вручную.

Но overflow: hidden не всегда обрезает выходящее за пределы содержимое бокса. Есть исключение: абсолютно спозиционированный элемент не будет обрезаться родительским элементом с overflow: hidden, если родитель не является содержащим блоком (containing block), то есть ему не заданы position со значением absolute, relative или fixed.

Пример:


<div class="overflow">
<div class="absolute-child">LonglonglonglonglongAbsolute</div>
<div class="static-child">LonglonglonglonglongStatic</div>
</div>
</div>



position: relative;
}

.overflow {
overflow: hidden;
}

.absolute-child {
position: absolute;
}

.static-child {
position: static;
}


В примере бокс .absolute-child не обрезается по границам бокса .overflow, а при этом .static-child — обрезается, так как он имеет обычное позиционирование.

Пример в codepen
1
#webplatform

На сайте Веб-стандартов лучше легло 🙂
Forwarded from Веб-стандарты (Веб-стандарты)
Как организована веб-платформа. Виталий Зюзин помогает разобраться, как устроено создание спецификаций и как можно принять участие в развитии веб-платформы.

https://web-standards.ru/articles/web-platform/
Channel name was changed to «Веб-платформа»
Статистика использования фич на сайтах

#webplatform #Лаборатория_веб_платформы

Как узнать, как часто та или иная HTML-, CSS- или JS-фича используются на сайтах в интернете? Окей, на Can I Use можно узнать, как фичи поддерживаются браузерами. Но как узнать часто ли фичи используются разработчиками?

К примеру, нужно узнать про сайты:

- как часто в стилевых файлах используется Grid Layout или @import?
- много ли где в HTML встречается атрибут placeholder или required?
- насколько в JS-файлах распространён fetch или ResizeObserver?

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

Сервис статистики Chrome Stats

Каждый браузер Chrome собирает анонимную статистику. Разработчики Chrome добавляют в код браузера «счётчики» использования фич. Если пользоваль открывает в своём браузере сайт с определённой фичей, то «счётчик» увеличивается. В итоге получается такая статистика: сколько процентов страниц, загруженных через Chrome, было с интересующей фичей (фича встретилась в коде хотя бы один раз).

Посмотреть статистику можно тут. На 1 июня 2021 года:

- по Grid Layout — стили 9-10% всех загруженных пользователями страниц содержит гриды (источник);
- по @import — в CSS-коде почти 15% всех загруженных страниц используются директива @import (источник);
- атрибут placeholder встретился в HTML-коде 52% сайтов (источник);
- атрибут required появился на 9% сайтов (источник);
- fetch использовался на 38% сайтах (источник);
- ResizeObserver был замечен на 17% загруженных сайтов (источник).

В отдельном разделе живёт детальная статистика по использованию в коде CSS-свойств.

Самое клёвое, что есть не только текущая статистика, но и график её «адаптации» на сайтах со временем (период в несколько лет).

Статистика HTTP Archive

«Счётчики» в Chrome Stats анонимные, то есть не показывают конкретные адреса сайтов и абсолютное количество проанализированных страниц. А вот у HTTP Archive, где собирается и анализируется «архив сайтов», есть публичный датасет в сервисе BigQuery. Превью этих данных есть также на самой странице Chrome Stats.

К примеру, в таблице за 1 июня 2021 года сохранено 6435567 десктопных и 7452047 мобильных страниц. По этим страницам собрана информация:

- по тем же «счётчикам», что и в Chrome Stats, только с привязкой к конкретному сайту;
- лог работы движка V8 на странице;
- метрики Web Vitals;
- количество HTML-элементов и доступность страницы;
- картинки и другой медиа-контент на странице;
- использованные сторонние приложения и библиотеки (jQuery, React, Google Analytics…).

Чтобы отобразить эту информацию, нужно знать SQL. В прочем, в HTTP Archive есть и уже готовые отчёты о состоянии веба, которые просто открываются в браузере.
1
var(--) валидно?

#CSS #Лаборатория_веб_платформы

Представьте себе CSS-код:


--: honeydew;
background-color: var(--);
}


Как вы думаете, сработает ли такой код в браузерах, и валидно ли имя кастомного свойства --?

Ответ: да, в июне 2021 код работает в Chrome, Firefox и Safari. Но имя переменной -- формально невалидно.

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

Объясню идею авторов спеки.

В CSS есть наследование: значения некоторых свойств (например, color или font-size), а также кастомных свойств передаются от родителя к детям:


color: rgb(51, 51, 51);
--gap-size: 10px;
}

p {
/* color тоже будет rgb(51, 51, 51) */
padding: var(--gap-size); /* 10px */
}


Если эффект наследования нежелателен, его можно отменить ключевым словом initial:


color: rgb(51, 51, 51);
--gap-size: 10px;
}

p {
color: initial; /* значение rgb(0, 0, 0) */
--gap-size: initial; /* значение «пустое» */
}


Если же хочется отменить все наследуемые свойства, но при этом не хочется их все перечислять, есть свойство all, которое обращается сразу ко всем возможным CSS-свойствам сразу:


color: rgb(51, 51, 51);
font-size: 20px;
}

p {
all: initial;
/*
color: initial, то есть rgb(0, 0, 0)
font-size: initial, то есть 16px
*/
}


У свойства all есть пара исключений: одно из них — оно не затрагивает кастомные свойства, для сброса их придётся перечислять все до единого:


--prop-1: 123;
--prop-2: red;
--prop-3: 10px;
}

p {
--prop-1: initial;
--prop-2: initial;
--prop-3: initial;
}


Так вот идея авторов спеки — сделать свойство с именем -- аналогом all только для кастомных свойств. Чтобы можно было написать:


--prop-1: 123;
--prop-2: red;
--prop-3: 10px;
}

p {
--: initial;
/*
--prop-1: initial, то есть значение «пустое»
--prop-2: initial, то есть значение «пустое»
--prop-3: initial, то есть значение «пустое»
*/
}


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

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

Баг уже починен в Firefox Nightly, а в Chrome багрепорт пока только заведён.
1
Декларативность и сотрудничество в HTML

#HTML #Лаборатория_веб_платформы

Есть некоторые клёвые API в HTML/CSS, которые прям доставляют. Чтоб решить задачу, вместо императивщины сообщаешь браузеру нужную инфу, а он делает всю остальную работу.

Хочу рассказать про srcset и sizes у <img>. Для чего они нужны? Прежде всего — подсказать браузеру, какую версию картинки начать загружать, когда только парсится HTML, и браузеру ещё ничего неизвестно о лейауте страницы. Браузер будет полагаться на подсказки, оставленные разработчиком, и исходя из них начинать сразу же загружать нужную картинку, не дожидаясь полного окончания парсинга страницы и применения стилей.

Частый кейс — указание обычной или ретиновой версии картинки. Браузер на этапе загрузки картинки ещё не знает какого она будет размера. Зато при загрузке картинки у браузера уже есть информация, что за экран у устройства: обычный или ретиновый. Поэтому разработчик подсказывает ему: вот эта картинка поменьше — для обычной плотности пикселей (1x), а вот эта картинка побольше — для большей (2x):


src="picture1.jpg"
srcset="picture1.jpg 1x,
picture2.jpg 2x"
alt=""
>


Браузер получает подсказку от разработчика, какая картинка для какого экрана подходит, и сам делает выбор: если экран ретиновый, грузит 2x-версию, а иначе — 1x.

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

При загрузке картинки у браузера уже есть информация, какой размер вьюпорта на устройстве. А у разработчика есть инфа, какая картинка какому размеру соответствует. То есть разработчик заранее подказывает браузеру: вот эта картинка шириной 100px, вот эта — 500px, а вот эта — 1000px.


src="default.jpg"
srcset="small.jpg 100w,
medium.jpg 300w,
large.jpg 1000w"
alt=""
>


Браузер руководствуется подсказкой разработчика и делает выбор: ага, сейчас вьюпорт десктопный >1000px, поэтому беру large.jpg и загружаю. Если бы вьюпорт был поменьше, то была бы загружена соответствующая более мелкая версия картинки.

Но картинки не всегда показываются на всю ширину вьюпорта! Ок, браузер выбрал картинку large.jpg, так как вьюпорт был >1000px. А на самом деле картинка после загрузки стилей будет показана не на всю ширину вьюпорта, а вписана в блок шириной 300px. Об этом браузер не мог знать заранее, на этапе, когда он встретил при парсинге разметки тег <img>, и поэтому сделал неверное предположение и загрузил слишком большую картинку.

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

Эту инфу и сообщаем браузеру в атрибуте sizes: начиная с 1000px картинка будет занимать треть размера вьюпорта 33.3vw, а в остальных случаях — весь вьюпорт 100vw:

sizes="(min-width: 1000px) 33.3vw, 100vw"


Таким образом у браузера есть все вводные для принятия решения: размер вьюпорта и плотность экрана (браузер знает сам), собственные размеры картинок и их размер на экране (разработчик сообщил в srcset и sizes).

И дальше браузер сам принимает решение, какую картинку выбрать. А разработчику не нужно плясать с императивными конструкциями <picture>, что именно и когда именно браузеру показывать.

Вроде всё классно, если разобраться, как это работает. Только вот если не разобраться, то выходит не очень: у sizes по умолчанию значение 100vw. То есть если не указать sizes, а указать только srcset, то браузер будет думать, что картинки всегда на всю ширину вьюпорта, а это часто не так.

Выходит так, что фича классная, но если не въехать, как она работает, то толку нет — пользователь будет получать слишком большие картинки там, где мог получить меньшие.
2
#Лаборатория_веб_платформы

Сборник примеров условных конструкций в CSS. «Встроенные умности» в синтаксисе для реализации типовых интерфейсных паттернов, которые CSS «додумывает сам» без необходимости добавления этой логики в JS.

Из побочных инсайтов: относительно Container Queries уже настолько привычно стало думать, что оно доедет до всех браузеров «когда-нибудь потом», что оказалось внезапно они уже легально есть и в Chrome, и в Safari, а в FF появятся в следующей версии. 🧙‍♂️

То же самое и про селектор :has, но в FF пока непонятно когда доедет до стабильной версии.

https://ishadeed.com/article/conditional-css/
1👍1
#Лаборатория_веб_платформы

В JS есть Numeric Separators для более удобного визуального разделения цифр в числах. Разделитель — это символ нижнего подчёркивания _ (U+005F).

1_000_000_000 // миллиард
101_475_938.38 // сотни миллионов

const fee = 123_00; // 12300
const fee = 12_300; // тоже 12300

const amount = 12345_00; // 1234500
const amount = 123_4500; // тоже 1234500
const amount = 1_234_500; // тоже 1234500


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

const a = 00_; // SyntaxError: No identifiers allowed directly after numeric literal

const b = _00; // ReferenceError: Can't find variable: _00
11
#Лаборатория_веб_платформы

В Chrome начали пилить реализацию нативного скоупинга в CSS 🎉

Это чтобы можно было делать, например, вот так:


@scope (.dark-theme) { a { color: plum; } }

<div class="dark-theme">
<a href="#">plum</a>
<div class="light-theme">
<a href="#">purple</a>
</div>
</div>

На самом деле спека, как это обычно бывает в CSS, довольно много всего предусматривает «с запасом», а не только показанное в примере.

Фича важная для CSS-in-JS и подобных «инкапсулирующих» решений, поэтому если фичу реализуют в этом году в Chrome, вангую, что остальные вендоры тоже быстро подтянутся.
1🔥1
#Лаборатория_веб_платформы

Look Ma, это я в 2025 пишу фича-флаги прямо в CSS!


/* обычные стили */
/* ... */

@container style(--wow-such-a-feature: true) {
/* стили за фича-флагом */
/* ... */
}

@container style(--wow-such-an-another-feature: true) {
/* стили за фича-флагом */
/* ... */
}


реф
🔥51
#Лаборатория_веб_платформы

В конце января – начале февраля 2023 будет проходить митинг TC39, на котором, среди прочего, stage 4 будет брать пропоузал с недеструктивными методами изменения массивов:


Array.prototype.toSorted(compareFn)
Array.prototype.toSpliced(start, deleteCount, ...items)
Array.prototype.with(index, value)


Суть методов toReversed, toSorted и toSpliced сходу понятна: это просто те же обычные reverse, sort и splice, которые возвращают новый массив вместо изменения старого:


> [1, 10, 3]

[3, 10, 1].toSorted()
> [1, 10, 3]

[3, 10, 1].toSpliced(1, 2, 5, 6)
> [3, 5, 6]


А вот метод with уже сходу не очень понятен. Он про точечную замену: меняет один элемент массива на указанном индексе на другой:


> [3, 10, 0]


Интересно ещё то, что эти методы уже доступны в бетке Chrome 110 без флага, то есть вероятно после встречи TC39, когда пропоузал выдет на stage 4, скоро выкатится и стабильный Chrome.
1🥱1
#Лаборатория_веб_платформы

В именованных CSS-цветах red — это алиас для #ff0000, а blue — это #0000ff.
Но green — это не #00ff00, что кажется логичным. На самом деле green — это #008800. А за цвет #00ff00 отвечает алиас lime.

реф
💅3😁21