Обновить

Фронтенд

Сначала показывать
Порог рейтинга

Делимся записью докладов с нашего митапа «Вперед в будущее!»

Павел Варнавский, руководитель группы разработки «ДАР» (Корус Консалтинг), рассказал, как их команда использует BI Magic в своих проектах для создания мощных аналитических решений.

Смотреть выступление

В записи - примеры и разбор:

  • Как сделать дэшборд с уникальной визуализацией

  • Как внедрять CI/CD для дэшбордов и масштабировать решения под конкретные процессы, там, где стандартных «коробочных» решений не хватает

  • Два практических кейса, где кастомная разработка на Luxms BI решила нетипичные задачи

Будет интересно всем, кто работает с нестандартной аналитикой, сложными требованиями бизнеса и хочет понимать, как кастомная BI-разработка может быть управляемой и удобной

Теги:
0
Комментарии0

Представлен открытый веб-редактор изображений DPaint.js (онлайн-версия) на JavaScript, созданный по образцу легендарного Deluxe Paint, с упором на ретро-форматы файлов Amiga. Помимо современных форматов изображений, DPaint.js может читать и записывать файлы иконок Amiga и изображения IFF ILBM.

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

Теги:
+1
Комментарии0

Коллеги привет, искал себе решение как реагировать на изменения в объекте и нашел отличный сервис, который используется внутри директив таких как NgClass и NgStyle.

KeyValueDiffers позволяет создать KeyValueDiffer для сравнения изменений текущих пар ключ-значение с новыми. Если вы используете иммутабельные объекты, то можно просто обернуть все в эффект, ну а если вы наследники крутого легаси, где все объекты мутируются по ссылке, тогда проверку нужно вешать в DoCheck, чтобы реагировать на каждый тик change detection.

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

Иммутабельный с effect:


@Component({
  selector: 'app-test',
  template: ''
})
export class TestComponent {
  public state = input.required<Record<string, string | number>>();
  
  private differs = inject(KeyValueDiffers);
  private differ: KeyValueDiffer<string, string | number> | undefined;

  constructor() {
    effect(() => {
      const currentState = this.state();
      
      // создаем диффер, если он еще не создан
      if (!this.differ) {
        this.differ = this.differs.find(currentState).create();
      }

      // Эффект будет перезапускаться при изменении инпут-сигнала.
      const changes = this.differ.diff(currentState);

      // только если есть изменения
      if (changes) {
        changes.forEachAddedItem((record) => {
          console.log(`В объект добавлена запись: Ключ: ${record.key} | Значение: ${record.currentValue}`)
        });

        changes.forEachChangedItem((record) => {
          console.log(`Изменено: ${record.key} | Новое значение: ${record.currentValue}`)
        });

        changes.forEachRemovedItem((record) => {
          console.log(`Удалено: ${record.key}`)
        });
        
        // Остальные методы forEachItem и forEachPreviousItem по необходимости
      }
    })
  }
}

Легаси подход, которого, надеюсь, ни у кого нет, но на всякий случай :)

@Component({
  selector: 'app-legacy',
  template: ''
})
export class LegacyComponent implements OnInit, DoCheck {
  @Input({ required: true }) state!: Record<string, string | number>;
  
  private differs = inject(KeyValueDiffers);
  private differ: KeyValueDiffer<string, string | number> | undefined;

  ngOnInit() {
    // Создаем диффер при инициализации
    this.differ = this.differs.find(this.state).create();
  }

  // Запускается на каждый тик change detection, так как мутации по-другому не отследим.
  ngDoCheck(): void {
    const changes = this.differ?.diff(this.state);

    if (changes) {
      changes.forEachAddedItem((record) => {
        console.log(`В объект добавлена запись: Ключ: ${record.key} | Значение: ${record.currentValue}`)
      });

      changes.forEachChangedItem((record) => {
        console.log(`Значение изменилось: ${record.key}`)
      });

      changes.forEachRemovedItem((record) => {
        console.log(`Запись удалена: ${record.key}`)
      });

      // Остальные методы forEachItem и forEachPreviousItem по необходимости
    }
  }
}
Теги:
+1
Комментарии0

Почему у PWA до сих пор нет полноценного «магазина приложений» — возможно ли это вообще?

Всем привет.

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

Почему в 2025 году у PWA до сих пор нет настоящего App Store?

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

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

  1. Установка остаётся непонятной для пользователей

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

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

