Роман Термянен | Frontend
25 subscribers
12 photos
26 links
Канал про код и грабли, управление командой, пет-проекты, находки и иногда просто про жизнь вне ИТ

Frontend Team Lead @expressms

https://termyanen.ru
Download Telegram
??

Оператор нулевого слияния. Закрывает конкретную боль.

Раньше дефолтные значения делали через ||:


const name = user.name || 'Аноним'
const count = settings.count || 10


Работает, но || считает ложными все подряд: null, undefined, 0, '', false. count равен нулю - получишь 10. name пустая строка - получишь 'Аноним'. Неочевидный баг, который легко пропустить.

?? проверяет только null и undefined, все остальное пропускает:


const count = settings.count ?? 10
// count = 0 → 0, а не 10

const label = input.value ?? 'Default'
// label = '' → '', а не 'Default'


Еще есть ??= - присваивает только если значение null или undefined:


user.role ??= 'viewer'
// если role уже задана - не трогает
// если null/undefined - ставит 'viewer'


Когда что

?? - если 0, false и '' валидные данные, а дефолт нужен только для отсутствующих значений.

|| - если любое falsy нужно заменить дефолтом.

?? поддерживается с Chrome 80, Safari 13.1, Node 14.
??= появился чуть позже - Chrome 85, Safari 14, Node 15. В целом уже везде.
Text Wrap Balance

Одиночное слово в последней строке заголовка. Одна строка CSS убирает его.

text-wrap: balance - браузер сам считает перенос так, чтобы строки были примерно одинаковой длины. Никакого JS, никаких ­.


h1, h2, h3 {
text-wrap: balance;
}


Работает только на коротких блоках: до 6 строк в Chrome, до 10 в Firefox. На длинных параграфах не применять - дорого по производительности.

После balance справа может остаться пустое место. Лечится через width: fit-content.

Для параграфов есть text-wrap: pretty - оптимизирует только последнюю строку.


h1, h2, h3 { text-wrap: balance; }
p { text-wrap: pretty; }


Поддержка

Balance: Chrome 114+, Firefox 121+, Safari 17.5+, Edge 114+

Pretty: Chrome 117+, Edge 117+, Opera 103+, Safari 26+. Firefox pretty пока не поддерживает
👍1
Sensors

Как тестировать геолокацию, ориентацию и другие датчики прямо в браузере?

DevTools -> три точки -> More tools -> Sensors. Или быстрее: Cmd+Shift+P -> "Show Sensors".

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


navigator.geolocation.getCurrentPosition(pos => {
console.log(pos.coords.latitude, pos.coords.longitude)
// получишь то что задал в Sensors
})


Берешь координаты Токио, открываешь сайт с картой или геозависимой логикой - видишь как оно работает там.

Есть еще опция "Location unavailable" - симулируешь отказ геолокации. Удобно тестировать error handling когда пользователь не дал разрешение.

Ориентация устройства. Эмулируешь поворот телефона (alpha, beta, gamma) для приложений которые реагируют на deviceorientation. Можно перетаскивать 3D-модель устройства мышкой.

Touch. Принудительно включаешь touch-события на десктопе, даже без тачскрина. Удобно тестировать свайпы и tap-хендлеры без физического телефона.

CPU Pressure. Эмулируешь нагрузку на процессор через Compute Pressure API: Nominal, Fair, Serious, Critical. Для приложений которые адаптируют поведение под загрузку CPU - например снижают качество анимаций или отключают тяжелые фичи.

Idle Detection. Эмулируешь что пользователь ушел или экран заблокирован - для приложений которые используют Idle Detection API. Только Chrome и Edge, Firefox и Safari не поддерживают.

Удобно тестировать все это без физического устройства и сложных заглушек в коде.
🔥2
URLSearchParams

Работать с параметрами URL без парсинга вручную.

Раньше параметры читали так:


const raw = location.search.slice(1)
const map = Object.fromEntries(raw.split('&').map(p => p.split('=')))


И это без decodeURIComponent, без обработки дублей, без пустой строки.

URLSearchParams берет это на себя:


