Публикация расширений

Расширения для Pikaday строятся вокруг принципа ненавязчивого расширения базового поведения без изменения исходного кода библиотеки. Основная задача публикации таких расширений — обеспечить повторяемость подключения, совместимость с различными сборками (UMD, ESM, CJS), а также предсказуемое взаимодействие с внутренним API календаря.

Ключевой архитектурный принцип — разделение расширения на три слоя:

  • слой инициализации (подключение к экземпляру календаря)
  • слой поведения (логика расширения)
  • слой интеграции (экспорт и публикация)

Такой подход позволяет публиковать расширения независимо от версии приложения, в котором используется календарь, при условии соблюдения контрактов API.


Форматы расширений и модели распространения

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

ES Modules

Современный формат публикации предполагает использование ES Modules:

export function highlightWeekends(pikadayInstance, options = {}) {
    const originalDraw = pikadayInstance.drawCalendar;

    pikadayInstance.drawCalendar = function () {
        originalDraw.call(this);

        const cells = this.el.querySelectorAll('.pika-day');
        cells.forEach(cell => {
            const date = new Date(cell.getAttribute('data-pika-day'));
            if (date.getDay() === 0 || date.getDay() === 6) {
                cell.classList.add(options.className || 'is-weekend');
            }
        });
    };
}

Данный формат оптимален для tree-shaking и позволяет подключать только нужные расширения.


CommonJS

Для сред старых сборщиков используется CommonJS:

function highlightWeekends(pikadayInstance, options) {
    options = options || {};
    const originalDraw = pikadayInstance.drawCalendar;

    pikadayInstance.drawCalendar = function () {
        originalDraw.call(this);

        const cells = this.el.querySelectorAll('.pika-day');
        for (let i = 0; i < cells.length; i++) {
            const date = new Date(cells[i].getAttribute('data-pika-day'));
            if (date.getDay() === 0 || date.getDay() === 6) {
                cells[i].classList.add(options.className || 'is-weekend');
            }
        }
    };
}

module.exports = highlightWeekends;

Основная цель — совместимость с Node.js и сборщиками без поддержки ESM.


UMD-формат

UMD используется для публикации расширений, которые должны работать без сборки, напрямую через CDN:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.PikadayExtensions = root.PikadayExtensions || {};
        root.PikadayExtensions.highlightWeekends = factory();
    }
}(typeof self !== 'undefined' ? self : this, function () {

    return function (pikadayInstance, options) {
        const originalDraw = pikadayInstance.drawCalendar;

        pikadayInstance.drawCalendar = function () {
            originalDraw.call(this);

            const cells = this.el.querySelectorAll('.pika-day');
            cells.forEach(cell => {
                const date = new Date(cell.getAttribute('data-pika-day'));
                if (date.getDay() === 0 || date.getDay() === 6) {
                    cell.classList.add((options && options.className) || 'is-weekend');
                }
            });
        };
    };

}));

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


Контракт расширения и точка интеграции

Публикуемые расширения для Pikaday должны опираться на стабильные точки расширения:

  • методы жизненного цикла (onOpen, onClose, onSelect)
  • перерисовка календаря (drawCalendar)
  • доступ к DOM-контейнеру (el)
  • доступ к текущему состоянию (getDate, setDate)

Типовой контракт расширения выглядит как функция, принимающая экземпляр календаря:

function extension(pikadayInstance, options) {
    // модификация поведения
}

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


Паттерны модификации поведения

Перехват методов (Monkey Patching)

Наиболее распространённый механизм:

const original = pikadayInstance.show;

pikadayInstance.show = function () {
    console.log('Calendar opened');
    return original.apply(this, arguments);
};

Такой подход требует строгого сохранения контекста this.


Подписка на события через обёртки

Некоторые расширения реализуют событийный слой:

function onSelectLogger(pikadayInstance) {
    const originalOnSelect = pikadayInstance._onInputChange;

    pikadayInstance._onInputChange = function () {
        console.log('Date changed');
        return originalOnSelect.apply(this, arguments);
    };
}

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


Композиция расширений

Расширения могут комбинироваться:

function compose(...extensions) {
    return function (pikadayInstance, options) {
        extensions.forEach(ext => ext(pikadayInstance, options));
    };
}

Это позволяет формировать цепочки поведения без модификации ядра.


Версионирование и совместимость

При публикации расширений критически важна привязка к версии Pikaday.

Используется семантическое версионирование:

  • MAJOR — несовместимые изменения API календаря
  • MINOR — добавление новых точек расширения
  • PATCH — исправления без изменения API

В package.json указывается peer dependency:

{
  "peerDependencies": {
    "pikaday": ">=1.8.0 <2.0.0"
  }
}

Это предотвращает установку расширения в несовместимую среду.


Публикация в npm и структура пакета

Типичная структура пакета расширения:

pikaday-extension-weekends/
  src/
    index.js
  dist/
    index.esm.js
    index.cjs.js
    index.umd.js
  package.json
  README.md

package.json:

{
  "name": "pikaday-extension-weekends",
  "version": "1.0.0",
  "main": "dist/index.cjs.js",
  "module": "dist/index.esm.js",
  "browser": "dist/index.umd.js",
  "sideEffects": false
}

Поле sideEffects: false позволяет сборщикам выполнять tree-shaking.


Интеграция с системой сборки

Rollup

Для публикации расширений часто используется Rollup:

export default {
    input: 'src/index.js',
    output: [
        { file: 'dist/index.esm.js', format: 'es' },
        { file: 'dist/index.cjs.js', format: 'cjs' },
        { file: 'dist/index.umd.js', format: 'umd', name: 'PikadayExtension' }
    ]
};

Webpack

Webpack применяется для сложных расширений с зависимостями:

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        library: 'PikadayExtension',
        libraryTarget: 'umd'
    }
};

Документирование расширений

Документация расширения для Pikaday должна фиксировать:

  • список поддерживаемых версий
  • точки интеграции
  • изменяемые методы
  • побочные эффекты DOM-манипуляций
  • требования к стилям CSS

Минимальный формат README:

## Usage

import highlightWeekends from 'pikaday-extension-weekends';

highlightWeekends(pikaday, {
    className: 'weekend-highlight'
});

Управление стилями и конфликтами

Расширения часто изменяют DOM-классы календаря. Для предотвращения конфликтов применяется нейминг:

  • префиксы (pika-ext-)
  • BEM-стиль
  • изолированные классы

Пример:

cell.classList.add(`pika-ext-${options.className}`);

Тестирование расширений

Расширения для Pikaday тестируются на уровне DOM:

import Pikaday from 'pikaday';
import highlightWeekends from '../src/index';

test('adds weekend class', () => {
    document.body.innerHTML = '<input id="date">';
    const picker = new Pikaday({ field: document.getElementById('date') });

    highlightWeekends(picker);

    picker.drawCalendar();

    expect(document.querySelectorAll('.is-weekend').length).toBeGreaterThan(0);
});

Используются Jest или Vitest с jsdom.


Публикация через CDN

UMD-сборки могут распространяться через CDN:

<script src="https://cdn.example.com/pikaday-extension-weekends.min.js"></script>
<script>
    PikadayExtensions.highlightWeekends(picker, { className: 'weekend' });
</script>

CDN-версия требует строгой стабильности API и отсутствия breaking changes.


Обратная совместимость и эволюция API расширений

С течением времени API Pikaday может расширяться, что приводит к необходимости адаптации расширений:

  • отказ от приватных методов
  • переход на события вместо патчинга
  • введение официальных хуков
  • стандартизация интерфейса расширений

Расширения, опубликованные ранее, сохраняют работоспособность через адаптеры совместимости, которые эмулируют устаревшее поведение API без изменения ядра библиотеки.