🚀 Как сделать виртуальный скролл на чистом CSS?
Все привыкли: если нужно вывести список из 10 000 элементов, мы тянем тяжелые JS-библиотеки вроде
Но в CSS уже есть нативная альтернатива, которая делает рендеринг огромных списков почти мгновенным.
Разбираемся, как работает
──
🤔 Как это работает?
Обычно браузер отрисовывает (расчитывает layout и paint) все элементы на странице сразу после загрузки, даже если они находятся в самом низу, далеко за пределами экрана. Это «вешает» процессор.
Свойство
Браузер буквально «пропускает» отрисовку элемента и расчет его геометрии, пока пользователь до него не доскроллит.
──
😬 Проблема прыгающего скролла
Если браузер не рисует элемент, он считает его высоту равной 0.
Из-за этого высота всей страницы кажется маленькой, а ползунок скролла — огромным.
Как только вы начинаете листать, элементы «распаковываются», страница резко удлиняется, и скроллбар начинает дико скакать.
Чтобы этого не было, нужно использовать свойство: contain-intrinsic-size.
──
✨ Пример
Этого достаточно, чтобы список из 50 000 строк перестал тормозить при загрузке.
──
🤷♂️ Но есть подвох (White Flashing)
Это не полноценная замена JS-виртуализации.
При очень быстром скролле (особенно на слабых устройствах) вы можете увидеть белые пустые места. Браузер просто не успевает «проснуться» и отрисовать контент вовремя.
В JS-библиотеках есть параметр overscan (рендерить заранее с запасом), в CSS его пока нет.
──
👏 Что и когда использовать?
▪️ CSS (
— Длинные статьи, ленты новостей, списки комментариев.
— Когда важна скорость загрузки (First Contentful Paint).
— Когда «белые вспышки» при бешеном скролле не критичны.
▪️ JS (Virtual Scroll):
— Сложные таблицы данных (Data Grids).
— Когда нужен идеальный UX без мелькания пустоты.
— Когда элементы имеют сложную зависимую логику.
──
🌐 Поддержка браузерами
▫️ Chrome 85
▫️ Safari 18
▫️ Firefox 125
──
✅ Вывод
Это не полная замена виртуализации, но для 90% типичных задач (вроде бесконечной ленты) работает отлично.
──
👉 Пример на CodePen:
https://codepen.io/webeach/pen/ZYWmxZa
#frontend #css #javascript
Все привыкли: если нужно вывести список из 10 000 элементов, мы тянем тяжелые JS-библиотеки вроде
react-window или react-virtualized.Но в CSS уже есть нативная альтернатива, которая делает рендеринг огромных списков почти мгновенным.
Разбираемся, как работает
content-visibility и в чем подвох 👇──
🤔 Как это работает?
Обычно браузер отрисовывает (расчитывает layout и paint) все элементы на странице сразу после загрузки, даже если они находятся в самом низу, далеко за пределами экрана. Это «вешает» процессор.
Свойство
content-visibility: auto включает «ленивый рендеринг» для любого HTML-элемента.Браузер буквально «пропускает» отрисовку элемента и расчет его геометрии, пока пользователь до него не доскроллит.
──
😬 Проблема прыгающего скролла
Если браузер не рисует элемент, он считает его высоту равной 0.
Из-за этого высота всей страницы кажется маленькой, а ползунок скролла — огромным.
Как только вы начинаете листать, элементы «распаковываются», страница резко удлиняется, и скроллбар начинает дико скакать.
Чтобы этого не было, нужно использовать свойство: contain-intrinsic-size.
──
✨ Пример
.list-item {
/* 1. Не рисуем элемент, пока он вне экрана */
content-visibility: auto;
/* 2. Задаем примерную высоту (40px) */
/* auto позволяет браузеру запомнить реальный размер после первого рендера */
contain-intrinsic-size: auto 40px;
}Этого достаточно, чтобы список из 50 000 строк перестал тормозить при загрузке.
──
🤷♂️ Но есть подвох (White Flashing)
Это не полноценная замена JS-виртуализации.
При очень быстром скролле (особенно на слабых устройствах) вы можете увидеть белые пустые места. Браузер просто не успевает «проснуться» и отрисовать контент вовремя.
В JS-библиотеках есть параметр overscan (рендерить заранее с запасом), в CSS его пока нет.
──
👏 Что и когда использовать?
▪️ CSS (
content-visibility):— Длинные статьи, ленты новостей, списки комментариев.
— Когда важна скорость загрузки (First Contentful Paint).
— Когда «белые вспышки» при бешеном скролле не критичны.
▪️ JS (Virtual Scroll):
— Сложные таблицы данных (Data Grids).
— Когда нужен идеальный UX без мелькания пустоты.
— Когда элементы имеют сложную зависимую логику.
──
🌐 Поддержка браузерами
content-visibility поддерживается всеми современными браузерами (глобальная поддержка — 91%).▫️ Chrome 85
▫️ Safari 18
▫️ Firefox 125
──
✅ Вывод
content-visibility: auto — это, пожалуй, самый простой способ ускорить рендеринг страницы одной строчкой кода.Это не полная замена виртуализации, но для 90% типичных задач (вроде бесконечной ленты) работает отлично.
──
👉 Пример на CodePen:
https://codepen.io/webeach/pen/ZYWmxZa
#frontend #css #javascript
❤7
🤔 Как правильно клонировать массив в JavaScript?
В этом посте мы поговорим о том, как оптимальнее всего поверхностно склонировать массив.
У нас есть 3 самых популярных способа сделать это:
▫️ через spread →
▫️ через concat →
▫️ через slice →
Мы провели замеры клонирования массива на 100к элементов в 1000 итераций на каждом браузерном движке, используя обычный
Наш первичный код:
Код теста:
Вокруг каждого цикла вызываем
⚙️ Железо: MacOS 15.6 / M4 Pro
──
🏁 Что показали тесты в браузерах:
🔹 Chrome (V8)
▫️ spread → ~1120ms
▫️ concat → ~44ms
▫️ slice → ~41ms
Spread медленнее, чем concat и slice, аж в 27 раз!
Почему? Потому что при клонировании через spread V8 создает итератор, проходится по каждому элементу и добавляет его в новый массив. Slice же использует быструю копию памяти.
🔹 Firefox (SpiderMonkey)
▫️ spread → ~366ms
▫️ concat → ~50ms
▫️ slice → ~18ms
SpiderMonkey справился быстрее всех!
Это значит, что он не копировал данные физически. Он использовал механизм COW (Copy-on-Write) или "ленивые массивы". Он просто создал "ссылку" на старые данные и сказал: "Скопирую по-настоящему только тогда, когда ты захочешь что-то изменить".
🔹 Safari (JavaScriptCore)
▫️ spread → ~300ms
▫️ concat → ~1055ms
▫️ slice → ~1800ms
В Safari (JavaScriptCore) явно есть специальная оптимизация для оператора Spread. Инженеры Apple видят, что весь современный фронтенд написан на спредах, и оптимизировали именно этот путь.
А вот
──
💁♂️ Так какой способ выбрать?
На простых задачах разницы нет никакой, вы просто её не почувствуете.
Но если вы работаете с большими данными (миллионы элементов), то:
▪️ Есть смысл использовать старый-добрый
▪️ Либо написать функцию-утилиту, которая проведет микро-тест при запуске и выберет оптимальный метод для браузера пользователя (пример).
#frontend #javascript
В этом посте мы поговорим о том, как оптимальнее всего поверхностно склонировать массив.
У нас есть 3 самых популярных способа сделать это:
▫️ через spread →
[...arr]▫️ через concat →
[].concat(arr)▫️ через slice →
arr.slice()Мы провели замеры клонирования массива на 100к элементов в 1000 итераций на каждом браузерном движке, используя обычный
console.time().Наш первичный код:
const sourceArray = Array(100_000).fill(0);
// создаем массив на 100к элементов и заполняем "нулями"
Код теста:
// spread
for (let i = 0; i < 1_000; i++) {
([...sourceArray]);
}
// concat
for (let i = 0; i < 1_000; i++) {
[].concat(sourceArray);
}
// slice
for (let i = 0; i < 1_000; i++) {
sourceArray.slice();
}
Вокруг каждого цикла вызываем
console.time() и console.timeEnd().⚙️ Железо: MacOS 15.6 / M4 Pro
──
🏁 Что показали тесты в браузерах:
🔹 Chrome (V8)
▫️ spread → ~1120ms
▫️ concat → ~44ms
▫️ slice → ~41ms
Spread медленнее, чем concat и slice, аж в 27 раз!
Почему? Потому что при клонировании через spread V8 создает итератор, проходится по каждому элементу и добавляет его в новый массив. Slice же использует быструю копию памяти.
🔹 Firefox (SpiderMonkey)
▫️ spread → ~366ms
▫️ concat → ~50ms
▫️ slice → ~18ms
SpiderMonkey справился быстрее всех!
Это значит, что он не копировал данные физически. Он использовал механизм COW (Copy-on-Write) или "ленивые массивы". Он просто создал "ссылку" на старые данные и сказал: "Скопирую по-настоящему только тогда, когда ты захочешь что-то изменить".
🔹 Safari (JavaScriptCore)
▫️ spread → ~300ms
▫️ concat → ~1055ms
▫️ slice → ~1800ms
В Safari (JavaScriptCore) явно есть специальная оптимизация для оператора Spread. Инженеры Apple видят, что весь современный фронтенд написан на спредах, и оптимизировали именно этот путь.
А вот
slice в Safari на больших объемах работает ужасно. Скорее всего, Safari честно аллоцирует новую память и копирует данные, в то время как V8 и Firefox используют хитрости.──
💁♂️ Так какой способ выбрать?
На простых задачах разницы нет никакой, вы просто её не почувствуете.
Но если вы работаете с большими данными (миллионы элементов), то:
▪️ Есть смысл использовать старый-добрый
slice() (быстрее везде, кроме Safari).▪️ Либо написать функцию-утилиту, которая проведет микро-тест при запуске и выберет оптимальный метод для браузера пользователя (пример).
#frontend #javascript
1👍6❤2
🔥 Представляю всем мою новую библиотеку небольшого расширения React
Вдохновился
──
🤔 Зачем это нужно?
Если ты устал от:
▫️
▫️ сложных
▫️ трудной работы с css-переменными при разработке UI-компонентов
…то react-x закрывает эти боли прямо на уровне JSX.
──
✨ Возможности
▪️ Удобная работа с классами
▪️ Работа с inline-стилями теперь проще и читабельнее:
▪️ CSS-переменные больше не "боль"
в стилях:
──
📦 Установка
──
📚 Документация
Полная документация и примеры доступны на английском и на русском языках:
👉 https://github.com/webeach/react-x
#frontend #javascript #react
react-x — небольшая либа, которая прокачивает JSX для работы с CSS-классами, стилями и CSS-переменными без лишнего шума, хаков и кастомных хелперов.Вдохновился
svelte 🙃──
🤔 Зачем это нужно?
Если ты устал от:
▫️
clsx(className, condition && styles.container)▫️ сложных
style объектов▫️ трудной работы с css-переменными при разработке UI-компонентов
…то react-x закрывает эти боли прямо на уровне JSX.
──
✨ Возможности
▪️ Удобная работа с классами
<x.div
class:container
class:disabled={isDisabled}
/>
// или
<x.div
classList={['container', isDisabled && 'disabled']}
/>
▪️ Работа с inline-стилями теперь проще и читабельнее:
<x.div
style:backgroundColor="blue"
style:width={100}
style:height={100}
/>
▪️ CSS-переменные больше не "боль"
<x.div
class:container
var:bg="blue"
var:size="100px"
/>
в стилях:
.container {
background-color: var(--bg);
width: var(--size);
height: var(--size);
}──
📦 Установка
npm install @webeach/react-x
──
📚 Документация
Полная документация и примеры доступны на английском и на русском языках:
👉 https://github.com/webeach/react-x
#frontend #javascript #react
1🔥4
🤔 Почему Math.random() опасен?
Все знают фразу: «не используй Math.random() для безопасности».
Но обычно никто не объясняет почему именно.
Давайте разберем конкретный механизм и реальный сценарий проблемы.
──
💠 Как Math.random() работает под капотом
В большинстве окружений Node.js это движок V8.
И начиная примерно с 2015 года V8 использует PRNG-алгоритм xorshift128+.
Он хранит внутреннее состояние из двух 64-битных чисел:
▫️
▫️
Каждый вызов
1) обновляет состояние
2) возвращает число, построенное на этом состоянии
Упрощённо это выглядит так:
❗️ Math.random() — это не «настоящая случайность», а всего лишь псевдослучайный генератор.
──
🫠 Так где же проблема?
Проблема в том, что если злоумышленник "разгадает":
▫️
▫️
…то он сможет предсказывать все будущие
То есть «рандом» превращается в предсказуемую последовательность.
──
🧩 Разве можно восстановить состояние?
Да. И это самое неприятное.
Если злоумышленнику известны несколько последних значений
Идея такая:
1) мы берем последовательность значений
2) подаем её в солвер
3) просим найти такие
Если солвер находит решение — всё, поток случайных чисел сломан.
──
🔻 Мини-демо
Я сделал небольшой скрипт на python, который предсказывает
👉 https://github.com/webeach-learn/math-random-predictor
Сгенерируйте 5 значений на Node.js:
И вставьте в скрипт
Запустите скрипт и он предскажет будущий
──
😱 Реальный сценарий атаки
Представим типичный функционал сброса пароля на сайте:
Сайт генерирует токен так:
И отправляет пользователю ссылку:
Теперь злоумышленник делает следующее:
1️⃣ отправляет 5 запросов на сброс пароля (на свою почту)
2️⃣ получает 5 токенов, сгенерированных сервером
3️⃣ восстанавливает внутреннее состояние
4️⃣ предсказывает следующие токены, которые сервер сгенерирует уже для других пользователей
В итоге злоумышленник может перехватить чужой сброс пароля и забрать чужой аккаунт.
──
✅ Вывод
Для токенов, паролей, reset-ссылок, session-id и всего «секьюрного» используйте криптографический генератор:
──
⚠️ Важно
Эта информация предоставлена только в образовательных целях — не используйте подобные техники для взлома, эксплуатации уязвимостей или получения доступа к чужим аккаунтам. Используйте это знание, чтобы делать свои системы безопаснее.
#frontend #javascript #security
Все знают фразу: «не используй Math.random() для безопасности».
Но обычно никто не объясняет почему именно.
Давайте разберем конкретный механизм и реальный сценарий проблемы.
──
💠 Как Math.random() работает под капотом
В большинстве окружений Node.js это движок V8.
И начиная примерно с 2015 года V8 использует PRNG-алгоритм xorshift128+.
Он хранит внутреннее состояние из двух 64-битных чисел:
▫️
state0▫️
state1Каждый вызов
Math.random():1) обновляет состояние
2) возвращает число, построенное на этом состоянии
Упрощённо это выглядит так:
let state0 = 1n; // неизвестно
let state1 = 2n; // неизвестно
function xorshift128plus() {
let s1 = state0
let s0 = state1
state0 = s0
s1 ^= (s1 << 23n) & 0xFFFFFFFFFFFFFFFFn
s1 ^= s1 >> 17n
s1 ^= s0
s1 ^= s0 >> 26n
state1 = s1
return (state0 + state1) & 0xFFFFFFFFFFFFFFFFn
}
❗️ Math.random() — это не «настоящая случайность», а всего лишь псевдослучайный генератор.
──
🫠 Так где же проблема?
Проблема в том, что если злоумышленник "разгадает":
▫️
state0▫️
state1…то он сможет предсказывать все будущие
Math.random()То есть «рандом» превращается в предсказуемую последовательность.
──
🧩 Разве можно восстановить состояние?
Да. И это самое неприятное.
Если злоумышленнику известны несколько последних значений
Math.random(), то он может восстановить внутреннее состояние через SMT-солвер (например, Z3 Microsoft Research).Идея такая:
1) мы берем последовательность значений
Math.random()2) подаем её в солвер
3) просим найти такие
state0/state1, которые могли сгенерировать эту последовательностьЕсли солвер находит решение — всё, поток случайных чисел сломан.
──
🔻 Мини-демо
Я сделал небольшой скрипт на python, который предсказывает
Math.random()👉 https://github.com/webeach-learn/math-random-predictor
Сгенерируйте 5 значений на Node.js:
console.log([...Array(5)].map(() => Math.random()))
И вставьте в скрипт
main.py.Запустите скрипт и он предскажет будущий
Math.random() вашего Node.js процесса.──
😱 Реальный сценарий атаки
Представим типичный функционал сброса пароля на сайте:
Сайт генерирует токен так:
const token = Math.random().toString(36).slice(2)
И отправляет пользователю ссылку:
https://some-site.com/reset-password?token=...
Теперь злоумышленник делает следующее:
1️⃣ отправляет 5 запросов на сброс пароля (на свою почту)
2️⃣ получает 5 токенов, сгенерированных сервером
3️⃣ восстанавливает внутреннее состояние
Math.random()const sourceRandom = parseInt(hash, 36) / Math.pow(36, hash.length)
4️⃣ предсказывает следующие токены, которые сервер сгенерирует уже для других пользователей
В итоге злоумышленник может перехватить чужой сброс пароля и забрать чужой аккаунт.
──
✅ Вывод
Для токенов, паролей, reset-ссылок, session-id и всего «секьюрного» используйте криптографический генератор:
import { randomBytes } from 'node:crypto';
const token = randomBytes(32).toString('hex');Math.random() используйте только на клиенте, например, для каких-либо "случайных" значений в UI.──
⚠️ Важно
Эта информация предоставлена только в образовательных целях — не используйте подобные техники для взлома, эксплуатации уязвимостей или получения доступа к чужим аккаунтам. Используйте это знание, чтобы делать свои системы безопаснее.
#frontend #javascript #security
1❤6👍4
🧐
Если вы когда-нибудь работали с сокетами, слушателями событий или таймерами, вы знаете эту боль: открыл ресурс — будь добр закрой его в блоке finally. Иначе получите утечку памяти.
Но начиная со стандарта ES2025 появилось ключевое слово
──
👎 Как мы писали раньше:
👍 Как пишем теперь:
──
🧩 Как это работает под капотом?
Никакой магии. Когда переменная, объявленная через
──
🤔 А если очистка асинхронная?
Для баз данных или веб-сокетов есть
──
🫡 На практике
Пока что фича совсем свежая. В старых браузерах понадобится полифил.
Но если вы пишете на TypeScript (начиная с версии 5.2) — эта магия уже доступна из коробки!
Чтобы всё заработало, достаточно добавить одну настройку в ваш
Для Node.js серверов это вообще абсолютный мастхэв, который делает код чище раза в два и страхует от типичных ошибок 😌
──
⚠️ Важный нюанс
Переменная, объявленная через
#frontend #javascript #typescript
using элегантная замена try...finally?Если вы когда-нибудь работали с сокетами, слушателями событий или таймерами, вы знаете эту боль: открыл ресурс — будь добр закрой его в блоке finally. Иначе получите утечку памяти.
Но начиная со стандарта ES2025 появилось ключевое слово
using, которое решает эту проблему элегантно и без лишнего кода.──
👎 Как мы писали раньше:
const file = openFile('data.txt');
try {
file.write('hello');
} finally {
file.close(); // рутина, которую легко забыть
}👍 Как пишем теперь:
function writeData() {
using file = openFile('data.txt');
file.write('hello');
} // При выходе из функции файл закроется САМ!──
🧩 Как это работает под капотом?
Никакой магии. Когда переменная, объявленная через
using, выходит из области видимости (заканчивается блок { ... }), то движок автоматически вызывает у этого объекта специальный метод — [Symbol.dispose]().class TempLogger {
constructor() {
this.timer = setInterval(() => console.log('tick'), 1000);
}
[Symbol.dispose]() {
clearInterval(this.timer);
console.log('Очищено!');
}
}
{
using logger = new TempLogger();
// ... делаем дела
} // тут в консоль упадет 'Очищено!' и таймер умрет──
🤔 А если очистка асинхронная?
Для баз данных или веб-сокетов есть
await using. Он ищет метод [Symbol.asyncDispose]() и честно ждёт (await), пока ресурс закроется, прежде чем отпустить поток дальше.async function dbWork() {
await using tx = new Transaction();
await tx.execute('UPDATE...');
// Если тут упадет ошибка, транзакция сама откатится перед выходом!
}──
🫡 На практике
Пока что фича совсем свежая. В старых браузерах понадобится полифил.
Но если вы пишете на TypeScript (начиная с версии 5.2) — эта магия уже доступна из коробки!
Чтобы всё заработало, достаточно добавить одну настройку в ваш
tsconfig.json:{
"compilerOptions": {
"lib": ["esnext.disposable"]
}
}Для Node.js серверов это вообще абсолютный мастхэв, который делает код чище раза в два и страхует от типичных ошибок 😌
──
⚠️ Важный нюанс
Переменная, объявленная через
using, строго иммутабельна и ведёт себя как const. Вы не можете случайно её переопределить в процессе:using db = connect();
db = newConnection(); // ❌ TypeError: Assignment to constant variable.
#frontend #javascript #typescript
❤6
🔥 Представляю всем ECSS — Extended CSS
Новый язык, который расширяет CSS тремя конструкциями для декларативного управления состояниями компонентов прямо в файлах стилей.
А точнее — полноценную экосистему:
— Сайт документации: https://ecss.webea.ch
— Исходники (Rust-парсер, Vite-плагин, TS-плагин и расширение): https://github.com/webeach/ecss
──
Зачем нужен ECSS?
Вы наверняка уставали писать
В ECSS логика остаётся в стилях, а в компонентах нет JS-классов:
А в компоненте вы просто передаёте пропсы (работает с React, Vue, Svelte, SolidJS):
──
🧠 Философия: новый уровень Separation of Concerns
С архитектурной точки зрения, ECSS предлагает переосмыслить сам интерфейс взаимодействия между логикой и представлением.
Вместо того чтобы заставлять компонент управлять визуальным «автоматом состояний», вы переносите ответственность туда, где она и должна быть — в стили. Вся логика вида «если состояние X, то вид Y» теперь инкапсулирована внутри
Компонент перестает быть контроллером визуальных состояний и превращается в простого поставщика данных.
Для архитектора это означает:
— Радикальное снижение когнитивной нагрузки при чтении кода.
— Упрощение юнит-тестирования.
— Компоненты становятся «чище», так как из них исчезает мусорная логика, относящаяся исключительно к визуализации.
──
💎 Фичи и инструменты
— Написано на Rust: супер-быстрый парсер (napi-rs).
— Без JS в компонентах:
— Полная типизация:
— VS Code расширение: подсветка синтаксиса, диагностика ошибок и подсказки типов при наведении.
──
📦 Установка
Для проекта на Vite:
──
⚛️ Пример использования в React
──
🟢 Пример использования в Vue
──
📚 Документация
Полная спецификация языка и руководства по интеграции со всеми популярными фреймворками (на русском и английском):
👉 https://ecss.webea.ch
──
⚠️ Это пока что первая альфа-версия, возможно будут изменения в синтаксисе.
#css #frontend #react #vue #svelte #solidjs #typescript
Новый язык, который расширяет CSS тремя конструкциями для декларативного управления состояниями компонентов прямо в файлах стилей.
А точнее — полноценную экосистему:
— Сайт документации: https://ecss.webea.ch
— Исходники (Rust-парсер, Vite-плагин, TS-плагин и расширение): https://github.com/webeach/ecss
──
Зачем нужен ECSS?
Вы наверняка уставали писать
className={clsx(styles.button, variant === 'primary' && styles.primary)}. Логика состояний размазывается между JS и CSS. Добавление варианта заставляет править оба файла. В ECSS логика остаётся в стилях, а в компонентах нет JS-классов:
@state-variant Variant {
values: primary, danger;
}
@state-def Button(--variant Variant: primary, --disabled boolean) {
padding: 8px 16px;
@if (--disabled) {
opacity: 0.5;
pointer-events: none;
}
@if (--variant == primary) {
background: blue;
}
}А в компоненте вы просто передаёте пропсы (работает с React, Vue, Svelte, SolidJS):
import styles from './Button.ecss'
<button {...styles.Button({ variant: 'primary' })}>
──
🧠 Философия: новый уровень Separation of Concerns
С архитектурной точки зрения, ECSS предлагает переосмыслить сам интерфейс взаимодействия между логикой и представлением.
Вместо того чтобы заставлять компонент управлять визуальным «автоматом состояний», вы переносите ответственность туда, где она и должна быть — в стили. Вся логика вида «если состояние X, то вид Y» теперь инкапсулирована внутри
.ecss файла через директивы @state-def и @if. Компонент перестает быть контроллером визуальных состояний и превращается в простого поставщика данных.
Для архитектора это означает:
— Радикальное снижение когнитивной нагрузки при чтении кода.
— Упрощение юнит-тестирования.
— Компоненты становятся «чище», так как из них исчезает мусорная логика, относящаяся исключительно к визуализации.
──
💎 Фичи и инструменты
— Написано на Rust: супер-быстрый парсер (napi-rs).
— Без JS в компонентах:
.ecss файлы компилируются в чистый CSS + крошечные функции-фабрики. Никакого рантайм-оверхеда.— Полная типизация:
@ecss/typescript-plugin на лету генерирует строгие типы для ваших стилей прямо в IDE.— VS Code расширение: подсветка синтаксиса, диагностика ошибок и подсказки типов при наведении.
──
📦 Установка
Для проекта на Vite:
npm install -D @ecss/vite-plugin @ecss/typescript-plugin
──
⚛️ Пример использования в React
import styles from './Button.ecss';
export function Button({ variant, disabled, children }) {
return (
<button {...styles.Button({ variant, disabled })}>
{children}
</button>
);
}
──
🟢 Пример использования в Vue
<script setup lang="ts">
import styles from './Button.ecss'
const { variant, disabled } = defineProps<{
variant?: 'primary' | 'danger'
disabled?: boolean
}>()
</script>
<template>
<button v-bind="styles.Button({ variant, disabled })">
<slot />
</button>
</template>
──
📚 Документация
Полная спецификация языка и руководства по интеграции со всеми популярными фреймворками (на русском и английском):
👉 https://ecss.webea.ch
──
⚠️ Это пока что первая альфа-версия, возможно будут изменения в синтаксисе.
#css #frontend #react #vue #svelte #solidjs #typescript
11🔥3🤯1
🛡 Представляю dist-guard — сканер секретов для бандлов
Инструмент, который проверяет ваши собранные файлы (dist, build) на утечки токенов, API-ключей, паролей и приватных данных — прямо перед деплоем.
──
🤔 Зачем это нужно?
Бандлеры (Vite, Webpack, Rollup) иногда тянут в production-сборку то, что не должно туда попасть. Переменные окружения, жёстко прописанные ключи, строки подключения к базам данных, локальные пути разработчика — всё это порой оказывается в dist/index.js в открытом виде.
dist-guard запускается после сборки и завершается с кодом 1, если находит что-то подозрительное — не давая деплою пройти.
──
💎 Что умеет
Проверяет более 50 типов утечек:
▪️ SaaS-токены: Stripe, GitHub, Slack, OpenAI, Figma, Supabase...
▪️ Cloud-ключи: AWS, GCP, Azure, DigitalOcean...
▪️ SSH/RSA/PGP приватные ключи
▪️ Строки подключения: PostgreSQL, MongoDB, MySQL, Redis...
▪️ Локальные пути разработчика: /Users/yourname/, C:\Users\yourname\
Найденные секреты автоматически маскируются в выводе, чтобы не засветить их в логах CI/CD.
──
🚀 Быстрый старт
──
🔧 Использование
Добавьте в npm scripts — запускается после сборки, блокирует деплой при утечке:
Или в GitHub Actions:
──
⚙️ Конфигурация
──
Подробное описание и документация в репозитории:
— https://github.com/webeach/dist-guard
#security #nodejs #cli #devtools #frontend #typescript
Инструмент, который проверяет ваши собранные файлы (dist, build) на утечки токенов, API-ключей, паролей и приватных данных — прямо перед деплоем.
──
🤔 Зачем это нужно?
Бандлеры (Vite, Webpack, Rollup) иногда тянут в production-сборку то, что не должно туда попасть. Переменные окружения, жёстко прописанные ключи, строки подключения к базам данных, локальные пути разработчика — всё это порой оказывается в dist/index.js в открытом виде.
dist-guard запускается после сборки и завершается с кодом 1, если находит что-то подозрительное — не давая деплою пройти.
──
💎 Что умеет
Проверяет более 50 типов утечек:
▪️ SaaS-токены: Stripe, GitHub, Slack, OpenAI, Figma, Supabase...
▪️ Cloud-ключи: AWS, GCP, Azure, DigitalOcean...
▪️ SSH/RSA/PGP приватные ключи
▪️ Строки подключения: PostgreSQL, MongoDB, MySQL, Redis...
▪️ Локальные пути разработчика: /Users/yourname/, C:\Users\yourname\
Найденные секреты автоматически маскируются в выводе, чтобы не засветить их в логах CI/CD.
──
🚀 Быстрый старт
npx @webeach/dist-guard
──
🔧 Использование
Добавьте в npm scripts — запускается после сборки, блокирует деплой при утечке:
{
"scripts": {
"build": "vite build",
"scan": "dist-guard",
"release": "npm run build && npm run scan && npm run deploy"
}
}Или в GitHub Actions:
- name: Security Scan
run: pnpm dist-guard
──
⚙️ Конфигурация
// package.json
{
"distGuard": {
"include": ["{dist,build}/**/*.*"],
"exclude": ["**/*.map"],
"ignoreRules": ["GenericAPIKey"]
}
}
──
Подробное описание и документация в репозитории:
— https://github.com/webeach/dist-guard
#security #nodejs #cli #devtools #frontend #typescript
🔥3
😅 Что такое «миры» (realms) в JavaScript?
По сути, «мир» (realm) в JS — это отдельная область выполнения кода. Она может существовать внутри вкладки/окна браузера или, например, внутри
Но иногда эти «миры» сталкиваются 💥
Давайте разберёмся на реальных примерах 👇
──
🔸 Айфреймовое проникновение
Теперь попробуем сравнить любые объекты/функции из iframe с текущим окном:
Всё будет
Следовательно, все созданные инстансы тоже не будут корректно сравниваться:
Здесь тоже будет
──
🔹 Реальный пример
Представим, что на странице есть виджет, подключённый через iframe (в рамках того же домена):
На основной странице есть конфиг, сгенерированный бэкендом:
Внутри виджета мы получаем этот конфиг через родительское окно:
Наша задача — пройтись по массиву
Но разработчик не доверяет бэкенду и добавляет проверку:
Как думаете, выполнится ли условие? 😌
Ответ — НИКОГДА. Массив создан в другом окружении, а
Именно поэтому instanceof Array считается плохой практикой. Вместо этого нужно использовать
Array.isArray работает корректно независимо от realm.
──
🔹 Ещё пример
Та же проблема возникает не только с iframe, но и с окнами.
Представим, что мы открываем новое окно через
──
🤔 А как быть с объектами?
Если для массивов есть
Но можно написать свою проверку:
#javascript #frontend
По сути, «мир» (realm) в JS — это отдельная область выполнения кода. Она может существовать внутри вкладки/окна браузера или, например, внутри
iframe.Но иногда эти «миры» сталкиваются 💥
Давайте разберёмся на реальных примерах 👇
──
🔸 Айфреймовое проникновение
// создаём iframe
const iframe = document.createElement('iframe');
document.body.append(iframe);
// объект window из iframe
const iframeWindow = iframe.contentWindow;
Теперь попробуем сравнить любые объекты/функции из iframe с текущим окном:
console.log(window.Array === iframeWindow.Array);
console.log(window.Object === iframeWindow.Object);
console.log(window.setTimeout === iframeWindow.setTimeout);
Всё будет
false, потому что iframe — это отдельный контекст выполнения JS, и все сущности там лежат в другой области памяти.Следовательно, все созданные инстансы тоже не будут корректно сравниваться:
// создаём объект внутри iframe
iframeWindow.eval('window.testObject = {};');
// получаем ссылку на него
const testObject = iframeWindow.testObject;
// проверяем принадлежность к Object
console.log(testObject instanceof Object);
Здесь тоже будет
false, потому что мы проверяем объект, созданный в другом окружении, через Object из текущего.──
🔹 Реальный пример
Представим, что на странице есть виджет, подключённый через iframe (в рамках того же домена):
<iframe class="my-widget" src="/widgets/my-widget.html"></iframe>
На основной странице есть конфиг, сгенерированный бэкендом:
<script>
window.CONFIG = {
data: {},
entities: [{ name: 'one', value: 1 }],
};
</script>
Внутри виджета мы получаем этот конфиг через родительское окно:
const rootConfig = window.top.CONFIG;
Наша задача — пройтись по массиву
entities и отрисовать список:rootConfig.entities.map((item) => { ... });Но разработчик не доверяет бэкенду и добавляет проверку:
if (rootConfig.entities instanceof Array) {
// ...
}Как думаете, выполнится ли условие? 😌
Ответ — НИКОГДА. Массив создан в другом окружении, а
Array — из текущего.Именно поэтому instanceof Array считается плохой практикой. Вместо этого нужно использовать
Array.isArray:if (Array.isArray(rootConfig.entities)) {
// ...
}Array.isArray работает корректно независимо от realm.
──
🔹 Ещё пример
Та же проблема возникает не только с iframe, но и с окнами.
Представим, что мы открываем новое окно через
window.open(...), где происходит авторизация:// основная вкладка
window.SOME_DATA = {};
const authWindow = window.open();
// код в новом окне
const data = window.opener.SOME_DATA;
if (data instanceof Object) {
// никогда не выполнится
}
──
🤔 А как быть с объектами?
Если для массивов есть
Array.isArray, то для объектов универсального метода нет. Это связано с тем, что понятие «объект» в JS довольно размыто.Но можно написать свою проверку:
function isPlainObject(object) {
if (object !== null && typeof object === 'object') {
const proto = Object.getPrototypeOf(object);
return (
// обычный объект из текущего контекста
proto === Object.prototype ||
// объект без прототипа (Object.create(null))
proto === null ||
// объект из другого realm (например, iframe)
// проверяем, что его прототип — базовый Object.prototype того мира
Object.getPrototypeOf(proto) === null
);
}
return false;
}#javascript #frontend
1❤5
😌 Что такое CSS FlexBox?
По сути, FlexBox — это система раскладки элементов в CSS, которая позволяет удобно управлять расположением блоков внутри контейнера.
Раньше для этого приходилось использовать
Но потом появился FlexBox — и верстать интерфейсы стало намного проще ✨
Давайте разберёмся на практике 👇
──
🔹 Включаем FlexBox
Допустим, у нас есть контейнер с карточками:
По умолчанию элементы будут идти сверху вниз:
Но если добавить:
то элементы сразу выстроятся в одну линию 😌
Потому что
──
🔹 Главная ось (
У FlexBox всегда есть главная ось (main axis).
По умолчанию она направлена слева направо:
Но направление можно поменять:
И тогда элементы будут идти сверху вниз.
Есть и другие варианты:
Они просто переворачивают порядок элементов.
──
🔹 Выравнивание по главной оси (
Теперь представим, что контейнер стал шире, чем элементы внутри.
Как распределить свободное место? 🤔
Для этого используется:
Элементы окажутся по центру.
Другие популярные значения:
💡 justify-content работает вдоль главной оси.
Если
──
🔹 Выравнивание по поперечной оси (
Теперь поговорим про вторую ось — cross axis.
Если главная ось идёт слева направо, то поперечная — сверху вниз.
Для управления выравниванием используется:
Элементы будут выровнены по центру поперечной оси.
Другие значения:
💡 align-items работает поперёк главной оси.
Это очень важно 😄
──
🔹 Перенос элементов (
По умолчанию FlexBox пытается уместить всё в одну строку.
Из-за этого элементы могут начать сжиматься:
Чтобы разрешить перенос строк:
Теперь элементы смогут переноситься на новую строку.
──
🔹 Размеры элементов (
По умолчанию
По умолчанию
А
Например:
В таком случае первый элемент (
💡 Все эти свойства можно записать сокращённо через:
Например:
Но многие разработчики считают сокращённое свойство
──
🔹 Почему FlexBox стал таким популярным?
Потому что он:
▫️ нормально центрирует элементы
▫️ умеет распределять пространство
▫️ хорошо адаптируется
▫️ избавляет от старых CSS-хаков
▫️ делает верстку гораздо предсказуемее
Сейчас почти любой современный UI использует FlexBox.
Но важно понимать:
💡 FlexBox — это инструмент для одномерной раскладки.
То есть либо строка, либо колонка.
А вот для сложных двумерных сеток уже лучше подходит CSS Grid.
──
🔖 Сохраняй, чтобы не забыть!
#frontend #css #flexbox
По сути, FlexBox — это система раскладки элементов в CSS, которая позволяет удобно управлять расположением блоков внутри контейнера.
Раньше для этого приходилось использовать
float, таблицы, хаки и страдания 😭Но потом появился FlexBox — и верстать интерфейсы стало намного проще ✨
Давайте разберёмся на практике 👇
──
🔹 Включаем FlexBox
Допустим, у нас есть контейнер с карточками:
<div class="container">
<div class="item">One</div>
<div class="item">Two</div>
<div class="item">Three</div>
</div>
По умолчанию элементы будут идти сверху вниз:
.item {
border: 1px solid black;
}Но если добавить:
.container {
display: flex;
}то элементы сразу выстроятся в одну линию 😌
Потому что
display: flex превращает элемент в flex-контейнер, а его дочерние элементы становятся flex-элементами.──
🔹 Главная ось (
flex-direction)У FlexBox всегда есть главная ось (main axis).
По умолчанию она направлена слева направо:
.container {
display: flex;
flex-direction: row;
}Но направление можно поменять:
.container {
flex-direction: column;
}И тогда элементы будут идти сверху вниз.
Есть и другие варианты:
flex-direction: row-reverse;
flex-direction: column-reverse;
Они просто переворачивают порядок элементов.
──
🔹 Выравнивание по главной оси (
justify-content)Теперь представим, что контейнер стал шире, чем элементы внутри.
Как распределить свободное место? 🤔
Для этого используется:
.container {
justify-content: center;
}Элементы окажутся по центру.
Другие популярные значения:
justify-content: flex-start;
justify-content: flex-end;
justify-content: space-between;
justify-content: space-around;
justify-content: space-evenly;
💡 justify-content работает вдоль главной оси.
Если
flex-direction: column, то выравнивание будет уже по вертикали.──
🔹 Выравнивание по поперечной оси (
align-items)Теперь поговорим про вторую ось — cross axis.
Если главная ось идёт слева направо, то поперечная — сверху вниз.
Для управления выравниванием используется:
.container {
align-items: center;
}Элементы будут выровнены по центру поперечной оси.
Другие значения:
align-items: flex-start;
align-items: flex-end;
align-items: stretch;
💡 align-items работает поперёк главной оси.
Это очень важно 😄
──
🔹 Перенос элементов (
flex-wrap)По умолчанию FlexBox пытается уместить всё в одну строку.
Из-за этого элементы могут начать сжиматься:
.container {
display: flex;
}Чтобы разрешить перенос строк:
.container {
flex-wrap: wrap;
}Теперь элементы смогут переноситься на новую строку.
──
🔹 Размеры элементов (
flex-grow, flex-shrink, flex-basis)flex-grow отвечает за коэффициент расширения элемента, если в контейнере есть свободное место.По умолчанию
flex-grow: 0, поэтому элементы сами по себе не растягиваются.flex-shrink отвечает за коэффициент сжатия элемента, если места не хватает.По умолчанию
flex-shrink: 1, поэтому элементы могут сжиматься.А
flex-basis задаёт базовый размер элемента до распределения свободного пространства.Например:
.item:nth-child(1) {
flex-grow: 2;
}
.item:nth-child(2) {
flex-grow: 1;
}
.item:nth-child(3) {
flex-grow: 1;
}В таком случае первый элемент (
One) получит в 2 раза больше свободного пространства, чем остальные элементы.💡 Все эти свойства можно записать сокращённо через:
flex: grow shrink basis;
Например:
flex: 1 1 auto;
Но многие разработчики считают сокращённое свойство
flex плохой практикой, потому что оно скрывает реальные значения и усложняет чтение кода 👀──
🔹 Почему FlexBox стал таким популярным?
Потому что он:
▫️ нормально центрирует элементы
▫️ умеет распределять пространство
▫️ хорошо адаптируется
▫️ избавляет от старых CSS-хаков
▫️ делает верстку гораздо предсказуемее
Сейчас почти любой современный UI использует FlexBox.
Но важно понимать:
💡 FlexBox — это инструмент для одномерной раскладки.
То есть либо строка, либо колонка.
А вот для сложных двумерных сеток уже лучше подходит CSS Grid.
──
#frontend #css #flexbox
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2