const params = new URLSearchParams(location.search)

params.get('id') // читать
params.set('page', 2) // менять
params.delete('sort') // удалять
params.has('filter') // проверять


Работает с любой строкой параметров, не только с location.search:


const params = new URLSearchParams('sort=date&order=desc&page=1')
params.get('sort') // 'date'


Итерация и конвертация в объект:


for (const [key, value] of params) {
console.log(key, value)
}

const obj = Object.fromEntries(params)


Дублирующиеся ключи

get() возвращает только первое вхождение. Если ключ встречается несколько раз - нужен getAll():


const params = new URLSearchParams('tag=css&tag=js&tag=html')
params.get('tag') // 'css'
params.getAll('tag') // ['css', 'js', 'html']


Object.fromEntries(params) при дублях оставляет только последнее значение по каждому ключу.

set() против append()

set() удаляет все существующие значения ключа и ставит одно новое. append() добавляет еще одно, не трогая остальные:


params.set('tag', 'react') // все старые 'tag' пропадут
params.append('tag', 'react') // добавит еще один 'tag'


Кодировка

+ в строке параметров декодируется как пробел (формат application/x-www-form-urlencoded). Это стандарт, но может удивить:


new URLSearchParams('q=hello+world').get('q') // 'hello world', не 'hello+world'


Поддержка: Chrome 49+, Firefox 44+, Safari 10.1+ - с 2017-2018.
👍1
structuredClone

Глубокое копирование объекта. Встроено в браузер, без lodash.


const copy = structuredClone(obj)


Копирует вложенные объекты, массивы, Date, Map, Set. С циклическими ссылками не падает.

JSON.parse(JSON.stringify()) с этим не справится: Date превратится в строку, Map и Set схлопнутся в пустые объекты, undefined пропадет, на циклических ссылках упадет с ошибкой. Годится только для плоских данных.


const original = {
date: new Date(),
map: new Map([['key', 'value']]),
nested: { arr: [1, 2, 3] }
}

const copy = structuredClone(original)
copy.nested.arr.push(4)

console.log(original.nested.arr) // [1, 2, 3] - не тронут
console.log(copy.date instanceof Date) // true - не строка


Функции и DOM-узлы не сериализуются, выбросит DataCloneError. Класс-инстансы теряют прототип: данные скопируются, методы и instanceof - нет.

vs _.cloneDeep

Для plain data - полная замена без зависимостей. Но если в проекте классы:


class User {
constructor(name) { this.name = name }
greet() { return `Hi, ${this.name}` }
}

const user = new User('Alice')
const clone = structuredClone(user)

clone.greet() // TypeError - метод пропал
clone instanceof User // false


_.cloneDeep сохраняет прототип. Для обычного стейта хватает structuredClone.

Поддержка везде с Chrome 98, Firefox 94, Safari 15.4, Node 17.
Coverage в DevTools - находит CSS и JS которые вообще не выполняются на странице.

DevTools -> три точки -> More tools -> Coverage. Жмешь запись, ходишь по странице, кликаешь, останавливаешь. Красный - не выполнялось, зеленый - выполнялось. По каждому файлу виден процент использования.

Для автоматизации в puppeteer есть API:


await page.coverage.startCSSCoverage()
await page.coverage.startJSCoverage()

await page.goto('https://example.com')
// взаимодействуй со страницей

const css = await page.coverage.stopCSSCoverage()
// css[i].ranges - диапазоны использованного кода


Ограничения

Coverage фиксирует только то что выполнилось во время записи. CSS на ховер, фокус и print-стили будут красными если не активировал их руками. Lazy-loaded модули не появятся пока страница их не подгрузит.

Поддержка: Chrome и Edge. Firefox имеет похожий инструмент в шторке, менее детальный.

Документация: https://developer.chrome.com/docs/devtools/coverage
CSS Overview в DevTools - быстрый аудит всего CSS на странице. Не нужно ничего устанавливать.

DevTools -> три точки -> More tools -> CSS Overview. Кнопка "Capture overview" - и получаешь четыре секции:

