Расширения для Pikaday строятся вокруг принципа ненавязчивого расширения базового поведения без изменения исходного кода библиотеки. Основная задача публикации таких расширений — обеспечить повторяемость подключения, совместимость с различными сборками (UMD, ESM, CJS), а также предсказуемое взаимодействие с внутренним API календаря.
Ключевой архитектурный принцип — разделение расширения на три слоя:
Такой подход позволяет публиковать расширения независимо от версии приложения, в котором используется календарь, при условии соблюдения контрактов API.
Расширения для Pikaday обычно распространяются в нескольких форматах, каждый из которых ориентирован на определённую экосистему сборки.
Современный формат публикации предполагает использование 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:
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 используется для публикации расширений, которые должны работать без сборки, напрямую через 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)el)getDate,
setDate)Типовой контракт расширения выглядит как функция, принимающая экземпляр календаря:
function extension(pikadayInstance, options) {
// модификация поведения
}
Недопустимо полагаться на внутренние приватные свойства, не задокументированные в API, поскольку они могут изменяться между версиями.
Наиболее распространённый механизм:
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.
Используется семантическое версионирование:
В package.json указывается peer dependency:
{
"peerDependencies": {
"pikaday": ">=1.8.0 <2.0.0"
}
}
Это предотвращает установку расширения в несовместимую среду.
Типичная структура пакета расширения:
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:
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 применяется для сложных расширений с зависимостями:
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
library: 'PikadayExtension',
libraryTarget: 'umd'
}
};
Документация расширения для Pikaday должна фиксировать:
Минимальный формат README:
## Usage
import highlightWeekends from 'pikaday-extension-weekends';
highlightWeekends(pikaday, {
className: 'weekend-highlight'
});
Расширения часто изменяют DOM-классы календаря. Для предотвращения конфликтов применяется нейминг:
pika-ext-)Пример:
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.
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 Pikaday может расширяться, что приводит к необходимости адаптации расширений:
Расширения, опубликованные ранее, сохраняют работоспособность через адаптеры совместимости, которые эмулируют устаревшее поведение API без изменения ядра библиотеки.