Архитектура Moment.js построена вокруг расширяемого объекта
moment, который одновременно выступает фабрикой для
создания экземпляров и контейнером для статических методов. Такой подход
позволяет подключать плагины без изменения исходного кода библиотеки,
добавляя функциональность через прототипы и расширение пространства
имён.
Основная идея плагина заключается в том, что он представляет собой
функцию, принимающую объект moment и модифицирующую
его:
(function (moment) {
// расширение moment
})(moment);
При использовании CommonJS или ES Modules структура остаётся аналогичной:
// CommonJS
const moment = require("moment");
require("./myPlugin")(moment);
// ES Modules
import moment from "moment";
import myPlugin from "./myPlugin.js";
myPlugin(moment);
moment.fn — это прототип всех экземпляров даты. Любой
метод, добавленный в moment.fn, становится доступным для
каждого объекта moment().
Базовый принцип:
moment.fn.isWeekend = function () {
const day = this.day();
return day === 0 || day === 6;
};
Использование:
moment().isWeekend();
Внутри методов плагина this всегда указывает на
экземпляр Moment. Это позволяет безопасно использовать встроенные
методы:
moment.fn.addBusinessDays = function (days) {
let date = this.clone();
while (days > 0) {
date = date.add(1, "day");
if (date.day() !== 0 && date.day() !== 6) {
days--;
}
}
return date;
};
Ключевой момент — обязательное использование clone(),
чтобы не мутировать исходный объект.
Статические методы расширяют сам объект moment, а не его
экземпляры. Это полезно для утилитарных функций:
moment.isMomentStrict = function (obj) {
return moment.isMoment(obj) && obj.isValid();
};
Использование:
moment.isMomentStrict(moment());
Также статические методы часто используются для фабрик:
moment.fromUnixSeconds = function (sec) {
return moment(sec * 1000);
};
Плагины могут добавлять новые способы интерпретации строковых дат
через обёртку над moment.parseZone, moment.utc
или кастомные функции.
Пример простого парсера:
moment.fn.fromCustomFormat = function (str) {
const [day, month, year] = str.split("-");
return moment(`${year}-${month}-${day}`);
};
Механизм длительностей также расширяем:
moment.duration.fn.toBusinessHours = function () {
const hours = this.asHours();
return Math.floor(hours * 0.75);
};
Здесь moment.duration.fn аналогичен
moment.fn, но применяется к объектам длительности.
Типовая структура плагина выглядит следующим образом:
(function (moment) {
function addWeekOffset(m, weeks) {
return m.clone().add(weeks * 7, "days");
}
moment.fn.addWeeksStrict = function (weeks) {
return addWeekOffset(this, weeks);
};
moment.isMomentEmpty = function (m) {
return !m || !moment.isMoment(m) || !m.isValid();
};
})(moment);
Плагины часто включают проверку, предотвращающую повторную регистрацию:
(function (moment) {
if (moment.__myPluginInstalled) return;
moment.__myPluginInstalled = true;
moment.fn.example = function () {
return this.format();
};
})(moment);
Плагины могут расширять форматирование через
moment.fn.format-подобные обёртки:
moment.fn.formatWithPrefix = function (prefix, formatStr) {
return prefix + this.format(formatStr);
};
Одним из ключевых требований является сохранение цепочечной модели:
moment.fn.startOfWeek = function () {
return this.clone().startOf("week");
};
moment.fn.nextBusinessDay = function () {
return this.clone().add(1, "day");
};
Каждый метод должен возвращать новый экземпляр moment,
если предполагается дальнейшее продолжение цепочки.
Несмотря на то, что Moment.js не является полностью immutable-библиотекой, плагины часто реализуют иммутабельное поведение вручную:
moment.fn.setImmutable = function (unit, value) {
const clone = this.clone();
clone.set(unit, value);
return clone;
};
Расширения могут принимать параметры:
function businessDaysPlugin(moment, options) {
const weekendDays = options?.weekends || [0, 6];
moment.fn.isBusinessDay = function () {
return !weekendDays.includes(this.day());
};
}
businessDaysPlugin(moment, { weekends: [0, 6] });
Плагины могут учитывать локализацию через
moment.localeData():
moment.fn.localizedDayName = function () {
return this.localeData().weekdays(this);
};
this вместо возврата нового объектаclone() при модификации датыmomentif (!moment.fn.myMethod) {
moment.fn.myMethod = function () {
return this;
};
}
Такой подход снижает риск конфликтов между плагинами и сторонними расширениями.