Day.js реализует модульную архитектуру расширений, основанную на
функции extend. Вся функциональность библиотеки изначально
минимальна и расширяется через подключаемые плагины, которые
модифицируют прототип объекта Day.js.
Плагин представляет собой функцию, которая получает два аргумента:
Основная задача плагина — добавить новые методы, расширить прототип, внедрить утилиты или изменить поведение парсинга и форматирования.
Базовый механизм подключения выглядит следующим образом:
import dayjs from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat'
dayjs.extend(customParseFormat)
Функция extend выполняет регистрацию плагина в
глобальном контексте библиотеки и гарантирует, что расширения будут
доступны всем создаваемым экземплярам.
Плагин в Day.js представляет собой чистую функцию без состояния. Стандартная сигнатура:
export default (option, Dayjs, dayjs) => {
// расширение прототипа
}
Где:
option — конфигурация, переданная при регистрацииDayjs — конструкторdayjs — глобальный экземпляр APIРасширения обычно добавляются через:
Dayjs.prototype.newMethod = function () {
return this
}
или через утилитарные функции:
dayjs.newUtility = () => {}
Основной способ работы плагинов заключается в добавлении методов к
Dayjs.prototype. Это позволяет каждому экземпляру получать
новые цепочечные вызовы.
Пример плагина, добавляющего метод форматирования относительного времени:
export default (option, Dayjs, dayjs) => {
Dayjs.prototype.toRelative = function () {
const now = dayjs()
const diff = this.diff(now, 'minute')
return diff > 0 ? `${diff} minutes ago` : `${-diff} minutes later`
}
}
После регистрации:
dayjs.extend(relativePlugin)
dayjs().toRelative()
Важное свойство архитектуры — отсутствие копирования методов на каждый экземпляр. Все экземпляры используют один прототип, что обеспечивает низкие накладные расходы.
Функция extend в Day.js выполняет несколько шагов:
Псевдологика:
function extend(plugin, option) {
if (plugin.isPluginInstalled) return
plugin(option, Dayjs, dayjs)
plugin.isPluginInstalled = true
}
Такой механизм предотвращает дублирование расширений и потенциальные конфликты методов.
Часть плагинов в Day.js влияет на этап разбора даты. Это более сложный тип расширений, так как они вмешиваются в цепочку создания объекта.
Пример: плагин customParseFormat
Dayjs.prototype.parse = function (date, format) {
// кастомная логика разбора строки
}
Подобные плагины должны аккуратно работать с внутренними структурами, чтобы не нарушить совместимость с базовыми форматами ISO.
Ключевая особенность: парсер должен быть обратимо совместимым с другими плагинами.
Day.js поддерживает композицию, при которой несколько плагинов могут модифицировать один и тот же метод.
Порядок подключения имеет значение:
dayjs.extend(pluginA)
dayjs.extend(pluginB)
Если оба плагина модифицируют format, последний
подключённый плагин получает приоритет.
Для управления конфликтами применяются техники:
Пример обёртки:
const originalFormat = Dayjs.prototype.format
Dayjs.prototype.format = function (args) {
const base = originalFormat.call(this, args)
return `[${base}]`
}
Плагины могут добавлять не только методы экземпляра, но и статические методы:
dayjs.isBusinessDay = (date) => {
return [0, 6].includes(dayjs(date).day())
}
Это расширяет функциональность без изменения структуры экземпляров.
Также допустимо добавление утилит:
dayjs.MINUTES_IN_HOUR = 60
Однако такие расширения должны использоваться аккуратно, чтобы не загрязнять глобальный API.
Одной из ключевых концепций Day.js является неизменяемость объектов. Каждый вызов метода возвращает новый экземпляр.
Плагины должны учитывать это правило:
Dayjs.prototype.addBusinessDays = function (n) {
let date = this
while (n > 0) {
date = date.add(1, 'day')
if (date.day() !== 0 && date.day() !== 6) {
n--
}
}
return date
}
Метод всегда возвращает новый объект, не изменяя исходный.
Плагины подключаются строго до использования экземпляров, если они влияют на базовое поведение.
Правильный порядок:
dayjs.extend(pluginA)
dayjs.extend(pluginB)
const d = dayjs()
Неправильный порядок может привести к тому, что экземпляры будут созданы без нужных методов.
Некоторые плагины допускают динамическую установку, но это зависит от внутренней реализации.
В Day.js отсутствует жёсткая изоляция плагинов, поэтому конфликт имён является потенциальной проблемой.
Рекомендации при разработке:
format,
parse)toX, isX,
getX)Пример безопасного расширения:
Dayjs.prototype.isLeapYearCustom = function () {
const year = this.year()
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
}
Некоторые плагины поддерживают параметры конфигурации:
dayjs.extend(plugin, { locale: 'ru', strict: true })
Плагин получает опции и адаптирует поведение:
export default (option, Dayjs) => {
const locale = option.locale
Dayjs.prototype.localeAwareFormat = function () {
return locale === 'ru' ? 'формат' : 'format'
}
}
Это позволяет создавать универсальные расширения, адаптирующиеся под окружение.
Day.js предоставляет поддержку типизации через расширение интерфейсов.
Пример декларации:
declare module 'dayjs' {
interface Dayjs {
toRelative(): string
}
}
Реализация:
dayjs.extend((option, Dayjs) => {
Dayjs.prototype.toRelative = function () {
return 'custom'
}
})
Типизация позволяет сохранять корректный автокомплит и предотвращает ошибки при использовании расширений.
Механизм удаления плагинов отсутствует как стандартная функция. Это архитектурное решение связано с тем, что расширения модифицируют глобальный прототип.
Возможные стратегии обхода:
Пример фабричного подхода:
function createDayjsWithPlugin() {
const localDayjs = dayjs
localDayjs.extend(plugin)
return localDayjs
}
В экосистеме Day.js часто используются следующие паттерны:
Используется для расширения существующих методов без их замены.
Позволяет создавать изолированные экземпляры с набором плагинов.
Применяется для последовательного изменения входных данных (например, парсинга даты).
Используется для модификации поведения методов без изменения исходной реализации.
Система расширений имеет ряд ограничений:
Эти ограничения компенсируются простотой и предсказуемостью модели расширений.