Во многих PWA-каталогах всё ограничивается текстовой инструкцией — и на этом взаимодействие с сервисом фактически заканчивается.

  1. Отсутствие доверия

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

Со стороны разработчиков наблюдаются крайности:
• либо любой может опубликовать приложение без подтверждения права собственности,
• либо проверка обязательна, но сложна и ограничена одним способом (например, через DNS-записи).

В итоге доверие не формируется ни у одной из сторон.

  1. Разработчики — второстепенные участники экосистемы

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

Экосистема не стимулирует разработчиков поддерживать и развивать свои PWA.

  1. Интерфейс не воспринимается как «нативный»

Это тонкий, но важный момент.

Если магазин:
• выглядит как обычный веб-сайт,
• не вызывает ассоциаций с App Store или Google Play,

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

При этом сами PWA как технология за последние годы заметно повзрослели: офлайн-режим, push-уведомления, installability, Web APIs.
Однако именно слой распространения и доверия остаётся самым слабым звеном.

Главный вопрос, к которому мы пришли

Возможно ли вообще создать PWA-магазин, который:
• пользователи будут воспринимать как настоящий магазин приложений,
• не станет источником боли для разработчиков,
• сможет устойчиво развиваться, а не быть заброшенным через несколько месяцев?

Или же сама идея магазина PWA в текущей экосистеме изначально ошибочна?

Будет интересно узнать ваш опыт.

Вы публиковали PWA-приложения в существующих магазинах или каталогах?
Что вызывало наибольшие сложности — у разработчиков или у пользователей?

Теги:
+1
Комментарии9

Открытый скрипт для браузера IKEA 3D Model Downloader добавляет на сайте IKEA кнопку «Download 3D» на страницы товаров. Проект позволяет скачать файл с точной 3D-моделью дивана, стола или шкафа. Очень удобно для планирования ремонта. Можно закинуть мебель в 3D-планировщик квартиры, посмотреть, как она встанет по размерам, прикинуть цвета и сочетания, а не покупать вслепую «по картинке» Работает на всех версиях сайта IKEA и с новой версией тоже. По сути — примерка мебели у себя дома, только в цифре.

Теги:
+3
Комментарии2

Попробовал вчера Kурсор впервые:
ШОК!!!

-Сказать дорого, ничего не сказать. После всех оптимизаций какие толкьо можно было выкрутить с помощью перплексити от 1 до 16м токенов в запрос? Да ну на х…
-Молниеносное отупение. Вместо того чтобы понимать какой компонент нужно доделать руками или через чат, тупо жми кнопку все будет ок. Только плати.

Вообщем вывод:

  • если у тебя большой проект: плати и тупи;

  • если у тебя сайтик, то повезло. Радуйся и не думай.

А лично я , кроме удобного интерфейса , не увидел ни чего хоть на 1% нужного. Знаю тут ни кто не поддержит) ну и ладно)))

Теги:
+4
Комментарии2
Теги:
-8
Комментарии0

Функциональное программирование перевернуло фронтенд: почему JS возвращается к платформам?

Функциональное программирование перевернуло фронтенд-разработку, но теперь индустрия возвращается к платформенным подходам — почему и как это меняет JS-экосистему?

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

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

Теги:
+2
Комментарии0

С новым рабочим годом, Хабр
Мы в SSP SOFT опять расширяем команду и ждем ваши резюме

Кто мы? Занимаемся заказной разработкой ПО и предоставляем крупным клиентам выделенные команды на ИТ-аутсорсинг.

В 2025 году SSP SOFT вошел в число лидеров найма ИТ-специалистов на российском рынке за год мы наняли 179 сотрудников!
И главное в 2026 году найм продолжается.

У нас новый московский офис в 2025 году у самой Красной площади! А еще есть вакансии в офис в Томске и на удаленку из любой точки России.

Открытые вакансии в SSP SOFT: это реальные проекты, дружная команда и атмосфера, где работать — продуктивно, без выноса мозга и микро-менеджмента. В январе 2026 ищем гуру, кто готов в новое профессиональное будущее вместе с нами.