- Colors: все цвета на странице, сгруппированные. Элементы с проблемами контраста отмечены отдельно
- Font info: все семейства, размеры и начертания которые реально используются
- Unused declarations: CSS-свойства которые не дали эффекта. Например width: 200px на span - у инлайн-элемента ширина не работает
- Media queries: все брейкпойнты на странице

Unused declarations vs Coverage

Легко перепутать. Coverage находит селекторы которые вообще не сматчились ни с одним элементом. CSS Overview находит декларации которые сматчились, но не применились. Это разные вещи и оба инструмента нужны.

CSS Overview делает снепшот. Изменил страницу - жми Capture overview заново.

Поддержка: Chrome и Edge.
Документация: https://developer.chrome.com/docs/devtools/css-overview
🔥1
Атрибут inert

Заблокировать целый блок одним атрибутом.


<form id="form" inert>
<input type="text" placeholder="Имя" />
<button type="submit">Отправить</button>
</form>


Все внутри перестает реагировать на клики, уходит из таб-порядка, уходит из дерева доступности для скринридеров. Убрал атрибут - все работает обратно.

Без inert для такого нужно: pointer-events: none, tabindex="-1" на каждый элемент, aria-hidden, и все равно JS-обработчики могут сработать.

Удобно для формы пока идет отправка:


form.inert = true
await submitForm()
form.inert = false


Или для панели которая скрыта - чтобы скринридеры не читали то что не видно пользователю.

Поддержка везде с апреля 2023: Chrome 102, Firefox 112, Safari 15.5.

Дока: https://doka.guide/html/inert/
Комикс-билдер

Пока все вайбкодят саасы, я навайбкодил себе конструктор комиксов про айти. Ради прикола, но вышло на удивление рабочее.

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

Как устроено: React + SVG, канваса нет вообще. Персонаж - это не картинка, а данные. Поза задается путями конечностей, эмоция - отдельный слой лица, одежда - просто цвета, которые меняются свотчами. Новый персонаж - файл на десять строк:


export const pm = makeHuman(
{ id: 'pm', nameRu: 'Менеджер', defaultOutfit: { shirt: '#5B8DEF', ... } },
{ tag: 'PM', tie: true }
)


Сейчас в наборе 12 персонажей (включая кота), 9 поз, 14 фонов, 22 предмета и комиксовые эффекты типа лампочки над головой. Реплики - пузырь, крик, мысль, шепот и отдельный робо-голос для ИИ: квадратные углы плюс моноширинный шрифт. Есть пузырь-терминал с чипами команд, как окно подтверждения в агентах.

Все элементы таскаются мышкой прямо по панели, работает undo, есть шаблоны сцен - "прод упал", "код-ревью", "ночной фикс" ставят сцену одним кликом. Экспорт - PNG 1080x1080 целиком или карусель по панелям.

Самый неочевидный момент всей задачи - шрифты в экспорте. SVG растеризуется через img, а в этом режиме браузер блокирует сетевые запросы: вебшрифт молча подменяется системным. Лечится встраиванием woff2 в SVG как base64 прямо перед экспортом.

Сценарий выпуска - это JSON. Весь стрип описывается декларативно: персонажи, позы, реплики, эффекты. Можно собирать выпуск руками в редакторе, а можно генерить скриптом.

Буду постить выпуски)
🆒2🥰1
Роман Термянен | Frontend
Комикс-билдер Пока все вайбкодят саасы, я навайбкодил себе конструктор комиксов про айти. Ради прикола, но вышло на удивление рабочее. Давно хотел выпускать комикс про жизнь разработчика, но рисовать не умею совсем. Сначала собрал пиксельную версию, она…
Comic Builder немного оформил и выложил на гитхаб

Добавил всякие мелочи:
- полный английский интерфейс, переключается на лету. Парсер сценариев тоже двуязычный: "hero angry: line" и "герой злой: реплика" собирают одинаковый стрип
- вертикальный экспорт 1x4 под телеграм, карусель по панелям под инсту и кнопка "Выпустить", которая отдает сразу все форматы, шесть файлов одним кликом
- шрифт самохостится: ноль внешних запросов, экспорт работает офлайн
- мини-лендинг с примерами стрипов
- мобильная версия

