Создание собственных плагинов

Библиотека Day.js построена вокруг минималистичного ядра, которое намеренно не перегружено функциональностью. Почти вся дополнительная логика подключается через систему расширений. Этот подход позволяет сохранять небольшой размер пакета и при этом гибко наращивать возможности за счёт плагинов.

Плагин в Day.js — это функция, которая получает объект экземпляра Day.js и модифицирует его прототип или добавляет новые статические возможности. Подключение осуществляется через метод extend.

Ключевой принцип системы расширений:

ядро остаётся неизменным, вся функциональность добавляется извне


Базовый механизм подключения плагинов

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

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

dayjs.extend(plugin)

Метод extend принимает два аргумента:

  • сам плагин (функцию)
  • необязательные параметры конфигурации

Внутри Day.js происходит вызов функции плагина с передачей конструктора и объекта Day.js:

plugin(dayjs, options)

Таким образом плагин получает доступ к:

  • конструктору Day.js
  • прототипу Dayjs.prototype
  • статическим методам

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

Плагин представляет собой функцию с предсказуемой сигнатурой:

export default (option, dayjsClass, dayjsFactory) => {
  // расширение прототипа
}

Однако на практике чаще используется упрощённая форма:

export default (o, c) => {
  c.prototype.example = function () {
    return this.add(1, 'day')
  }
}

где:

  • o — options (если переданы через extend)
  • c — класс Day.js

Добавление методов экземпляра

Основная задача плагинов — расширение экземпляров Day.js.

Пример добавления метода форматирования:

export default (option, Dayjs) => {
  Dayjs.prototype.toUpperFormat = function () {
    return this.format('YYYY-MM-DD').toUpperCase()
  }
}

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

import dayjs from 'dayjs'
import upperFormat from './plugins/upperFormat'

dayjs.extend(upperFormat)

dayjs().toUpperFormat()

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

  • this внутри метода — экземпляр Day.js
  • можно использовать любые встроенные методы (format, add, subtract)
  • возвращать следует либо строку, либо новый Day.js объект

Изменение поведения через обёртку прототипа

Некоторые плагины не добавляют новые методы, а изменяют существующие:

export default (option, Dayjs) => {
  const oldAdd = Dayjs.prototype.add

  Dayjs.prototype.add = function (value, unit) {
    const result = oldAdd.call(this, value, unit)
    return result.startOf('day')
  }
}

Такой подход требует осторожности:

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

Работа со статическими методами

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

export default (option, Dayjs) => {
  Dayjs.isWeekday = function (date) {
    const d = Dayjs(date)
    return d.day() !== 0 && d.day() !== 6
  }
}

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

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

Это полезно для:

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

Передача параметров в плагин

extend поддерживает передачу опций:

dayjs.extend(plugin, { locale: 'ru' })

Внутри плагина:

export default (options, Dayjs) => {
  const locale = options?.locale || 'en'

  Dayjs.prototype.localeFormat = function () {
    return this.locale(locale).format('LLLL')
  }
}

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

  • адаптировать поведение плагина
  • избегать дублирования кода
  • создавать универсальные расширения

Проверка наличия Day.js экземпляра

Во многих плагинах важно определить, является ли объект экземпляром Day.js:

export default (o, Dayjs, dayjsFactory) => {
  function isDayjsObject(obj) {
    return dayjsFactory.isDayjs(obj)
  }
}

Это предотвращает ошибки при работе с:

  • обычными объектами JavaScript
  • строками и числами
  • внешними библиотеками

Композиция плагинов

Плагины Day.js могут зависеть друг от друга. Например, один плагин может расширять поведение другого.

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

Порядок подключения имеет значение:

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

Пример зависимого плагина:

export default (o, Dayjs) => {
  if (!Dayjs.prototype.isCustom) return

  Dayjs.prototype.combined = function () {
    return this.isCustom().format()
  }
}

Защита от повторного подключения

Хорошей практикой является предотвращение повторного расширения:

let installed = false

export default (o, Dayjs) => {
  if (installed) return
  installed = true

  Dayjs.prototype.safeMethod = function () {
    return this.add(1, 'hour')
  }
}

Это важно для:

  • больших приложений
  • SSR-среды
  • микрофронтендов

Плагины с модификацией парсинга

Некоторые расширения изменяют процесс создания дат:

export default (o, Dayjs, dayjsFactory) => {
  const oldParse = dayjsFactory.parse

  dayjsFactory.parse = function (date) {
    if (typeof date === 'string' && date.includes('custom')) {
      return new Date()
    }
    return oldParse(date)
  }
}

Такие плагины:

  • влияют на входные данные
  • требуют строгого тестирования
  • могут ломать совместимость

Использование TypeScript в плагинах

Типизация улучшает надёжность расширений:

import { PluginFunc } from 'dayjs'

const myPlugin: PluginFunc = (option, Dayjs) => {
  Dayjs.prototype.double = function () {
    return this.add(1, 'day')
  }
}

export default myPlugin

Дополнительно можно расширить интерфейсы:

declare module 'dayjs' {
  interface Dayjs {
    double(): Dayjs
  }
}

Тестирование плагинов

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

import dayjs from 'dayjs'
import plugin from './plugin'

dayjs.extend(plugin)

test('custom method works', () => {
  const result = dayjs('2020-01-01').double()
  expect(result.date()).toBe(2)
})

Важно проверять:

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

Типичные ошибки при создании плагинов

Распространённые проблемы:

  • изменение прототипа без проверки наличия метода
  • отсутствие защиты от повторного подключения
  • нарушение цепочек иммутабельности
  • прямое изменение внутренних структур Day.js
  • игнорирование порядка подключения

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

Dayjs.prototype.add = function () {
  return 'broken'
}

Такой подход полностью ломает библиотеку.


Организация сложных расширений

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

plugin/
  index.js
  methods/
  utils/
  constants/

Главный файл объединяет части:

import { addFeature } from './methods/addFeature'

export default (o, Dayjs) => {
  addFeature(Dayjs)
}

Такой подход упрощает:

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