Правила хорошого тону при написанні плагіна на jQuery

Правила хорошого тону при написанні плагіна на jQueryЯ написав безліч плагінів на jQuery. Якщо подивитися код всіх плагінів, сортуючи їх за датою публікації на github, то можна простежити еволюцію коду. Ні в одному з цих плагінів не дотримані всі рекомендації, які будуть описані нижче. Все що буде описано, лише мій особистий досвід, накопичений від проекту до проекту.
Писати розширення на jQuery досить просто, але якщо хочете дізнатися, як написати їх так, щоб потім їх було просто підтримувати і розширювати, ласкаво просимо під кат.


Далі піде якийсь звід рекомендацій. Передбачається, що читач знайомий з розробкою плагінів на jQuery. Але я все-таки наведу приклад jQuery плагін, структуру якого зазвичай рекомендують у відповідних статтях.

(function ($) {
$.fn.tooltips = function (opt) {
var options = $.extend(true, {
position: 'top'
}, opt);
return this.each(function () {
$(this).after('<div class="xd_tooltips ' + options.position + '">' + $(this).data('title') + '</div>');
});
};
}(jQuery));

Це весь функціонал нашого плагіна. Він додає після кожного елемента вибірки, div.xd_tooltips новий елемент. Це все, що він робить.

Всі операції треба робити в окремому класі

Задумавши серйозний плагін, який буде рости, не потрібно всю логіку засовувати в анонімну функцію метод each, як у прикладі. Створіть окрему функцію конструктор (клас). Потім для кожного елемента вибірки, створюйте окремий екземпляр цього класу, і зберігайте посилання на нього в data вихідного елемента. При повторному виклику плагіна перевіряємо data. Це вирішує проблему повторної ініціалізації.

(function ($) {
var Tooltips = function (elm) {
$(elm).after('<div class="xd_tooltips">' + $(elm).data('title') + '</div>');
};
$.fn.tooltips = function (opt) {
var options = $.extend(true, {
position: 'top'
}, opt);
return this.each(function () {
if (!$(this).data('tooltips')) {
$(this).data('tooltips', new Tooltips(this, options));
}
});
};
}(jQuery));

Всі загальні операції hide,show,destroy робіть методами класу

Коли плагін буде великим, і ви захочете використовувати частину його функціоналу в іншому плагіні/коді, буде дуже зручно, якщо у класу будуть доступні відкриті методи. Методи можна робити через прототипи, однак тут є мінус — не можна використовувати приватні змінні. Найкращим варіантом буде використання локальних функцій, які потім повернуться в хеше.

Приблизно так:
// зайвий код, і потрібно стежити за this
var PluginName = function () {
this.init = function () {
// ініціалізація 
};
};
// нормально, але немає локальних приватних змінних 
// і також є проблема з this
var PluginName = function () {};
PluginName.prototype.init = function () {
// ініціалізація 
};
// ідеально
var PluginName = function () {
var self,
init = function () {
// ініціалізація 
};
self = {
init: init
};
return self;
};

Чому третій варіант кращий?
По перше — тепер методи всередині класу, можна використовувати без префікса this. this — в невмілих руках це зло. В JS він може бути чим завгодно, і дуже рекомендую використовувати його як можна рідше.

По-друге, коли плагін буде великим, ви вирішите пропустити його через uglify. Тоді в останньому варіанті, всі згадки init (крім ключа хеш self.init) будуть замінені на однобуквенную змінну, яка при частому використанні методу, пристойно зменшить код.

По-третє, вам доступні приватні методи і змінні, які ви оголосіть у верхньому var. Це дуже зручно. Щось на зразок private методів у «дорослих» ЯП.

З останнього правила потрібно вичавити ще одне:

Всі ініціалізацію об'єкта також треба тримати в окремому методі init

Не робіть по можливості нічого в момент створення екземпляра класу. Це просто досвід. Ваш плагін рано чи пізно виросте, і вам захочеться використовувати його код з іншого місця, не обов'язково з jQuery. Наприклад у вас в коді буде крутий парсер, і ви захочете використовувати його поза всяких DOM елементів.

Тоді:

var tooltips = new Tooltips();
tooltips.parse(somedata);

Не буде нічого створювати в дереві DOM, а зробить те, що потрібно.

Робіть для кожного екземпляра класу окремий екземпляр налаштувань

У першому прикладі ми зробили одну глобальну options змінну. А в scope jQuery могло потрапити кілька об'єктів. Відчуваєте чим це загрожує? JS так працює, що якщо один з елементів, потім буде змінювати ці параметри, то вони зміняться всіх елементів. Це не завжди вірно. Тому options подаємо другим елементом в конструктор нашого класу.

З усього вищевикладеного, плагін буде виглядати так:

(function ($) {
var Tooltips = function (elm, options) {
var self,
init = function () {
$(elm).after('<div class="xd_tooltips ' + options.position + '">' + $(elm).data('title') + '</div>');
};
self = {
init: init
};
return self;
};
$.fn.tooltips = function (opt) {
return this.each(function () {
var tooltip;
if (!$(this).data('tooltips')) {
tooltip = new Tooltips(this, $.extend(true, {
position: 'top'
}, opt));
tooltip.init();
$(this).data('tooltips', tooltip);
}
});
};
}(jQuery));

Налаштування за замовчуванням повинні бути видні глобально

У прикладі вище, ми з'єднуємо налаштування opt і налаштування за замовчуванням у вигляді хешу {}. Це не вірно — завжди знайдеться користувач, якому такі установки не підійдуть, а викликати кожен раз плагін зі своїми настройками буде накладно. Звичайно, він може залізти в код плагіна і виправити налаштування на свої, але тоді він втратить оновлення плагіна. Тому налаштування за замовчуванням повинні бути видні глобально, і їх можна було змінити також глобально.