Попробовать: https://termyanen.github.io/comic-builder/ru/
Код: https://github.com/termyanen/comic-builder
Animations panel в DevTools

DevTools → ⋮ → More tools → Animations

Показывает все CSS-анимации и переходы которые сейчас работают на странице. Можно замедлить до 10% или 25%, поставить на паузу, перемотать по таймлайну - без изменений в коде.

На таймлайне каждый анимированный элемент - своя строка. Видно duration, delay, позиции кейфреймов. Кликаешь на строку - элемент открывается в Elements панели.

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

Работает с CSS keyframes, CSS transitions и Web Animations API.

Документация: https://developer.chrome.com/docs/devtools/css/animations
layer

Один из лучших инструментов для управления специфичностью в больших CSS.

Раньше когда стили библиотеки перебивали твои - только !important или более тяжелый селектор. @layer решает это нормально.


@layer base, components, utilities;

@layer base {
h1 { font-size: 2rem; }
}

@layer components {
.card h1 { font-size: 1.5rem; }
}

@layer utilities {
.text-sm { font-size: 0.875rem; }
}


Порядок объявления определяет приоритет: utilities всегда выигрывает у components, даже если у components селектор тяжелее. Никакой войны специфичностей.

Сторонние стили оборачиваешь в свой слой - они становятся самыми слабыми:


@import url('vendor.css') layer(third-party);


Теперь любой твой стиль вне @layer их перебивает, потому что стили вне слоев имеют наивысший приоритет.

Поддержка: Chrome 99, Firefox 97, Safari 15.4 - все браузеры с марта 2022.

Дока: https://developer.mozilla.org/en-US/docs/Web/CSS/@layer
Recorder в Chrome DevTools

Панель, которая записывает действия на странице и потом повторяет их сама. Появилась еще в Chrome 97, но до сих пор живет в тени: прячется в меню три точки → More tools → Recorder.

Раньше для такого поднимали Selenium или писали Puppeteer-скрипты руками. Теперь жмешь Start recording, прокликиваешь сценарий - логин, добавление в корзину, отправка формы - и останавливаешь запись. Все клики, вводы и переходы превращаются в список шагов.

Что умеет:
- Replay - браузер сам прогоняет сценарий, шаг за шагом
- реплей с троттлингом - в Replay settings выбираешь Slow 3G и смотришь, как флоу переживает медленную сеть
- Measure performance - прогоняет сценарий и открывает трейс в Performance панели. Перф-аудит не одной страницы, а целого флоу
- шаги редактируются: можно удалить лишний, добавить свой, поменять селектор или значение

Экспорт - самое вкусное:
- Puppeteer-скрипт: записанный флоу становится автотестом, ни одной строчки руками
- JSON: сохранил, передал коллеге, импортировал обратно
- расширения из Chrome Web Store добавляют экспорт в Cypress, Nightwatch, WebdriverIO

Кейсы:
- записал репро бага один раз - гоняешь реплей после каждого фикса, руками не прокликиваешь
- ловишь баги, которые вылезают только на медленной сети
- отдаешь тестерам готовый сценарий вместо описания "кликни туда, потом туда"

Ограничения:
- селекторы записываются автоматически и бывают хрупкими: поменялась верстка - шаг упал. Лечится своим атрибутом: в настройках записи указываешь data-testid, селекторы строятся по нему
- панель есть в Chrome и Edge, в Firefox и Safari такого нет

Дока: https://developer.chrome.com/docs/devtools/recorder?hl=ru
scrollbar-color и scrollbar-width

Стандартные CSS-свойства для стилизации скроллбара. Раньше это делали через зоопарк из псевдоэлементов ::-webkit-scrollbar.

Теперь два свойства:


.scroll-area {
scrollbar-color: #6366f1 transparent;
scrollbar-width: thin;
}


scrollbar-color: первое значение красит ползунок, второе - дорожку.
scrollbar-width: auto, thin или none. none скрывает скроллбар совсем, но скролл продолжает работать.

