JavaScript na straně serveru s MooTools a Node.js

Autorem tohoto příspěvku je Christoph Pojer. Chcete-li se o Christophovi dozvědět více, klikněte sem.

Tento blogový příspěvek má poskytnout výchozí bod pro vývoj ServerSide JavaScript (SSJS) pomocí MooTools. Zaměřuje se na Node.js (http://nodejs.org) a snaží se vysvětlit hlavní pojmy a rozdíly od vývoje na straně klienta. Vychází výhradně z mých současných zkušeností, mé současné práce a osvědčených postupů, které jsem si doposud definoval – i když většina z nich byla silně ovlivněna lidmi z jiných lidí z týmu MooTools.

Jak nastavit Node.js

  • Stahujte z http://nodejs.org/#download
  • Sestavit pomocí ./configure &&make &&sudo make install

Jednodušší už to být nemůže.

Aktuální stav MooTools a SSJS

Naše aktuální vydání, MooTools Core 1.2 a 1.3beta2, nefungují hned po vybalení s Node.js. Node.js, stejně jako další implementace JavaScriptu na straně serveru, přijaly standard CommonJS, který zahrnuje modulový systém. Každý modul, který vytvoříte, může exportovat objekty prostřednictvím objektu „exports“. Soubor můžete zahrnout pomocí "require('path/to/module')", což vám umožní přístup k exportovaným proměnným modulu:

math.js

exports.add = function(a, b){
	return a + b;
};

myApplication.js

var math = require('./math');

var sys = require('sys'); // System module

sys.puts(math.add(13, 37)); // Outputs "50"

Tento skript můžete spustit prostřednictvím "node myApplication.js" na příkazovém řádku.

Více informací o tom můžete najít na CommonJS Wiki:http://wiki.commonjs.org/wiki/Modules/1.1.

Klíčový rozdíl mezi systémem modulů, jak je uveden v CommonJS, a běžnými skriptovými značkami na straně klienta je v tom, že nesdílejí stejný (globální) rozsah. To znamená, že vytvoření proměnné pomocí "var foo" v modulu ji automaticky nezpřístupní na globálním objektu. Globální objekt na straně klienta je objekt "window", který obvykle není dostupný na straně serveru. V Node.js se globální objekt nazývá GLOBAL, zatímco některé jiné implementace jednoduše používají název „global“ nebo na něj odkazují pouze „toto“ uvnitř modulu. Toto není definováno CommonJS, takže každé prostředí to řeší jiným způsobem.

I když je relativně snadné přidat podporu pro moduly v knihovně JavaScript, která se točí pouze kolem jednoho objektu, MooTools poskytuje několik globálních proměnných, jako je třída, události, typ (nativní) atd. Kromě toho je tento nový standard velmi mladý a pokud vždy jsme implementovali podporu pro CommonJS přímo do MooTools, chceme definovat nejlepší postupy, které můžeme doporučit celé naší komunitě.

Poznámka:CommonJS není ve skutečnosti standardem, ale spíše souborem specifikací, které může (nebo by měla) dodržovat implementace JavaScriptu na straně serveru, aby sjednotila různá prostředí a umožnila vytvářet moduly, které fungují na všech platformách bez jakýchkoli úpravy.

Stáhněte si ServerSide MooTools Build

Spusťte MooTools na Node.js

Během posledních několika měsíců někteří členové týmu MooTools přišli s různými způsoby, jak zajistit kompatibilitu MooTools CommonJS. Nyní jsem vytvořil úložiště na GitHubu, které pomáhá vytvořit verzi sestavení MooTools. To je většinou založeno na práci @keeto a mě. Budeme používat rozpracovanou verzi MooTools Core, což je verze před 1.3. Pokud nemáte nainstalovaný git nebo se vám nechce zadávat nějaké příkazy, můžete přeskočit na další sekci a stáhnout si předpřipravenou verzi MooTools:MooTools.js (na základě tohoto odevzdání).

Získejte MooTools 1.3wip (příkazový řádek)

git clone git://github.com/cpojer/mootools-core.git

Získejte Packager (vyžaduje instalaci php-cli)

git clone http://github.com/kamicane/packager.git

Získejte MooTools CommonJS Loader

git clone git://github.com/cpojer/mootools-loader.git

Vytvoření vlastní verze MooTools

cd packager # Switch into the Packager folder

./packager register /path/to/mootools-core
./packager register /path/to/mootools-loader

./packager build Loader/Prefix Core/Class Core/Class.Extras Loader/Loader -blocks 1.2compat > MooTools.js

Měli byste vidět nějaký výstup jako tento:

Build using: Core, Loader
Included Files/Components:
- Loader/Prefix: [Prefix]
- Core/Core: [Core, MooTools, Type, typeOf, instanceOf]
- Core/Array: [Array]
- Core/String: [String]
- Core/Function: [Function]
- Core/Number: [Number]
- Core/Class: [Class]
- Core/Class.Extras: [Class.Extras, Chain, Events, Options]
- Core/Object: [Object, Hash]
- Loader/Loader: [Loader]

a měl by být vytvořen soubor "MooTools.js", který je připraven k použití.

Používejte MooTools v Node.js

require.paths.push('path/to/mootoolsjs/');

require('MooTools').apply(GLOBAL);

var sys = require('sys');

var MyClass = new Class({

	initialize: function(){
		sys.puts('It works!');
	}

});

new MyClass;

Spusťte to znovu pomocí příkazu "node" na příkazovém řádku a měli byste vidět "Funguje to!" jako výstup.

Vezměte prosím na vědomí, že se jedná o poměrně experimentální verzi, funkce, které zde najdete, se mohou změnit a mohou obsahovat chyby. Výše uvedené řešení bylo zatím testováno pouze na Node.js, ale mělo by fungovat i na jiných implementacích SSJS

Rozdíl mezi aplikacemi a moduly

Jedna věc, kterou chci zdůraznit, je, že jsem přesvědčen, že moduly by neměly vytvářet globální proměnné. Výše uvedené řešení však dává vše, co MooTools poskytuje, na globální působnost. I když je rozumné to udělat, když vyvíjíte aplikaci a máte kontrolu nad každým jejím aspektem, nemyslím si, že je to dobrý nápad dělat to, když vytváříte modul (plugin), který používá MooTools. To je důvod, proč řešení, se kterým jsem přišel, má jiný způsob, jak s ním pracovat, zvažte následující příklad.

MyPlugin.js

var Moo = require('MooTools'),
	Class = Moo.Class,
	Options = Moo.Options,
	typeOf = Moo.typeOf;

exports.MyPlugin = new Class({

	Implements: [Options],
	
	options: {
		name: ''
	},
	
	initialize: function(options){
		if (!options) options = {};

		if (typeOf(options.name) != 'string')
			throw new Error("Ohmy!");
		
		this.setOptions(options);
	}

});

MyApplication.js

// Add path to MooTools module so every module can just require "MooTools" without specifying the exact path
require.paths.push('path/to/mootoolsjs/');

var MyPlugin = require('path/to/MyPlugin').MyPlugin;

new MyPlugin({name: 'Kid Rock'});

// We can still add all the MooTools objects to the global scope without breaking anything
require('MooTools').apply(GLOBAL);

new Class(..);

Nyní můžete třídu MyPlugin sdílet s dalšími lidmi a bude jim fungovat, i když neumístí objekty MooTools na globální rozsah.

Poznámka:MooTools stále přidává rozšíření k nativním typům, jako je String, Array a Function, i když je nenastavíte v globálním rozsahu. Spuštění "require('MooTools')" jednou zpřístupní všechna rozšíření v libovolném rozsahu. Všimněte si, že alespoň v tuto chvíli všechny moduly sdílejí přesně stejné datové typy; neexistují žádné datové typy v sandboxu. Pokud rozšíření nativních typů v JavaScriptu neodpovídá vašemu stylu kódování, pravděpodobně byste neměli používat MooTools (nebo JavaScript, protože je to základní funkce jazyka). Jedním z cílů projektu MooTools je poskytnout rámec, který působí přirozeně a nerozlišuje mezi základním jazykem a funkčností knihovny.

Proč „událost“? Async, co?

JavaScript, jako jazyk většinou používaný na straně klienta, má silné asynchronní schopnosti. To znamená, že většinu času ve své aplikaci definujete určitou funkcionalitu, která se spustí, když uživatel – skutečná osoba – interaguje s obsahem webové stránky. Obvykle to uděláte přidáním posluchačů k určitým událostem na prvcích DOM:

myElement.addEvent('click', function(){
	// This function gets executed upon interaction
});

Zavoláte metodu addEvent a přidáte k ní funkci, program pokračuje normálně a funkce je volána asynchronně, kdykoli na ni uživatel klikne. V žádném případě nechcete čekat se spuštěním jakéhokoli jiného kódu, dokud se tato událost nespustí. Nechcete, aby posluchač události kliknutí blokoval. Toto je hlavní cíl návrhu Node.js:být neblokující pro jakékoli I/O operace, jako je čtení nebo zápis souboru, ukládání nebo načítání dat z databáze atd. To znamená, že většinu kódu budete psát na straně serveru předává funkce (zpětná volání, posluchače událostí), které se provádějí později (např. když jsou připraveny výsledky operace). Jednoduchý příklad najdete přímo na webu Node.js:http://nodejs.org/

Abyste lépe porozuměli, toto je ukázkový kód toho, jak by mohlo vypadat ověřování uživatele pomocí MooTools:

var Moo = require('MooTools'),
	Class = Moo.Class,
	Db = require('Database').getDatabase(),
	sha1 = require('Sha1');

exports.User = new Class({

	initialize: function(name){
  		this.name = name;
	},

	authenticate: function(password, callback){
		var user = this;
		Db.open(function(db){
			db.collection('users', function(err, collection){
				if (err) return callback(err);

				collection.findOne({name: user.name, password: sha1.hex(password)}, function(err, data){
					if (err) return callback(err);

					callback(null, data != null);
				});
			});
		});
	}

});

Ve své aplikaci byste nyní vytvořili novou instanci User a zavolali metodu autentizace pomocí zpětného volání, jako je toto

	var User = require('User');

	var sys = require('sys');

	var instance = new User('david');
	instance.authenticate('kidrock', function(err, result){
		if (err) return; // handle database error

		if (result) sys.puts('User authenticated');
		else sys.puts('User does not exist');
	});
	sys.puts('Authenticating user');

Tento příklad vytiskne dvě zprávy "Authenticating user" a výsledek/úspěch ověření. Objednávka závisí na rychlosti databáze a jako první se pravděpodobně vytiskne „Authenticating user“. Můžete to porovnat s příkladem setTimout

setTimeout(function(){
	log('Bye');
}, 1);
log('Hello');

Poznámka:Styl zpětného volání s chybou jako prvním argumentem je zarovnán se způsobem, jakým Node.js aktuálně pracuje s asynchronními operacemi. Předtím byl používán systém "Promises", ale ten byl odstraněn. Implementace na vysoké úrovni může abstrahovat od současného řešení. Neváhejte implementovat svůj vlastní callback/event systém pomocí MooTools a sdílet jej s komunitou :)

