Структура плагина

В Day.js плагин представляет собой функцию, которая принимает два аргумента: сам конструктор Day.js и опциональный объект настроек. Базовый контракт строится вокруг идеи расширения функциональности без изменения ядра библиотеки.

Плагин всегда имеет форму:

export default function pluginName(option, dayjsClass, dayjsFactory) {
  // расширение API
}

В зависимости от стиля подключения аргументы могут быть представлены иначе:

  • option — конфигурация, переданная при подключении плагина
  • dayjsClass — ссылка на конструктор Day.js
  • dayjsFactory — функция создания экземпляра (аналог dayjs())

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


Регистрация через extend

Подключение плагина осуществляется через метод расширения:

import dayjs from 'dayjs'
import plugin from 'dayjs/plugin/customParseFormat'

dayjs.extend(plugin)

Метод extend выполняет следующие действия:

  • вызывает функцию плагина
  • передаёт ей конструктор Day.js
  • сохраняет изменения в прототипе
  • предотвращает повторную регистрацию одного и того же плагина

Внутренняя логика опирается на список уже подключённых расширений, что исключает дублирование поведения.


Расширение прототипа Day.js

Основной механизм расширения заключается в модификации dayjs.prototype. Именно через него добавляются новые методы экземпляра:

export default function relativeTimePlugin(option, dayjs, dayjsFactory) {
  dayjs.prototype.fromNow = function () {
    return this.from(dayjsFactory())
  }
}

После подключения:

dayjs.extend(relativeTimePlugin)

dayjs().fromNow()

Типовые точки расширения:

  • dayjs.prototype — методы экземпляра
  • dayjs — статические методы
  • dayjs.factory — создание новых объектов
  • dayjs.enhance (внутренние реализации в некоторых сборках)

Модификация статических методов

Плагин может расширять сам конструктор:

export default function isTodayPlugin(option, dayjs) {
  dayjs.isToday = function (date) {
    return dayjs(date).isSame(dayjs(), 'day')
  }
}

Использование:

dayjs.isToday('2026-01-01')

Статические методы применяются для утилитарных функций, не зависящих от конкретного экземпляра.


Работа с экземпляром Day.js

Экземпляр Day.js является неизменяемым объектом (immutable). Любая операция возвращает новый экземпляр.

Плагин обязан учитывать этот принцип:

dayjs.prototype.addBusinessDay = function () {
  return this.add(1, 'day')
}

Важно:

  • нельзя мутировать внутреннее состояние объекта
  • необходимо возвращать новый экземпляр через API Day.js
  • цепочки вызовов должны сохраняться

Использование внутренних API

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

  • this.$d — оригинальный Date
  • this.$y, this.$M, this.$D — компоненты даты
  • this.clone() — создание копии экземпляра
  • this.$utils — вспомогательные функции (внутренние)

Пример:

dayjs.prototype.startOfWeek = function () {
  const day = this.$W
  return this.subtract(day, 'day')
}

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


Сигнатура плагина и параметры

Плагин может принимать опции:

export default function weekdayPlugin(option, dayjs) {
  const startOfWeek = option?.startOfWeek ?? 1

  dayjs.prototype.startOfWeek = function () {
    return this.day(startOfWeek)
  }
}

Подключение с конфигурацией:

dayjs.extend(weekdayPlugin, { startOfWeek: 0 })

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


Совместимость модульных систем

Плагин Day.js должен учитывать разные окружения:

ESM

export default function plugin(dayjs) {
  dayjs.prototype.example = function () {}
}

CommonJS

module.exports = function plugin(dayjs) {
  dayjs.prototype.example = function () {}
}

UMD

(function (global, factory) {
  factory(global.dayjs)
})(this, function (dayjs) {
  dayjs.prototype.example = function () {}
})

Унифицированная цель — отсутствие зависимости от среды исполнения.


Порядок подключения плагинов

Порядок extend влияет на поведение системы:

dayjs.extend(pluginA)
dayjs.extend(pluginB)

Особенности:

  • плагины могут переопределять методы друг друга
  • последний подключённый имеет приоритет
  • зависимости между плагинами не управляются автоматически

Пример конфликтующего поведения:

dayjs.extend(formatA)
dayjs.extend(formatB) // может переопределить format()

Расширение цепочек вызовов

Day.js активно использует цепочки:

dayjs()
  .add(1, 'day')
  .format()

Плагин должен сохранять цепочную структуру:

dayjs.prototype.doubleAdd = function (num, unit) {
  return this.add(num, unit).add(num, unit)
}

Нарушение цепочек приводит к потере согласованности API.


Кэширование и производительность

Некоторые плагины используют кэширование через замыкания:

export default function cachePlugin(dayjs) {
  const cache = new Map()

  dayjs.prototype.expensive = function () {
    const key = this.valueOf()
    if (cache.has(key)) return cache.get(key)

    const result = this.add(10, 'day')
    cache.set(key, result)

    return result
  }
}

Особенности:

  • кэш живёт в области плагина
  • не привязан к экземпляру
  • требует контроля утечек памяти

Взаимодействие с форматированием

Плагины часто расширяют форматирование:

dayjs.prototype.formatISOWeek = function () {
  const week = this.isoWeek()
  return `W${week}`
}

Механизм форматирования интегрируется через:

  • dayjs.prototype.format
  • кастомные токены
  • расширение парсера (через другие плагины)

Типовая структура файла плагина

Стандартная организация кода:

// зависимые функции
function helper() {}

export default function plugin(option, dayjs, dayjsFactory) {
  // расширение экземпляра
  dayjs.prototype.method = function () {
    return this.clone()
  }

  // статические методы
  dayjs.custom = function () {}

  // вспомогательная логика
}

Разделение ответственности обычно включает:

  • вспомогательные функции
  • расширение prototype
  • статические утилиты
  • конфигурацию

Ограничения архитектуры плагинов

Плагинная система Day.js имеет ряд конструктивных ограничений:

  • отсутствует автоматическое управление зависимостями
  • невозможна частичная загрузка методов
  • порядок подключения критичен
  • отсутствует изоляция namespace

Эти особенности требуют строгой дисциплины при проектировании расширений.


Типичные ошибки реализации

Распространённые проблемы при создании плагинов:

  • мутирование dayjs.prototype без возврата нового экземпляра
  • переопределение встроенных методов без сохранения оригинала
  • использование глобального состояния без контроля
  • зависимость от внутреннего API без проверки версии
  • нарушение цепочного API

Пример проблемного кода:

dayjs.prototype.bad = function () {
  this.$d.setDate(1)
  return this
}

Такой подход нарушает неизменяемость экземпляра и приводит к непредсказуемым результатам.