Подводный камень, если у вас уже есть webkit-стилизация

С Chrome 121 стандартные свойства перебивают ::-webkit-scrollbar. Добавили scrollbar-color где-нибудь в общих стилях - и вся старая стилизация с border-radius и hover-эффектами молча отключилась. В январе 2024 так сломалась куча сайтов.

Если webkit-версию нужно оставить, стандартные свойства заворачивают в supports:


@supports not selector(::-webkit-scrollbar) {
html { scrollbar-color: #6366f1 transparent; }
}


Так они применятся только там, где webkit-псевдоэлементов нет, то есть в Firefox.

Ограничения

- умеют меньше, чем webkit-псевдоэлементы: только цвет и ширина. Ни border-radius, ни градиентов, ни отдельного стиля на hover
- на macOS скроллбары overlay, система показывает их только во время прокрутки. И в Safari на macOS scrollbar-color по репортам работает криво даже в свежих версиях, в MDN висит открытый issue
- в наборе есть и третье свойство: scrollbar-gutter, резервирует место под скроллбар, чтобы контент не дергался при его появлении

Поддержка

Firefox с 64 версии, аж с 2018. Chrome и Edge с 121 (январь 2024). Safari догнал последним: scrollbar-width в 18.2 (декабрь 2024), scrollbar-color только в 26.2 (декабрь 2025). На старых браузерах деградация мягкая: свойство игнорируется, скроллбар остается дефолтным.
🔥1
document.designMode

Одна строчка в консоли превращает всю страницу в редактируемый документ:


document.designMode = 'on'


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

Зачем на практике

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

Детали

- выключается так же: document.designMode = 'off'
- перезагрузка стирает все правки, это чистый DOM без сохранения
👍3
HAR-файл

Это весь сетевой трафик вкладки - запросы, ответы, заголовки, тайминги - в одном JSON.

Экспорт

Network → кнопка со стрелкой вниз (Export HAR). Перед записью стоит включить Preserve log, чтобы трафик не стерся при редиректах и перезагрузках.

Импорт

Самая недооцененная часть: HAR грузится обратно. Import HAR или просто перетащить файл в Network - и видно трафик чужого бага у себя в DevTools: статусы, тела ответов, тайминги, водопад. Разбираться можно прямо на месте.

Что не попадет

Запись идет, только пока открыта панель Network: если баг воспроизвели до открытия DevTools, файл будет пустой. Сначала панель и Preserve log, потом ловим баг.

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

Консольные логи туда тоже не попадают, хотя многие уверены в обратном. Ошибки из консоли к баг-репорту прикладывать отдельно: правый клик в Console → Save as.

Безопасность

Раньше HAR писал куки и заголовки авторизации открытым текстом: такой файл в публичном тикете сливал данные. С Chrome 130 экспорт по умолчанию вычищает чувствительные данные, полная версия включается отдельной настройкой в Preferences → Network.

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

HAR как моки в автотестах

Playwright умеет проигрывать HAR вместо живого бэкенда:


await page.routeFromHAR('./hars/api.har', {
url: '**/api/**',
});


С опцией update: true запишет свежий HAR при прогоне, без нее отдает ответы из файла. Тесты гоняются на реальных, но замороженных данных. Записать HAR можно и через CLI: npx playwright open --save-har=api.har

Чем открыть без DevTools

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

Пересечение двух списков каждый пишет по-своему: filter + includes на массивах, [...a].filter(x => b.has(x)) через Set, кто-то тащит lodash ради _.intersection. Вариант с includes еще и квадратичный, на больших списках заметно. Теперь у Set есть семь встроенных методов, и это уже Baseline.

Четыре возвращают новый Set:


const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);

a.intersection(b); // Set {2, 3}
a.union(b); // Set {1, 2, 3, 4}
a.difference(b); // Set {1}
a.symmetricDifference(b); // Set {1, 4}


Три отвечают boolean:


const required = new Set(['read', 'write']);
const granted = new Set(['read', 'write', 'admin']);

required.isSubsetOf(granted); // true
granted.isSupersetOf(required); // true

// a и b из примера выше
a.isDisjointFrom(b); // false, есть общие


