Розробка JavaScript API: 5 принципів написання вбудованих скриптів

Напевно, ви стикалися з принципами (нехай і суперечливими) про написання модулів і класів на JavaScript. Коли мені знадобилося написати вбудований в веб-сторінку скрипт, який надає API для роботи певного сервісу, то я не зміг знайти гідних рекомендацій про проектуванні подібних скриптів.
Отже, ось (досить очевидні) вимоги до скрипту, з якими я зіткнувся:
  • він буде вбудовуватися в сторінки сторонніх веб-додатків;
  • він повинен якісно виконувати свою роботу;
  • він повинен завантажуватися швидко;
  • він не повинен (непередбачене) впливати на роботу веб-додатки;
  • повинен відповідати вимогам безпеки;
  • … // багато чого ще :)
image
З реальної практики народилися принципи, описані нижче. Це не повністю унікальні ідеї, а скоріше збірка кращих практик, яких я бачив у чужих рішеннях, наприклад, в библиотечках google analytics і jquery.
1. Система складання
Вона потрібна. Спочатку здається, що можна просто все тримати в одному файлі (можна навіть з цього почати), але потім стає ясно, що збірка необхідна. Тому що використовуються сторонні бібліотечки. Тому що є кілька варіантів поставки скрипта. Тому що скрипт може завантажувати файли ресурсів по мірі необхідності. І про це варто думати відразу, навіть коли ви ще тримайте весь скрипт в одному файлі.
Як збирати? Тільки конкатенація. Тому що основний скрипт повинен завантажуватися швидко, тобто одним файлом.
Це не означає, що потрібно все залити в один файл, і сподіватися, що все буде добре. Необов'язкові, додаткові можливості потрібно завантажувати лише тоді, коли клієнт бібліотечки викликає відповідні методи. Але ядро має завантажитися швидко, добре закэшироваться і відразу надати клієнту API.
Весь скрипт при цьому треба загорнути в один scope. Очевидно? Так.
(function () {
// Тут буде твій код
}());

до Речі, щоб обернути код в scope з допомогою Grunt, використовуйте
options
banner
та
footer
:
concat: {
injectScriptProd: {
src: [...],
dest: 'someScript.js',
options: {
banner: '(function(){\n',
footer: '\n}());'
}
},

2. Перемикання між локальною і продакшн конфігурацією
Щоб можна було легко керувати збірками та конфігураціями, мені дуже допомогло завести одну змінну
config
, покласти її в окремий файл
configDev.js
або
configProd.js
та мати окремі збірки скрипта. А варіантів збірок по-інших причин знадобилося більше двох. У результаті цих простих файлів дуже полегшило мені і збірку, і код, і життя. При конкатенації просто вказуєте, з яких файлів зібрати скрипт, — і цілісний файл-скрипт готовий.
Погана практика: мати замещаемые змінні по всьому JavaScript-коду виду:
<% serverUrl %>/someApi
. Псує читаність коду, повільніше збирається. І хочеться, щоб grunt watch працював дійсно швидко, чи не правда?
Приклад нашого prod config-файлу:
var config = {
server: "https://www.yourserver.com/api/",
resourcesServer: "https://www.yourserver.com/cdn/",
envSuffix: "Prod",
globalName: "yourProjectName"
}; 

// Маленький, та молодецький!

3. Як передати API назовні?
Є різні способи, але зараз робимо так:
window[config.globalName] = yourApiVar;

Це дозволяє:
  • Тестувати кілька версій бібліотечки на сторінці, причому так, що вони один одному не заважають.
  • Весь скрипт помістити в один закритий scope.
  • (Якщо раптом знадобиться) вирішувати проблеми з сумісністю. Адже ми будемо знати, що управління примірником API відбувається в коді самого скрипта, а не в коді клієнта бібліотечки. І тому у нас є повний контроль над усіма примірниками.
4. «Правильна» система модулів
Я знаю, щоб я тут не сказав, в мене полетять гнилі помідори від людей, які вибирає іншу систему модулів. Починаємо.
Правильно робити так:
var module = (function () { // for each module have this structure
var someInnerModuleVar;

// тут міг би бути твій геніальний код

return {
publicMethod: publicMethod
};
}());

А чому саме так? Відповідь очевидна: коли ви сконкатенируете код таких модулів, все буде працювати без всяких бібліотечок для модулів.
5. Ініціалізація API
Якщо у вашій бібліотечці є хоч якась ініціалізація (а вона там є, навіть якщо ви думаєте по-іншому), то винесіть її в окремий метод. Можна навіть створити окремий метод для ініціалізації в кожному модулі. І потім викликати їх явно і з чітким розумінням, як це працює і в якій послідовності.
Для першого разу, напевно, вистачить. Ось структура отриманого модуля:
(function () {
config = {}; 
sharedState = {}; 

var module = (function () { 
var someInnerModuleVar;

// крутий js код

return {
publicMethod: publicMethod,
init: init
};
}());

start();
}());

Якщо у вас є ідеї, як поліпшити шаблон, то буду радий їх почути. Я в основному писав на java, цей проект, — мій самий інтенсивний досвід в JavaScript. Напишіть, як зробити краще, в коментарях.
Ще думаю написати про роботу з
cookies
,
localStorage
, db, network. Напишіть, які теми найбільш цікаві.

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

0 коментарів

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