Poznámka:CommonJS ve skutečnosti specifikuje identifikátory modulů na malá písmena. Líbí se mi však, že moje názvy souborů začínají velkými písmeny. Vždy mě uvidíte, že ve svých aplikacích dělám „vyžadovat('Uživatel')“ místo „uživatel“.

Proč ServerSide MooTools?

Jedním z důvodů existence knihoven JavaScriptu je nedostatek určitých funkcí zejména na úrovni DOM a obrovské množství problémů mezi různými renderovacími enginy. Nemáte žádný z těchto problémů na straně serveru. MooTools jako knihovna funguje na mnohem nižší úrovni než některé jiné knihovny, a proto poskytuje užitečné funkce, které podle nás v JavaScriptu chybí. Kromě toho, i když existují specifikace CommonJS, některé implementace se liší od ostatních. Jádro MooTools a nad ním abstrakční vrstva (jako Deck ( http://github.com/keeto/deck )) vám mohou být velkým přínosem a pomoci vám odstranit nebo omezit problémy nízké úrovně, se kterými se můžete v určité fázi vývoje setkat.

Kromě toho sofistikovaný a čistý systém tříd poskytovaný MooTools umožňuje psát abstraktní pluginy, které budou fungovat jak na straně serveru, tak na straně klienta bez jakýchkoliv dalších úprav (tj. třída Language/Internalization, validátor schématu atd. ). Pro více informací o tomto neváhejte sledovat moji prezentaci na FOSDEM 2010 (ve 25:00 + otázky a odpovědi).

Stáhněte si ServerSide MooTools Build

Další věci (nesouvisející s MooTools)

  • Ovladač pro MongoDB, do kterého jsem přispíval:http://github.com/christkv/node-mongodb-native
  • WebSocket Server pro NodeJS od vývojáře MooTools Guillerma Raucha:http://github.com/LearnBoost/Socket.IO-node
  • ORM nad ovladačem node-mongodb-driver od Nathana Whitea a Guillerma Raucha:http://github.com/nw/mongoose
  • Další moduly uzlů:http://wiki.github.com/ry/node/modules

Bonus:Spuštění skriptu v Node.js z TextMate pomocí Node.js

Přidejte tento příkaz jako skript:

#!/usr/bin/env node

var sys = require('sys'),
    spawn = require('child_process').spawn;

spawn('node', [process.ENV.TM_FILEPATH]).stdout.addListener('data', function(data) {
  sys.puts(data);
});

Takhle:

Pro větší zobrazení klikněte na obrázek výše.

O Christophu Pojerovi

Christoph je studentem softwarového inženýrství a obchodní administrativy na Technické univerzitě v Grazu v Rakousku. Je to zkušený webový vývojář a hlavní vývojář MooTools JavaScript Framework.

Christoph's Website • GitHub • Twitter • Forge