Раньше проверку прав писали как [...required].every(p => granted.has(p)): тоже одна строчка, но Set приходится разворачивать в массив, а isSubsetOf читается как обычное предложение.

Тонкости

Методы не мутируют исходные сеты, всегда возвращается новый.

Аргументом может быть не только Set, но и set-like объект: нужны свойство size и методы has() и keys(). Map подходит - элементами результата станут его ключи. А вот массив не подходит: у него нет ни size, ни has(), будет TypeError. Сначала new Set(arr), потом операции.

Сравнение элементов - SameValueZero, как и везде в Set: NaN считается равным NaN, а объекты сравниваются по ссылке. Два одинаковых по содержимому объекта - разные элементы.

Поддержка

Chrome и Edge со 122, Safari с 17, Firefox со 127 - с него, с июня 2024, это Baseline. В Node с 22 версии. Типы в TypeScript начиная с 5.5.

Важно для прода: это рантайм-фича, Babel и TypeScript ее не транспилируют. Билд соберется, типы сойдутся, а на старом устройстве будет TypeError. Главная зона риска - айфоны на iOS 16: Safari 17 туда не приходит. Если такие есть в аналитике проекта - только полифил из core-js.
🔥1
GitLab MR Actions 1.10.3 и 1.11.0

Вышло 2 версии подряд, были сложности с публикацией, google долго апрувил. Пачка фиксов против зависаний и новая кнопка в графе пайплайна.

1.11.0 - запуск всех manual-джоб стейджа

У GitLab есть родная кнопка "Run all manual" у стейджа, но она пропадает, как только запускаешь хоть одну джобу из группы. Дальше остальные кликаешь по одной.

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

Настройка "Кнопка запуска группы в пайплайне", включена по умолчанию.

1.10.3 - фиксы зависаний

Главная боль: вечный лоадер в Дневной активности. Отчет мог крутиться бесконечно, если фоновая вкладка GitLab заморожена (Chrome Memory Saver) или ее content script не отвечает. Теперь прокси API предпочитает вкладку, из которой открыт отчет, ставит таймаут 20 секунд на чтение и пропускает мертвые вкладки вместо ожидания.

Остальное:
• Таймаут: генерация отчета ограничена 180 секундами, при превышении - понятная ошибка вместо бесконечного спиннера
- Любая ошибка Jira-обогащения теперь показывает обычный отчет без Jira-аннотаций, а не вешает все. Regex, совпадающий с пустой строкой, больше не может заморозить расширение
- Та же логика приоритета вкладки и таймаутов применена ко всем фоновым API-вызовам: ребейз, Ship, цепочки джоб. Протухшая вкладка GitLab их больше не стопорит
- Мутирующие запросы (запуск и перезапуск джобы) не переотправляются на другую вкладку при неоднозначной ошибке доставки - джоба не может запуститься дважды. Вместо этого через 60 секунд показывается понятная ошибка

Chrome Web Store
GitHub
Иммутабельные методы массивов: toSorted, toReversed, toSpliced, with

Каждый, кто пишет на React, хоть раз писал [...items].sort(...), потому что sort() мутирует исходный массив. Забыл скопировать - стейт изменился под ногами, ре-рендер не случился. В ES2023 добавили копирующие версии, и с января 2026 они официально Baseline widely available.

Четыре метода, все возвращают новый массив:


const prices = [30, 10, 20];

prices.toSorted((a, b) => a - b);
// [10, 20, 30]
prices.toReversed();
// [20, 10, 30]
prices.toSpliced(1, 1);
// [30, 20]
prices.with(0, 99);
// [99, 10, 20]

prices; // [30, 10, 20] - не тронут


with() - копирующая версия arr[i] = x. Отрицательные индексы работают как в at(): prices.with(-1, 0) заменит последний элемент.

Раньше выкручивались так: [...arr].sort(), arr.slice().reverse(), а замена элемента - arr.map((el, i) => i === 2 ? newEl : el). Работает, но копию легко забыть, и линтер такое ловит не всегда.

Тонкости