Для нашого плагіна можна використовувати це так:

$.fn.tooltips = function (opt) {
return this.each(function () {
var tooltip;
if (!$(this).data('tooltips')) {
tooltip = new Tooltips(this, $.fn.tooltips.defaultOptions, opt);
tooltip.init();
$(this).data('tooltips', tooltip);
}
});
};
$.fn.tooltips.defaultOptions = {
position: 'top'
};

Тоді будь-розробник зможе у себе в скрипті оголосити:

$.fn.tooltips.defaultOptions.position = 'left';

А не робити це при кожній ініціалізації модуля.

Не завжди повертайте this

У всіх наведених вище прикладах, код ініціалізації зводився до того, що ми відразу ж повертали this.each. Насправді майже всі методи (плагіни) jQuery повертають this.

Тому можливі такі речі як:

$('.tooltips')
.css('position', 'absolute')
.show()
.append('<div>1</div>');

Це дуже зручно. Але до прикладу метод val, якщо немає параметрів, повертає значення елемента. Так і у нас, треба передбачити спосіб, за яким, наш плагін міг би повертати якісь значення.

$.fn.tooltips = function (opt, opt2) {
var result = this;
this.each(function () {
var tooltip;
if (!$(this).data('tooltips')) {
tooltip = new Tooltips(this, $.fn.tooltips.defaultOptions, opt);
tooltip.init();
$(this).data('tooltips', tooltip);
} else {
tooltip = $(this).data('tooltips');
}
if ($.type(opt) === 'string' && tooltip[opt] !== undefined && $.isFunction(tooltip[opt])) {
result = tooltip[opt](opt2);
}
});
return result;
};

Зверніть увагу, ми додали другий параметр opt2. Він нам буде потрібен саме для випадків виклику якихось методів.
Наприклад, для плагінів введення/виводу актуально змінити початкове value.

$('input[type=date]').datetimepicker();
$('input[type=date]').datetimepicker('setValue', '12.02.2016');
console.log($('input[type=date]').datetimepicker('getValue'));

Використовуйте 'use strict'

Ні, серйозно, ви ще не використовуєте? JS прощає занадто багато, і звідси виростає величезна кількість помилок. Використання цієї директиви вбереже вас хоча б від глобальних змінних.

(function ($) {
'use strict';
//...
} (jQuery));

Хтось скаже, що можна оголосити 'use strict' на самому початку файлу, але я не рекомендую цього робити. Коли проект зросте, і його будуть використовувати інші плагіни. А творці тих плагінів не використовували 'use script'. Коли grunt/gulp/npm збере всі пакети в один build файл, то вас буде чекати неприємний сюрприз.

Дуже рекомендую ще JSLint для валідації коду. Я багато років використовую notepad++ для розробки, і в ньому немає підсвічування помилок. Може це не актуально для IDE, але мені JSLint дозволяє писати більш якісний код.

Використовуйте свій внутрішній CSS reset

На початку свого CSS файлу, обнуляйте весь CSS для всього плагіна. Тобто потрібен якийсь головний клас, під яким будуть усі інші. При використанні Less, це додасть додаткову читабельність:

.jodit {
font-family: Helvetica, Arial, Verdana, Tahoma, sans-serif;
&, & *{
box-sizing: border-box;
padding:0;
margin: 0;
}
}

Використовуйте префікси в назвах CSS класів

CSS в браузерах річ глобальна. А передбачити в якому оточенні буде ваш плагін, неможливо. Тому всі, навіть малозначні класи, пишіть з префіксом. Я дуже часто бачив, як допоміжні класи disable,active,hover, повністю ламають зовнішній вигляд плагіна.

Використовуйте сучасні способи складання проекту

В коментарях правильно підказали, що жоден сучасний JS проект вже не живе без оновлень, автоматичних збирачів, і відстеження залежностей.
У коді ми наївно вважали, що jQuery до проекту вже підключено. А що якщо це не так. Все зламається.
;(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS для Browserify
module.exports = factory;
} else {
// Використовуючи глобальні змінні браузера
factory(jQuery);
}
}(function ($) {
'use script';
// код плагіна
}));

Такий скрипт за замовчуванням у браузері відпрацює точно також як і раніше, однак при використанні Browserify і йому подібних систем, автоматично підтягне усі потрібні залежності. І тут може бути не тільки jQuery. Будь-яка інша бібліотека.

Реєструйте свій проект в bower і npm

Серйозні хлопці не будуть використовувати ваш плагін, якщо він не буде мати можливість оновлення. Качати плагін архівом з github це вже вчорашній день. Зараз, достатньо зареєструватися на npmjs.com
Потім в корені свого проекту виконати
npm init

І слідувати інструкціям. В корені проекту з'явиться package.json
Після чого команда
npm publish ./

З того ж місця, опублікує плагін в npm.

І кінцеві користувачі зможуть встановлювати його собі так:
npm install tooltips

Виконувати npm publish ./ треба буде при кожній новій версії.

Для bower ситуація майже аналогічна. Тільки ніде реєструватися не треба.
Якщо у вас ще не встановлений bower.
npm install -g bower

Потім
init bower

В корені проекту.
Коли буде створено bower.json, опублікуйте все на github, або інший хостинг.

Після цього можна реєструвати пакет:
bower register jodit https://github.com/xdan/jodit.git


На цьому поки все, пишіть свої поради в коментарях. Спасибі за увагу!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.