Записки программиста, обо всем и ни о чем. Но, наверное, больше профессионального.

2014-01-08

CoffeeScript

Веб разработчики знают, что такое CoffeeScript, а другим достаточно такого объяснения: это JavaScript с изрядным количеством синтаксического сахара. Многие яваскриптовые конструкции пишутся и, что важнее, читаются легче на кофескрипт. Кто хочет знать больше — велкам http://jashkenas.github.io/coffee-script/

Я же хочу поделиться практическим опытом применения CoffeeScript, на примере переписывания одного из модулей одного из проектов. Проект завязан на dojo, поэтому модуль — суть модуль dojo, что доставляет (одни только скобочки типа «)}))» после блока в сотню строк стоят отдельной песни).

Начну с начала. Основное рабочее место — MS Windows 2003 (да, у меня есть планы переползти на один из юниксов, но пока руки не доходят, не все сразу). Поэтому мне нужен Node.js такой: http://nodejs.org/dist/v0.10.24/node-v0.10.24-x86.msi
Собственно, его и предлагает по умолчанию страница http://nodejs.org/
Качаю, ставлю — easy-peasy. При установке выбрал папку d:\node

Внутри этой папки запускаю cmd.exe, получаю типа консоль.
В консоли запускаю установку кофескрипта
npm install -g coffee-script
Тоже не рокет сайенс.

Внутри рабочего проекта создаю файл build.coffee.cmd с содержимым:
set wd=%~dp0
pushd "%wd%"
coffee -w -c -o c:\mapedit\javascript\vs\obj\ c:\mapedit\javascript\vs\
Его задача — запустить вечный цикл, в котором проверяется состояние файлов *.coffee в папке c:\mapedit\javascript\vs\ и как только любой из них изменился, компилять его, создавая одноименный файл (но с расширением js) в папке c:\mapedit\javascript\vs\obj\
В качестве бонуса компиляции получаю проверку кода на предмет ошибок. Удобно — сохраняешь файл и тут же в окошке рядом видно — ОК или облом.

