• Веб-разработка в Eclipse: JavaScript

    Как отмечалось ранее, для работы JS вместо Spket IDE я теперь использую Eclipse JSDT, который входит в состав Eclipse Web Tools Project. Причины для такого перехода вполне естественные: проекты, с которыми я работаю, становятся всё сложнее и больше, нужно больше удобства и контроля над ситуацией.

    В JSDT меня больше всего привлекло следующее:

    • Рефакторинг: переименование объектов, выделение блока в отдельную переменную, объединение определения переменной и присваивания и т.д. Некоторые вещи вроде выделения в метод пока толком не работают, но, надеюсь, в ближайшее время это будет исправлено.
    • Валидация кода. Помимо обычной проверки синтаксиса, можно настроить более сложные проверки вроде поиска неиспользованных переменных, недостижимый код, переопределение переменной из внешней области видимости и т.д.
    • Выделение фрагментов camelCase-переменных с помощью Shift+Alt+← и Shift+Alt+→
    • Поддержка JSDoc.
    • Удобный Outline/Quick outline; дополнительные окна, в которых показывается документация и код определения текущего объекта.
    • Автоматическая отбивка кода при его перемещении из/в блок.
    • Подключение внешних библиотек.
    • Встроенный дебаггер.

    Лучше всего будет, если читатель поставит Eclipse for JavaScript Web Developers и изучит все настройки и менюшки — в том числе контекстные — JSDT (лучше включить перспективу JavaScript), потому что возможностей действительно очень много и их сложно описать в одной статье.

    Однако при всей «крутости» этой среды разработки, в ней есть ряд проблем, с которыми пришлось столкнуться прежде, чем окончательно перейти на JSDT.

    Начинаем работу

    Для того, чтобы полноценно использовать все возможности JSDT, обязательно нужно создать проект с JavaScript-природой. Делается очень легко: вызываем File > New > Project… и в появившемся окошке выбираем JavaScript Project. В появившемся диалоговом окне вводим название проекта и жмём Finish. По умолчанию создаётся веб-проект с поддержкой DOM, папкой с JS-исходниками является сам проект. Когда вы лучше освоитесь с JSDT, то сможете более тонко настраивать проект: указывать подключаемые библиотеки, исключать ненужные файлы и папки из индекса. Пока оставим как есть.

    Module pattern

    Главным преимуществом для меня в Spket IDE была поддержка современных паттернов, в том числе и модуля:

    var module = (function() {
    	return {
    		method: function() {}
    	};
    })();
    

    В Spket такая конструкция без проблем отображается в outline и по ней работает code complete, но в JSDT ни то, ни другое не работает:

    ss011

    Небольшой JSDoc исправит ситуацию:

    /**
     * @type module
     */
    var module = (function() {
    	return {
    		/**
    		 * @memberOf module
    		 */
    		method: function() {}
    	};
    })();
    

    Теперь работает как надо:

    ss021

    Но есть ещё одна проблема: приватные переменные и методы модуля не отображаются в Outline, а очень хотелось бы. Это можно исправить, описав самовызывающуюся функцию как конструктор несуществующего класса:

    /**
     * @memberOf __module
     * @type module
     */
    var module = (/** @constructor */ function() {
    
    	function myPrivateMethod() {
    
    	}
    
    	return {
    		/**
    		 * @memberOf module
    		 */
    		method: function() {}
    	};
    })();
    

    ss031

    Как видно из примера, я описал несуществующий класс __module, двойное подчёркивание я использую в качестве своеобразного соглашения об именовании объектов. Проблема в том, что этот несуществующий класс попадёт в code complete всего проекта, и использование двойного подчёркивания — простой и понятный способ отфильтровать ненужные данные при вызове code complete. Однако этот недостаток очень легко можно превратить в достоинство: таким образом можно описывать структуры объектов, доступ к описанию которых затруднён из-за отсутствия строгой типизации в JS:

    ss041

    Поддержка популярных библиотек

    Базовый набор библиотек для JSDT довольно невелик: это стандартные объекты JavaScript (Array, String и прочее) и стандартный DOM (HTMLElement, document и так далее). То есть если мы напишем, например, document. и вызовем code complete, то увидем список свойств и методов объекта document, экземпляра класса Document. Но современная веб-разработка немыслима без использования популярных библиотек вроде jQuery.

    Имея в своём распоряжении JSDoc, можно создать описание практически любого популярно фреймворка и использовать его для подсказок в коде. Так как таких описаний в интернете найдено не было, я воспользовался своим любимым принципом «если хочешь, чтобы что-то было сделано хорошо, сделай это сам» и запустил проект, в котором создаю описания популярных библиотек и фреймворков, с которыми работаю:

    jsdt-docs — JSDoc для популярных библиотек

    В этом проекте сейчас есть следующие библиотеки:

    • Modernizr 2
    • Browser Addons — разные методы и свойства, которые почему-то отсутствуют в стандартном описании DOM в JSDT.
    • console — небольшое описание объекта console, который присутствует в современных браузерах.
    • CSS2Properties — список CSS-свойств для свойства style DOM-элементов. Несмотря на то, что он называется CSS2, в нём присутствуют и CSS3-свойства: такое название выбрано потому, что в стандартном описании Element.prototype.style является объектом класса CSS2Properties.
    • JS SIgnals
    • Node.JS
    • Socket.IO
    • Underscore.js.
    • Zepto.js

    Добавить библиотеку довольно просто:

    1. Идём в настройки: Preferences > JavaScript > Include Path > User Libraries.
    2. Создаём новую библиотеку: нажимаем кнопку New…
    3. Вводим название библиотеки в появившемся окне и жмём ОК.
    4. Выделив только что добавленную библиотеку, нажимаем на кнопку Add .js file… и выбираем один или несколько файлов, относящихся к данной библиотеке.

    После того, как библиотека была создана, нужно добавить её в проект:

    1. Идём в настройки проекта: Project > Properties > JavaScript > Include Path > Libraries.
    2. Жмём кнопку Add JavaScript Library, выбираем User Library, а затем и библиотеки, которые хотим добавить.

    Теперь у вас в проекте будет работать code complete для указанных библиотек:

    ss051

    Поддержка jQuery

    Добавление поддержки своего любимого фреймворка оказалась не такой уж и простой задачей. Во-первых, он довольно большой и содержит внушительных объемов документацию. Во-вторых — многие методы в нём «перегружены»: например, val() возвращает текстовое значение поля, а val(str) — записывает его и возвращает уже jQuery-объект.

    К счастью, у документации к jQuery есть публичный API, который выдаёт описание всех методов в XML. Я написал парсер на node.js, который опрашивает API и преобразует XML в JSDoc, понятный для JSDT (и, надеюсь, другим IDE). Так что в случае выхода новой версии jQuery можно быстро перегенерировать всё документацию. Парсер я писал для себя и по-простому, он не поддерживает передачу параметров через командную строку, всё настраивается в main.js:

    • Можно сгенерировать документацию для определённой версии jQuery (TARGET_VERSION).
    • Можно сгенерировать описание в виде нескольких файлов. Это связано с особенностью JSDT. Как уже отмечалось ранее, в jQuery много «перегруженных» методов, и если их описать в одном JS-файле, то JSDT будет использовать только одно (самое последнее) описание. Однако если сохранить описание методов с одинаковым названием в разных файлах, то JSDT вполне неплохо покажет по ним code complete и документацию. Так что у вас есть выбор: при создании библиотеки jQuery добавить все файлы вида jquery-jsdoc-N.js (Eclipse JSDT) или же только jquery-jsdoс.js если ваша IDE способна прочитать описание с перегруженными методами.
    • Можно переписать некоторые определения для более удобной работы с code complete (class_map, prefix_map). Например, так переписываются описания для Deferred и Promise объектов, чтобы можно было использовать их описание в JSDoc:

    ss06

    В описании есть ряд классов, которые можно использовать в JSDoc: __jQueryDeferred, __jQueryPromise и __jQueryEvent.

    Пишем плагины к jQuery

    Собственно, как писать плагины рассказывать нет смысла, это подробно описано в документации. Покажу лишь как сделать так, чтобы ваш плагин появился в code complete. А сделать это очень просто, достаточно методу плагина указать, что он является членом класса jQuery:

    jQuery.extend(jQuery.fn, {
    	/** @memberOf jQuery */
    	myPlugin: function() {
    
    	}
    });
    
    $('div').m // тут можно вызвать code complete
    

    Поддержка Node.JS

    Отдельно стоит упомянуть Node.JS, так как к модулям нужно обращаться не напрямую, а через функцию require():

    var http = require('http');
    http.createServer(function() {
    
    });
    

    То есть подсказки для модуля должны зависеть от того, какой аргумент передали в функцию require(). В целом, в JSDT эта ситуация решаема: нужно написать отдельный плагин в виде подключаемой библиотеки, который будет находить вызовы функции require(), смотреть на аргумент и возвращать виртуальный объект с необходимыми методами, и может быть я когда-нибудь напишу такую библиотеку. А пока будем довольствоваться малым: указывать тип модуля через JSDoc.

    Все модули в моей документации описаны в виде классов Node{Name}Module, например: httpNodeHttpModule, utilNodeUtilModule. Поэтому для переменной, в которую записывается модуль, указываем через JSDoc нужный тип:

    /** @type NodeHttpModule */
    var http = require('http');
    http. // вызываем code complete
    

    Советы

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

    • Не используйте фигурные скобки при указании типа переменной с помощью тэга @type — в некоторых случаях JSDT не сможет подцепить тип переменной. Лучше писать так: @type Array (вместо @type {Array}).
    • При описании модуля тэг @memberOf moduleName достаточно указать только у одного свойства/метода в литерале, все остальные подцепятся автоматически.
    • Если у вас в проекте есть непосредственно код библиотек (например, jQuery), то его лучше исключать из индекса, чтобы он не мешал работе code complete. Делается это так: заходите в настройки проекта Project > Properties > JavaScript > Include Path > Source. Разворачиваете папку с исходниками, двойной клик по фильтру Excluded и в диалоговом окне в секцию Exclusion patterns добавляете файлы, которые надо исключить.
  • 35 комментариев

    1. 20 июля 2011

      Для Closure «Советы» не очень подходят, к сожалению…

    2. Сергей Чикуенок
      20 июля 2011

      Смотря с какими настройками сжимать. При стандартных у меня вроде не ругался на JSDoc.

    3. 20 июля 2011

      Стандартные — это Advanced или Simple? Проверю в обеих.

    4. Сергей Чикуенок
      20 июля 2011

      Simple

    5. 20 июля 2011

      Advanced рулит. В Closure есть сложные типы данных типа @typedef {{status: number, result: Object}}, которые не обходятся без фигурных скобок.

    6. 20 июля 2011

      И проблема с goog.base при наследовании, к сожалению. Но лучше Eclipse пока нет.

    7. Сергей Чикуенок
      20 июля 2011

      Ну вообще фигурные скобки в @typeникто не запрещает использовать, я просто указал, что в JSDT с этим могут быть проблемы 🙂 А какие проблемы с goog.base?

    8. 21 июля 2011

      Прочитал предыдущий пост, даже немного воодушевился, решил окончательно и бесповоротно перейти на eclipse. До этого, конечно, были эпизодические попытки перейти на него, и даже пару месяцев назад написал простенькое html5 приложение для android при помощи eclipse, но все равно так его и не осилил, все смущала неповоротливость и прожорливость ресурсов (в сравнении с notepad++).
      Как и советовали, скачал Eclipse JSDT, денёк побаловался с локальными проектами — все было замечательно (если не считать лагание и глючность самого eclipse). Для локальной неторопливой разработки оказалось вполне себе ничего, но когда мне понадобилось перенести проект на сервер для интеграции с серверной частью (ajax, xml и т.п.) я понял, что это северная белая лиса. Как оказалось встроенных инструментов для работы с удаленными фс у eclipse нет, всезнающий гугл показал мне 3 буквы — rse (remote system explorer), хаброюзеры пугали муками при установке rse под линкус и я не желая испытывать судьбу решил для начала попробовать с виндой — это толстенная северная белая лиса, чуть сервер задержался с ответом — висит, случайно нажал сохранить во время синхронизации файлов с сервером — висит, после каждого сохранения он зачем-то заново загружает список файлов с сервера — нажал какую-нить кнопку — опять висит.
      Потерянных 4х рабочих часов, в попытках хоть как-то это заставить работать стабильно, хвалило, что б очень на долго отбило у меня желание снова пытаться подружиться с eclipse, я даже подумывал вернуться к старым и верным notepad++ и winscp, но проекты все сложнее и обьемнее и без ide уже никак. Выбор пал на netbeans, и я не прогадал — стабильно, быстро, удобно, а eclipse в дальний сектор винчестера под запароленый архив, а код потерять

    9. Сергей Чикуенок
      21 июля 2011

      Вообще, конечно, работать напрямую с проектом через FTP — не самый правильный способ поддерживать проект в рабочем состоянии, ну да ладно 🙂 Мы у себя решаем эту проблему довольно просто: монтируем удалённый сервер по NFS/SSHFS, а версии синхронизируем через плагин FileSync, который автоматом закачивает изменившиеся файлы в нужную папку. За 2 года активного использования такого метода проблем не обнаружил.

    10. sap1ens
      22 июля 2011

      Добрый день, Сергей.
      Подскажите пожалуйста, возможно ли настроить отображение в Outline локальных переменных, расположенных внутри анонимных функций (не Module pattern, а обычная анонимная функция).

    11. 24 июля 2011

      Сергей, спасибо, интересная инфа. Но по этому редактору, если сравнивать его с SPKet IDE, к которому я очень привык, есть несколько моментов:
      1. Не удалось найти нормальной доки по JSDoc-диалекту JSDT — не ясно назначение некоторых специфичных тегов, которых нет ни в документации jsdoc-toolkit`а, ни где-то ещё, а он их предлагает, например, «@addon».
      2. Почему-то не работает переход к коду по нажатию Ctrl+{клик мышкой на переменную или метод, описанный в другом файле}. При чём не только к внешней библиотеке, но даже между файлами внутри проекта :((( SPKet IDE с за-@include`ными файлами это проделывает легко.
      3. Почему-то подчёркивает красным теговую разметку внутри JSDoc-комментариев, например, «@author Вячеслав«, да и дата в стандартном формате ему не нравится — «@date Jul 24, 2011 8:04:17 PM» — «Jul» подчёркивает… Есть простой способ, как в ворде, добавлять слова в личный словарь соответствующим пунктом из попап-меню?

    12. 24 июля 2011

      (в п.3 предыдущего комментария имелись в виду HTML-теги внутри JSDoc — подчёркивает параметры, например, «href»)

    13. sap1ens
      25 июля 2011
    14. Денис
      25 июля 2011

      Почему-то code complete вызывается только по ctrl+space. Подскажите, пожалуйста, это поправить?

    15. Макс
      11 августа 2011

      Здравствуй. Немного не по теме, но все же есть такой вопрос. Если есть пару объектов и нужно наследование одного объекта другим, то какой из вариантов кода будет более предпочтителен:

      1. с замыканием, как в твоих примерах. Здесь как понимаю наследование можно сделать через Parent.call(this).

      var module = (/** @constructor */ function() {
      function myPrivateMethod() {}
      return { method: function() {} };
      })();

      2. Или через простые функции, например

      function MainModel(){};
      MainModel.prototype.somemethod = function(){};

      function Article(){}
      Article.prototype.somemethod2 = function(){};

      а потом наследовать так
      Article.prototype = MainModel; () // но еклипс не подцепляет методы MainModel.
      ну или так
      Article.prototype = new MainModel(); // при каждом наследовании вызывается конструктор модели, что не есть хорошо.

      В приватных методах пока особую нужду не испытывал. Сомнения мучают потому, что второй вариант в файрбаге отображается как куча вложенных друг в друга prototype constructor. Нормальна ли такая ситуация? В разных книгах пишут по разному, а описания когда как лучше или как оптимально — нет.
      Если можешь отпиши, нет — подскажи хорошую книгу, буду читать.

    16. sap1ens
      12 августа 2011

      Макс, позволю себе ответить за автора =)
      Лучшая книга по ООП и шаблонам проектирования в JS — http://jsdesignpatterns.com/ — глава 4, подробно описано классическое и основанное на прототипах наследование

    17. Макс
      12 августа 2011

      sap1ens, спасибо за книгу. Ознакомился с подходами. Определил, что для меня наиболее подходящим является вариант mixin. Теперь вопрос уже по теме. Как научить eclipse правильно определять родителя в такой конструкции и соответственно давать на code complete все методы родителя (так как передача всего идет через третью функцию, то оно ничегошеньки не видит):

      var Parent = function() {};

      Parent.prototype.cancel = function(id) {
      this.convertToText();
      };

      /* Child */
      function Child(id, parent, value) {
      this.id = id;
      this.value = value || ‘default value’;
      this.parentElement = parent;
      this.createElements(this.id);
      this.attachEvents();
      };
      augment(Child, Parent);

      function augment(receivingClass, givingClass) {
      if (arguments[2]) { // Only give certain methods.
      for ( var i = 2, len = arguments.length; i < len; i++) {
      receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]];
      }
      } else { // Give all methods.
      for (methodName in givingClass.prototype) {
      if (!receivingClass.prototype[methodName]) {
      receivingClass.prototype[methodName] = givingClass.prototype[methodName];
      }
      }
      }
      }

      Пробовал писать jsDoc, но ничего хорошего с этого не получилось. Подскажите пожалуйста.

    18. hipot
      16 сентября 2011

      // эх, почему так не работает?
      $(‘div’).click(function(/** @type Event */ evt){
      evt.| //ctrl+пробел
      });

    19. Сергей Чикуенок
      16 сентября 2011

      hipot, правильно писать вот так:

      $(’div’).click(/** @param {Event} evt */ function(evt) {
      	evt.| //ctrl+пробел
      });
      
    20. Astaroth
      24 сентября 2011

      Добрый день. Не планируется ли подробной статьи по JSDoc в eclipse? В интернете только разрозненная информация, а душа просит чего-то упорядоченного.

    21. 26 октября 2011

      Поставил WDT, создал JavaScript проект, добавил библиотеку (документацию) jQuery с GIT-а.
      При вызове дополнения кода и объекта Jquery и переходе (стрелкой вниз) в панели методов на первый доступный метод — Eclipse залипает на 30-40 сек. и все тут. Т.е. сами методы объекта отображаются, но при вызове панели описания редактор вешается.
      В проекте всего один JS-файл.

      Также не работает code assist для такой конструкции:
      $.map($(‘div’), function(/** @param jQuery el */ el) {
      el;
      });
      и так не работает:
      $.map($(‘div’), function(/** @type jQuery */ a) {
      a;
      });
      и так:
      $.map($(‘div’), function(/**jQuery*/ a) {
      a;
      });

      В чем может быть дело? Спасибо.

    22. hipot
      26 октября 2011

      to Sorbing, смотрите выше, вам следует так писать:
      $.map($(‘div’), /** @param {jQuery} el */ function(el) {
      el.|
      });

    23. Сергей Чикуенок
      26 октября 2011

      В версии Eclipse 3.7.1 появились какие-то странные баги с code complete в JS, попробуйте откатиться на версию 3.7

      А в вашем примере нужно писать так:
      $.map($(’div’), /** @param {jQuery} a */function(a) {});

    24. 26 октября 2011

      > …следует так писать /** @param {jQuery} el */ function(el) {}
      Блиин! Где были мои глаза?! Спасибо, работает. Правда вариант записи Spket — /*Array*/ — более лаконичен, но да не будем уходить от темы.
      > В версии Eclipse 3.7.1 появились какие-то странные баги с code complete в JS..
      Ага, он оно что. Я то уже думал у меня с настройкой что-то не так, пустой Indigo 3.7.1 распаковывал, тестил. Да, работать анреал(.
      Останусь пока на Spket.
      Сергей, я начал работать в Eclipse PDT + Spket после просмотра Вашего скринкаста по код комплиту JavaScript.
      Что было основной причиной для перехода на JSDT? Не увидел в нем ничего, чтобы можно было сказать «ВАУ! Ухтышка)».
      Спасибо.
      P.S. Offtop! Кстати, о Вас как раз сегодня вспоминали) — http://habrahabr.ru/blogs/eclipse/131211/

    25. 26 октября 2011

      И еще, на дефолтной документации jQuery (c GIT-а) не поддерживается дополнение кода в цепочке методов.
      Вот так: http://dl.dropbox.com/u/16177201/files/jsdt-jquery-assist-chain-not-support.png

    26. Сергей Чикуенок
      26 октября 2011

      Проверьте, может вы не так подключили (или вообще не подключили) библиотеку.

      Возможно, это опять Eclipse 3.7.1 глючит. Я локально вносил кое-какие правки, у меня всё работает

    27. 27 октября 2011

      Сергей, а Вы на какой версии Eclipse сейчас используете WDT?
      Я скачал Eclipse 3.7, поставил WDT 3.3.1.(v201107072200). Подключил библиотеку jQuery (одним файлом) так:

      Так же подключил ее в самом проекте — глюки те же. Окно комплита вешает Eclipse на 30-60 сек.

    28. 27 октября 2011

      Блин, думал тег IMG работает в комментах..
      Подключил библиотеку jQuery (одним файлом) так: http://dl.dropbox.com/u/16177201/files/jsdt-3.7.1-add-jquery-library.png

    29. Сергей Чикуенок
      27 октября 2011

      Попробуйте подключить несколько файлов. У меня автокомплит тоже подтормаживает на вызове статических методов jQuery, попробуйте в моём файле закомментировать строчку:
      $.prototype = new jQuery();

      и проверить работу автокомплита на $('div').

    30. 27 октября 2011

      Хе) Другое дело! Даже Eclipse не нужно рестартовать, только F5 на проекте и все работает как надо! Спасибо.
      А зачем эта строчка была в документации?

    31. Сергей Чикуенок
      27 октября 2011

      Строчка нужна, чтобы работал кодкомплит по статическим методам в переменной $, например, $.ajax. Сейчас работает только с jQuery.ajax

    32. Виктор
      21 марта 2012

      Подскажите пожалуйста, как правильно написать JSDoc для вот такого примера (все свои классы ложу в один namespace):

      var namespace = {};
      namespace.Class1 = Class.create({
      method1: function() {}
      });

      Пробовал указывать @type namespace.Class1 и @memberOf namespace.Class1 — не помогло, пробовал @type Class1 и @memberOf Class1 — тоже не помогает, Outline показывает только
      namespace : {}
      Class1 : any

    33. lancer
      18 августа 2012

      Indigo под win 7 не запускается, The Eclipse executable launcher was unable to locate its companion

      Нашел только такое лекарство
      http://vsingleton.blogspot.com/2009/09/eclipse-executable-launcher-was-unable.html

      Попробывал сделать эти команды через командную строку,
      пишу $ pwd, отдает ошибку «не удается найти $…». Я в командной строке еще тот мастер.

      Попробывал другой способ с правами, что он пишет ниже.
      Застрял на 13.-ом пункте, у меня нет в этом окне пользователя admin нет, есть только группы, выставил всем пользователям админской группы, где не было большинства галочек, но так и не запускается.

    34. lancer
      18 августа 2012

      Я просто скачал его, залил в Program Files, и запускаю. Может что-то еще нужно перед этим сделать?

    35. 10 октября 2012

      Спасибо за информацию.