- toSorted без компаратора сортирует как строки, ровно как sort: [10, 1, 2].toSorted() даст [1, 10, 2]. Компаратор все еще нужен.
- toSpliced возвращает новый массив уже без вырезанных элементов. У splice наоборот: возвращает вырезанное, а исходник мутирует. При переезде со splice легко перепутать.
- with() с несуществующим индексом кидает RangeError. arr[100] = x молча растянул бы массив до 101 элемента, with такого не позволяет.
- Копия неглубокая: объекты внутри остаются теми же, по ссылке.
- Дырки в разреженных массивах превращаются в undefined, разреженность методы не сохраняют.

Поддержка

Chrome и Edge со 110, Safari с 16, Firefox со 115 - Baseline с июля 2023, с 4 января 2026 widely available. В Node с 20 версии, типы в TypeScript с 5.2 (lib es2023).

Как и с методами Set: это рантайм-фича, Babel и TypeScript ее не транспилируют. Зона риска - iOS 15 и старше, то есть айфоны до восьмерки. Если такие есть в аналитике - полифил из core-js.

Бонус: те же методы есть у типизированных массивов, кроме toSpliced - у Uint8Array и компании длина фиксированная.
👍1
Мой фикс попал в Electron

Вчера смержили мой PR в Electron: https://github.com/electron/electron/pull/51817

Суть бага. app.disableHardwareAcceleration() должен полностью выключать работу с GPU. Внутри он дергает GpuDataManager::DisableHardwareAcceleration(), и раньше этого хватало. Но с Chromium 139 (это Electron 38) часть подсистем Chromium смотрит не в состояние GpuDataManager, а напрямую в флаги командной строки. Флага --disable-gpu там нет, и GPU-процесс продолжает жить своей жизнью: у меня на Windows он держал около 10% GPU и 375 МБ видеопамяти при формально выключенном ускорении.

Фикс на девять строчек: при вызове disableHardwareAcceleration() Electron теперь сам дописывает --disable-gpu в командную строку, чтобы все ветки Chromium видели одно и то же.

Пока апдейт не доехал до вашей версии, воркэраунд:


app.disableHardwareAcceleration();
app.commandLine.appendSwitch('disable-gpu');


Фикс уйдет в Electron 45, бэкпорты в 41-44 уже в пути. Для 38-40 остается воркэраунд.
🔥21
npm 12 выходит в июле, и это самый жесткий по безопасности релиз за годы.

После года supply chain атак через npm-пакеты GitHub закрывает главную дыру: код зависимостей больше не запускается сам при установке.

Что меняется

Install-скрипты выключены по умолчанию. preinstall, install и postinstall у зависимостей не запускаются, пока их явно не разрешишь. Под блокировку попадают и неявные сборки node-gyp: пакет с binding.gyp без единого скрипта тоже не соберется сам. И prepare-скрипты у git-, file- и link-зависимостей.

Разрешать точечно:


npm approve-scripts --allow-scripts-pending
# посмотреть, что заблокировано

npm approve-scripts / npm deny-scripts
# управлять списком


Список разрешенных пакетов живет в package.json и коммитится в репозиторий.

Git-зависимости заблокированы. --allow-git по умолчанию none, транзитивные тоже. Причина показательная: .npmrc внутри git-зависимости мог подменить сам git-бинарник, и --ignore-scripts от этого не спасал.

Зависимости по URL тоже. Тарболы по https не резолвятся без --allow-remote. --allow-file и --allow-directory не тронули.

Как подготовиться

Все это уже есть в npm 11.16.0+ в виде варнингов. Обновись, прогони обычный npm install и посмотри, что заругается. Дальше approve-scripts на доверенные пакеты и коммит package.json.

Про токены

Заодно хоронят granular access tokens с обходом 2FA: с августа они теряют доступ к чувствительным операциям с аккаунтом и пакетами, а с января 2027 ими нельзя будет публиковать. Останутся trusted publishing (OIDC) или публикация с подтверждением по 2FA.

Подробности: https://github.blog/changelog/2026-06-09-upcoming-breaking-changes-for-npm-v12/
🔥1