Остается немного, заменить в коде (другие файлы проекта) подключение модуля с
require("vs/meapp"], function(meapp) {
    try {vsapp.log('try vsapp');} catch(ex) {vsapp = new meapp();}
    vsapp.log(" vsapp ready");
на
require(["vs/obj/meapp"], function(meapp) {
    try {vsapp.log('try vsapp');} catch(ex) {vsapp = new meapp();}
    vsapp.log(" vsapp ready");
и переписать файл vs\meapp.js на аналогичный vs\meapp.coffee

Сейчас вы увидите, чем конкретно coffeescript код лучше кода javascript.

Было (vs\meapp.js):

Стало (vs\meapp.coffee):

А это код JS на выходе компилятора coffee:

Ниже тройной гист с кодом:
// deprecated. using meapp.coffee now
console.log('meapp enter');
var vappFactory = null;
define(
["dojo/_base/declare", "dojo/dom", 'dojo/has',
"vs/mpvlib"],
function(declare, dom, has, mpvlib) {
console.log('meapp dojo/define. define app classes');
try {vslib.log('try vslib');} catch(ex) {vslib = new mpvlib();}
vslib.log('vs.mpvlib declare vappFactory');
vappFactory = {
constructor: function(args) {
vslib.log('vs.meapp constructor');
declare.safeMixin(this, args);
},
//Create menu of editor options
createEditorMenu : function createEditorMenu() {
require(
["dijit/Menu",
"dijit/MenuItem",
"dijit/CheckedMenuItem",
"dijit/MenuSeparator",
"dijit/PopupMenuItem",
"dojo/domReady!"],
dojo.hitch(this, function(Menu, MenuItem, CheckedMenuItem, MenuSeparator, PopupMenuItem){
this.log("createEditorMenu");
// editor menu
var menu = new dijit.DropDownMenu({
style : "display: none;"
});
var menuItem = new dijit.MenuItem({
label : "Шаблоны",
iconClass : "dijitIconSearch",
onClick : function() {
console.log("editor menu, templates menuitem onclick. ", arguments);
navigateStack('editPanel');
}
});
menu.addChild(menuItem);
// tables submenu
var tabsmenu = new Menu();
tabsmenu.addChild(new MenuItem({
label: "Отключающие устройства",
iconClass : "dijitFolderOpened",
onClick : dojo.hitch(this, function() {
console.log("editor menu, table onclick. ", arguments);
this.loadFCTable("http://cgis.allgis.org/arcgis/rest/services/edit_Газовая_сеть/FeatureServer/0");
})
}));
tabsmenu.addChild(new MenuItem({
label: "СКЗ",
iconClass : "dijitFolderOpened",
onClick : dojo.hitch(this, function() {
console.log("editor menu, table onclick. ", arguments);
this.loadFCTable("http://cgis.allgis.org/arcgis/rest/services/edit_Газовая_сеть/FeatureServer/1");
})
}));
tabsmenu.addChild(new MenuItem({
label: "Газопроводы",
iconClass : "dijitFolderOpened",
onClick : dojo.hitch(this, function() {
console.log("editor menu, table onclick. ", arguments);
this.loadFCTable("http://cgis.allgis.org/arcgis/rest/services/edit_Газовая_сеть/FeatureServer/2");
}) }));
tabsmenu.addChild(new MenuItem({
label: "ГРП",
iconClass : "dijitFolderOpened",
onClick : dojo.hitch(this, function() {
console.log("editor menu, table onclick. ", arguments);
this.loadFCTable("http://cgis.allgis.org/arcgis/rest/services/edit_Газовая_сеть/FeatureServer/3");
}) }));
menu.addChild(new PopupMenuItem({
label: "Таблицы",
popup: tabsmenu,
iconClass : "dijitFolderOpened"
}));
// create dropdown button to display menu
var menuButton = new dijit.form.DropDownButton({
label : "Редактирование",
id : 'editorDropDownButton',
title : "Шаблоны, таблицы",
dropDown : menu,
iconClass : "dijitFolderOpened"
});
menuButton.startup();
dojo.byId('webmap-toolbar-left').appendChild(menuButton.domNode);
}));
}, // createEditorMenu
//this.loadFCTable("http://cgis.allgis.org/arcgis/rest/services/edit_Газовая_сеть/FeatureServer/0");
loadFCTable: function(fcUrl) {
this.log("loadFCTable");
console.debug("loadFCTable. fcUrl: ", arguments);
}, // loadFCTable
log: function(str, doalert) { // write app messages to log
vslib.log("vs.meapp." + str, doalert);
} // log
}; // vlibFactory
vslib.log('meapp dojo/declare vs.meapp');
return declare("vs.meapp", null, vappFactory);
}
); // define
console.log('meapp leave');
view raw 1 meapp.js hosted with ❤ by GitHub
###
Mapedit app classes.
Author: vasnake@gmail.com
###
console.log "meapp enter"
if vappFactory?
alert "vappFactory defined already!"
else
vappFactory = {}
define(["dojo/_base/declare", "dojo/dom", 'dojo/has', "vs/mpvlib"],
(declare, dom, has, mpvlib) ->
console.log 'meapp dojo/define. define app classes'
try
vslib.log('try vslib')
catch
window.vslib = new mpvlib()
vslib.log('vs.mpvlib declare vappFactory')
vappFactory = { # meapp object factory
# used as: require(["vs/obj/meapp"], function(meapp) { vsapp = new meapp(); ...
constructor: (args) ->
vslib.log('vs.meapp constructor')
declare.safeMixin(this, args)
log: (str, doalert) -> # write app messages to log
vslib.log("vs.meapp.#{str}", doalert);
# this.loadFCTable("http://cgis.allgis.org/arcgis/rest/services/edit_Газовая_сеть/FeatureServer/0");
loadFCTable: (fcUrl, fcName = '') ->
this.log("loadFCTable")
console.debug("loadFCTable. fcUrl: ", arguments)
# Create editor menu
createEditorMenu : () ->
require(
["dijit/Menu",
"dijit/MenuItem",
"dijit/CheckedMenuItem",
"dijit/MenuSeparator",
"dijit/PopupMenuItem",
"dojo/domReady!"],
dojo.hitch(this,
(Menu, MenuItem, CheckedMenuItem, MenuSeparator, PopupMenuItem) ->
this.log("createEditorMenu")
# tables submenu
subdata = [
{label: "Отключающие устройства", url: "http://cgis.allgis.org/arcgis/rest/services/edit_Газовая_сеть/FeatureServer/0"},
{label: "СКЗ", url: "http://cgis.allgis.org/arcgis/rest/services/edit_Газовая_сеть/FeatureServer/1"},
{label: "Газопроводы", url: "http://cgis.allgis.org/arcgis/rest/services/edit_Газовая_сеть/FeatureServer/2"},
{label: "ГРП", url: "http://cgis.allgis.org/arcgis/rest/services/edit_Газовая_сеть/FeatureServer/3"}
]
self = this
tabsmenu = new Menu
tabsmenu.addChild new MenuItem {
label: sd.label,
iconClass : "dijitFolderOpened",
fClassUrl: sd.url,
onClick : () ->
console.log("editor menu, table menuitem onclick. ", arguments)
self.loadFCTable(this.fClassUrl, this.label)
} for sd, idx in subdata
# editor menu
menu = new dijit.DropDownMenu { style : "display: none;" }
menu.addChild MenuItem {
label : "Шаблоны",
iconClass : "dijitIconSearch",
onClick : () ->
console.log("editor menu, templates menuitem onclick. ", arguments)
navigateStack('editPanel')
}
menu.addChild new PopupMenuItem {
label: "Таблицы",
popup: tabsmenu,
iconClass : "dijitFolderOpened"
}
# create dropdown button to display menu
menuButton = new dijit.form.DropDownButton {
label : "Редактирование",
id : 'editorDropDownButton',
title : "Шаблоны, таблицы",
dropDown : menu,
iconClass : "dijitFolderOpened"
}
menuButton.startup
dojo.byId('webmap-toolbar-left').appendChild(menuButton.domNode)
) # dojo.hitch
) # require
# createEditorMenu
} # vappFactory
vslib.log('meapp dojo/declare vs.meapp')
# return from define
declare("vs.meapp", null, vappFactory)
# define inner function
) # dojo/define
console.log "meapp leave"
view raw 2 meapp.coffee hosted with ❤ by GitHub
// Generated by CoffeeScript 1.6.3
/*
Mapedit app classes.
Author: vasnake@gmail.com
*/
(function() {
var vappFactory;
console.log("meapp enter");
if (typeof vappFactory !== "undefined" && vappFactory !== null) {
alert("vappFactory defined already!");
} else {
vappFactory = {};
}
define(["dojo/_base/declare", "dojo/dom", 'dojo/has', "vs/mpvlib"], function(declare, dom, has, mpvlib) {
console.log('meapp dojo/define. define app classes');
try {
vslib.log('try vslib');
} catch (_error) {
window.vslib = new mpvlib();
}
vslib.log('vs.mpvlib declare vappFactory');
vappFactory = {
constructor: function(args) {
vslib.log('vs.meapp constructor');
return declare.safeMixin(this, args);
},
log: function(str, doalert) {
return vslib.log("vs.meapp." + str, doalert);
},
loadFCTable: function(fcUrl, fcName) {
if (fcName == null) {
fcName = '';
}
this.log("loadFCTable");
return console.debug("loadFCTable. fcUrl: ", arguments);
},
createEditorMenu: function() {
return require(["dijit/Menu", "dijit/MenuItem", "dijit/CheckedMenuItem", "dijit/MenuSeparator", "dijit/PopupMenuItem", "dojo/domReady!"], dojo.hitch(this, function(Menu, MenuItem, CheckedMenuItem, MenuSeparator, PopupMenuItem) {
var idx, menu, menuButton, sd, self, subdata, tabsmenu, _i, _len;
this.log("createEditorMenu");
subdata = [
{
label: "Отключающие устройства",
url: "http://cgis.allgis.org/arcgis/rest/services/edit_Газовая_сеть/FeatureServer/0"
}, {
label: "СКЗ",
url: "http://cgis.allgis.org/arcgis/rest/services/edit_Газовая_сеть/FeatureServer/1"
}, {
label: "Газопроводы",
url: "http://cgis.allgis.org/arcgis/rest/services/edit_Газовая_сеть/FeatureServer/2"
}, {
label: "ГРП",
url: "http://cgis.allgis.org/arcgis/rest/services/edit_Газовая_сеть/FeatureServer/3"
}
];
self = this;
tabsmenu = new Menu;
for (idx = _i = 0, _len = subdata.length; _i < _len; idx = ++_i) {
sd = subdata[idx];
tabsmenu.addChild(new MenuItem({
label: sd.label,
iconClass: "dijitFolderOpened",
fClassUrl: sd.url,
onClick: function() {
console.log("editor menu, table menuitem onclick. ", arguments);
return self.loadFCTable(this.fClassUrl, this.label);
}
}));
}
menu = new dijit.DropDownMenu({
style: "display: none;"
});
menu.addChild(MenuItem({
label: "Шаблоны",
iconClass: "dijitIconSearch",
onClick: function() {
console.log("editor menu, templates menuitem onclick. ", arguments);
return navigateStack('editPanel');
}
}));
menu.addChild(new PopupMenuItem({
label: "Таблицы",
popup: tabsmenu,
iconClass: "dijitFolderOpened"
}));
menuButton = new dijit.form.DropDownButton({
label: "Редактирование",
id: 'editorDropDownButton',
title: "Шаблоны, таблицы",
dropDown: menu,
iconClass: "dijitFolderOpened"
});
menuButton.startup;
return dojo.byId('webmap-toolbar-left').appendChild(menuButton.domNode);
}));
}
};
vslib.log('meapp dojo/declare vs.meapp');
return declare("vs.meapp", null, vappFactory);
});
console.log("meapp leave");
}).call(this);


Как видно, кроме небольшой экономии на количестве строк, изрядно улучшается читаемость кода за счет применения Python-подобных (Python — the best!) методов — использование отступов вместо скобок (хотя можно продолжать использовать скобки — либерализм), расширенного набора операторов для циклов и условий, и … читайте http://jashkenas.github.io/coffee-script/ там все очень подробно и понятно нарисовано.
А я свою задачу выполнил — показал на примере, как можно переписать dojo-завязанный код на CoffeScript.
Мне понравилось.

Осталось упомянуть про удобство редактирования кода. Тут все не так радужно.
К сожалению, плагин для Eclipse, которому положено ублажать лень разработчика, не рабочий. От слова «совсем». Может быть, когда-нибудь он заработает. Для SciTE поддержки CoffeeScript тоже пока не наблюдается. На сегодня самый удобный способ (под MS Windows) редактирования кофескриптов — это использование Notepad++ с подключенным https://github.com/blakmatrix/CoffeeScript_notepad_UDL
Подключается он проще пареной свеклы: меню Language — Define your language — Import - https://github.com/blakmatrix/CoffeeScript_notepad_UDL/raw/master/CoffeeScript.xml
закрыть окно, перезапустить Нотепад++.


Дополнительно:


original post http://vasnake.blogspot.com/2013/12/coffeescript.html

Комментариев нет:

Отправить комментарий

Архив блога

Ярлыки

linux (241) python (191) citation (186) web-develop (170) gov.ru (159) video (124) бытовуха (115) sysadm (100) GIS (97) Zope(Plone) (88) бурчалки (84) Book (83) programming (82) грабли (77) Fun (76) development (73) windsurfing (72) Microsoft (64) hiload (62) internet provider (57) opensource (57) security (57) опыт (55) movie (52) Wisdom (51) ML (47) driving (45) hardware (45) language (45) money (42) JS (41) curse (40) bigdata (39) DBMS (38) ArcGIS (34) history (31) PDA (30) howto (30) holyday (29) Google (27) Oracle (27) tourism (27) virtbox (27) health (26) vacation (24) AI (23) Autodesk (23) SQL (23) humor (23) Java (22) knowledge (22) translate (20) CSS (19) cheatsheet (19) hack (19) Apache (16) Klaipeda (15) Manager (15) web-browser (15) Никонов (15) functional programming (14) happiness (14) music (14) todo (14) PHP (13) course (13) scala (13) weapon (13) HTTP. Apache (12) SSH (12) frameworks (12) hero (12) im (12) settings (12) HTML (11) SciTE (11) USA (11) crypto (11) game (11) map (11) HTTPD (9) ODF (9) Photo (9) купи/продай (9) benchmark (8) documentation (8) 3D (7) CS (7) DNS (7) NoSQL (7) cloud (7) django (7) gun (7) matroska (7) telephony (7) Microsoft Office (6) VCS (6) bluetooth (6) pidgin (6) proxy (6) Donald Knuth (5) ETL (5) NVIDIA (5) Palanga (5) REST (5) bash (5) flash (5) keyboard (5) price (5) samba (5) CGI (4) LISP (4) RoR (4) cache (4) car (4) display (4) holywar (4) nginx (4) pistol (4) spark (4) xml (4) Лебедев (4) IDE (3) IE8 (3) J2EE (3) NTFS (3) RDP (3) holiday (3) mount (3) Гоблин (3) кухня (3) урюк (3) AMQP (2) ERP (2) IE7 (2) NAS (2) Naudoc (2) PDF (2) address (2) air (2) british (2) coffee (2) fitness (2) font (2) ftp (2) fuckup (2) messaging (2) notify (2) sharepoint (2) ssl/tls (2) stardict (2) tests (2) tunnel (2) udev (2) APT (1) Baltic (1) CRUD (1) Canyonlands (1) Cyprus (1) DVDShrink (1) Jabber (1) K9Copy (1) Matlab (1) Portugal (1) VBA (1) WD My Book (1) autoit (1) bike (1) cannabis (1) chat (1) concurrent (1) dbf (1) ext4 (1) idioten (1) join (1) krusader (1) license (1) life (1) migration (1) mindmap (1) navitel (1) pneumatic weapon (1) quiz (1) regexp (1) robot (1) science (1) seaside (1) serialization (1) shore (1) spatial (1) tie (1) vim (1) Науру (1) крысы (1) налоги (1) пианино (1)