1️⃣ Fullstack разработчика (C# + React) (https://vk.cc/cT6vMP)
2️⃣ Python Разработчика (ML Engineer) (https://vk.cc/cT6vNp)
3️⃣ Программиста 1С (https://vk.cc/cT6vNR)
4️⃣ Fullstack-разработчика (Java+Go/React) (https://vk.cc/cT6vOl)

Что вас ждет в SSP SOFT:
✅ Рост: Центр компетенций для максимального апгрейда скиллов.
✅ Свобода геолокации: Возможность работать удаленно, гибрид или офис.
✅ Баланс: Работаем, чтобы жить, а не наоборот.

🎁 Приятные бонусы: ивенты для всей команды, ДМС для штата, обучение и бенефиты.

Подробности о вакансиях читайте на нашей странице ХХ.ру, но туда откликаться необязательно. Ждем резюме в ЛС нашему HR Lead Алине (https://t.me/AONikitina). Не забудьте добавить «секретную фразу» в сопроводительное письмо, «Увидел(а) вакансию на Хабре».

Желаем всем успешной карьеры в 2026 году 🚀)

Теги:
+5
Комментарии0

Решил сегодня почитать, что пишут в Ангуляр комьюнити Хабра, и увидел сильно популярный пост с аж 51 лайком и 71 закладкой.

Начал читать и был удивлен примерами. Автор с уверенностью говорит, как писать на Ангуляр грамотно, и при этом приводит плохие практики в качестве примеров. Я дошел до примера с RxJS, который меня немного триггернул.

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

Автор условно говорит, что у нас есть плохой пример использования:

this.http.get('/api/data').subscribe((data) =>; {
  this.data = data; // Что если запрос не вернётся?
});

и затем приводит хороший пример с сигналами и RxJs:

readonly data = signal([]);
readonly error = signal(null);

loadData() {
  this.http.get('/api/data').pipe(
    tap(() =>; this.error.set(null)), // Сбрасываем предыдущую ошибку перед загрузкой
    catchError((err) =>; {
      this.error.set('Не удалось загрузить данные');
      return of([]); // Возвращаем пустой массив, чтобы поток не прерывался
    })
  ).subscribe((result) =>; {
    this.data.set(result);
  });
}

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

interface State<T = object> {
  data: T[];
  error: string | null;
}

@Component({...})
export class BestRxJs {
  private http = inject(HttpClient);
  private loadDataAction$ = new Subject<void>();

  private state$ = this.loadDataAction$.pipe(
    switchMap(() => 
      this.http.get<State[]>('/api/data').pipe(
        map((result) => ({ data: result, error: null })),
        catchError(() => of({ data: [], error: 'Не удалось загрузить данные' })),
        startWith({ data: [], error: null })
      )
    ),
    shareReplay({ bufferSize: 1, refCount: true })
  );

  readonly private state = toSignal(this.state$, {
    initialValue: { data: [], error: null } 
  });

  readonly protected data = computed(() => this.state().data);
  readonly protected error = computed(() => this.state().error);

  protected loadData(): void {
    this.loadDataAction$.next();
  }
}

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

Теги:
+3
Комментарии6

Почему стоит использовать protected в Angular компонентах?

Если вы используете в своих компонентах только public и private, вы упускаете возможность сделать архитектуру чище. Я предлагаю четко разделять ответственность членов класса.

Часто мы по инерции делаем public любые методы и свойства, которые нужны в шаблоне (HTML). Но public в TypeScript означает, что это публичный API компонента - к этим методам может получить доступ любой родительский компонент через @ViewChild.

Почему стоит использовать protected:

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

2. Защита от регрессии: Если другой разработчик попытается вызвать такой метод через @ViewChild, TypeScript выдаст ошибку. Это заставит его задуматься: «Действительно ли мне нужно делать этот метод публичным?» или «Может, стоит создать отдельный метод для API?».

3. Читаемость: Открывая код, вы сразу видите: public - для внешнего мира, protected - для шаблона, private - для внутренней логики сервисов и подписок.

Разделяйте Public API и внутреннюю логику шаблона - ваш код станет надежнее и понятнее.

@Component({
  selector: 'app-user-profile',
  template: `
    <!-- В шаблоне мы без проблем обращаемся к protected свойствам -->
    <div class="card">
      <h3>{{ userName() }}</h3>
      <button (click)="onUpdateClick()">Обновить</button>
        @if(isLoading()) {
          <div>Загрузка...</div>
        }
    </div>
  `
})
export class UserProfileComponent {
  // PRIVATE: Внутренняя логика. 
  // Не доступно ни в шаблоне, ни родительскому компоненту.
  private _userId = 123;

  // PROTECTED: Доступно только внутри класса и в ШАБЛОНЕ.
  // Идеально для переменных состояния UI и обработчиков событий.
  protected userName = signal('Алексей');
  protected isLoading = signal(false);

  protected onUpdateClick(): void {
    this.logAction();
    console.log('Кнопку нажали в шаблоне');
  }

  // PUBLIC: Публичный API компонента.
  // Только эти методы мы разрешаем вызывать родительскому компоненту.
  public resetState(): void {
    this.userName.set('Гость');
    this.isLoading.set(false);
  }

  private logAction(): void {
    console.log(`Action logged for userId: ${this._userId}`);
  }
}
Теги:
+2
Комментарии1

пет-проект невозможно доделать - только выпустить в открытую бету

Что ж, встречайте открытую бету проекта

📚📚📚📚📚📚📚📚📚📚
SweetReader!
📚📚📚📚📚📚📚📚📚📚

Что это:
Пространство для авторов и читателей, упор сделан на книги с высоким уровнем визуала (графические романы, комиксы, манги, книги с упором на иллюстрации). 

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

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

_____

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

Буду рад обратной связи тут в комментариях. Если найдёте баг или ошибку (а это на текущей стадии несложно), в меню есть кнопка "Ошибка?" специально для неравнодушных пользователей.

Теги:
-2
Комментарии0

Ближайшие события

Lex Kravetski в ФБ написал оду Микрософт Ворду, с которым у меня плохие отношения еще с конца 1980-х, когда он был под DOS-ом в графическом режиме. Причем тогда Ворд был еще более-менее, хотя бОльшая часть его функциональности лично мне не была нужна, для форматированных текстов хватало Лексикона от Eugene Veselov из ВЦ Академии Наук, затем уехавшим в Микрософт и ныне ставшим очень политизированным.

Главные проблемы с Вордом для меня начались после 2000 года, когда у него стало прыгать форматирование невпопад, особенно в текстах с комбинацией списков, таблиц и картинок. Lex такую проблему упоминает с позиции своих оппонентов: "блин, даже пробел в нём как-то странно работает, по коей причине даже простое форматирование сделать тяжело."

Из-за этого прыгания я сейчас для редактирования форматированных текстов как правило использую простой текстовый редактор joe (который имитирует редактор в TurboC 1988 года, но с квадратными блоками), а в нем - .md Markdown, который потом конвертирую в .pdf с помощью программы pandoc.

Также использую Google Doc, в нем форматирование не прыгает, как в Microsoft Word и в Libre Office, а сделано по человечески.

Раньше еще писал в текстовом редакторе на HTML, но Markdown удобнее, так как читабильнее в голом виде. Если вы еще не выучили Markdown и мучаетесь Вордом - просто нагуглите его в википедии, он учится за 15 минут.

Теги:
+6
Комментарии12

Как получить почти бесконечное зацикливание без использования циклов и без переполнения стека вызовов:

// Установите N = 64, и эта функция никогда не завершится  
// Количество вызовов (calls) = 2^(N+1)  
// Максимальная глубина вложенности = N

let calls = 0
const N = 18
function func(state, visited) {
  calls++
  if (calls > 10_000_000) {
    throw new Error('calls: ' + calls)
  }
  if (visited.includes(state)) return

  const newVisited = [...visited, state]

  func((state + 1) % N, newVisited)
  func((state + 1) % N, newVisited)
}

func(0, [])
console.log('calls:', calls)

Почему это работает без переполнения стека?

func(0, [])
├── func(1, [0])
│   ├── func(2, [0,1])
│   │   └── ... глубина растёт до N
│   │           и перебираются все возможные комбинации значений в newVisited
│   └── func(2, [0,1]) - возвращается, глубина УМЕНЬШАЕТСЯ
└── func(1, [0])       - второй вызов, стек уже освободился

А Garbage Collector (GC) при этом бесконечно удаляет созданные ранее массивы newVisited

Стек "дышит" - достигает максимума N, потом сворачивается, потом снова растёт. Это обход огромного дерева, имеющего небольшую глубину, но очень большую ширину. Это не бесконечная рекурсия. Но при N = 64 количество вызовов будет 2^65 (примерно 10^19) - это займёт тысячи лет, и стек никогда не переполнится.

Теги:
+1
Комментарии2

Коллеги, всем привет!

За годы менторства по Angular (в том числе в HTML Academy) я заметил одну системную проблему: студенты и даже миддлы часто знают синтаксис RxJS, но не понимают реактивного мышления. В итоге мы получаем subscribe внутри subscribe и императивную лапшу.

Я искал интерактивные курсы, но большинство бесплатных ресурсов ограничиваются основами.

Курс бесплатный. Делал для себя и студентов, но теперь делюсь со всеми.

Буду рад фидбеку и баг-репортам (проект активно допиливаю).

Ссылка на курс: https://rxjs-course-avy.web.app/

Теги:
Всего голосов 7: ↑7 и ↓0+8
Комментарии1

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

Теги:
Рейтинг0
Комментарии0

Друзья, классная новость! Мы с коллегами из GitVerse закончили разработку интеграции! 

Теперь в Gramax можно подключить GitVerse в качестве хранилища. Работает в лучшем виде: клонирование, синхронизация, коммит, пуш — все как и должно быть ✨

Это была масштабная и интересная работа: мы вместе анализировали API, чтобы получилось максимально удобно. Потому будем очень рады увидеть ваши плюсики!

Gramax — Open Source-платформа для работы с документацией в подходе Docs as Code. GitVerse — AI-first платформа для работы с кодом.

Теги:
Всего голосов 2: ↑1 и ↓10
Комментарии0

React One Click Component

Поделюсь самодельным расширением для VS Code, которое позволяет создавать React-компоненты в один клик.

Демонстрация работы расширения
Демонстрация работы расширения

Что умеет:

  • гибкое именование файлов: выбор между PascalCase, camelCase, kebab-case или snake_case для генерируемых файлов;

  • работа с .tsx и .jsx для файлов компонентов, а также .scss, .css, .less и .sass для стилей;

  • редактируемые шаблоны: настройка содержимого генерируемых файлов прямо в VS Code;

  • опциональное создание файлов реэкспорта и стилей.

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

На всякий случай: ни с какими внешними сервисами и нейронками расширение не взаимодействует)

Теги:
Всего голосов 1: ↑1 и ↓0+1
Комментарии0

Массовая замена HTML-разметки в Sublime Text с помощью RegExp

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

В моём случае это были заглушки вида:

<p>Image 1</p>
<p>Image 2</p>
<p>Image 3</p>

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

Почему не руками

Ручная правка подходит только для пары строк. Когда элементов больше 10–20, возрастает риск:

  • ошибиться в номере изображения

  • забыть закрыть тег

  • нарушить единообразие разметки

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

Решение через Sublime Text и RegExp

В Sublime Text есть мощный поиск и замена с поддержкой регулярных выражений. Этого более чем достаточно для задачи.

Открываем HTML-файл и вызываем панель замены:

  • Ctrl + H (Windows / Linux)

  • Cmd + Alt + F (macOS)

Обязательно включаем режим Regular Expression (иконка .*).

Шаблон поиска

<p>Image (\d+)</p>

Разберём выражение:

  • <p>Image — фиксированная часть

  • (\d+) — группа захвата, которая находит любое число

  • </p> — закрывающий тег

Число внутри скобок сохраняется как первая группа.

Шаблон замены

<img src="images/\1.jpg">

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

В результате:

  • Image 1images/1.jpg

  • Image 12images/12.jpg

Проверка и замена

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

После этого можно смело использовать Replace All — Sublime Text заменит все подходящие строки за один шаг.

Подводные камни

Несколько моментов, о которых легко забыть:

  • если не включить RegExp, \1 подставлен не будет

  • в Sublime используется именно \1, а не $1

  • шаблон чувствителен к пробелам и регистру

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

Для подобных задач подойдут и другие инструменты:

  • sed в терминале

  • массовая генерация HTML через bash-циклы

  • аналогичная замена в VS Code

Но если файл уже открыт в Sublime Text, RegExp — самый быстрый вариант.

Вывод

Регулярные выражения в Sublime Text позволяют эффективно автоматизировать рутинные правки HTML без дополнительных скриптов и плагинов. Даже простые приёмы вроде групп захвата экономят время и снижают количество ошибок.

Дополнительно

Более подробная пошаговая инструкция с ориентацией на новичков опубликована в моём блоге:

https://kodprog.ru/kak-bystro-zamenit-tekst-na-tegi-img-v-sublime-text

Теги:
Рейтинг0
Комментарии3
1
23 ...