Hot Module Replacement

Hot Module Replacement (HMR) представляет собой механизм, позволяющий обновлять модули приложения в браузере без полной перезагрузки страницы. Для веб-компонентов, созданных с помощью SkateJS, HMR особенно важен, так как перезагрузка может привести к потере состояния элементов или их повторной инициализации. SkateJS не предоставляет встроенный HMR, но полностью совместим с популярными инструментами, такими как Webpack или Vite, что позволяет интегрировать HMR на уровне модулей и компонентов.


Основы HMR

HMR работает через три ключевых процесса:

  1. Отслеживание изменений – сборщик (Webpack, Vite) мониторит изменения в исходных файлах.
  2. Обновление модулей – при изменении файла отправляется сигнал в браузер, который заменяет старую версию модуля новой.
  3. Сохранение состояния – компоненты могут сохранять локальное состояние, избегая полной перезагрузки страницы.

Для SkateJS HMR важен потому, что компоненты определяются как классы или фабрики с рендер-функциями. При полной перезагрузке теряется DOM-элемент и его внутренние свойства, а при HMR можно заменить только код класса, сохранив текущий DOM и состояние.


Настройка HMR с Webpack

Для работы с HMR в проекте SkateJS через Webpack необходимы следующие шаги:

  1. Включение режима HMR в конфигурации Webpack:
const path = require('path');
const webpack = require('webpack');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  devServer: {
    hot: true,
    open: true,
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ],
};
  1. Подключение HMR в главном модуле SkateJS:
import { define, h } from 'skatejs';

class MyComponent extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `<p>Компонент загружен</p>`;
  }
}

define('my-component', MyComponent);

if (module.hot) {
  module.hot.accept('./index.js', () => {
    console.log('Модуль обновлен через HMR');
  });
}

Ключевой момент: HMR обновляет модуль index.js, но сам DOM-элемент my-component остаётся на странице, что позволяет сохранить внутреннее состояние.


HMR и реактивные свойства SkateJS

SkateJS использует props и реактивные рендер-функции через h() и JSX-подобный синтаксис. Для корректной работы HMR необходимо правильно обновлять реактивные свойства, чтобы изменения сразу применялись в уже существующих экземплярах компонента.

Пример:

import { define, h, props } from 'skatejs';

class Counter extends HTMLElement {
  static props = {
    count: props.number
  };

  connectedCallback() {
    this.render();
  }

  render() {
    this.innerHTML = `
      <div>
        <p>Счётчик: ${this.count || 0}</p>
        <button id="increment">+</button>
      </div>
    `;
    this.querySelector('#increment').addEventListener('click', () => {
      this.count = (this.count || 0) + 1;
      this.render();
    });
  }
}

define('x-counter', Counter);

if (module.hot) {
  module.hot.accept('./counter.js', () => {
    console.log('Counter модуль обновлён');
  });
}

При использовании HMR состояние count сохраняется, так как DOM-элемент не удаляется. Новая версия класса обновляет только методы и рендер.


Обработка удаления и переопределения компонентов

SkateJS не позволяет дважды определять один и тот же тег. При HMR необходимо учитывать этот момент:

if (customElements.get('my-component')) {
  customElements.define('my-component', class extends customElements.get('my-component') {
    connectedCallback() {
      super.connectedCallback();
      console.log('Обновлённый компонент подключён');
    }
  });
} else {
  define('my-component', MyComponent);
}

Это позволяет безопасно обновлять компонент без ошибок DOMException: A custom element with name "my-component" has already been defined.


Интеграция с Vite

Vite поддерживает HMR “из коробки”, и для SkateJS достаточно использовать модульный импорт компонентов и простой блок:

import './my-component.js';

if (import.meta.hot) {
  import.meta.hot.accept('./my-component.js', (newModule) => {
    console.log('Компонент обновлён через Vite HMR');
  });
}

Vite автоматически заменяет старый класс новым, сохраняя существующие DOM-элементы.


Рекомендации по HMR для крупных приложений

  • Разделять компоненты на отдельные модули, чтобы HMR заменял только изменённый модуль.
  • Использовать состояние через свойства компонентов или внешние хранилища (например, Redux, Zustand) для сохранения состояния между обновлениями.
  • Избегать глобальных побочных эффектов при подключении компонентов, чтобы HMR не вызывал дублирование обработчиков событий.
  • Логировать обновления HMR, чтобы понимать, какие модули были заменены.

Hot Module Replacement в SkateJS позволяет ускорить процесс разработки, сохраняя состояние компонентов и минимизируя перезагрузку страницы. Правильная настройка HMR через Webpack или Vite обеспечивает стабильное и реактивное обновление компонентов, что особенно важно для больших приложений с динамическими веб-компонентами.