Compare commits

..

30 Commits
0.6.11 ... 06

Author SHA1 Message Date
Guillermo Rauch
4395b86c60 README updates. 2011-05-17 18:56:02 -07:00
Guillermo Rauch
43d58186d8 Rebuild client 2011-05-16 13:19:35 -07:00
Guillermo Rauch
2066ddd8fe Release 0.6.18 2011-05-16 13:12:08 -07:00
Guillermo Rauch
db50f3e8ee Updated client 2011-05-13 14:37:24 -07:00
Guillermo Rauch
b79faf8998 Updated client. 2011-05-13 14:17:36 -07:00
Justin Randell
ce88922bea #169: fix returning 'ws' for location even when the client connects via 'wss' 2011-03-31 12:01:05 +11:00
Guillermo Rauch
5c4c681a83 Updated client 2011-03-30 12:01:23 -07:00
Guillermo Rauch
7ca0606670 Release 0.6.17 2011-03-30 11:45:06 -07:00
Guillermo Rauch
f6036007fc Merge branch 'master' of github.com:LearnBoost/Socket.IO-node 2011-03-30 11:43:23 -07:00
Guillermo Rauch
aa027ab571 Preparing awesome 0.7 changelog 2011-03-07 10:16:51 -08:00
Guillermo Rauch
b4f24c6995 Added reconnect events to chat example 2011-03-07 10:11:24 -08:00
Guillermo Rauch
3a53c63778 Added Arnout as official contributor 2011-03-07 09:46:12 -08:00
Arnout Kazemier
d8e3ccc637 Cleaned up the code, changed tabs to spaces and incorporated feedback on from the pull request 2011-03-06 15:25:35 +01:00
Arnout Kazemier
cef5fb3574 Merge branch 'master' of git://github.com/LearnBoost/Socket.IO-node 2011-03-05 19:36:42 +01:00
Guillermo Rauch
cfbae2ac15 Release 0.6.16 2011-03-04 09:06:01 -08:00
Guillermo Rauch
1072add73f Merge branch 'master' of https://github.com/tifroz/Socket.IO-node into tifroz-master 2011-03-04 09:03:43 -08:00
Arnout Kazemier
648b1d0792 Passes test-cases again, without hanging the test suite.
The fix for it is not as elegant as I would have hoped but it does the
job, preventing un-needed listeners to be added on the connections.
2011-03-03 21:39:27 +01:00
Arnout Kazemier
63624e5996 Added more listeners, to prevent uncaught exceptions. See https://gist.github.com/615009 for uncaptured 'timeout' errors. 2011-03-03 14:13:19 +01:00
Arnout Kazemier
1ee5285136 Fixed the 'possible EventEmitter memory leak detected' bug for the XHR transport. 2011-03-03 13:36:06 +01:00
Guillermo Rauch
5308452b8a Release 0.6.15 2011-02-23 11:20:33 -08:00
mlinnell
2ee09436ce Setting WebSocket buffer to empty once nonce is received (intended to include in previous commit) 2011-02-23 19:01:35 +00:00
mlinnell
d2ecaff462 Fixed memory leak in WebSocket transport.
The WebSocket.buffer would continue to grow, unabated, for each new message.
This buffer is now only utilized to process the nonce, and is set to empty (and no longer filled) once nonce has been received.
Parser instance appears to handle all message buffering, instead of WebSocket entity.
2011-02-23 18:55:24 +00:00
Guillermo Rauch
023566e03b Release 0.6.14 2011-02-22 11:09:42 -08:00
Shripad K
c306a3c303 fixed minor bug in xhr-polling, xhr-multipart, htmlfile. (this.listener.options.log -> self.listener.options.log) 2011-02-22 17:34:39 +05:30
Guillermo Rauch
80f1d9780b Release 0.6.13 2011-02-18 15:18:46 -08:00
Guillermo Rauch
ff10eeecba Fixed references to listener when logging 2011-02-18 15:18:10 -08:00
Guillermo Rauch
cd9cbb500b Fixed listener reference 2011-02-18 15:17:14 -08:00
Guillermo Rauch
de8d573948 Release 0.6.12 2011-02-18 10:36:13 -08:00
Guillermo Rauch
ea9e5ed2cc Fixed noDelay missing file descriptor problem 2011-02-18 10:34:51 -08:00
tifroz
d648fc5a72 Allow x-domain xhr-polling with Origin: null for Chrome/Safari 2011-02-16 00:16:41 -08:00
123 changed files with 9706 additions and 4607 deletions

View File

@@ -1,4 +1,41 @@
0.6.18 / 2011-05-16
===================
* Updated client with multiple fixes.
* Fixed returning 'ws' for location even when the client connects via 'wss'.
0.6.17 / 2011-03-30
==================
* Fixed the 'possible EventEmitter memory leak detected' bug for the XHR transport
* Reconnection support added to chat example. [3rd-Eden]
0.6.16 / 2011-03-04
===================
* Fixed cross domain xhr-polling in Safari [tifroz]
0.6.15 / 2011-02-23
===================
* Fixed memory leak in WebSocket transport [belorion]
0.6.14 / 2011-02-18
===================
* Fixed logging scope issue [shripad]
0.6.13 / 2011-02-18
===================
* Fixed references to listener when logging
0.6.12 / 2011-02-18
===================
* Fixed noDelay missing file descriptor problem
0.6.11 / 2011-02-15
===================

View File

@@ -32,10 +32,14 @@ and point your browser to `http://localhost:8080`. In addition to `8080`, if the
By default, the server will intercept requests that contain `socket.io` in the path / resource part of the URI. You can change this as shown in the available options below.
On the server:
On the server, install socket.io with NPM:
npm install socket.io
Then:
var http = require('http'),
io = require('./path/to/socket.io'),
io = require('socket.io'),
server = http.createServer(function(req, res){
// your normal server code

View File

@@ -13,6 +13,8 @@
var el = document.createElement('p');
if ('announcement' in obj) el.innerHTML = '<em>' + esc(obj.announcement) + '</em>';
else if ('message' in obj) el.innerHTML = '<b>' + esc(obj.message[0]) + ':</b> ' + esc(obj.message[1]);
if( obj.message && window.console && console.log ) console.log(obj.message[0], obj.message[1]);
document.getElementById('chat').appendChild(el);
document.getElementById('chat').scrollTop = 1000000;
}
@@ -38,11 +40,17 @@
for (var i in obj.buffer) message(obj.buffer[i]);
} else message(obj);
});
socket.on('connect', function(){ message({ message: ['System', 'Connected']})});
socket.on('disconnect', function(){ message({ message: ['System', 'Disconnected']})});
socket.on('reconnect', function(){ message({ message: ['System', 'Reconnected to server']})});
socket.on('reconnecting', function( nextRetry ){ message({ message: ['System', 'Attempting to re-connect to the server, next attempt in ' + nextRetry + 'ms']})});
socket.on('reconnect_failed', function(){ message({ message: ['System', 'Reconnected to server FAILED.']})});
</script>
<h1>Sample chat client</h1>
<div id="chat"><p>Connecting...</p></div>
<form id="form" onsubmit="send(); return false">
<form id="form" onSubmit="send(); return false">
<input type="text" autocomplete="off" id="text"><input type="submit" value="Send">
</form>

View File

@@ -61,33 +61,44 @@ Client.prototype._onMessage = function(data){
};
Client.prototype._onConnect = function(req, res){
var self = this;
var self = this
, attachConnection = !this.connection;
this.request = req;
this.response = res;
this.connection = req.connection;
this.connection.addListener('end', function(){
self._onClose();
if (self.connection)
self.connection.destroy();
});
if(!attachConnection) attachConnection = !attachConnection && this.connection.eventsAttached === undefined;
this.connection.eventsAttached = true;
if (attachConnection){
function destroyConnection(){
self._onClose();
self.connection && self.connection.destroy()
};
this.connection.addListener('end', destroyConnection);
this.connection.addListener('timeout', destroyConnection);
this.connection.addListener('error', destroyConnection);
}
if (req){
req.addListener('error', function(err){
function destroyRequest(){
req.destroy && req.destroy();
});
if (res) res.addListener('error', function(err){
res.destroy && res.destroy();
});
req.connection.addListener('error', function(err){
req.connection.destroy && req.connection.destroy();
});
};
req.addListener('error', destroyRequest);
req.addListener('timeout', destroyRequest);
if (res){
function destroyResponse(){
res.destroy && res.destroy();
}
res.addListener('error', destroyResponse);
res.addListener('timeout', destroyResponse);
}
if (this._disconnectTimeout) clearTimeout(this._disconnectTimeout);
}
};
Client.prototype._payload = function(){
var payload = [];

View File

@@ -23,4 +23,4 @@ exports.Listener = require('./listener');
* Version
*/
exports.version = '0.6.11';
exports.version = '0.6.18';

View File

@@ -33,7 +33,7 @@ HTMLFile.prototype._onConnect = function(req, res){
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){
listener.options.log('htmlfile message handler error - ' + e.stack);
self.listener.options.log('htmlfile message handler error - ' + e.stack);
}
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write('ok');

View File

@@ -33,7 +33,7 @@ WebSocket.prototype._onConnect = function(req, socket){
}
var origin = this.request.headers.origin,
location = (origin && origin.substr(0, 5) == 'https' ? 'wss' : 'ws')
location = (this.request.socket.encrypted ? 'wss' : 'ws')
+ '://' + this.request.headers.host + this.request.url;
this.waitingForNonce = false;
@@ -78,15 +78,14 @@ WebSocket.prototype._onConnect = function(req, socket){
try {
this.connection.write(headers.concat('', '').join('\r\n'));
this.connection.setTimeout(0);
this.connection.setNoDelay(true);
this.connection.setEncoding('utf-8');
} catch(e){
this._onClose();
return;
}
this.connection.setTimeout(0);
this.connection.setNoDelay(true);
this.connection.setEncoding('utf-8');
if (this.waitingForNonce) {
// Since we will be receiving the binary nonce through the normal HTTP
// data event, set the connection to 'binary' temporarily
@@ -100,18 +99,16 @@ WebSocket.prototype._onConnect = function(req, socket){
this.buffer = "";
this.connection.addListener('data', function(data){
self.buffer += data;
if (self.waitingForNonce) {
self.buffer += data;
if (self.buffer.length < 8) { return; }
// Restore the connection to utf8 encoding after receiving the nonce
self.connection.setEncoding('utf8');
self.waitingForNonce = false;
// Stuff the nonce into the location where it's expected to be
self.upgradeHead = self.buffer.substr(0,8);
self.buffer = self.buffer.substr(8);
if (self.buffer.length > 0) {
self.parser.add(self.buffer);
}
self.buffer = '';
if (self._proveReception(self._headers)) { self._payload(); }
return;
}

View File

@@ -47,7 +47,7 @@ Multipart.prototype._onConnect = function(req, res){
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){
listener.options.log('xhr-multipart message handler error - ' + e.stack);
self.listener.options.log('xhr-multipart message handler error - ' + e.stack);
}
res.writeHead(200, headers);
res.write('ok');

View File

@@ -49,7 +49,7 @@ Polling.prototype._onConnect = function(req, res){
var msg = qs.parse(body);
self._onMessage(msg.data);
} catch(e){
listener.options.log('xhr-polling message handler error - ' + e.stack);
self.listener.options.log('xhr-polling message handler error - ' + e.stack);
}
res.writeHead(200, headers);
res.write('ok');
@@ -69,7 +69,7 @@ Polling.prototype._write = function(message){
var headers = {'Content-Type': 'text/plain; charset=UTF-8', 'Content-Length': Buffer.byteLength(message)};
// https://developer.mozilla.org/En/HTTP_Access_Control
if (this.request.headers.origin && this._verifyOrigin(this.request.headers.origin)){
headers['Access-Control-Allow-Origin'] = this.request.headers.origin;
headers['Access-Control-Allow-Origin'] = (this.request.headers.origin === 'null' ? '*' : this.request.headers.origin);
if (this.request.headers.cookie) headers['Access-Control-Allow-Credentials'] = 'true';
}
this.response.writeHead(200, headers);

View File

@@ -1,17 +1,21 @@
{ "name" : "socket.io"
, "description" : "The cross-browser WebSocket"
, "version" : "0.6.11"
, "author" : "LearnBoost"
, "licenses" :
[ { "type" : "MIT"
, "url" : "http://github.com/learnboost/Socket.IO-node/raw/master/README.md"
{
"name": "socket.io"
, "description": "The cross-browser WebSocket"
, "version": "0.6.18"
, "author": "Guillermo Rauch <guillermo@learnboost.com>"
, "contributors": [
{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }
, { "name": "Arnout Kazemier", "email": "info@3rd-eden.com" }
]
, "licenses": [{
"type": "MIT"
, "url": "http://github.com/learnboost/Socket.IO-node/raw/master/README.md"
}]
, "repository": {
"type": "git"
, "url": "http://github.com/learnboost/Socket.IO-node.git"
}
]
, "repository" :
{ "type" : "git"
, "url" : "http://github.com/learnboost/Socket.IO-node.git"
}
, "engine" : [ "node >=0.1.102" ]
, "main" : "./index"
, "scripts" : { "test" : "make test" }
, "engine": [ "node >=0.1.102" ]
, "main": "./index"
, "scripts": { "test" : "make test" }
}

View File

@@ -0,0 +1,21 @@
0.7.0 / 2011-??-??
==================
* Fixed JSONP interaction with jQuery. [saschagehlich]
* Fixed; different port now considered cross-domain.
* Added compatibility for inclusion in non-browser environments.
* Added package.json.
* Added noConflict support. [kreichgauer]
* Added reconnection support with exponential backoff. [3rd-Eden]
0.6.2 / 2011-02-05
==================
* Fixed problem with xhr-multipart buffering
* Updated Flash websocket transport
* Fixed tryTransportsOnConnectTimeout option
* Added 'connect_failed' event after the last available transport fails to connect
within the timeout
* Add 'connecting' event emit on each connection attempt.

View File

@@ -64,27 +64,25 @@ If you are serving you .swf from a other domain than socket.io.js you will need
The insecure version can be found [here](http://github.com/gimite/web-socket-js/blob/master/WebSocketMainInsecure.zip).
IMPORTANT! When checking out the git repo, make sure to include the submodules. One way to do it is:
git clone [repo] --recursive
Another, once cloned
git submodule update --init --recursive
### Documentation
#### io.Socket
new io.Socket(host, [options]);
Options:
##### Options:
- *secure*
false
Use secure connections
- *port*
Current port or 80
The port `socket.io` server is attached to (defaults to the document.location port)
The port `socket.io` server is attached to (defaults to the document.location port).
- *resource*
@@ -94,9 +92,9 @@ Options:
- *transports*
['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling']
['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling', 'jsonp-polling']
A list of the transports to attempt to utilize (in order of preference)
A list of the transports to attempt to utilize (in order of preference).
- *transportOptions*
@@ -109,11 +107,48 @@ Options:
An object containing (optional) options to pass to each transport.
Properties:
- *rememberTransport*
true
A boolean indicating if the utilized transport should be remembered in a cookie.
- *connectTimeout*
5000
The amount of miliseconds a transport has to create a connection before we consider it timed out.
- *tryTransportsOnConnectTimeout*
true
A boolean indicating if we should try other transports when the connectTimeout occurs.
- *reconnect*
true
A boolean indicating if we should automatically reconnect if a connection is disconnected.
- *reconnectionDelay*
500
The amount of milliseconds before we try to connect to the server again. We are using a exponential back off algorithm for the following reconnections, on each reconnect attempt this value will get multiplied (500 > 1000 > 2000 > 4000 > 8000).
- *maxReconnectionAttempts*
10
The amount of attempts should we make using the current transport to connect to the server? After this we will do one final attempt, and re-try with all enabled transport methods before we give up.
##### Properties:
- *options*
The passed in options combined with the defaults
The passed in options combined with the defaults.
- *connected*
@@ -122,16 +157,20 @@ Properties:
- *connecting*
Whether the socket is connecting or not.
- *reconnecting*
Whether we are reconnecting or not.
- *transport*
The transport instance.
Methods:
##### Methods:
- *connect*
- *connect(λ)*
Establishes a connection
Establishes a connection. If λ is supplied as argument, it will be called once the connection is established.
- *send(message)*
@@ -139,29 +178,33 @@ Methods:
- *disconnect*
Closes the connection
Closes the connection.
- *on(event, λ)*
Adds a listener for the event *event*
Adds a listener for the event *event*.
- *once(event, λ)*
Adds a one time listener for the event *event*. The listener is removed after the first time the event is fired.
- *removeEvent(event, λ)*
Removes the listener λ for the event *event*
Removes the listener λ for the event *event*.
Events:
##### Events:
- *connect*
Fired when the connection is established and the handshake successful
Fired when the connection is established and the handshake successful.
- *connecting(transport_type)*
Fired when a connection is attempted, passing the transport name
Fired when a connection is attempted, passing the transport name.
- *connect_failed*
Fired when the connection timeout occurs after the last connection attempt.
Fired when the connection timeout occurs after the last connection attempt.
This only fires if the `connectTimeout` option is set.
If the `tryTransportsOnConnectTimeout` option is set, this only fires once all
possible transports have been tried.
@@ -177,11 +220,25 @@ Events:
- *disconnect*
Fired when the connection is considered disconnected.
- *reconnect(transport_type,reconnectionAttempts)*
### Credits
Fired when the connection has been re-established. This only fires if the `reconnect` option is set.
- *reconnecting(reconnectionDelay,reconnectionAttempts)*
Fired when a reconnection is attempted, passing the next delay for the next reconnection.
- *reconnect_failed*
Fired when all reconnection attempts have failed and we where unsuccessful in reconnecting to the server.
### Contributors
Guillermo Rauch &lt;guillermo@learnboost.com&gt;
Arnout Kazemier &lt;info@3rd-eden.com&gt;
### License
(The MIT License)

View File

@@ -14,8 +14,10 @@
*/
var fs = require('fs'),
sys = require('sys'),
socket = require('../lib/io'),
jsp = require('../lib/vendor/uglifyjs/lib/parse-js'),
pro = require("../lib/vendor/uglifyjs/lib/process"),
ast,
files = [
'io.js',
'util.js',
@@ -29,22 +31,29 @@ var fs = require('fs'),
'transports/jsonp-polling.js',
'socket.js',
'vendor/web-socket-js/swfobject.js',
'vendor/web-socket-js/FABridge.js',
'vendor/web-socket-js/web_socket.js'
],
content = "/** Socket.IO "+ socket.io.version +" - Built with build.js */\n";
content = "/** Socket.IO "+ socket.io.version +" - Built with build.js */\n",
license = "/* Socket.IO.min "+ socket.io.version +" @author Guillermo Rauch <guillermo@learnboost.com>, @license The MIT license., @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com> */\n";
sys.log('Reading files…');
console.log('Reading files…');
files.forEach(function(file){
var path = __dirname + '/../lib/' + file;
sys.log (' + ' + path);
console.log(' + ' + path);
content += fs.readFileSync(path) + "\n";
});
sys.log('Generating…');
console.log('Generating…');
fs.write(fs.openSync(__dirname + '/../socket.io.js', 'w'), content, 0, 'utf8');
sys.log(' + ' + __dirname + '/../socket.io.js');
console.log(' + ' + __dirname + '/../socket.io.js');
sys.log('All done!');
console.log('Uglyfying…');
ast = jsp.parse(content);
ast = pro.ast_mangle(ast); // get a new AST with mangled names
ast = pro.ast_squeeze(ast);
fs.write(fs.openSync(__dirname + '/../socket.io.min.js', 'w'), license + pro.gen_code(ast), 0, 'utf8');
console.log(' + ' + __dirname + '/../socket.io.min.js');
console.log('All done!');

View File

@@ -1,23 +1,44 @@
/**
* Socket.IO client
*
* @author Guillermo Rauch <guillermo@learnboost.com>
* @license The MIT license.
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
* socket.io-node-client
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
this.io = {
version: '0.6.2',
setPath: function(path){
if (window.console && console.error) console.error('io.setPath will be removed. Please set the variable WEB_SOCKET_SWF_LOCATION pointing to WebSocketMain.swf');
this.path = /\/$/.test(path) ? path : path + '/';
/**
* @namespace
*/
var io = this.io = {
/**
* Library version.
*/
version: '0.6.3',
/**
* Updates the location of the WebSocketMain.swf file that is required for the Flashsocket transport.
* This should only be needed if you want to load in the WebSocketMainInsecure.swf or if you want to
* host the .swf file on a other server.
*
* @static
* @deprecated Set the variable `WEB_SOCKET_SWF_LOCATION` pointing to WebSocketMain.swf
* @param {String} path The path of the .swf file
* @api public
*/
setPath: function(path){
if (window.console && console.error) console.error('io.setPath will be removed. Please set the variable WEB_SOCKET_SWF_LOCATION pointing to WebSocketMain.swf');
this.path = /\/$/.test(path) ? path : path + '/';
WEB_SOCKET_SWF_LOCATION = path + 'lib/vendor/web-socket-js/WebSocketMain.swf';
}
}
};
/**
* Expose Socket.IO in jQuery
*/
if ('jQuery' in this) jQuery.io = this.io;
/**
* Default path to the .swf file.
*/
if (typeof window != 'undefined'){
// WEB_SOCKET_SWF_LOCATION = (document.location.protocol == 'https:' ? 'https:' : 'http:') + '//cdn.socket.io/' + this.io.version + '/WebSocketMain.swf';
if (typeof WEB_SOCKET_SWF_LOCATION === 'undefined')

View File

@@ -1,163 +1,462 @@
/**
* Socket.IO client
*
* @author Guillermo Rauch <guillermo@learnboost.com>
* @license The MIT license.
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
* socket.io-node-client
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
(function(){
var Socket = io.Socket = function(host, options){
this.host = host || document.domain;
this.options = {
secure: false,
document: document,
port: document.location.port || 80,
resource: 'socket.io',
transports: ['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling', 'jsonp-polling'],
transportOptions: {
'xhr-polling': {
timeout: 25000 // based on polling duration default
},
'jsonp-polling': {
timeout: 25000
}
},
connectTimeout: 5000,
tryTransportsOnConnectTimeout: true,
rememberTransport: true
};
io.util.merge(this.options, options);
this.connected = false;
this.connecting = false;
this._events = {};
this.transport = this.getTransport();
if (!this.transport && 'console' in window) console.error('No transport available');
};
Socket.prototype.getTransport = function(override){
var transports = override || this.options.transports, match;
if (this.options.rememberTransport && !override){
match = this.options.document.cookie.match('(?:^|;)\\s*socketio=([^;]*)');
if (match){
this._rememberedTransport = true;
transports = [decodeURIComponent(match[1])];
}
}
for (var i = 0, transport; transport = transports[i]; i++){
if (io.Transport[transport]
&& io.Transport[transport].check()
&& (!this._isXDomain() || io.Transport[transport].xdomainCheck())){
return new io.Transport[transport](this, this.options.transportOptions[transport] || {});
}
}
return null;
};
Socket.prototype.connect = function(){
if (this.transport && !this.connected){
if (this.connecting) this.disconnect();
this.connecting = true;
this.emit('connecting', [this.transport.type]);
this.transport.connect();
if (this.options.connectTimeout){
var self = this;
this.connectTimeoutTimer = setTimeout(function(){
if (!self.connected){
self.disconnect();
if (self.options.tryTransportsOnConnectTimeout && !self._rememberedTransport){
if(!self._remainingTransports) self._remainingTransports = self.options.transports.slice(0);
var transports = self._remainingTransports;
while(transports.length > 0 && transports.splice(0,1)[0] != self.transport.type){}
if(transports.length){
self.transport = self.getTransport(transports);
self.connect();
}
}
if(!self._remainingTransports || self._remainingTransports.length == 0) self.emit('connect_failed');
}
if(self._remainingTransports && self._remainingTransports.length == 0) delete self._remainingTransports;
}, this.options.connectTimeout);
}
}
return this;
};
Socket.prototype.send = function(data){
if (!this.transport || !this.transport.connected) return this._queue(data);
this.transport.send(data);
return this;
};
Socket.prototype.disconnect = function(){
var io = this.io;
/**
* Create a new `Socket.IO client` which can establish a persisted
* connection with a Socket.IO enabled server.
*
* Options:
* - `secure` Use secure connections, defaulting to false.
* - `document` Reference to the document object to retrieve and set cookies, defaulting to document.
* - `port` The port where the Socket.IO server listening on, defaulting to location.port.
* - `resource` The path or namespace on the server where the Socket.IO requests are intercepted, defaulting to 'socket.io'.
* - `transports` A ordered list with the available transports, defaulting to all transports.
* - `transportOption` A {Object} containing the options for each transport. The key of the object should reflect
* name of the transport and the value a {Object} with the options.
* - `connectTimeout` The duration in milliseconds that a transport has to establish a working connection, defaulting to 5000.
* - `tryTransportsOnConnectTimeout` Should we attempt other transport methods when the connectTimeout occurs, defaulting to true.
* - `reconnect` Should reconnection happen automatically, defaulting to true.
* - `reconnectionDelay` The delay in milliseconds before we attempt to establish a working connection. This value will
* increase automatically using a exponential back off algorithm. Defaulting to 500.
* - `maxReconnectionAttempts` Number of attempts we should make before seizing the reconnect operation, defaulting to 10.
* - `rememberTransport` Should the successfully connected transport be remembered in a cookie, defaulting to true.
*
* Examples:
*
* Create client with the default settings.
*
* var socket = new io.Socket();
* socket.connect();
* socket.on('message', function(msg){
* console.log('Received message: ' + msg );
* });
* socket.on('connect', function(){
* socket.send('Hello from client');
* });
*
* Create a connection with server on a different port and host.
*
* var socket = new io.Socket('http://example.com',{port:1337});
*
* @constructor
* @exports Socket as io.Socket
* @param {String} [host] The host where the Socket.IO server is located, it defaults to the host that runs the page.
* @param {Objects} [options] The options that will configure the Socket.IO client.
* @property {String} host The supplied host arguments or the host that page runs.
* @property {Object} options The passed options combined with the defaults.
* @property {Boolean} connected Whether the socket is connected or not.
* @property {Boolean} connecting Whether the socket is connecting or not.
* @property {Boolean} reconnecting Whether the socket is reconnecting or not.
* @property {Object} transport The selected transport instance.
* @api public
*/
var Socket = io.Socket = function(host, options){
this.host = host || document.domain;
this.options = {
secure: false,
document: document,
port: document.location.port || 80,
resource: 'socket.io',
transports: ['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling', 'jsonp-polling'],
transportOptions: {
'xhr-polling': {
timeout: 25000 // based on polling duration default
},
'jsonp-polling': {
timeout: 25000
}
},
connectTimeout: 5000,
tryTransportsOnConnectTimeout: true,
reconnect: true,
reconnectionDelay: 500,
maxReconnectionAttempts: 10,
rememberTransport: true
};
io.util.merge(this.options, options);
this.connected = false;
this.connecting = false;
this.reconnecting = false;
this.events = {};
this.transport = this.getTransport();
if (!this.transport && 'console' in window) console.error('No transport available');
};
/**
* Find an available transport based on the options supplied in the constructor. For example if the
* `rememberTransport` option was set we will only connect with the previous successfully connected transport.
* The supplied transports can be overruled if the `override` argument is supplied.
*
* Example:
*
* Override the existing transports.
*
* var socket = new io.Socket();
* socket.getTransport(['jsonp-polling','websocket']);
* // returns the json-polling transport because it's availabe in all browsers.
*
* @param {Array} [override] A ordered list with transports that should be used instead of the options.transports.
* @returns {Null|Transport} The available transport.
* @api private
*/
Socket.prototype.getTransport = function(override){
var transports = override || this.options.transports, match;
if (this.options.rememberTransport && !override){
match = this.options.document.cookie.match('(?:^|;)\\s*socketio=([^;]*)');
if (match){
this.rememberedTransport = true;
transports = [decodeURIComponent(match[1])];
}
}
for (var i = 0, transport; transport = transports[i]; i++){
if (io.Transport[transport]
&& io.Transport[transport].check()
&& (!this.isXDomain() || io.Transport[transport].xdomainCheck())){
return new io.Transport[transport](this, this.options.transportOptions[transport] || {});
}
}
return null;
};
/**
* Establish a new connection with the Socket.IO server. This is done using the selected transport by the
* getTransport method. If the `connectTimeout` and the `tryTransportsOnConnectTimeout` options are set
* the client will keep trying to connect to the server using a different transports when the timeout occurs.
*
* Example:
*
* Create a Socket.IO client with a connect callback (We assume we have the WebSocket transport avaliable).
*
* var socket = new io.Socket();
* socket.connect(function(transport){
* console.log("Connected to server using the " + socket.transport.type + " transport.");
* });
* // => "Connected to server using the WebSocket transport."
*
* @param {Function} [fn] Callback.
* @returns {io.Socket}
* @api public
*/
Socket.prototype.connect = function(fn){
if (this.transport && !this.connected){
if (this.connecting) this.disconnect(true);
this.connecting = true;
this.emit('connecting', [this.transport.type]);
this.transport.connect();
if (this.options.connectTimeout){
var self = this;
this.connectTimeoutTimer = setTimeout(function(){
if (!self.connected){
self.disconnect(true);
if (self.options.tryTransportsOnConnectTimeout && !self.rememberedTransport){
if(!self.remainingTransports) self.remainingTransports = self.options.transports.slice(0);
var transports = self.remainingTransports;
while(transports.length > 0 && transports.splice(0,1)[0] != self.transport.type){}
if(transports.length){
self.transport = self.getTransport(transports);
self.connect();
}
}
if(!self.remainingTransports || self.remainingTransports.length == 0) self.emit('connect_failed');
}
if(self.remainingTransports && self.remainingTransports.length == 0) delete self.remainingTransports;
}, this.options.connectTimeout);
}
}
if (fn && typeof fn == 'function') this.once('connect',fn);
return this;
};
/**
* Sends the data to the Socket.IO server. If there isn't a connection to the server
* the data will be forwarded to the queue.
*
* @param {Mixed} data The data that needs to be send to the Socket.IO server.
* @returns {io.Socket}
* @api public
*/
Socket.prototype.send = function(data){
if (!this.transport || !this.transport.connected) return this.queue(data);
this.transport.send(data);
return this;
};
/**
* Disconnect the established connect.
*
* @param {Boolean} [soft] A soft disconnect will keep the reconnect settings enabled.
* @returns {io.Socket}
* @api public
*/
Socket.prototype.disconnect = function(soft){
if (this.connectTimeoutTimer) clearTimeout(this.connectTimeoutTimer);
this.transport.disconnect();
return this;
};
Socket.prototype.on = function(name, fn){
if (!(name in this._events)) this._events[name] = [];
this._events[name].push(fn);
return this;
};
if (!soft) this.options.reconnect = false;
this.transport.disconnect();
return this;
};
/**
* Adds a new eventListener for the given event.
*
* Example:
*
* var socket = new io.Socket();
* socket.on("connect", function(transport){
* console.log("Connected to server using the " + socket.transport.type + " transport.");
* });
* // => "Connected to server using the WebSocket transport."
*
* @param {String} name The name of the event.
* @param {Function} fn The function that is called once the event is emitted.
* @returns {io.Socket}
* @api public
*/
Socket.prototype.on = function(name, fn){
if (!(name in this.events)) this.events[name] = [];
this.events[name].push(fn);
return this;
};
/**
* Adds a one time listener, the listener will be removed after the event is emitted.
*
* Example:
*
* var socket = new io.Socket();
* socket.once("custom:event", function(){
* console.log("I should only log once.");
* });
* socket.emit("custom:event");
* socket.emit("custom:event");
* // => "I should only log once."
*
* @param {String} name The name of the event.
* @param {Function} fn The function that is called once the event is emitted.
* @returns {io.Socket}
* @api public
*/
Socket.prototype.once = function(name, fn){
var self = this
, once = function(){
self.removeEvent(name, once);
fn.apply(self, arguments);
};
once.ref = fn;
self.on(name, once);
return this;
};
/**
* Emit a event to all listeners.
*
* Example:
*
* var socket = new io.Socket();
* socket.on("custom:event", function(){
* console.log("Emitted a custom:event");
* });
* socket.emit("custom:event");
* // => "Emitted a custom:event"
*
* @param {String} name The name of the event.
* @param {Array} args Arguments for the event.
* @returns {io.Socket}
* @api private
*/
Socket.prototype.emit = function(name, args){
if (name in this._events){
var events = this._events[name].concat();
if (name in this.events){
var events = this.events[name].concat();
for (var i = 0, ii = events.length; i < ii; i++)
events[i].apply(this, args === undefined ? [] : args);
}
return this;
};
Socket.prototype.removeEvent = function(name, fn){
if (name in this._events){
for (var a = 0, l = this._events[name].length; a < l; a++)
if (this._events[name][a] == fn) this._events[name].splice(a, 1);
}
return this;
};
Socket.prototype._queue = function(message){
if (!('_queueStack' in this)) this._queueStack = [];
this._queueStack.push(message);
return this;
};
Socket.prototype._doQueue = function(){
if (!('_queueStack' in this) || !this._queueStack.length) return this;
this.transport.send(this._queueStack);
this._queueStack = [];
return this;
};
Socket.prototype._isXDomain = function(){
return this.host !== document.domain;
};
Socket.prototype._onConnect = function(){
this.connected = true;
this.connecting = false;
this._doQueue();
if (this.options.rememberTransport) this.options.document.cookie = 'socketio=' + encodeURIComponent(this.transport.type);
this.emit('connect');
};
Socket.prototype._onMessage = function(data){
this.emit('message', [data]);
};
Socket.prototype._onDisconnect = function(){
var wasConnected = this.connected;
this.connected = false;
this.connecting = false;
this._queueStack = [];
if (wasConnected) this.emit('disconnect');
};
/**
* Removes a event listener from the listener array for the specified event.
*
* Example:
*
* var socket = new io.Socket()
* , event = function(){};
* socket.on("connect", event);
* socket.removeEvent("connect", event);
*
* @param {String} name The name of the event.
* @param {Function} fn The function that is called once the event is emitted.
* @returns {io.Socket}
* @api public
*/
Socket.prototype.removeEvent = function(name, fn){
if (name in this.events){
for (var a = 0, l = this.events[name].length; a < l; a++)
if (this.events[name][a] == fn || this.events[name][a].ref && this.events[name][a].ref == fn) this.events[name].splice(a, 1);
}
return this;
};
/**
* Queues messages when there isn't a active connection available. Once a connection has been
* established you should call the `doQueue` method to send the queued messages to the server.
*
* @param {Mixed} message The message that was originally send to the `send` method.
* @returns {io.Socket}
* @api private
*/
Socket.prototype.queue = function(message){
if (!('queueStack' in this)) this.queueStack = [];
this.queueStack.push(message);
return this;
};
/**
* If there are queued messages we send all messages to the Socket.IO server and empty
* the queue.
*
* @returns {io.Socket}
* @api private
*/
Socket.prototype.doQueue = function(){
if (!('queueStack' in this) || !this.queueStack.length) return this;
this.transport.send(this.queueStack);
this.queueStack = [];
return this;
};
/**
* Check if we need to use cross domain enabled transports. Cross domain would
* be a different port or different domain name.
*
* @returns {Boolean}
* @api private
*/
Socket.prototype.isXDomain = function(){
var locPort = window.location.port || 80;
return this.host !== document.domain || this.options.port != locPort;
};
/**
* When the transport established an working connection the Socket.IO server it notifies us
* by calling this method so we can set the `connected` and `connecting` properties and emit
* the connection event.
*
* @api private
*/
Socket.prototype.onConnect = function(){
this.connected = true;
this.connecting = false;
this.doQueue();
if (this.options.rememberTransport) this.options.document.cookie = 'socketio=' + encodeURIComponent(this.transport.type);
this.emit('connect');
};
/**
* When the transport receives new messages from the Socket.IO server it notifies us by calling
* this method with the decoded `data` it received.
*
* @param data The message from the Socket.IO server.
* @api private
*/
Socket.prototype.onMessage = function(data){
this.emit('message', [data]);
};
/**
* When the transport is disconnected from the Socket.IO server it notifies us by calling
* this method. If we where connected and the `reconnect` is set we will attempt to reconnect.
*
* @api private
*/
Socket.prototype.onDisconnect = function(){
var wasConnected = this.connected;
this.connected = false;
this.connecting = false;
this.queueStack = [];
if (wasConnected){
this.emit('disconnect');
if (this.options.reconnect && !this.reconnecting) this.onReconnect();
}
};
/**
* The reconnection is done using an exponential back off algorithm to prevent
* the server from being flooded with connection requests. When the transport
* is disconnected we wait until the `reconnectionDelay` finishes. We multiply
* the `reconnectionDelay` (if the previous `reconnectionDelay` was 500 it will
* be updated to 1000 and than 2000>4000>8000>16000 etc.) and tell the current
* transport to connect again. When we run out of `reconnectionAttempts` we will
* do one final attempt and loop over all enabled transport methods to see if
* other transports might work. If everything fails we emit the `reconnect_failed`
* event.
*
* @api private
*/
Socket.prototype.onReconnect = function(){
this.reconnecting = true;
this.reconnectionAttempts = 0;
this.reconnectionDelay = this.options.reconnectionDelay;
var self = this
, tryTransportsOnConnectTimeout = this.options.tryTransportsOnConnectTimeout
, rememberTransport = this.options.rememberTransport;
function reset(){
if(self.connected) self.emit('reconnect',[self.transport.type,self.reconnectionAttempts]);
self.removeEvent('connect_failed', maybeReconnect).removeEvent('connect', maybeReconnect);
self.reconnecting = false;
delete self.reconnectionAttempts;
delete self.reconnectionDelay;
delete self.reconnectionTimer;
delete self.redoTransports;
self.options.tryTransportsOnConnectTimeout = tryTransportsOnConnectTimeout;
self.options.rememberTransport = rememberTransport;
return;
};
function maybeReconnect(){
if (!self.reconnecting) return;
if (!self.connected){
if (self.connecting && self.reconnecting) return self.reconnectionTimer = setTimeout(maybeReconnect, 1000);
if (self.reconnectionAttempts++ >= self.options.maxReconnectionAttempts){
if (!self.redoTransports){
self.on('connect_failed', maybeReconnect);
self.options.tryTransportsOnConnectTimeout = true;
self.transport = self.getTransport(self.options.transports); // override with all enabled transports
self.redoTransports = true;
self.connect();
} else {
self.emit('reconnect_failed');
reset();
}
} else {
self.reconnectionDelay *= 2; // exponential back off
self.connect();
self.emit('reconnecting', [self.reconnectionDelay,self.reconnectionAttempts]);
self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay);
}
} else {
reset();
}
};
this.options.tryTransportsOnConnectTimeout = false;
this.reconnectionTimer = setTimeout(maybeReconnect, this.reconnectionDelay);
this.on('connect', maybeReconnect);
};
/**
* API compatiblity
*/
Socket.prototype.fire = Socket.prototype.emit;
Socket.prototype.addListener = Socket.prototype.addEvent = Socket.prototype.addEventListener = Socket.prototype.on;
})();
Socket.prototype.addListener = Socket.prototype.addEvent = Socket.prototype.addEventListener = Socket.prototype.on;
Socket.prototype.removeListener = Socket.prototype.removeEventListener = Socket.prototype.removeEvent;
})();

View File

@@ -1,141 +1,295 @@
/**
* Socket.IO client
*
* @author Guillermo Rauch <guillermo@learnboost.com>
* @license The MIT license.
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
* socket.io-node-client
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
// abstract
(function(){
var frame = '~m~',
stringify = function(message){
if (Object.prototype.toString.call(message) == '[object Object]'){
if (!('JSON' in window)){
if ('console' in window && console.error) console.error('Trying to encode as JSON, but JSON.stringify is missing.');
return '{ "$error": "Invalid message" }';
}
return '~j~' + JSON.stringify(message);
} else {
return String(message);
}
};
Transport = io.Transport = function(base, options){
this.base = base;
this.options = {
timeout: 15000 // based on heartbeat interval default
};
io.util.merge(this.options, options);
};
var io = this.io,
/**
* Message frame for encoding and decoding responses from the Socket.IO server.
*
* @const
* @type {String}
*/
frame = '~m~',
/**
* Transforms the message to a string. If the message is an {Object} we will convert it to
* a string and prefix it with the `~j~` flag to indicate that message is JSON encoded.
*
* Example:
*
* stringify({foo:"bar"});
* // => "~j~{"foo":"bar"}"
*
* @param {String|Array|Object} message The messages that needs to be transformed to a string.
* @throws {Error} When the JSON.stringify implementation is missing in the browser.
* @returns {String} Message.
* @api private
*/
stringify = function(message){
if (Object.prototype.toString.call(message) == '[object Object]'){
if (!('JSON' in window)){
var error = 'Socket.IO Error: Trying to encode as JSON, but JSON.stringify is missing.';
if ('console' in window && console.error){
console.error(error);
} else {
throw new Error(error);
}
return '{ "$error": "'+ error +'" }';
}
return '~j~' + JSON.stringify(message);
} else {
return String(message);
}
},
/**
* This is the transport template for all supported transport methods. It provides the
* basic functionality to create a working transport for Socket.IO.
*
* Options:
* - `timeout` Transport shutdown timeout in milliseconds, based on the heartbeat interval.
*
* Example:
*
* var transport = io.Transport.mytransport = function(){
* io.Transport.apply(this, arguments);
* };
* io.util.inherit(transport, io.Transport);
*
* ... // more code here
*
* // connect with your new transport
* var socket = new io.Socket(null,{transports:['mytransport']});
*
* @constructor
* @param {Object} base The reference to io.Socket.
* @param {Object} options The transport options.
* @property {io.Socket|Object} base The reference to io.Socket.
* @property {Object} options The transport options, these are used to overwrite the default options
* @property {String} sessionid The sessionid of the established connection, this is only available a connection is established
* @property {Boolean} connected The connection has been established.
* @property {Boolean} connecting We are still connecting to the server.
* @api public
*/
Transport = io.Transport = function(base, options){
this.base = base;
this.options = {
timeout: 15000 // based on heartbeat interval default
};
io.util.merge(this.options, options);
};
Transport.prototype.send = function(){
throw new Error('Missing send() implementation');
};
/**
* Send the message to the connected Socket.IO server.
*
* @throws {Error} When the io.Transport is inherited, it should override this method.
* @api public
*/
Transport.prototype.send = function(){
throw new Error('Missing send() implementation');
};
/**
* Establish a connection with the Socket.IO server..
*
* @throws {Error} When the io.Transport is inherited, it should override this method.
* @api public
*/
Transport.prototype.connect = function(){
throw new Error('Missing connect() implementation');
};
Transport.prototype.connect = function(){
throw new Error('Missing connect() implementation');
};
Transport.prototype.disconnect = function(){
throw new Error('Missing disconnect() implementation');
};
Transport.prototype._encode = function(messages){
var ret = '', message,
messages = io.util.isArray(messages) ? messages : [messages];
for (var i = 0, l = messages.length; i < l; i++){
message = messages[i] === null || messages[i] === undefined ? '' : stringify(messages[i]);
ret += frame + message.length + frame + message;
}
return ret;
};
Transport.prototype._decode = function(data){
var messages = [], number, n;
do {
if (data.substr(0, 3) !== frame) return messages;
data = data.substr(3);
number = '', n = '';
for (var i = 0, l = data.length; i < l; i++){
n = Number(data.substr(i, 1));
if (data.substr(i, 1) == n){
number += n;
} else {
data = data.substr(number.length + frame.length);
number = Number(number);
break;
}
}
messages.push(data.substr(0, number)); // here
data = data.substr(number);
} while(data !== '');
return messages;
};
Transport.prototype._onData = function(data){
this._setTimeout();
var msgs = this._decode(data);
if (msgs && msgs.length){
for (var i = 0, l = msgs.length; i < l; i++){
this._onMessage(msgs[i]);
}
}
};
Transport.prototype._setTimeout = function(){
var self = this;
if (this._timeout) clearTimeout(this._timeout);
this._timeout = setTimeout(function(){
self._onTimeout();
}, this.options.timeout);
};
Transport.prototype._onTimeout = function(){
this._onDisconnect();
};
Transport.prototype._onMessage = function(message){
if (!this.sessionid){
this.sessionid = message;
this._onConnect();
} else if (message.substr(0, 3) == '~h~'){
this._onHeartbeat(message.substr(3));
} else if (message.substr(0, 3) == '~j~'){
this.base._onMessage(JSON.parse(message.substr(3)));
} else {
this.base._onMessage(message);
}
},
Transport.prototype._onHeartbeat = function(heartbeat){
this.send('~h~' + heartbeat); // echo
};
Transport.prototype._onConnect = function(){
this.connected = true;
this.connecting = false;
this.base._onConnect();
this._setTimeout();
};
Transport.prototype._onDisconnect = function(){
this.connecting = false;
this.connected = false;
this.sessionid = null;
this.base._onDisconnect();
};
Transport.prototype._prepareUrl = function(){
return (this.base.options.secure ? 'https' : 'http')
+ '://' + this.base.host
+ ':' + this.base.options.port
+ '/' + this.base.options.resource
+ '/' + this.type
+ (this.sessionid ? ('/' + this.sessionid) : '/');
};
/**
* Disconnect the established connection.
*
* @throws {Error} When the io.Transport is inherited, it should override this method.
* @api private
*/
Transport.prototype.disconnect = function(){
throw new Error('Missing disconnect() implementation');
};
/**
* Encode the message by adding the `frame` to each message. This allows
* the client so send multiple messages with only one request.
*
* @param {String|Array} messages Messages that need to be encoded.
* @returns {String} Encoded message.
* @api private
*/
Transport.prototype.encode = function(messages){
var ret = '', message;
messages = io.util.isArray(messages) ? messages : [messages];
for (var i = 0, l = messages.length; i < l; i++){
message = messages[i] === null || messages[i] === undefined ? '' : stringify(messages[i]);
ret += frame + message.length + frame + message;
}
return ret;
};
/**
* Decoded the response from the Socket.IO server, as the server could send multiple
* messages in one response.
*
* @param (String} data The response from the server that requires decoding
* @returns {Array} Decoded messages.
* @api private
*/
Transport.prototype.decode = function(data){
var messages = [], number, n;
do {
if (data.substr(0, 3) !== frame) return messages;
data = data.substr(3);
number = '', n = '';
for (var i = 0, l = data.length; i < l; i++){
n = Number(data.substr(i, 1));
if (data.substr(i, 1) == n){
number += n;
} else {
data = data.substr(number.length + frame.length);
number = Number(number);
break;
}
}
messages.push(data.substr(0, number)); // here
data = data.substr(number);
} while(data !== '');
return messages;
};
/**
* Handles the response from the server. When a new response is received
* it will automatically update the timeout, decode the message and
* forwards the response to the onMessage function for further processing.
*
* @param {String} data Response from the server.
* @api private
*/
Transport.prototype.onData = function(data){
this.setTimeout();
var msgs = this.decode(data);
if (msgs && msgs.length){
for (var i = 0, l = msgs.length; i < l; i++){
this.onMessage(msgs[i]);
}
}
};
/**
* All the transports have a dedicated timeout to detect if
* the connection is still alive. We clear the existing timer
* and set new one each time this function is called. When the
* timeout does occur it will call the `onTimeout` method.
*
* @api private
*/
Transport.prototype.setTimeout = function(){
var self = this;
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(function(){
self.onTimeout();
}, this.options.timeout);
};
/**
* Disconnect from the Socket.IO server when a timeout occurs.
*
* @api private
*/
Transport.prototype.onTimeout = function(){
this.onDisconnect();
};
/**
* After the response from the server has been parsed to individual
* messages we need to decode them using the the Socket.IO message
* protocol: <https://github.com/learnboost/socket.io-node/>.
*
* When a message is received we check if a session id has been set,
* if the session id is missing we can assume that the received message
* contains the sessionid of the connection.
* When a message is prefixed with `~h~` we dispatch it our heartbeat
* processing method `onHeartbeat` with the content of the heartbeat.
*
* When the message is prefixed with `~j~` we can assume that the contents
* of the message is JSON encoded, so we parse the message and notify
* the base of the new message.
*
* If none of the above, we consider it just a plain text message and
* notify the base of the new message.
*
* @param {String} message A decoded message from the server.
* @api private
*/
Transport.prototype.onMessage = function(message){
if (!this.sessionid){
this.sessionid = message;
this.onConnect();
} else if (message.substr(0, 3) == '~h~'){
this.onHeartbeat(message.substr(3));
} else if (message.substr(0, 3) == '~j~'){
this.base.onMessage(JSON.parse(message.substr(3)));
} else {
this.base.onMessage(message);
}
},
/**
* Send the received heartbeat message back to server. So the server
* knows we are still connected.
*
* @param {String} heartbeat Heartbeat response from the server.
* @api private
*/
Transport.prototype.onHeartbeat = function(heartbeat){
this.send('~h~' + heartbeat); // echo
};
/**
* Notifies the base when a connection to the Socket.IO server has
* been established. And it starts the connection `timeout` timer.
*
* @api private
*/
Transport.prototype.onConnect = function(){
this.connected = true;
this.connecting = false;
this.base.onConnect();
this.setTimeout();
};
/**
* Notifies the base when the connection with the Socket.IO server
* has been disconnected.
*
* @api private
*/
Transport.prototype.onDisconnect = function(){
this.connecting = false;
this.connected = false;
this.sessionid = null;
this.base.onDisconnect();
};
/**
* Generates a connection url based on the Socket.IO URL Protocol.
* See <https://github.com/learnboost/socket.io-node/> for more details.
*
* @returns {String} Connection url
* @api private
*/
Transport.prototype.prepareUrl = function(){
return (this.base.options.secure ? 'https' : 'http')
+ '://' + this.base.host
+ ':' + this.base.options.port
+ '/' + this.base.options.resource
+ '/' + this.type
+ (this.sessionid ? ('/' + this.sessionid) : '/');
};
})();

View File

@@ -1,53 +1,88 @@
/**
* Socket.IO client
*
* @author Guillermo Rauch <guillermo@learnboost.com>
* @license The MIT license.
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
* socket.io-node-client
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
(function(){
var Flashsocket = io.Transport.flashsocket = function(){
io.Transport.websocket.apply(this, arguments);
};
io.util.inherit(Flashsocket, io.Transport.websocket);
Flashsocket.prototype.type = 'flashsocket';
Flashsocket.prototype.connect = function(){
var self = this, args = arguments;
WebSocket.__addTask(function(){
io.Transport.websocket.prototype.connect.apply(self, args);
});
return this;
};
Flashsocket.prototype.send = function(){
var self = this, args = arguments;
WebSocket.__addTask(function(){
io.Transport.websocket.prototype.send.apply(self, args);
});
return this;
};
Flashsocket.check = function(){
if (typeof WebSocket == 'undefined' || !('__addTask' in WebSocket)) return false;
if (io.util.opera) return false; // opera is buggy with this transport
if ('navigator' in window && 'plugins' in navigator && navigator.plugins['Shockwave Flash']){
return !!navigator.plugins['Shockwave Flash'].description;
}
if ('ActiveXObject' in window) {
try {
return !!new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
} catch (e) {}
}
return false;
};
Flashsocket.xdomainCheck = function(){
return true;
};
var io = this.io,
/**
* The Flashsocket transport. This is a API wrapper for the HTML5 WebSocket specification.
* It uses a .swf file to communicate with the server. If you want to serve the .swf file
* from a other server than where the Socket.IO script is coming from you need to use the
* insecure version of the .swf. More information about this can be found on the github page.
*
* @constructor
* @extends {io.Transport.websocket}
* @api public
*/
Flashsocket = io.Transport.flashsocket = function(){
io.Transport.websocket.apply(this, arguments);
};
io.util.inherit(Flashsocket, io.Transport.websocket);
/**
* The transport type, you use this to identify which transport was chosen.
*
* @type {String}
* @api public
*/
Flashsocket.prototype.type = 'flashsocket';
/**
* Disconnect the established `Flashsocket` connection. This is done by adding a new
* task to the Flashsocket. The rest will be handled off by the `WebSocket` transport.
*
* @returns {Transport}
* @api public
*/
Flashsocket.prototype.connect = function(){
var self = this, args = arguments;
WebSocket.__addTask(function(){
io.Transport.websocket.prototype.connect.apply(self, args);
});
return this;
};
/**
* Sends a message to the Socket.IO server. This is done by adding a new
* task to the Flashsocket. The rest will be handled off by the `WebSocket` transport.
*
* @returns {Transport}
* @api public
*/
Flashsocket.prototype.send = function(){
var self = this, args = arguments;
WebSocket.__addTask(function(){
io.Transport.websocket.prototype.send.apply(self, args);
});
return this;
};
/**
* Check if the Flashsocket transport is supported as it requires that the Adobe Flash Player
* plugin version `10.0.0` or greater is installed. And also check if the polyfill is correctly
* loaded.
*
* @returns {Boolean}
* @api public
*/
Flashsocket.check = function(){
if (typeof WebSocket == 'undefined' || !('__addTask' in WebSocket) || !swfobject) return false;
return swfobject.hasFlashPlayerVersion("10.0.0");
};
/**
* Check if the Flashsocket transport can be used as cross domain / cross origin transport.
* Because we can't see which type (secure or insecure) of .swf is used we will just return true.
*
* @returns {Boolean}
* @api public
*/
Flashsocket.xdomainCheck = function(){
return true;
};
})();

View File

@@ -1,73 +1,138 @@
/**
* Socket.IO client
*
* @author Guillermo Rauch <guillermo@learnboost.com>
* @license The MIT license.
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
* socket.io-node-client
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
(function(){
var HTMLFile = io.Transport.htmlfile = function(){
io.Transport.XHR.apply(this, arguments);
};
io.util.inherit(HTMLFile, io.Transport.XHR);
HTMLFile.prototype.type = 'htmlfile';
HTMLFile.prototype._get = function(){
var self = this;
this._open();
window.attachEvent('onunload', function(){ self._destroy(); });
};
HTMLFile.prototype._open = function(){
this._doc = new ActiveXObject('htmlfile');
this._doc.open();
this._doc.write('<html></html>');
this._doc.parentWindow.s = this;
this._doc.close();
var _iframeC = this._doc.createElement('div');
this._doc.body.appendChild(_iframeC);
this._iframe = this._doc.createElement('iframe');
_iframeC.appendChild(this._iframe);
this._iframe.src = this._prepareUrl() + '/' + (+ new Date);
};
HTMLFile.prototype._ = function(data, doc){
this._onData(data);
var script = doc.getElementsByTagName('script')[0];
script.parentNode.removeChild(script);
};
HTMLFile.prototype._destroy = function(){
if (this._iframe){
this._iframe.src = 'about:blank';
this._doc = null;
var io = this.io,
/**
* The HTMLFile transport creates a `forever iframe` based transport
* for Internet Explorer. Regular forever iframe implementations will
* continuously trigger the browsers buzy indicators. If the forever iframe
* is created inside a `htmlfile` these indicators will not be trigged.
*
* @constructor
* @extends {io.Transport.XHR}
* @api public
*/
HTMLFile = io.Transport.htmlfile = function(){
io.Transport.XHR.apply(this, arguments);
};
io.util.inherit(HTMLFile, io.Transport.XHR);
/**
* The transport type, you use this to identify which transport was chosen.
*
* @type {String}
* @api public
*/
HTMLFile.prototype.type = 'htmlfile';
/**
* Starts the HTMLFile data stream for incoming messages. And registers a
* onunload event listener so the HTMLFile will be destroyed.
*
* @api private
*/
HTMLFile.prototype.get = function(){
var self = this;
this.open();
window.attachEvent('onunload', function(){ self.destroy(); });
};
/**
* Creates a new ActiveX `htmlfile` with a forever loading iframe
* that can be used to listen to messages. Inside the generated
* `htmlfile` a reference will be made to the HTMLFile transport.
*
* @api private
*/
HTMLFile.prototype.open = function(){
this.doc = new ActiveXObject('htmlfile');
this.doc.open();
this.doc.write('<html></html>');
this.doc.parentWindow.s = this;
this.doc.close();
var iframeC = this.doc.createElement('div');
this.doc.body.appendChild(iframeC);
this.iframe = this.doc.createElement('iframe');
iframeC.appendChild(this.iframe);
this.iframe.src = this.prepareUrl() + '/' + (+ new Date);
};
/**
* The Socket.IO server will write script tags inside the forever
* iframe, this function will be used as callback for the incoming
* information.
*
* @param {String} data The message
* @param {document} doc Reference to the context
* @api private
*/
HTMLFile.prototype._ = function(data, doc){
this.onData(data);
var script = doc.getElementsByTagName('script')[0];
script.parentNode.removeChild(script);
};
/**
* Destroy the established connection, iframe and `htmlfile`.
* And calls the `CollectGarbage` function of Internet Explorer
* to release the memory.
*
* @api private
*/
HTMLFile.prototype.destroy = function(){
if (this.iframe){
try {
this.iframe.src = 'about:blank';
} catch(e){}
this.doc = null;
CollectGarbage();
}
};
HTMLFile.prototype.disconnect = function(){
this._destroy();
return io.Transport.XHR.prototype.disconnect.call(this);
};
HTMLFile.check = function(){
if ('ActiveXObject' in window){
try {
var a = new ActiveXObject('htmlfile');
return a && io.Transport.XHR.check();
} catch(e){}
}
return false;
};
HTMLFile.xdomainCheck = function(){
// we can probably do handling for sub-domains, we should test that it's cross domain but a subdomain here
return false;
};
/**
* Disconnects the established connection.
*
* @returns {Transport} Chaining.
* @api public
*/
HTMLFile.prototype.disconnect = function(){
this.destroy();
return io.Transport.XHR.prototype.disconnect.call(this);
};
/**
* Checks if the browser supports this transport. The browser
* must have an `ActiveXObject` implementation.
*
* @return {Boolean}
* @api public
*/
HTMLFile.check = function(){
if ('ActiveXObject' in window){
try {
var a = new ActiveXObject('htmlfile');
return a && io.Transport.XHR.check();
} catch(e){}
}
return false;
};
/**
* Check if cross domain requests are supported.
*
* @returns {Boolean}
* @api public
*/
HTMLFile.xdomainCheck = function(){
// we can probably do handling for sub-domains, we should test that it's cross domain but a subdomain here
return false;
};
})();

View File

@@ -1,116 +1,175 @@
/**
* Socket.IO client
*
* @author Guillermo Rauch <guillermo@learnboost.com>
* @license The MIT license.
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
* socket.io-node-client
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
io.JSONP = [];
JSONPPolling = io.Transport['jsonp-polling'] = function(){
io.Transport.XHR.apply(this, arguments);
this._insertAt = document.getElementsByTagName('script')[0];
this._index = io.JSONP.length;
io.JSONP.push(this);
};
io.util.inherit(JSONPPolling, io.Transport['xhr-polling']);
JSONPPolling.prototype.type = 'jsonp-polling';
JSONPPolling.prototype._send = function(data){
var self = this;
if (!('_form' in this)){
var form = document.createElement('FORM'),
area = document.createElement('TEXTAREA'),
id = this._iframeId = 'socket_io_iframe_' + this._index,
iframe;
form.style.position = 'absolute';
form.style.top = '-1000px';
form.style.left = '-1000px';
form.target = id;
form.method = 'POST';
form.action = this._prepareUrl() + '/' + (+new Date) + '/' + this._index;
area.name = 'data';
form.appendChild(area);
this._insertAt.parentNode.insertBefore(form, this._insertAt);
document.body.appendChild(form);
this._form = form;
this._area = area;
}
function complete(){
initIframe();
self._posting = false;
self._checkSend();
};
function initIframe(){
if (self._iframe){
self._form.removeChild(self._iframe);
}
try {
// ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
iframe = document.createElement('<iframe name="'+ self._iframeId +'">');
} catch(e){
iframe = document.createElement('iframe');
iframe.name = self._iframeId;
}
iframe.id = self._iframeId;
self._form.appendChild(iframe);
self._iframe = iframe;
};
initIframe();
this._posting = true;
this._area.value = data;
try {
this._form.submit();
} catch(e){}
if (this._iframe.attachEvent){
iframe.onreadystatechange = function(){
if (self._iframe.readyState == 'complete') complete();
};
} else {
this._iframe.onload = complete;
}
};
JSONPPolling.prototype._get = function(){
var self = this,
script = document.createElement('SCRIPT');
if (this._script){
this._script.parentNode.removeChild(this._script);
this._script = null;
}
script.async = true;
script.src = this._prepareUrl() + '/' + (+new Date) + '/' + this._index;
script.onerror = function(){
self._onDisconnect();
};
this._insertAt.parentNode.insertBefore(script, this._insertAt);
this._script = script;
};
JSONPPolling.prototype._ = function(){
this._onData.apply(this, arguments);
this._get();
return this;
};
JSONPPolling.check = function(){
return true;
};
JSONPPolling.xdomainCheck = function(){
return true;
};
(function(){
var io = this.io,
/**
* The JSONP transport creates an persistent connection by dynamically
* inserting a script tag in the page. This script tag will receive the
* information of the Socket.IO server. When new information is received
* it creates a new script tag for the new data stream.
*
* @constructor
* @extends {io.Transport.xhr-polling}
* @api public
*/
JSONPPolling = io.Transport['jsonp-polling'] = function(){
io.Transport.XHR.apply(this, arguments);
this.insertAt = document.getElementsByTagName('script')[0];
this.index = io.JSONP.length;
io.JSONP.push(this);
};
io.util.inherit(JSONPPolling, io.Transport['xhr-polling']);
/**
* A list of all JSONPolling transports, this is used for by
* the Socket.IO server to distribute the callbacks.
*
* @type {Array}
* @api private
*/
io.JSONP = [];
/**
* The transport type, you use this to identify which transport was chosen.
*
* @type {String}
* @api public
*/
JSONPPolling.prototype.type = 'jsonp-polling';
/**
* Posts a encoded message to the Socket.IO server using an iframe.
* The iframe is used because script tags can create POST based requests.
* The iframe is positioned outside of the view so the user does not
* notice it's existence.
*
* @param {String} data A encoded message.
* @api private
*/
JSONPPolling.prototype.sendIORequest = function(data){
var self = this;
if (!('form' in this)){
var form = document.createElement('FORM'),
area = document.createElement('TEXTAREA'),
id = this.iframeId = 'socket_io_iframe_' + this.index,
iframe;
form.style.position = 'absolute';
form.style.top = '-1000px';
form.style.left = '-1000px';
form.target = id;
form.method = 'POST';
form.action = this.prepareUrl() + '/' + (+new Date) + '/' + this.index;
area.name = 'data';
form.appendChild(area);
this.insertAt.parentNode.insertBefore(form, this.insertAt);
document.body.appendChild(form);
this.form = form;
this.area = area;
}
function complete(){
initIframe();
self.posting = false;
self.checkSend();
};
function initIframe(){
if (self.iframe){
self.form.removeChild(self.iframe);
}
try {
// ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
iframe = document.createElement('<iframe name="'+ self.iframeId +'">');
} catch(e){
iframe = document.createElement('iframe');
iframe.name = self.iframeId;
}
iframe.id = self.iframeId;
self.form.appendChild(iframe);
self.iframe = iframe;
};
initIframe();
this.posting = true;
this.area.value = data;
try {
this.form.submit();
} catch(e){}
if (this.iframe.attachEvent){
iframe.onreadystatechange = function(){
if (self.iframe.readyState == 'complete') complete();
};
} else {
this.iframe.onload = complete;
}
};
/**
* Creates a new JSONP poll that can be used to listen
* for messages from the Socket.IO server.
*
* @api private
*/
JSONPPolling.prototype.get = function(){
var self = this,
script = document.createElement('SCRIPT');
if (this.script){
this.script.parentNode.removeChild(this.script);
this.script = null;
}
script.async = true;
script.src = this.prepareUrl() + '/' + (+new Date) + '/' + this.index;
script.onerror = function(){
self.onDisconnect();
};
this.insertAt.parentNode.insertBefore(script, this.insertAt);
this.script = script;
};
/**
* Callback function for the incoming message stream from the Socket.IO server.
*
* @param {String} data The message
* @param {document} doc Reference to the context
* @api private
*/
JSONPPolling.prototype._ = function(){
this.onData.apply(this, arguments);
this.get();
return this;
};
/**
* Checks if browser supports this transport.
*
* @return {Boolean}
* @api public
*/
JSONPPolling.check = function(){
return true;
};
/**
* Check if cross domain requests are supported
*
* @returns {Boolean}
* @api public
*/
JSONPPolling.xdomainCheck = function(){
return true;
};
})();

View File

@@ -1,65 +1,121 @@
/**
* Socket.IO client
*
* @author Guillermo Rauch <guillermo@learnboost.com>
* @license The MIT license.
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
* socket.io-node-client
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
(function(){
var WS = io.Transport.websocket = function(){
io.Transport.apply(this, arguments);
};
io.util.inherit(WS, io.Transport);
WS.prototype.type = 'websocket';
WS.prototype.connect = function(){
var self = this;
this.socket = new WebSocket(this._prepareUrl());
this.socket.onmessage = function(ev){ self._onData(ev.data); };
this.socket.onclose = function(ev){ self._onClose(); };
this.socket.onerror = function(e){ self._onError(e); };
return this;
};
WS.prototype.send = function(data){
if (this.socket) this.socket.send(this._encode(data));
return this;
};
WS.prototype.disconnect = function(){
if (this.socket) this.socket.close();
return this;
};
WS.prototype._onClose = function(){
this._onDisconnect();
return this;
};
WS.prototype._onError = function(e){
var io = this.io,
/**
* The WebSocket transport uses the HTML5 WebSocket API to establish an persistent
* connection with the Socket.IO server. This transport will also be inherited by the
* FlashSocket fallback as it provides a API compatible polyfill for the WebSockets.
*
* @constructor
* @extends {io.Transport}
* @api public
*/
WS = io.Transport.websocket = function(){
io.Transport.apply(this, arguments);
};
io.util.inherit(WS, io.Transport);
/**
* The transport type, you use this to identify which transport was chosen.
*
* @type {String}
* @api public
*/
WS.prototype.type = 'websocket';
/**
* Initializes a new `WebSocket` connection with the Socket.IO server. We attach
* all the appropriate listeners to handle the responses from the server.
*
* @returns {Transport}
* @api public
*/
WS.prototype.connect = function(){
var self = this;
this.socket = new WebSocket(this.prepareUrl());
this.socket.onmessage = function(ev){ self.onData(ev.data); };
this.socket.onclose = function(ev){ self.onDisconnect(); };
this.socket.onerror = function(e){ self.onError(e); };
return this;
};
/**
* Send a message to the Socket.IO server. The message will automatically be encoded
* in the correct message format.
*
* @returns {Transport}
* @api public
*/
WS.prototype.send = function(data){
if (this.socket) this.socket.send(this.encode(data));
return this;
};
/**
* Disconnect the established `WebSocket` connection.
*
* @returns {Transport}
* @api public
*/
WS.prototype.disconnect = function(){
if (this.socket) this.socket.close();
return this;
};
/**
* Handle the errors that `WebSocket` might be giving when we
* are attempting to connect or send messages.
*
* @param {Error} e The error.
* @api private
*/
WS.prototype.onError = function(e){
this.base.emit('error', [e]);
};
WS.prototype._prepareUrl = function(){
return (this.base.options.secure ? 'wss' : 'ws')
+ '://' + this.base.host
+ ':' + this.base.options.port
+ '/' + this.base.options.resource
+ '/' + this.type
+ (this.sessionid ? ('/' + this.sessionid) : '');
};
WS.check = function(){
// we make sure WebSocket is not confounded with a previously loaded flash WebSocket
return 'WebSocket' in window && WebSocket.prototype && ( WebSocket.prototype.send && !!WebSocket.prototype.send.toString().match(/native/i)) && typeof WebSocket !== "undefined";
};
WS.xdomainCheck = function(){
return true;
};
/**
* Generate a `WebSocket` compatible URL based on the options
* the user supplied in our Socket.IO base.
*
* @returns {String} Connection url
* @api private
*/
WS.prototype.prepareUrl = function(){
return (this.base.options.secure ? 'wss' : 'ws')
+ '://' + this.base.host
+ ':' + this.base.options.port
+ '/' + this.base.options.resource
+ '/' + this.type
+ (this.sessionid ? ('/' + this.sessionid) : '');
};
/**
* Checks if the browser has support for native `WebSockets` and that
* it's not the polyfill created for the FlashSocket transport.
*
* @return {Boolean}
* @api public
*/
WS.check = function(){
// we make sure WebSocket is not confounded with a previously loaded flash WebSocket
return 'WebSocket' in window && WebSocket.prototype && ( WebSocket.prototype.send && !!WebSocket.prototype.send.toString().match(/native/i)) && typeof WebSocket !== "undefined";
};
/**
* Check if the `WebSocket` transport support cross domain communications.
*
* @returns {Boolean}
* @api public
*/
WS.xdomainCheck = function(){
return true;
};
})();

View File

@@ -1,36 +1,66 @@
/**
* Socket.IO client
*
* @author Guillermo Rauch <guillermo@learnboost.com>
* @license The MIT license.
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
* socket.io-node-client
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
(function(){
var XHRMultipart = io.Transport['xhr-multipart'] = function(){
io.Transport.XHR.apply(this, arguments);
};
io.util.inherit(XHRMultipart, io.Transport.XHR);
XHRMultipart.prototype.type = 'xhr-multipart';
XHRMultipart.prototype._get = function(){
var self = this;
this._xhr = this._request('', 'GET', true);
this._xhr.onreadystatechange = function(){
if (self._xhr.readyState == 4) self._onData(self._xhr.responseText);
};
this._xhr.send(null);
};
XHRMultipart.check = function(){
return 'XMLHttpRequest' in window && 'prototype' in XMLHttpRequest && 'multipart' in XMLHttpRequest.prototype;
};
XHRMultipart.xdomainCheck = function(){
return true;
};
})();
var io = this.io,
/**
* The XHR-Multipart transport uses the a multipart XHR connection to
* stream in the data from the Socket.IO server
*
* @constructor
* @extends {io.Transport.XHR}
* @api public
*/
XHRMultipart = io.Transport['xhr-multipart'] = function(){
io.Transport.XHR.apply(this, arguments);
};
io.util.inherit(XHRMultipart, io.Transport.XHR);
/**
* The transport type, you use this to identify which transport was chosen.
*
* @type {String}
* @api public
*/
XHRMultipart.prototype.type = 'xhr-multipart';
/**
* Starts the multipart stream for incomming messages.
*
* @api private
*/
XHRMultipart.prototype.get = function(){
var self = this;
this.xhr = this.request('', 'GET', true);
this.xhr.onreadystatechange = function(){
if (self.xhr.readyState == 4) self.onData(self.xhr.responseText);
};
this.xhr.send(null);
};
/**
* Checks if browser supports this transport.
*
* @return {Boolean}
* @api public
*/
XHRMultipart.check = function(){
return 'XMLHttpRequest' in window && 'prototype' in XMLHttpRequest && 'multipart' in XMLHttpRequest.prototype;
};
/**
* Check if cross domain requests are supported.
*
* @returns {Boolean}
* @api public
*/
XHRMultipart.xdomainCheck = function(){
return true;
};
})();

View File

@@ -1,61 +1,97 @@
/**
* Socket.IO client
*
* @author Guillermo Rauch <guillermo@learnboost.com>
* @license The MIT license.
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
* socket.io-node-client
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
(function(){
var empty = new Function(),
XHRPolling = io.Transport['xhr-polling'] = function(){
io.Transport.XHR.apply(this, arguments);
};
io.util.inherit(XHRPolling, io.Transport.XHR);
XHRPolling.prototype.type = 'xhr-polling';
XHRPolling.prototype.connect = function(){
if (io.util.ios || io.util.android){
var self = this;
io.util.load(function(){
setTimeout(function(){
io.Transport.XHR.prototype.connect.call(self);
}, 10);
});
} else {
io.Transport.XHR.prototype.connect.call(this);
}
};
XHRPolling.prototype._get = function(){
var self = this;
this._xhr = this._request(+ new Date, 'GET');
this._xhr.onreadystatechange = function(){
var io = this.io,
/**
* A small stub function that will be used to reduce memory leaks.
*
* @type {Function}
* @api private
*/
empty = new Function(),
/**
* The XHR-polling transport uses long polling XHR requests to create a
* "persistent" connection with the server.
*
* @constructor
* @extends {io.Transport.XHR}
* @api public
*/
XHRPolling = io.Transport['xhr-polling'] = function(){
io.Transport.XHR.apply(this, arguments);
};
io.util.inherit(XHRPolling, io.Transport.XHR);
/**
* The transport type, you use this to identify which transport was chosen.
*
* @type {string}
* @api public
*/
XHRPolling.prototype.type = 'xhr-polling';
/**
* Establish a connection, for iPhone and Android this will be done once the page
* is loaded.
*
* @returns {Transport} Chaining.
* @api public
*/
XHRPolling.prototype.connect = function(){
var self = this;
io.util.defer(function(){ io.Transport.XHR.prototype.connect.call(self) });
return false;
};
/**
* Starts a XHR request to wait for incoming messages.
*
* @api private
*/
XHRPolling.prototype.get = function(){
var self = this;
this.xhr = this.request(+ new Date, 'GET');
this.xhr.onreadystatechange = function(){
var status;
if (self._xhr.readyState == 4){
self._xhr.onreadystatechange = empty;
try { status = self._xhr.status; } catch(e){}
if (self.xhr.readyState == 4){
self.xhr.onreadystatechange = empty;
try { status = self.xhr.status; } catch(e){}
if (status == 200){
self._onData(self._xhr.responseText);
self._get();
self.onData(self.xhr.responseText);
self.get();
} else {
self._onDisconnect();
self.onDisconnect();
}
}
};
this._xhr.send(null);
};
XHRPolling.check = function(){
return io.Transport.XHR.check();
};
XHRPolling.xdomainCheck = function(){
return io.Transport.XHR.xdomainCheck();
};
this.xhr.send(null);
};
/**
* Checks if browser supports this transport.
*
* @return {Boolean}
* @api public
*/
XHRPolling.check = function(){
return io.Transport.XHR.check();
};
/**
* Check if cross domain requests are supported
*
* @returns {Boolean}
* @api public
*/
XHRPolling.xdomainCheck = function(){
return io.Transport.XHR.xdomainCheck();
};
})();

View File

@@ -1,135 +1,221 @@
/**
* Socket.IO client
*
* @author Guillermo Rauch <guillermo@learnboost.com>
* @license The MIT license.
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
* socket.io-node-client
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
(function(){
var empty = new Function,
XMLHttpRequestCORS = (function(){
if (!('XMLHttpRequest' in window)) return false;
// CORS feature detection
var a = new XMLHttpRequest();
return a.withCredentials != undefined;
})(),
request = function(xdomain){
if ('XDomainRequest' in window && xdomain) return new XDomainRequest();
if ('XMLHttpRequest' in window && (!xdomain || XMLHttpRequestCORS)) return new XMLHttpRequest();
if (!xdomain){
try {
var a = new ActiveXObject('MSXML2.XMLHTTP');
return a;
} catch(e){}
try {
var b = new ActiveXObject('Microsoft.XMLHTTP');
return b;
} catch(e){}
}
return false;
},
XHR = io.Transport.XHR = function(){
io.Transport.apply(this, arguments);
this._sendBuffer = [];
};
io.util.inherit(XHR, io.Transport);
XHR.prototype.connect = function(){
this._get();
return this;
};
XHR.prototype._checkSend = function(){
if (!this._posting && this._sendBuffer.length){
var encoded = this._encode(this._sendBuffer);
this._sendBuffer = [];
this._send(encoded);
}
};
XHR.prototype.send = function(data){
if (io.util.isArray(data)){
this._sendBuffer.push.apply(this._sendBuffer, data);
} else {
this._sendBuffer.push(data);
}
this._checkSend();
return this;
};
XHR.prototype._send = function(data){
var self = this;
this._posting = true;
this._sendXhr = this._request('send', 'POST');
this._sendXhr.onreadystatechange = function(){
var status;
if (self._sendXhr.readyState == 4){
self._sendXhr.onreadystatechange = empty;
try { status = self._sendXhr.status; } catch(e){}
self._posting = false;
if (status == 200){
self._checkSend();
} else {
self._onDisconnect();
}
}
};
this._sendXhr.send('data=' + encodeURIComponent(data));
};
XHR.prototype.disconnect = function(){
// send disconnection signal
this._onDisconnect();
return this;
};
XHR.prototype._onDisconnect = function(){
if (this._xhr){
this._xhr.onreadystatechange = empty;
var io = this.io,
/**
* A small stub function that will be used to reduce memory leaks.
*
* @type {Function}
* @api private
*/
empty = new Function,
/**
* We preform a small feature detection to see if `Cross Origin Resource Sharing`
* is supported in the `XMLHttpRequest` object, so we can use it for cross domain requests.
*
* @type {Boolean}
* @api private
*/
XMLHttpRequestCORS = (function(){
if (!('XMLHttpRequest' in window)) return false;
// CORS feature detection
var a = new XMLHttpRequest();
return a.withCredentials != undefined;
})(),
/**
* Generates the correct `XMLHttpRequest` for regular and cross domain requests.
*
* @param {Boolean} [xdomain] Create a request that can be used cross domain.
* @returns {XMLHttpRequest|false} If we can create a XMLHttpRequest we will return that.
* @api private
*/
request = function(xdomain){
if ('XDomainRequest' in window && xdomain) return new XDomainRequest();
if ('XMLHttpRequest' in window && (!xdomain || XMLHttpRequestCORS)) return new XMLHttpRequest();
if (!xdomain){
try {
this._xhr.abort();
var a = new ActiveXObject('MSXML2.XMLHTTP');
return a;
} catch(e){}
this._xhr = null;
}
if (this._sendXhr){
this._sendXhr.onreadystatechange = empty;
try {
this._sendXhr.abort();
var b = new ActiveXObject('Microsoft.XMLHTTP');
return b;
} catch(e){}
this._sendXhr = null;
}
this._sendBuffer = [];
io.Transport.prototype._onDisconnect.call(this);
};
XHR.prototype._request = function(url, method, multipart){
var req = request(this.base._isXDomain());
if (multipart) req.multipart = true;
req.open(method || 'GET', this._prepareUrl() + (url ? '/' + url : ''));
if (method == 'POST' && 'setRequestHeader' in req){
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8');
}
return req;
};
XHR.check = function(xdomain){
try {
if (request(xdomain)) return true;
} catch(e){}
return false;
};
XHR.xdomainCheck = function(){
return XHR.check(true);
};
XHR.request = request;
}
return false;
},
/**
* This is the base for XHR based transports, the `XHR-Polling` and the `XHR-multipart`
* transports will extend this class.
*
* @constructor
* @extends {io.Transport}
* @property {Array} sendBuffer Used to queue up messages so they can be send as one request.
* @api public
*/
XHR = io.Transport.XHR = function(){
io.Transport.apply(this, arguments);
this.sendBuffer = [];
};
io.util.inherit(XHR, io.Transport);
/**
* Establish a connection
*
* @returns {Transport}
* @api public
*/
XHR.prototype.connect = function(){
this.get();
return this;
};
/**
* Check if we need to send data to the Socket.IO server, if we have data in our buffer
* we encode it and forward it to the sendIORequest method.
*
* @api private
*/
XHR.prototype.checkSend = function(){
if (!this.posting && this.sendBuffer.length){
var encoded = this.encode(this.sendBuffer);
this.sendBuffer = [];
this.sendIORequest(encoded);
}
};
/**
* Send data to the Socket.IO server.
*
* @param data The message
* @returns {Transport}
* @api public
*/
XHR.prototype.send = function(data){
if (io.util.isArray(data)){
this.sendBuffer.push.apply(this.sendBuffer, data);
} else {
this.sendBuffer.push(data);
}
this.checkSend();
return this;
};
/**
* Posts a encoded message to the Socket.IO server.
*
* @param {String} data A encoded message.
* @api private
*/
XHR.prototype.sendIORequest = function(data){
var self = this;
this.posting = true;
this.sendXHR = this.request('send', 'POST');
this.sendXHR.onreadystatechange = function(){
var status;
if (self.sendXHR.readyState == 4){
self.sendXHR.onreadystatechange = empty;
try { status = self.sendXHR.status; } catch(e){}
self.posting = false;
if (status == 200){
self.checkSend();
} else {
self.onDisconnect();
}
}
};
this.sendXHR.send('data=' + encodeURIComponent(data));
};
/**
* Disconnect the established connection.
*
* @returns {Transport}.
* @api public
*/
XHR.prototype.disconnect = function(){
// send disconnection signal
this.onDisconnect();
return this;
};
/**
* Handle the disconnect request.
*
* @api private
*/
XHR.prototype.onDisconnect = function(){
if (this.xhr){
this.xhr.onreadystatechange = empty;
try {
this.xhr.abort();
} catch(e){}
this.xhr = null;
}
if (this.sendXHR){
this.sendXHR.onreadystatechange = empty;
try {
this.sendXHR.abort();
} catch(e){}
this.sendXHR = null;
}
this.sendBuffer = [];
io.Transport.prototype.onDisconnect.call(this);
};
/**
* Generates a configured XHR request
*
* @param {String} url The url that needs to be requested.
* @param {String} method The method the request should use.
* @param {Boolean} multipart Do a multipart XHR request
* @returns {XMLHttpRequest}
* @api private
*/
XHR.prototype.request = function(url, method, multipart){
var req = request(this.base.isXDomain());
if (multipart) req.multipart = true;
req.open(method || 'GET', this.prepareUrl() + (url ? '/' + url : ''));
if (method == 'POST' && 'setRequestHeader' in req){
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8');
}
return req;
};
/**
* Check if the XHR transports are supported
*
* @param {Boolean} xdomain Check if we support cross domain requests.
* @returns {Boolean}
* @api public
*/
XHR.check = function(xdomain){
try {
if (request(xdomain)) return true;
} catch(e){}
return false;
};
/**
* Check if the XHR transport supports corss domain requests.
*
* @returns {Boolean}
* @api public
*/
XHR.xdomainCheck = function(){
return XHR.check(true);
};
XHR.request = request;
})();

View File

@@ -1,60 +1,161 @@
/**
* Socket.IO client
*
* @author Guillermo Rauch <guillermo@learnboost.com>
* @license The MIT license.
* @copyright Copyright (c) 2010 LearnBoost <dev@learnboost.com>
* socket.io-node-client
* Copyright(c) 2011 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
(function(){
var _pageLoaded = false;
io.util = {
ios: false,
load: function(fn){
if (/loaded|complete/.test(document.readyState) || _pageLoaded) return fn();
if ('attachEvent' in window){
window.attachEvent('onload', fn);
} else {
window.addEventListener('load', fn, false);
}
},
inherit: function(ctor, superCtor){
// no support for `instanceof` for now
for (var i in superCtor.prototype){
ctor.prototype[i] = superCtor.prototype[i];
}
},
indexOf: function(arr, item, from){
for (var l = arr.length, i = (from < 0) ? Math.max(0, l + from) : from || 0; i < l; i++){
if (arr[i] === item) return i;
}
return -1;
},
isArray: function(obj){
return Object.prototype.toString.call(obj) === '[object Array]';
},
var io = this.io,
/**
* Set when the `onload` event is executed on the page. This variable is used by
* `io.util.load` to detect if we need to execute the function immediately or add
* it to a onload listener.
*
* @type {Boolean}
* @api private
*/
pageLoaded = false;
/**
* @namespace
*/
io.util = {
/**
* Executes the given function when the page is loaded.
*
* Example:
*
* io.util.load(function(){ console.log('page loaded') });
*
* @param {Function} fn
* @api public
*/
load: function(fn){
if (/loaded|complete/.test(document.readyState) || pageLoaded) return fn();
if ('attachEvent' in window){
window.attachEvent('onload', fn);
} else {
window.addEventListener('load', fn, false);
}
},
/**
* Defers the function untill it's the function can be executed without
* blocking the load process. This is especially needed for WebKit based
* browsers. If a long running connection is made before the onload event
* a loading indicator spinner will be present at all times untill a
* reconnect has been made.
*
* @param {Function} fn
* @api public
*/
defer: function(fn){
if (!io.util.webkit) return fn();
io.util.load(function(){
setTimeout(fn,100);
});
},
/**
* Inherit the prototype methods from one constructor into another.
*
* Example:
*
* function foo(){};
* foo.prototype.hello = function(){ console.log( this.words )};
*
* function bar(){
* this.words = "Hello world";
* };
*
* io.util.inherit(bar,foo);
* var person = new bar();
* person.hello();
* // => "Hello World"
*
* @param {Constructor} ctor The constructor that needs to inherit the methods.
* @param {Constructor} superCtor The constructor to inherit from.
* @api public
*/
inherit: function(ctor, superCtor){
// no support for `instanceof` for now
for (var i in superCtor.prototype){
ctor.prototype[i] = superCtor.prototype[i];
}
},
/**
* Finds the index of item in a given Array.
*
* Example:
*
* var data = ['socket',2,3,4,'socket',5,6,7,'io'];
* io.util.indexOf(data,'socket',1);
* // => 4
*
* @param {Array} arr The array
* @param item The item that we need to find
* @param {Integer} from Starting point
* @api public
*/
indexOf: function(arr, item, from){
for (var l = arr.length, i = (from < 0) ? Math.max(0, l + from) : from || 0; i < l; i++){
if (arr[i] === item) return i;
}
return -1;
},
/**
* Checks if the given object is an Array.
*
* Example:
*
* io.util.isArray([]);
* // => true
* io.util.isArray({});
* // => false
*
* @param obj
* @api public
*/
isArray: function(obj){
return Object.prototype.toString.call(obj) === '[object Array]';
},
/**
* Merges the properties of two objects.
*
* Example:
*
* var a = {foo:'bar'}
* , b = {bar:'baz'};
*
* io.util.merge(a,b);
* // => {foo:'bar',bar:'baz'}
*
* @param {Object} target The object that receives the keys
* @param {Object} additional The object that supplies the keys
* @api public
*/
merge: function(target, additional){
for (var i in additional)
if (additional.hasOwnProperty(i))
target[i] = additional[i];
}
};
/**
* Detect the Webkit platform based on the userAgent string.
* This includes Mobile Webkit.
*
* @type {Boolean}
* @api public
*/
io.util.webkit = /webkit/i.test(navigator.userAgent);
io.util.load(function(){
pageLoaded = true;
});
};
io.util.ios = /iphone|ipad/i.test(navigator.userAgent);
io.util.android = /android/i.test(navigator.userAgent);
io.util.opera = /opera/i.test(navigator.userAgent);
io.util.load(function(){
_pageLoaded = true;
});
})();
})();

View File

@@ -0,0 +1,4 @@
.DS_Store
.tmp*~
*.local.*
.pinf-*

View File

@@ -0,0 +1,782 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
lang="en" xml:lang="en">
<head>
<title>UglifyJS -- a JavaScript parser/compressor/beautifier</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<meta name="generator" content="Org-mode"/>
<meta name="generated" content="2011-02-28 22:35:00 EET"/>
<meta name="author" content="Mihai Bazon"/>
<meta name="description" content="a JavaScript parser/compressor/beautifier in JavaScript"/>
<meta name="keywords" content="javascript, js, parser, compiler, compressor, mangle, minify, minifier"/>
<style type="text/css">
<!--/*--><![CDATA[/*><!--*/
html { font-family: Times, serif; font-size: 12pt; }
.title { text-align: center; }
.todo { color: red; }
.done { color: green; }
.tag { background-color: #add8e6; font-weight:normal }
.target { }
.timestamp { color: #bebebe; }
.timestamp-kwd { color: #5f9ea0; }
p.verse { margin-left: 3% }
pre {
border: 1pt solid #AEBDCC;
background-color: #F3F5F7;
padding: 5pt;
font-family: courier, monospace;
font-size: 90%;
overflow:auto;
}
table { border-collapse: collapse; }
td, th { vertical-align: top; }
dt { font-weight: bold; }
div.figure { padding: 0.5em; }
div.figure p { text-align: center; }
textarea { overflow-x: auto; }
.linenr { font-size:smaller }
.code-highlighted {background-color:#ffff00;}
.org-info-js_info-navigation { border-style:none; }
#org-info-js_console-label { font-size:10px; font-weight:bold;
white-space:nowrap; }
.org-info-js_search-highlight {background-color:#ffff00; color:#000000;
font-weight:bold; }
/*]]>*/-->
</style>
<link rel="stylesheet" type="text/css" href="docstyle.css" />
<script type="text/javascript">
<!--/*--><![CDATA[/*><!--*/
function CodeHighlightOn(elem, id)
{
var target = document.getElementById(id);
if(null != target) {
elem.cacheClassElem = elem.className;
elem.cacheClassTarget = target.className;
target.className = "code-highlighted";
elem.className = "code-highlighted";
}
}
function CodeHighlightOff(elem, id)
{
var target = document.getElementById(id);
if(elem.cacheClassElem)
elem.className = elem.cacheClassElem;
if(elem.cacheClassTarget)
target.className = elem.cacheClassTarget;
}
/*]]>*///-->
</script>
</head>
<body>
<div id="content">
<h1 class="title">UglifyJS &ndash; a JavaScript parser/compressor/beautifier</h1>
<div id="table-of-contents">
<h2>Table of Contents</h2>
<div id="text-table-of-contents">
<ul>
<li><a href="#sec-1">1 UglifyJS &mdash; a JavaScript parser/compressor/beautifier </a>
<ul>
<li><a href="#sec-1_1">1.1 Unsafe transformations </a>
<ul>
<li><a href="#sec-1_1_1">1.1.1 Calls involving the global Array constructor </a></li>
</ul>
</li>
<li><a href="#sec-1_2">1.2 Usage </a>
<ul>
<li><a href="#sec-1_2_1">1.2.1 API </a></li>
<li><a href="#sec-1_2_2">1.2.2 Beautifier shortcoming &ndash; no more comments </a></li>
</ul>
</li>
<li><a href="#sec-1_3">1.3 Compression &ndash; how good is it? </a></li>
<li><a href="#sec-1_4">1.4 Bugs? </a></li>
<li><a href="#sec-1_5">1.5 Links </a></li>
<li><a href="#sec-1_6">1.6 License </a></li>
</ul>
</li>
</ul>
</div>
</div>
<div id="outline-container-1" class="outline-2">
<h2 id="sec-1"><span class="section-number-2">1</span> UglifyJS &mdash; a JavaScript parser/compressor/beautifier </h2>
<div class="outline-text-2" id="text-1">
<p>
<b>Update</b>: please read the section on <a href="#sec-1_1">unsafe transformations</a>.
</p>
<p>
This package implements a general-purpose JavaScript
parser/compressor/beautifier toolkit. It is developed on <a href="http://nodejs.org/">NodeJS</a>, but it
should work on any JavaScript platform supporting the CommonJS module system
(and if your platform of choice doesn't support CommonJS, you can easily
implement it, or discard the <code>exports.*</code> lines from UglifyJS sources).
</p>
<p>
The tokenizer/parser generates an abstract syntax tree from JS code. You
can then traverse the AST to learn more about the code, or do various
manipulations on it. This part is implemented in <a href="../lib/parse-js.js">parse-js.js</a> and it's a
port to JavaScript of the excellent <a href="http://marijn.haverbeke.nl/parse-js/">parse-js</a> Common Lisp library from <a href="http://marijn.haverbeke.nl/">Marijn Haverbeke</a>.
</p>
<p>
( See <a href="http://github.com/mishoo/cl-uglify-js">cl-uglify-js</a> if you're looking for the Common Lisp version of
UglifyJS. )
</p>
<p>
The second part of this package, implemented in <a href="../lib/process.js">process.js</a>, inspects and
manipulates the AST generated by the parser to provide the following:
</p>
<ul>
<li>
ability to re-generate JavaScript code from the AST. Optionally
indented&mdash;you can use this if you want to “beautify” a program that has
been compressed, so that you can inspect the source. But you can also run
our code generator to print out an AST without any whitespace, so you
achieve compression as well.
</li>
<li>
shorten variable names (usually to single characters). Our mangler will
analyze the code and generate proper variable names, depending on scope
and usage, and is smart enough to deal with globals defined elsewhere, or
with <code>eval()</code> calls or <code>with{}</code> statements. In short, if <code>eval()</code> or
<code>with{}</code> are used in some scope, then all variables in that scope and any
variables in the parent scopes will remain unmangled, and any references
to such variables remain unmangled as well.
</li>
<li>
various small optimizations that may lead to faster code but certainly
lead to smaller code. Where possible, we do the following:
<ul>
<li>
foo["bar"] ==&gt; foo.bar
</li>
<li>
remove block brackets <code>{}</code>
</li>
<li>
join consecutive var declarations:
var a = 10; var b = 20; ==&gt; var a=10,b=20;
</li>
<li>
resolve simple constant expressions: 1 +2 * 3 ==&gt; 7. We only do the
replacement if the result occupies less bytes; for example 1/3 would
translate to 0.333333333333, so in this case we don't replace it.
</li>
<li>
consecutive statements in blocks are merged into a sequence; in many
cases, this leaves blocks with a single statement, so then we can remove
the block brackets.
</li>
<li>
various optimizations for IF statements:
<ul>
<li>
if (foo) bar(); else baz(); ==&gt; foo?bar():baz();
</li>
<li>
if (!foo) bar(); else baz(); ==&gt; foo?baz():bar();
</li>
<li>
if (foo) bar(); ==&gt; foo&amp;&amp;bar();
</li>
<li>
if (!foo) bar(); ==&gt; foo||bar();
</li>
<li>
if (foo) return bar(); else return baz(); ==&gt; return foo?bar():baz();
</li>
<li>
if (foo) return bar(); else something(); ==&gt; {if(foo)return bar();something()}
</li>
</ul>
</li>
<li>
remove some unreachable code and warn about it (code that follows a
<code>return</code>, <code>throw</code>, <code>break</code> or <code>continue</code> statement, except
function/variable declarations).
</li>
</ul>
</li>
</ul>
</div>
<div id="outline-container-1_1" class="outline-3">
<h3 id="sec-1_1"><span class="section-number-3">1.1</span> <span class="target">Unsafe transformations</span> </h3>
<div class="outline-text-3" id="text-1_1">
<p>
UglifyJS tries its best to achieve great compression while leaving the
semantics of the code intact. In general, if your code logic is broken by
UglifyJS then it's a bug in UglifyJS and you should report it and I should
fix it. :-)
</p>
<p>
However, I opted to include the following potentially unsafe transformations
as default behavior. Discussion is welcome, if you have ideas of how to
handle this better, or any objections to these optimizations, please let me
know.
</p>
</div>
<div id="outline-container-1_1_1" class="outline-4">
<h4 id="sec-1_1_1"><span class="section-number-4">1.1.1</span> Calls involving the global Array constructor </h4>
<div class="outline-text-4" id="text-1_1_1">
<p>
The following transformations occur:
</p>
<pre class="src src-espresso"><span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(1, 2, 3, 4) =&gt; [1,2,3,4]
Array(a, b, c) =&gt; [a,b,c]
<span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(5) =&gt; Array(5)
<span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(a) =&gt; Array(a)
</pre>
<p>
These are all safe if the Array name isn't redefined. JavaScript does allow
one to globally redefine Array (and pretty much everything, in fact) but I
personally don't see why would anyone do that.
</p>
<p>
UglifyJS does handle the case where Array is redefined locally, or even
globally but with a <code>function</code> or <code>var</code> declaration. Therefore, in the
following cases UglifyJS <b>doesn't touch</b> calls or instantiations of Array:
</p>
<pre class="src src-espresso"><span style="color: #add8e6;">// </span><span style="color: #add8e6;">case 1. globally declared variable
</span> <span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">Array</span>;
<span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(1, 2, 3);
Array(a, b);
<span style="color: #add8e6;">// </span><span style="color: #add8e6;">or (can be declared later)
</span> <span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(1, 2, 3);
<span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">Array</span>;
<span style="color: #add8e6;">// </span><span style="color: #add8e6;">or (can be a function)
</span> <span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(1, 2, 3);
<span style="color: #afeeee; font-weight: bold;">function</span> <span style="color: #7fffd4; font-weight: bold;">Array</span>() { ... }
<span style="color: #add8e6;">// </span><span style="color: #add8e6;">case 2. declared in a function
</span> (<span style="color: #afeeee; font-weight: bold;">function</span>(){
a = <span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(1, 2, 3);
b = Array(5, 6);
<span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">Array</span>;
})();
<span style="color: #add8e6;">// </span><span style="color: #add8e6;">or
</span> (<span style="color: #afeeee; font-weight: bold;">function</span>(<span style="color: #40e0d0; font-weight: bold;">Array</span>){
<span style="color: #afeeee; font-weight: bold;">return</span> Array(5, 6, 7);
})();
<span style="color: #add8e6;">// </span><span style="color: #add8e6;">or
</span> (<span style="color: #afeeee; font-weight: bold;">function</span>(){
<span style="color: #afeeee; font-weight: bold;">return</span> <span style="color: #afeeee; font-weight: bold;">new</span> <span style="color: #87ceeb; font-weight: bold;">Array</span>(1, 2, 3, 4);
<span style="color: #afeeee; font-weight: bold;">function</span> <span style="color: #7fffd4; font-weight: bold;">Array</span>() { ... }
})();
<span style="color: #add8e6;">// </span><span style="color: #add8e6;">etc.
</span></pre>
</div>
</div>
</div>
<div id="outline-container-1_2" class="outline-3">
<h3 id="sec-1_2"><span class="section-number-3">1.2</span> Usage </h3>
<div class="outline-text-3" id="text-1_2">
<p>
There is a helper script now &mdash; <code>bin/uglifyjs</code> &mdash; that uses the library to
compress a script using the maximum compression settings. Synopsis:
</p>
<pre class="src src-sh">uglifyjs [ options... ] [ filename ]
</pre>
<p>
<code>filename</code> should be the last argument and should name the file from which
to read the JavaScript code. If you don't specify it, it will read code
from STDIN.
</p>
<p>
Supported options:
</p>
<ul>
<li>
<code>-b</code> or <code>--beautify</code> &mdash; output indented code; when passed, additional
options control the beautifier:
<ul>
<li>
<code>-i N</code> or <code>--indent N</code> &mdash; indentation level (number of spaces)
</li>
<li>
<code>-q</code> or <code>--quote-keys</code> &mdash; quote keys in literal objects (by default,
only keys that cannot be identifier names will be quotes).
</li>
</ul>
</li>
<li>
<code>--ascii</code> &mdash; pass this argument to encode non-ASCII characters as
<code>\uXXXX</code> sequences. By default UglifyJS won't bother to do it and will
output Unicode characters instead. (the output is always encoded in UTF8,
but if you pass this option you'll only get ASCII).
</li>
<li>
<code>-nm</code> or <code>--no-mangle</code> &mdash; don't mangle variable names
</li>
<li>
<code>-ns</code> or <code>--no-squeeze</code> &mdash; don't call <code>ast_squeeze()</code> (which does various
optimizations that result in smaller, less readable code).
</li>
<li>
<code>-mt</code> or <code>--mangle-toplevel</code> &mdash; mangle names in the toplevel scope too
(by default we don't do this).
</li>
<li>
<code>--no-seqs</code> &mdash; when <code>ast_squeeze()</code> is called (thus, unless you pass
<code>--no-squeeze</code>) it will reduce consecutive statements in blocks into a
sequence. For example, "a = 10; b = 20; foo();" will be written as
"a=10,b=20,foo();". In various occasions, this allows us to discard the
block brackets (since the block becomes a single statement). This is ON
by default because it seems safe and saves a few hundred bytes on some
libs that I tested it on, but pass <code>--no-seqs</code> to disable it.
</li>
<li>
<code>--no-dead-code</code> &mdash; by default, UglifyJS will remove code that is
obviously unreachable (code that follows a <code>return</code>, <code>throw</code>, <code>break</code> or
<code>continue</code> statement and is not a function/variable declaration). Pass
this option to disable this optimization.
</li>
<li>
<code>-nc</code> or <code>--no-copyright</code> &mdash; by default, <code>uglifyjs</code> will keep the initial
comment tokens in the generated code (assumed to be copyright information
etc.). If you pass this it will discard it.
</li>
<li>
<code>-o filename</code> or <code>--output filename</code> &mdash; put the result in <code>filename</code>. If
this isn't given, the result goes to standard output (or see next one).
</li>
<li>
<code>--overwrite</code> &mdash; if the code is read from a file (not from STDIN) and you
pass <code>--overwrite</code> then the output will be written in the same file.
</li>
<li>
<code>--ast</code> &mdash; pass this if you want to get the Abstract Syntax Tree instead
of JavaScript as output. Useful for debugging or learning more about the
internals.
</li>
<li>
<code>-v</code> or <code>--verbose</code> &mdash; output some notes on STDERR (for now just how long
each operation takes).
</li>
<li>
<code>--extra</code> &mdash; enable additional optimizations that have not yet been
extensively tested. These might, or might not, break your code. If you
find a bug using this option, please report a test case.
</li>
<li>
<code>--unsafe</code> &mdash; enable other additional optimizations that are known to be
unsafe in some contrived situations, but could still be generally useful.
For now only this:
<ul>
<li>
foo.toString() ==&gt; foo+""
</li>
</ul>
</li>
<li>
<code>--max-line-len</code> (default 32K characters) &mdash; add a newline after around
32K characters. I've seen both FF and Chrome croak when all the code was
on a single line of around 670K. Pass &ndash;max-line-len 0 to disable this
safety feature.
</li>
<li>
<code>--reserved-names</code> &mdash; some libraries rely on certain names to be used, as
pointed out in issue #92 and #81, so this option allow you to exclude such
names from the mangler. For example, to keep names <code>require</code> and <code>$super</code>
intact you'd specify &ndash;reserved-names "require,$super".
</li>
</ul>
</div>
<div id="outline-container-1_2_1" class="outline-4">
<h4 id="sec-1_2_1"><span class="section-number-4">1.2.1</span> API </h4>
<div class="outline-text-4" id="text-1_2_1">
<p>
Symlink the <b>lib</b> directory as <b>~/.node_libraries/uglifyjs</b>, so that the
<b>require</b> calls in the following sample will work:
</p>
<pre class="src src-espresso"><span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">jsp</span> = require(<span style="color: #87cefa;">"uglifyjs/parse-js"</span>);
<span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">pro</span> = require(<span style="color: #87cefa;">"uglifyjs/process"</span>);
<span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">orig_code</span> = <span style="color: #87cefa;">"... JS code here"</span>;
<span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">ast</span> = jsp.parse(orig_code); <span style="color: #add8e6;">// </span><span style="color: #add8e6;">parse code and get the initial AST
</span>ast = pro.ast_mangle(ast); <span style="color: #add8e6;">// </span><span style="color: #add8e6;">get a new AST with mangled names
</span>ast = pro.ast_squeeze(ast); <span style="color: #add8e6;">// </span><span style="color: #add8e6;">get an AST with compression optimizations
</span><span style="color: #afeeee; font-weight: bold;">var</span> <span style="color: #40e0d0; font-weight: bold;">final_code</span> = pro.gen_code(ast); <span style="color: #add8e6;">// </span><span style="color: #add8e6;">compressed code here
</span></pre>
<p>
The above performs the full compression that is possible right now. As you
can see, there are a sequence of steps which you can apply. For example if
you want compressed output but for some reason you don't want to mangle
variable names, you would simply skip the line that calls
<code>pro.ast_mangle(ast)</code>.
</p>
<p>
Some of these functions take optional arguments. Here's a description:
</p>
<ul>
<li>
<code>jsp.parse(code, strict_semicolons)</code> &ndash; parses JS code and returns an AST.
<code>strict_semicolons</code> is optional and defaults to <code>false</code>. If you pass
<code>true</code> then the parser will throw an error when it expects a semicolon and
it doesn't find it. For most JS code you don't want that, but it's useful
if you want to strictly sanitize your code.
</li>
<li>
<code>pro.ast_mangle(ast, options)</code> &ndash; generates a new AST containing mangled
(compressed) variable and function names. It supports the following
options:
<ul>
<li>
<code>toplevel</code> &ndash; mangle toplevel names (by default we don't touch them).
</li>
<li>
<code>except</code> &ndash; an array of names to exclude from compression.
</li>
</ul>
</li>
<li>
<code>pro.ast_squeeze(ast, options)</code> &ndash; employs further optimizations designed
to reduce the size of the code that <code>gen_code</code> would generate from the
AST. Returns a new AST. <code>options</code> can be a hash; the supported options
are:
<ul>
<li>
<code>make_seqs</code> (default true) which will cause consecutive statements in a
block to be merged using the "sequence" (comma) operator
</li>
<li>
<code>dead_code</code> (default true) which will remove unreachable code.
</li>
</ul>
</li>
<li>
<code>pro.gen_code(ast, options)</code> &ndash; generates JS code from the AST. By
default it's minified, but using the <code>options</code> argument you can get nicely
formatted output. <code>options</code> is, well, optional :-) and if you pass it it
must be an object and supports the following properties (below you can see
the default values):
<ul>
<li>
<code>beautify: false</code> &ndash; pass <code>true</code> if you want indented output
</li>
<li>
<code>indent_start: 0</code> (only applies when <code>beautify</code> is <code>true</code>) &ndash; initial
indentation in spaces
</li>
<li>
<code>indent_level: 4</code> (only applies when <code>beautify</code> is <code>true</code>) --
indentation level, in spaces (pass an even number)
</li>
<li>
<code>quote_keys: false</code> &ndash; if you pass <code>true</code> it will quote all keys in
literal objects
</li>
<li>
<code>space_colon: false</code> (only applies when <code>beautify</code> is <code>true</code>) &ndash; wether
to put a space before the colon in object literals
</li>
<li>
<code>ascii_only: false</code> &ndash; pass <code>true</code> if you want to encode non-ASCII
characters as <code>\uXXXX</code>.
</li>
</ul>
</li>
</ul>
</div>
</div>
<div id="outline-container-1_2_2" class="outline-4">
<h4 id="sec-1_2_2"><span class="section-number-4">1.2.2</span> Beautifier shortcoming &ndash; no more comments </h4>
<div class="outline-text-4" id="text-1_2_2">
<p>
The beautifier can be used as a general purpose indentation tool. It's
useful when you want to make a minified file readable. One limitation,
though, is that it discards all comments, so you don't really want to use it
to reformat your code, unless you don't have, or don't care about, comments.
</p>
<p>
In fact it's not the beautifier who discards comments &mdash; they are dumped at
the parsing stage, when we build the initial AST. Comments don't really
make sense in the AST, and while we could add nodes for them, it would be
inconvenient because we'd have to add special rules to ignore them at all
the processing stages.
</p>
</div>
</div>
</div>
<div id="outline-container-1_3" class="outline-3">
<h3 id="sec-1_3"><span class="section-number-3">1.3</span> Compression &ndash; how good is it? </h3>
<div class="outline-text-3" id="text-1_3">
<p>
(XXX: this is somewhat outdated. On the jQuery source code we beat Closure
by 168 bytes (560 after gzip) and by many seconds.)
</p>
<p>
There are a few popular JS minifiers nowadays &ndash; the two most well known
being the GoogleClosure (GCL) compiler and the YUI compressor. For some
reason they are both written in Java. I didn't really hope to beat any of
them, but finally I did &ndash; UglifyJS compresses better than the YUI
compressor, and safer than GoogleClosure.
</p>
<p>
I tested it on two big libraries. <a href="http://www.dynarchlib.com/">DynarchLIB</a> is my own, and it's big enough
to contain probably all the JavaScript tricks known to mankind. <a href="http://jquery.com/">jQuery</a> is
definitely the most popular JavaScript library (to some people, it's a
synonym to JavaScript itself).
</p>
<p>
I cannot swear that there are no bugs in the generated codes, but they
appear to work fine.
</p>
<p>
Compression results:
</p>
<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
<caption></caption>
<colgroup><col align="left" /><col align="right" /><col align="right" /><col align="left" /><col align="left" />
</colgroup>
<thead>
<tr><th scope="col">Library</th><th scope="col">Orig. size</th><th scope="col">UglifyJS</th><th scope="col">YUI</th><th scope="col">GCL</th></tr>
</thead>
<tbody>
<tr><td>DynarchLIB</td><td>636896</td><td>241441</td><td>246452 (+5011)</td><td>240439 (-1002) (buggy)</td></tr>
<tr><td>jQuery</td><td>163855</td><td>72006</td><td>79702 (+7696)</td><td>71858 (-148)</td></tr>
</tbody>
</table>
<p>
UglifyJS is the fastest to run. On my laptop UglifyJS takes 1.35s for
DynarchLIB, while YUI takes 2.7s and GCL takes 6.5s.
</p>
<p>
GoogleClosure does a lot of smart ass optimizations. I had to strive really
hard to get close to it. It should be possible to even beat it, but then
again, GCL has a gazillion lines of code and runs terribly slow, so I'm not
sure it worths spending the effort to save a few bytes. Also, GCL doesn't
cope with <code>eval()</code> or <code>with{}</code> &ndash; it just dumps a warning and proceeds to
mangle names anyway; my DynarchLIB compiled with it is buggy because of
this.
</p>
<p>
UglifyJS consists of ~1100 lines of code for the tokenizer/parser, and ~1100
lines for the compressor and code generator. That should make it very
maintainable and easily extensible, so I would say it has a good place in
this field and it's bound to become the de-facto standard JS minifier. And
I shall rule the world. :-) Use it, and <b>spread the word</b>!
</p>
</div>
</div>
<div id="outline-container-1_4" class="outline-3">
<h3 id="sec-1_4"><span class="section-number-3">1.4</span> Bugs? </h3>
<div class="outline-text-3" id="text-1_4">
<p>
Unfortunately, for the time being there is no automated test suite. But I
ran the compressor manually on non-trivial code, and then I tested that the
generated code works as expected. A few hundred times.
</p>
<p>
DynarchLIB was started in times when there was no good JS minifier.
Therefore I was quite religious about trying to write short code manually,
and as such DL contains a lot of syntactic hacks<sup><a class="footref" name="fnr.1" href="#fn.1">1</a></sup> such as “foo == bar ? a
= 10 : b = 20”, though the more readable version would clearly be to use
“if/else”.
</p>
<p>
Since the parser/compressor runs fine on DL and jQuery, I'm quite confident
that it's solid enough for production use. If you can identify any bugs,
I'd love to hear about them (<a href="http://groups.google.com/group/uglifyjs">use the Google Group</a> or email me directly).
</p>
</div>
</div>
<div id="outline-container-1_5" class="outline-3">
<h3 id="sec-1_5"><span class="section-number-3">1.5</span> Links </h3>
<div class="outline-text-3" id="text-1_5">
<ul>
<li>
Project at GitHub: <a href="http://github.com/mishoo/UglifyJS">http://github.com/mishoo/UglifyJS</a>
</li>
<li>
Google Group: <a href="http://groups.google.com/group/uglifyjs">http://groups.google.com/group/uglifyjs</a>
</li>
<li>
Common Lisp JS parser: <a href="http://marijn.haverbeke.nl/parse-js/">http://marijn.haverbeke.nl/parse-js/</a>
</li>
<li>
JS-to-Lisp compiler: <a href="http://github.com/marijnh/js">http://github.com/marijnh/js</a>
</li>
<li>
Common Lisp JS uglifier: <a href="http://github.com/mishoo/cl-uglify-js">http://github.com/mishoo/cl-uglify-js</a>
</li>
</ul>
</div>
</div>
<div id="outline-container-1_6" class="outline-3">
<h3 id="sec-1_6"><span class="section-number-3">1.6</span> License </h3>
<div class="outline-text-3" id="text-1_6">
<p>
UglifyJS is released under the BSD license:
</p>
<pre class="example">Copyright 2010 (c) Mihai Bazon &lt;mihai.bazon@gmail.com&gt;
Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
</pre>
</div>
</div>
</div>
<div id="footnotes">
<h2 class="footnotes">Footnotes: </h2>
<div id="text-footnotes">
<p class="footnote"><sup><a class="footnum" name="fn.1" href="#fnr.1">1</a></sup> I even reported a few bugs and suggested some fixes in the original
<a href="http://marijn.haverbeke.nl/parse-js/">parse-js</a> library, and Marijn pushed fixes literally in minutes.
</p>
</div>
</div>
<div id="postamble">
<p class="author"> Author: Mihai Bazon
</p>
<p class="date"> Date: 2011-02-28 22:35:00 EET</p>
<p class="creator">HTML generated by org-mode 7.01trans in emacs 23</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,408 @@
#+TITLE: UglifyJS -- a JavaScript parser/compressor/beautifier
#+KEYWORDS: javascript, js, parser, compiler, compressor, mangle, minify, minifier
#+DESCRIPTION: a JavaScript parser/compressor/beautifier in JavaScript
#+STYLE: <link rel="stylesheet" type="text/css" href="docstyle.css" />
#+AUTHOR: Mihai Bazon
#+EMAIL: mihai.bazon@gmail.com
* UglifyJS --- a JavaScript parser/compressor/beautifier
*Update*: please read the section on [[unsafe transformations]].
This package implements a general-purpose JavaScript
parser/compressor/beautifier toolkit. It is developed on [[http://nodejs.org/][NodeJS]], but it
should work on any JavaScript platform supporting the CommonJS module system
(and if your platform of choice doesn't support CommonJS, you can easily
implement it, or discard the =exports.*= lines from UglifyJS sources).
The tokenizer/parser generates an abstract syntax tree from JS code. You
can then traverse the AST to learn more about the code, or do various
manipulations on it. This part is implemented in [[../lib/parse-js.js][parse-js.js]] and it's a
port to JavaScript of the excellent [[http://marijn.haverbeke.nl/parse-js/][parse-js]] Common Lisp library from [[http://marijn.haverbeke.nl/][Marijn
Haverbeke]].
( See [[http://github.com/mishoo/cl-uglify-js][cl-uglify-js]] if you're looking for the Common Lisp version of
UglifyJS. )
The second part of this package, implemented in [[../lib/process.js][process.js]], inspects and
manipulates the AST generated by the parser to provide the following:
- ability to re-generate JavaScript code from the AST. Optionally
indented---you can use this if you want to “beautify” a program that has
been compressed, so that you can inspect the source. But you can also run
our code generator to print out an AST without any whitespace, so you
achieve compression as well.
- shorten variable names (usually to single characters). Our mangler will
analyze the code and generate proper variable names, depending on scope
and usage, and is smart enough to deal with globals defined elsewhere, or
with =eval()= calls or =with{}= statements. In short, if =eval()= or
=with{}= are used in some scope, then all variables in that scope and any
variables in the parent scopes will remain unmangled, and any references
to such variables remain unmangled as well.
- various small optimizations that may lead to faster code but certainly
lead to smaller code. Where possible, we do the following:
- foo["bar"] ==> foo.bar
- remove block brackets ={}=
- join consecutive var declarations:
var a = 10; var b = 20; ==> var a=10,b=20;
- resolve simple constant expressions: 1 +2 * 3 ==> 7. We only do the
replacement if the result occupies less bytes; for example 1/3 would
translate to 0.333333333333, so in this case we don't replace it.
- consecutive statements in blocks are merged into a sequence; in many
cases, this leaves blocks with a single statement, so then we can remove
the block brackets.
- various optimizations for IF statements:
- if (foo) bar(); else baz(); ==> foo?bar():baz();
- if (!foo) bar(); else baz(); ==> foo?baz():bar();
- if (foo) bar(); ==> foo&&bar();
- if (!foo) bar(); ==> foo||bar();
- if (foo) return bar(); else return baz(); ==> return foo?bar():baz();
- if (foo) return bar(); else something(); ==> {if(foo)return bar();something()}
- remove some unreachable code and warn about it (code that follows a
=return=, =throw=, =break= or =continue= statement, except
function/variable declarations).
** <<Unsafe transformations>>
UglifyJS tries its best to achieve great compression while leaving the
semantics of the code intact. In general, if your code logic is broken by
UglifyJS then it's a bug in UglifyJS and you should report it and I should
fix it. :-)
However, I opted to include the following potentially unsafe transformations
as default behavior. Discussion is welcome, if you have ideas of how to
handle this better, or any objections to these optimizations, please let me
know.
*** Calls involving the global Array constructor
The following transformations occur:
#+BEGIN_SRC espresso
new Array(1, 2, 3, 4) => [1,2,3,4]
Array(a, b, c) => [a,b,c]
new Array(5) => Array(5)
new Array(a) => Array(a)
#+END_SRC
These are all safe if the Array name isn't redefined. JavaScript does allow
one to globally redefine Array (and pretty much everything, in fact) but I
personally don't see why would anyone do that.
UglifyJS does handle the case where Array is redefined locally, or even
globally but with a =function= or =var= declaration. Therefore, in the
following cases UglifyJS *doesn't touch* calls or instantiations of Array:
#+BEGIN_SRC espresso
// case 1. globally declared variable
var Array;
new Array(1, 2, 3);
Array(a, b);
// or (can be declared later)
new Array(1, 2, 3);
var Array;
// or (can be a function)
new Array(1, 2, 3);
function Array() { ... }
// case 2. declared in a function
(function(){
a = new Array(1, 2, 3);
b = Array(5, 6);
var Array;
})();
// or
(function(Array){
return Array(5, 6, 7);
})();
// or
(function(){
return new Array(1, 2, 3, 4);
function Array() { ... }
})();
// etc.
#+END_SRC
** Usage
There is a helper script now --- =bin/uglifyjs= --- that uses the library to
compress a script using the maximum compression settings. Synopsis:
#+BEGIN_SRC sh
uglifyjs [ options... ] [ filename ]
#+END_SRC
=filename= should be the last argument and should name the file from which
to read the JavaScript code. If you don't specify it, it will read code
from STDIN.
Supported options:
- =-b= or =--beautify= --- output indented code; when passed, additional
options control the beautifier:
- =-i N= or =--indent N= --- indentation level (number of spaces)
- =-q= or =--quote-keys= --- quote keys in literal objects (by default,
only keys that cannot be identifier names will be quotes).
- =--ascii= --- pass this argument to encode non-ASCII characters as
=\uXXXX= sequences. By default UglifyJS won't bother to do it and will
output Unicode characters instead. (the output is always encoded in UTF8,
but if you pass this option you'll only get ASCII).
- =-nm= or =--no-mangle= --- don't mangle variable names
- =-ns= or =--no-squeeze= --- don't call =ast_squeeze()= (which does various
optimizations that result in smaller, less readable code).
- =-mt= or =--mangle-toplevel= --- mangle names in the toplevel scope too
(by default we don't do this).
- =--no-seqs= --- when =ast_squeeze()= is called (thus, unless you pass
=--no-squeeze=) it will reduce consecutive statements in blocks into a
sequence. For example, "a = 10; b = 20; foo();" will be written as
"a=10,b=20,foo();". In various occasions, this allows us to discard the
block brackets (since the block becomes a single statement). This is ON
by default because it seems safe and saves a few hundred bytes on some
libs that I tested it on, but pass =--no-seqs= to disable it.
- =--no-dead-code= --- by default, UglifyJS will remove code that is
obviously unreachable (code that follows a =return=, =throw=, =break= or
=continue= statement and is not a function/variable declaration). Pass
this option to disable this optimization.
- =-nc= or =--no-copyright= --- by default, =uglifyjs= will keep the initial
comment tokens in the generated code (assumed to be copyright information
etc.). If you pass this it will discard it.
- =-o filename= or =--output filename= --- put the result in =filename=. If
this isn't given, the result goes to standard output (or see next one).
- =--overwrite= --- if the code is read from a file (not from STDIN) and you
pass =--overwrite= then the output will be written in the same file.
- =--ast= --- pass this if you want to get the Abstract Syntax Tree instead
of JavaScript as output. Useful for debugging or learning more about the
internals.
- =-v= or =--verbose= --- output some notes on STDERR (for now just how long
each operation takes).
- =--extra= --- enable additional optimizations that have not yet been
extensively tested. These might, or might not, break your code. If you
find a bug using this option, please report a test case.
- =--unsafe= --- enable other additional optimizations that are known to be
unsafe in some contrived situations, but could still be generally useful.
For now only this:
- foo.toString() ==> foo+""
- =--max-line-len= (default 32K characters) --- add a newline after around
32K characters. I've seen both FF and Chrome croak when all the code was
on a single line of around 670K. Pass --max-line-len 0 to disable this
safety feature.
- =--reserved-names= --- some libraries rely on certain names to be used, as
pointed out in issue #92 and #81, so this option allow you to exclude such
names from the mangler. For example, to keep names =require= and =$super=
intact you'd specify --reserved-names "require,$super".
*** API
Symlink the *lib* directory as *~/.node\_libraries/uglifyjs*, so that the
*require* calls in the following sample will work:
#+BEGIN_SRC espresso
var jsp = require("uglifyjs/parse-js");
var pro = require("uglifyjs/process");
var orig_code = "... JS code here";
var ast = jsp.parse(orig_code); // parse code and get the initial AST
ast = pro.ast_mangle(ast); // get a new AST with mangled names
ast = pro.ast_squeeze(ast); // get an AST with compression optimizations
var final_code = pro.gen_code(ast); // compressed code here
#+END_SRC
The above performs the full compression that is possible right now. As you
can see, there are a sequence of steps which you can apply. For example if
you want compressed output but for some reason you don't want to mangle
variable names, you would simply skip the line that calls
=pro.ast_mangle(ast)=.
Some of these functions take optional arguments. Here's a description:
- =jsp.parse(code, strict_semicolons)= -- parses JS code and returns an AST.
=strict_semicolons= is optional and defaults to =false=. If you pass
=true= then the parser will throw an error when it expects a semicolon and
it doesn't find it. For most JS code you don't want that, but it's useful
if you want to strictly sanitize your code.
- =pro.ast_mangle(ast, options)= -- generates a new AST containing mangled
(compressed) variable and function names. It supports the following
options:
- =toplevel= -- mangle toplevel names (by default we don't touch them).
- =except= -- an array of names to exclude from compression.
- =pro.ast_squeeze(ast, options)= -- employs further optimizations designed
to reduce the size of the code that =gen_code= would generate from the
AST. Returns a new AST. =options= can be a hash; the supported options
are:
- =make_seqs= (default true) which will cause consecutive statements in a
block to be merged using the "sequence" (comma) operator
- =dead_code= (default true) which will remove unreachable code.
- =pro.gen_code(ast, options)= -- generates JS code from the AST. By
default it's minified, but using the =options= argument you can get nicely
formatted output. =options= is, well, optional :-) and if you pass it it
must be an object and supports the following properties (below you can see
the default values):
- =beautify: false= -- pass =true= if you want indented output
- =indent_start: 0= (only applies when =beautify= is =true=) -- initial
indentation in spaces
- =indent_level: 4= (only applies when =beautify= is =true=) --
indentation level, in spaces (pass an even number)
- =quote_keys: false= -- if you pass =true= it will quote all keys in
literal objects
- =space_colon: false= (only applies when =beautify= is =true=) -- wether
to put a space before the colon in object literals
- =ascii_only: false= -- pass =true= if you want to encode non-ASCII
characters as =\uXXXX=.
*** Beautifier shortcoming -- no more comments
The beautifier can be used as a general purpose indentation tool. It's
useful when you want to make a minified file readable. One limitation,
though, is that it discards all comments, so you don't really want to use it
to reformat your code, unless you don't have, or don't care about, comments.
In fact it's not the beautifier who discards comments --- they are dumped at
the parsing stage, when we build the initial AST. Comments don't really
make sense in the AST, and while we could add nodes for them, it would be
inconvenient because we'd have to add special rules to ignore them at all
the processing stages.
** Compression -- how good is it?
(XXX: this is somewhat outdated. On the jQuery source code we beat Closure
by 168 bytes (560 after gzip) and by many seconds.)
There are a few popular JS minifiers nowadays -- the two most well known
being the GoogleClosure (GCL) compiler and the YUI compressor. For some
reason they are both written in Java. I didn't really hope to beat any of
them, but finally I did -- UglifyJS compresses better than the YUI
compressor, and safer than GoogleClosure.
I tested it on two big libraries. [[http://www.dynarchlib.com/][DynarchLIB]] is my own, and it's big enough
to contain probably all the JavaScript tricks known to mankind. [[http://jquery.com/][jQuery]] is
definitely the most popular JavaScript library (to some people, it's a
synonym to JavaScript itself).
I cannot swear that there are no bugs in the generated codes, but they
appear to work fine.
Compression results:
| Library | Orig. size | UglifyJS | YUI | GCL |
|------------+------------+----------+----------------+------------------------|
| DynarchLIB | 636896 | 241441 | 246452 (+5011) | 240439 (-1002) (buggy) |
| jQuery | 163855 | 72006 | 79702 (+7696) | 71858 (-148) |
UglifyJS is the fastest to run. On my laptop UglifyJS takes 1.35s for
DynarchLIB, while YUI takes 2.7s and GCL takes 6.5s.
GoogleClosure does a lot of smart ass optimizations. I had to strive really
hard to get close to it. It should be possible to even beat it, but then
again, GCL has a gazillion lines of code and runs terribly slow, so I'm not
sure it worths spending the effort to save a few bytes. Also, GCL doesn't
cope with =eval()= or =with{}= -- it just dumps a warning and proceeds to
mangle names anyway; my DynarchLIB compiled with it is buggy because of
this.
UglifyJS consists of ~1100 lines of code for the tokenizer/parser, and ~1100
lines for the compressor and code generator. That should make it very
maintainable and easily extensible, so I would say it has a good place in
this field and it's bound to become the de-facto standard JS minifier. And
I shall rule the world. :-) Use it, and *spread the word*!
** Bugs?
Unfortunately, for the time being there is no automated test suite. But I
ran the compressor manually on non-trivial code, and then I tested that the
generated code works as expected. A few hundred times.
DynarchLIB was started in times when there was no good JS minifier.
Therefore I was quite religious about trying to write short code manually,
and as such DL contains a lot of syntactic hacks[1] such as “foo == bar ? a
= 10 : b = 20”, though the more readable version would clearly be to use
“if/else”.
Since the parser/compressor runs fine on DL and jQuery, I'm quite confident
that it's solid enough for production use. If you can identify any bugs,
I'd love to hear about them ([[http://groups.google.com/group/uglifyjs][use the Google Group]] or email me directly).
[1] I even reported a few bugs and suggested some fixes in the original
[[http://marijn.haverbeke.nl/parse-js/][parse-js]] library, and Marijn pushed fixes literally in minutes.
** Links
- Project at GitHub: [[http://github.com/mishoo/UglifyJS][http://github.com/mishoo/UglifyJS]]
- Google Group: [[http://groups.google.com/group/uglifyjs][http://groups.google.com/group/uglifyjs]]
- Common Lisp JS parser: [[http://marijn.haverbeke.nl/parse-js/][http://marijn.haverbeke.nl/parse-js/]]
- JS-to-Lisp compiler: [[http://github.com/marijnh/js][http://github.com/marijnh/js]]
- Common Lisp JS uglifier: [[http://github.com/mishoo/cl-uglify-js][http://github.com/mishoo/cl-uglify-js]]
** License
UglifyJS is released under the BSD license:
#+BEGIN_EXAMPLE
Copyright 2010 (c) Mihai Bazon <mihai.bazon@gmail.com>
Based on parse-js (http://marijn.haverbeke.nl/parse-js/).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
#+END_EXAMPLE

View File

@@ -0,0 +1,212 @@
#! /usr/bin/env node
// -*- js2 -*-
global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util");
var fs = require("fs");
var uglify = require("uglify-js"), // symlink ~/.node_libraries/uglify-js.js to ../uglify-js.js
jsp = uglify.parser,
pro = uglify.uglify;
var options = {
ast: false,
mangle: true,
mangle_toplevel: false,
squeeze: true,
make_seqs: true,
dead_code: true,
verbose: false,
show_copyright: true,
out_same_file: false,
max_line_length: 32 * 1024,
unsafe: false,
reserved_names: null,
codegen_options: {
ascii_only: false,
beautify: false,
indent_level: 4,
indent_start: 0,
quote_keys: false,
space_colon: false
},
output: true // stdout
};
var args = jsp.slice(process.argv, 2);
var filename;
out: while (args.length > 0) {
var v = args.shift();
switch (v) {
case "-b":
case "--beautify":
options.codegen_options.beautify = true;
break;
case "-i":
case "--indent":
options.codegen_options.indent_level = args.shift();
break;
case "-q":
case "--quote-keys":
options.codegen_options.quote_keys = true;
break;
case "-mt":
case "--mangle-toplevel":
options.mangle_toplevel = true;
break;
case "--no-mangle":
case "-nm":
options.mangle = false;
break;
case "--no-squeeze":
case "-ns":
options.squeeze = false;
break;
case "--no-seqs":
options.make_seqs = false;
break;
case "--no-dead-code":
options.dead_code = false;
break;
case "--no-copyright":
case "-nc":
options.show_copyright = false;
break;
case "-o":
case "--output":
options.output = args.shift();
break;
case "--overwrite":
options.out_same_file = true;
break;
case "-v":
case "--verbose":
options.verbose = true;
break;
case "--ast":
options.ast = true;
break;
case "--unsafe":
options.unsafe = true;
break;
case "--max-line-len":
options.max_line_length = parseInt(args.shift(), 10);
break;
case "--reserved-names":
options.reserved_names = args.shift().split(",");
break;
case "--ascii":
options.codegen_options.ascii_only = true;
break;
default:
filename = v;
break out;
}
}
if (options.verbose) {
pro.set_logger(function(msg){
sys.debug(msg);
});
}
jsp.set_logger(function(msg){
sys.debug(msg);
});
if (filename) {
fs.readFile(filename, "utf8", function(err, text){
if (err) throw err;
output(squeeze_it(text));
});
} else {
var stdin = process.openStdin();
stdin.setEncoding("utf8");
var text = "";
stdin.on("data", function(chunk){
text += chunk;
});
stdin.on("end", function() {
output(squeeze_it(text));
});
}
function output(text) {
var out;
if (options.out_same_file && filename)
options.output = filename;
if (options.output === true) {
out = process.stdout;
} else {
out = fs.createWriteStream(options.output, {
flags: "w",
encoding: "utf8",
mode: 0644
});
}
out.write(text);
if (options.output !== true) {
out.end();
}
};
// --------- main ends here.
function show_copyright(comments) {
var ret = "";
for (var i = 0; i < comments.length; ++i) {
var c = comments[i];
if (c.type == "comment1") {
ret += "//" + c.value + "\n";
} else {
ret += "/*" + c.value + "*/";
}
}
return ret;
};
function squeeze_it(code) {
var result = "";
if (options.show_copyright) {
var tok = jsp.tokenizer(code), c;
c = tok();
result += show_copyright(c.comments_before);
}
try {
var ast = time_it("parse", function(){ return jsp.parse(code); });
if (options.mangle) ast = time_it("mangle", function(){
return pro.ast_mangle(ast, {
toplevel: options.mangle_toplevel,
except: options.reserved_names
});
});
if (options.squeeze) ast = time_it("squeeze", function(){
ast = pro.ast_squeeze(ast, {
make_seqs : options.make_seqs,
dead_code : options.dead_code,
keep_comps : !options.unsafe
});
if (options.unsafe)
ast = pro.ast_squeeze_more(ast);
return ast;
});
if (options.ast)
return sys.inspect(ast, null, null);
result += time_it("generate", function(){ return pro.gen_code(ast, options.codegen_options) });
if (!options.codegen_options.beautify && options.max_line_length) {
result = time_it("split", function(){ return pro.split_lines(result, options.max_line_length) });
}
return result;
} catch(ex) {
sys.debug(ex.stack);
sys.debug(sys.inspect(ex));
sys.debug(JSON.stringify(ex));
}
};
function time_it(name, cont) {
if (!options.verbose)
return cont();
var t1 = new Date().getTime();
try { return cont(); }
finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
};

View File

@@ -0,0 +1,75 @@
html { font-family: "Lucida Grande","Trebuchet MS",sans-serif; font-size: 12pt; }
body { max-width: 60em; }
.title { text-align: center; }
.todo { color: red; }
.done { color: green; }
.tag { background-color:lightblue; font-weight:normal }
.target { }
.timestamp { color: grey }
.timestamp-kwd { color: CadetBlue }
p.verse { margin-left: 3% }
pre {
border: 1pt solid #AEBDCC;
background-color: #F3F5F7;
padding: 5pt;
font-family: monospace;
font-size: 90%;
overflow:auto;
}
pre.src {
background-color: #333; color: #ffd; border: 1px solid #000;
}
table { border-collapse: collapse; }
td, th { vertical-align: top; }
dt { font-weight: bold; }
div.figure { padding: 0.5em; }
div.figure p { text-align: center; }
.linenr { font-size:smaller }
.code-highlighted {background-color:#ffff00;}
.org-info-js_info-navigation { border-style:none; }
#org-info-js_console-label { font-size:10px; font-weight:bold;
white-space:nowrap; }
.org-info-js_search-highlight {background-color:#ffff00; color:#000000;
font-weight:bold; }
sup {
vertical-align: baseline;
position: relative;
top: -0.5em;
font-size: 80%;
}
sup a:link, sup a:visited {
text-decoration: none;
color: #c00;
}
sup a:before { content: "["; color: #999; }
sup a:after { content: "]"; color: #999; }
h1.title { border-bottom: 4px solid #000; padding-bottom: 5px; margin-bottom: 2em; }
#postamble {
color: #777;
font-size: 90%;
padding-top: 1em; padding-bottom: 1em; border-top: 1px solid #999;
margin-top: 2em;
padding-left: 2em;
padding-right: 2em;
text-align: right;
}
#postamble p { margin: 0; }
#footnotes { border-top: 1px solid #000; }
h1 { font-size: 200% }
h2 { font-size: 175% }
h3 { font-size: 150% }
h4 { font-size: 125% }
h1, h2, h3, h4 { font-family: "Bookman",Georgia,"Times New Roman",serif; font-weight: normal; }
@media print {
html { font-size: 11pt; }
}

View File

@@ -0,0 +1,2 @@
exports.parser = require("./lib/parse-js");
exports.uglify = require("./lib/process");

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
var jsp = require("./parse-js"),
pro = require("./process"),
slice = jsp.slice,
member = jsp.member,
PRECEDENCE = jsp.PRECEDENCE,
OPERATORS = jsp.OPERATORS;
function ast_squeeze_more(ast) {
var w = pro.ast_walker(), walk = w.walk;
return w.with_walkers({
"call": function(expr, args) {
if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) {
// foo.toString() ==> foo+""
return [ "binary", "+", expr[1], [ "string", "" ]];
}
}
}, function() {
return walk(ast);
});
};
exports.ast_squeeze_more = ast_squeeze_more;

View File

@@ -0,0 +1,6 @@
{"name" : "uglify-js",
"author" : "Mihai Bazon - http://github.com/mishoo",
"version" : "0.0.1",
"main" : "index.js",
"bin" : { "uglifyjs" : "./bin/uglifyjs" },
}

View File

@@ -0,0 +1,28 @@
#! /usr/bin/env node
global.sys = require("sys");
var fs = require("fs");
var jsp = require("../lib/parse-js");
var pro = require("../lib/process");
var filename = process.argv[2];
fs.readFile(filename, "utf8", function(err, text){
try {
var ast = time_it("parse", function(){ return jsp.parse(text); });
ast = time_it("mangle", function(){ return pro.ast_mangle(ast); });
ast = time_it("squeeze", function(){ return pro.ast_squeeze(ast); });
var gen = time_it("generate", function(){ return pro.gen_code(ast, false); });
sys.puts(gen);
} catch(ex) {
sys.debug(ex.stack);
sys.debug(sys.inspect(ex));
sys.debug(JSON.stringify(ex));
}
});
function time_it(name, cont) {
var t1 = new Date().getTime();
try { return cont(); }
finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
};

View File

@@ -0,0 +1,402 @@
#! /usr/bin/env node
var parseJS = require("../lib/parse-js");
var sys = require("sys");
// write debug in a very straightforward manner
var debug = function(){
sys.log(Array.prototype.slice.call(arguments).join(', '));
};
ParserTestSuite(function(i, input, desc){
try {
parseJS.parse(input);
debug("ok " + i + ": " + desc);
} catch(e){
debug("FAIL " + i + " " + desc + " (" + e + ")");
}
});
function ParserTestSuite(callback){
var inps = [
["var abc;", "Regular variable statement w/o assignment"],
["var abc = 5;", "Regular variable statement with assignment"],
["/* */;", "Multiline comment"],
['/** **/;', 'Double star multiline comment'],
["var f = function(){;};", "Function expression in var assignment"],
['hi; // moo\n;', 'single line comment'],
['var varwithfunction;', 'Dont match keywords as substrings'], // difference between `var withsomevar` and `"str"` (local search and lits)
['a + b;', 'addition'],
["'a';", 'single string literal'],
["'a\\n';", 'single string literal with escaped return'],
['"a";', 'double string literal'],
['"a\\n";', 'double string literal with escaped return'],
['"var";', 'string is a keyword'],
['"variable";', 'string starts with a keyword'],
['"somevariable";', 'string contains a keyword'],
['"somevar";', 'string ends with a keyword'],
['500;', 'int literal'],
['500.;', 'float literal w/o decimals'],
['500.432;', 'float literal with decimals'],
['.432432;', 'float literal w/o int'],
['(a,b,c);', 'parens and comma'],
['[1,2,abc];', 'array literal'],
['var o = {a:1};', 'object literal unquoted key'],
['var o = {"b":2};', 'object literal quoted key'], // opening curly may not be at the start of a statement...
['var o = {c:c};', 'object literal keyname is identifier'],
['var o = {a:1,"b":2,c:c};', 'object literal combinations'],
['var x;\nvar y;', 'two lines'],
['var x;\nfunction n(){; }', 'function def'],
['var x;\nfunction n(abc){; }', 'function def with arg'],
['var x;\nfunction n(abc, def){ ;}', 'function def with args'],
['function n(){ "hello"; }', 'function def with body'],
['/a/;', 'regex literal'],
['/a/b;', 'regex literal with flag'],
['/a/ / /b/;', 'regex div regex'],
['a/b/c;', 'triple division looks like regex'],
['+function(){/regex/;};', 'regex at start of function body'],
// http://code.google.com/p/es-lab/source/browse/trunk/tests/parser/parsertests.js?r=86
// http://code.google.com/p/es-lab/source/browse/trunk/tests/parser/parsertests.js?r=430
// first tests for the lexer, should also parse as program (when you append a semi)
// comments
['//foo!@#^&$1234\nbar;', 'single line comment'],
['/* abcd!@#@$* { } && null*/;', 'single line multi line comment'],
['/*foo\nbar*/;','multi line comment'],
['/*x*x*/;','multi line comment with *'],
['/**/;','empty comment'],
// identifiers
["x;",'1 identifier'],
["_x;",'2 identifier'],
["xyz;",'3 identifier'],
["$x;",'4 identifier'],
["x$;",'5 identifier'],
["_;",'6 identifier'],
["x5;",'7 identifier'],
["x_y;",'8 identifier'],
["x+5;",'9 identifier'],
["xyz123;",'10 identifier'],
["x1y1z1;",'11 identifier'],
["foo\\u00D8bar;",'12 identifier unicode escape'],
//["foo<6F>bar;",'13 identifier unicode embedded (might fail)'],
// numbers
["5;", '1 number'],
["5.5;", '2 number'],
["0;", '3 number'],
["0.0;", '4 number'],
["0.001;", '5 number'],
["1.e2;", '6 number'],
["1.e-2;", '7 number'],
["1.E2;", '8 number'],
["1.E-2;", '9 number'],
[".5;", '10 number'],
[".5e3;", '11 number'],
[".5e-3;", '12 number'],
["0.5e3;", '13 number'],
["55;", '14 number'],
["123;", '15 number'],
["55.55;", '16 number'],
["55.55e10;", '17 number'],
["123.456;", '18 number'],
["1+e;", '20 number'],
["0x01;", '22 number'],
["0XCAFE;", '23 number'],
["0x12345678;", '24 number'],
["0x1234ABCD;", '25 number'],
["0x0001;", '26 number'],
// strings
["\"foo\";", '1 string'],
["\'foo\';", '2 string'],
["\"x\";", '3 string'],
["\'\';", '4 string'],
["\"foo\\tbar\";", '5 string'],
["\"!@#$%^&*()_+{}[]\";", '6 string'],
["\"/*test*/\";", '7 string'],
["\"//test\";", '8 string'],
["\"\\\\\";", '9 string'],
["\"\\u0001\";", '10 string'],
["\"\\uFEFF\";", '11 string'],
["\"\\u10002\";", '12 string'],
["\"\\x55\";", '13 string'],
["\"\\x55a\";", '14 string'],
["\"a\\\\nb\";", '15 string'],
['";"', '16 string: semi in a string'],
['"a\\\nb";', '17 string: line terminator escape'],
// literals
["null;", "null"],
["true;", "true"],
["false;", "false"],
// regex
["/a/;", "1 regex"],
["/abc/;", "2 regex"],
["/abc[a-z]*def/g;", "3 regex"],
["/\\b/;", "4 regex"],
["/[a-zA-Z]/;", "5 regex"],
// program tests (for as far as they havent been covered above)
// regexp
["/foo(.*)/g;", "another regexp"],
// arrays
["[];", "1 array"],
["[ ];", "2 array"],
["[1];", "3 array"],
["[1,2];", "4 array"],
["[1,2,,];", "5 array"],
["[1,2,3];", "6 array"],
["[1,2,3,,,];", "7 array"],
// objects
["{};", "1 object"],
["({x:5});", "2 object"],
["({x:5,y:6});", "3 object"],
["({x:5,});", "4 object"],
["({if:5});", "5 object"],
["({ get x() {42;} });", "6 object"],
["({ set y(a) {1;} });", "7 object"],
// member expression
["o.m;", "1 member expression"],
["o['m'];", "2 member expression"],
["o['n']['m'];", "3 member expression"],
["o.n.m;", "4 member expression"],
["o.if;", "5 member expression"],
// call and invoke expressions
["f();", "1 call/invoke expression"],
["f(x);", "2 call/invoke expression"],
["f(x,y);", "3 call/invoke expression"],
["o.m();", "4 call/invoke expression"],
["o['m'];", "5 call/invoke expression"],
["o.m(x);", "6 call/invoke expression"],
["o['m'](x);", "7 call/invoke expression"],
["o.m(x,y);", "8 call/invoke expression"],
["o['m'](x,y);", "9 call/invoke expression"],
["f(x)(y);", "10 call/invoke expression"],
["f().x;", "11 call/invoke expression"],
// eval
["eval('x');", "1 eval"],
["(eval)('x');", "2 eval"],
["(1,eval)('x');", "3 eval"],
["eval(x,y);", "4 eval"],
// new expression
["new f();", "1 new expression"],
["new o;", "2 new expression"],
["new o.m;", "3 new expression"],
["new o.m(x);", "4 new expression"],
["new o.m(x,y);", "5 new expression"],
// prefix/postfix
["++x;", "1 pre/postfix"],
["x++;", "2 pre/postfix"],
["--x;", "3 pre/postfix"],
["x--;", "4 pre/postfix"],
["x ++;", "5 pre/postfix"],
["x /* comment */ ++;", "6 pre/postfix"],
["++ /* comment */ x;", "7 pre/postfix"],
// unary operators
["delete x;", "1 unary operator"],
["void x;", "2 unary operator"],
["+ x;", "3 unary operator"],
["-x;", "4 unary operator"],
["~x;", "5 unary operator"],
["!x;", "6 unary operator"],
// meh
["new Date++;", "new date ++"],
["+x++;", " + x ++"],
// expression expressions
["1 * 2;", "1 expression expressions"],
["1 / 2;", "2 expression expressions"],
["1 % 2;", "3 expression expressions"],
["1 + 2;", "4 expression expressions"],
["1 - 2;", "5 expression expressions"],
["1 << 2;", "6 expression expressions"],
["1 >>> 2;", "7 expression expressions"],
["1 >> 2;", "8 expression expressions"],
["1 * 2 + 3;", "9 expression expressions"],
["(1+2)*3;", "10 expression expressions"],
["1*(2+3);", "11 expression expressions"],
["x<y;", "12 expression expressions"],
["x>y;", "13 expression expressions"],
["x<=y;", "14 expression expressions"],
["x>=y;", "15 expression expressions"],
["x instanceof y;", "16 expression expressions"],
["x in y;", "17 expression expressions"],
["x&y;", "18 expression expressions"],
["x^y;", "19 expression expressions"],
["x|y;", "20 expression expressions"],
["x+y<z;", "21 expression expressions"],
["x<y+z;", "22 expression expressions"],
["x+y+z;", "23 expression expressions"],
["x+y<z;", "24 expression expressions"],
["x<y+z;", "25 expression expressions"],
["x&y|z;", "26 expression expressions"],
["x&&y;", "27 expression expressions"],
["x||y;", "28 expression expressions"],
["x&&y||z;", "29 expression expressions"],
["x||y&&z;", "30 expression expressions"],
["x<y?z:w;", "31 expression expressions"],
// assignment
["x >>>= y;", "1 assignment"],
["x <<= y;", "2 assignment"],
["x = y;", "3 assignment"],
["x += y;", "4 assignment"],
["x /= y;", "5 assignment"],
// comma
["x, y;", "comma"],
// block
["{};", "1 block"],
["{x;};", "2 block"],
["{x;y;};", "3 block"],
// vars
["var x;", "1 var"],
["var x,y;", "2 var"],
["var x=1,y=2;", "3 var"],
["var x,y=2;", "4 var"],
// empty
[";", "1 empty"],
["\n;", "2 empty"],
// expression statement
["x;", "1 expression statement"],
["5;", "2 expression statement"],
["1+2;", "3 expression statement"],
// if
["if (c) x; else y;", "1 if statement"],
["if (c) x;", "2 if statement"],
["if (c) {} else {};", "3 if statement"],
["if (c1) if (c2) s1; else s2;", "4 if statement"],
// while
["do s; while (e);", "1 while statement"],
["do { s; } while (e);", "2 while statement"],
["while (e) s;", "3 while statement"],
["while (e) { s; };", "4 while statement"],
// for
["for (;;) ;", "1 for statement"],
["for (;c;x++) x;", "2 for statement"],
["for (i;i<len;++i){};", "3 for statement"],
["for (var i=0;i<len;++i) {};", "4 for statement"],
["for (var i=0,j=0;;){};", "5 for statement"],
//["for (x in b; c; u) {};", "6 for statement"],
["for ((x in b); c; u) {};", "7 for statement"],
["for (x in a);", "8 for statement"],
["for (var x in a){};", "9 for statement"],
["for (var x=5 in a) {};", "10 for statement"],
["for (var x = a in b in c) {};", "11 for statement"],
["for (var x=function(){a+b;}; a<b; ++i) some;", "11 for statement, testing for parsingForHeader reset with the function"],
["for (var x=function(){for (x=0; x<15; ++x) alert(foo); }; a<b; ++i) some;", "11 for statement, testing for parsingForHeader reset with the function"],
// flow statements
["while(1){ continue; }", "1 flow statement"],
["label: while(1){ continue label; }", "2 flow statement"],
["while(1){ break; }", "3 flow statement"],
["somewhere: while(1){ break somewhere; }", "4 flow statement"],
["while(1){ continue /* comment */ ; }", "5 flow statement"],
["while(1){ continue \n; }", "6 flow statement"],
["(function(){ return; })()", "7 flow statement"],
["(function(){ return 0; })()", "8 flow statement"],
["(function(){ return 0 + \n 1; })()", "9 flow statement"],
// with
["with (e) s;", "with statement"],
// switch
["switch (e) { case x: s; };", "1 switch statement"],
["switch (e) { case x: s1;s2; default: s3; case y: s4; };", "2 switch statement"],
["switch (e) { default: s1; case x: s2; case y: s3; };", "3 switch statement"],
["switch (e) { default: s; };", "4 switch statement"],
["switch (e) { case x: s1; case y: s2; };", "5 switch statement"],
// labels
["foo : x;", " flow statement"],
// throw
["throw x;", "1 throw statement"],
["throw x\n;", "2 throw statement"],
// try catch finally
["try { s1; } catch (e) { s2; };", "1 trycatchfinally statement"],
["try { s1; } finally { s2; };", "2 trycatchfinally statement"],
["try { s1; } catch (e) { s2; } finally { s3; };", "3 trycatchfinally statement"],
// debugger
["debugger;", "debuger statement"],
// function decl
["function f(x) { e; return x; };", "1 function declaration"],
["function f() { x; y; };", "2 function declaration"],
["function f(x,y) { var z; return x; };", "3 function declaration"],
// function exp
["(function f(x) { return x; });", "1 function expression"],
["(function empty() {;});", "2 function expression"],
["(function empty() {;});", "3 function expression"],
["(function (x) {; });", "4 function expression"],
// program
["var x; function f(){;}; null;", "1 program"],
[";;", "2 program"],
["{ x; y; z; }", "3 program"],
["function f(){ function g(){;}};", "4 program"],
["x;\n/*foo*/\n ;", "5 program"],
// asi
["foo: while(1){ continue \n foo; }", "1 asi"],
["foo: while(1){ break \n foo; }", "2 asi"],
["(function(){ return\nfoo; })()", "3 asi"],
["var x; { 1 \n 2 } 3", "4 asi"],
["ab /* hi */\ncd", "5 asi"],
["ab/*\n*/cd", "6 asi (multi line multilinecomment counts as eol)"],
["foo: while(1){ continue /* wtf \n busta */ foo; }", "7 asi illegal with multi line comment"],
["function f() { s }", "8 asi"],
["function f() { return }", "9 asi"],
// use strict
// XXX: some of these should actually fail?
// no support for "use strict" yet...
['"use strict"; \'bla\'\n; foo;', "1 directive"],
['(function() { "use strict"; \'bla\';\n foo; });', "2 directive"],
['"use\\n strict";', "3 directive"],
['foo; "use strict";', "4 directive"],
// tests from http://es5conform.codeplex.com/
['"use strict"; var o = { eval: 42};', "8.7.2-3-1-s: the use of eval as property name is allowed"],
['({foo:0,foo:1});', 'Duplicate property name allowed in not strict mode'],
['function foo(a,a){}', 'Duplicate parameter name allowed in not strict mode'],
['(function foo(eval){})', 'Eval allowed as parameter name in non strict mode'],
['(function foo(arguments){})', 'Arguments allowed as parameter name in non strict mode'],
// empty programs
['', '1 Empty program'],
['// test', '2 Empty program'],
['//test\n', '3 Empty program'],
['\n// test', '4 Empty program'],
['\n// test\n', '5 Empty program'],
['/* */', '6 Empty program'],
['/*\ns,fd\n*/', '7 Empty program'],
['/*\ns,fd\n*/\n', '8 Empty program'],
[' ', '9 Empty program'],
[' /*\nsmeh*/ \n ', '10 Empty program'],
// trailing whitespace
['a ', '1 Trailing whitespace'],
['a /* something */', '2 Trailing whitespace'],
['a\n // hah', '3 Trailing whitespace'],
['/abc/de//f', '4 Trailing whitespace'],
['/abc/de/*f*/\n ', '5 Trailing whitespace'],
// things the parser tripped over at one point or the other (prevents regression bugs)
['for (x;function(){ a\nb };z) x;', 'for header with function body forcing ASI'],
['c=function(){return;return};', 'resetting noAsi after literal'],
['d\nd()', 'asi exception causing token overflow'],
['for(;;){x=function(){}}', 'function expression in a for header'],
['for(var k;;){}', 'parser failing due to ASI accepting the incorrect "for" rule'],
['({get foo(){ }})', 'getter with empty function body'],
['\nreturnr', 'eol causes return statement to ignore local search requirement'],
[' / /', '1 whitespace before regex causes regex to fail?'],
['/ // / /', '2 whitespace before regex causes regex to fail?'],
['/ / / / /', '3 whitespace before regex causes regex to fail?'],
['\n\t// Used for trimming whitespace\n\ttrimLeft = /^\\s+/;\n\ttrimRight = /\\s+$/;\t\n','turned out this didnt crash (the test below did), but whatever.'],
['/[\\/]/;', 'escaped forward slash inside class group (would choke on fwd slash)'],
['/[/]/;', 'also broke but is valid in es5 (not es3)'],
['({get:5});','get property name thats not a getter'],
['({set:5});','set property name thats not a setter'],
['l !== "px" && (d.style(h, c, (k || 1) + l), j = (k || 1) / f.cur() * j, d.style(h, c, j + l)), i[1] && (k = (i[1] === "-=" ? -1 : 1) * k + j), f.custom(j, k, l)', 'this choked regex/div at some point'],
['(/\'/g, \'\\\\\\\'\') + "\'";', 'the sequence of escaped characters confused the tokenizer']
];
for (var i=0; i<inps.length; ++i) {
callback(i, inps[i][0], inps[i][1]);
};
};

View File

@@ -0,0 +1 @@
[],Array(1),[1,2,3]

View File

@@ -0,0 +1 @@
(function(){var a=function(){};return new a(1,2,3,4)})()

View File

@@ -0,0 +1 @@
(function(){function a(){}return new a(1,2,3,4)})()

View File

@@ -0,0 +1 @@
(function(){function a(){}(function(){return new a(1,2,3)})()})()

View File

@@ -0,0 +1 @@
a=1,b=a,c=1,d=b,e=d,longname=2;if(longname+1){x=3;if(x)var z=7}z=1,y=1,x=1,g+=1,h=g,++i,j=i,i++,j=i+17

View File

@@ -0,0 +1 @@
var a=a+"a"+"b"+1+c,b=a+"c"+"ds"+123+c,c=a+"c"+123+d+"ds"+c

View File

@@ -0,0 +1 @@
var a=13,b=1/3

View File

@@ -0,0 +1 @@
function mak(){for(;;);}function foo(){while(bar());}function bar(){return--x}var x=5

View File

@@ -0,0 +1 @@
a=func(),b=z;for(a++;i<10;i++)alert(i);var z=1;g=2;for(;i<10;i++)alert(i);var a=2;for(var i=1;i<10;i++)alert(i)

View File

@@ -0,0 +1 @@
var a=1;a==1?a=2:a=17

View File

@@ -0,0 +1 @@
function a(a){return a==1?2:17}

View File

@@ -0,0 +1 @@
function f(){var a;return(a="a")?a:a}f()

View File

@@ -0,0 +1 @@
new(A,B),new(A||B),new(X?A:B)

View File

@@ -0,0 +1 @@
var a=/^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#])(?::(\d))?)?(..?$|(?:[^?#\/]\/))([^?#]*)(?:\?([^#]))?(?:#(.))?/

View File

@@ -0,0 +1 @@
var a={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"}

View File

@@ -0,0 +1 @@
var a=3250441966

View File

@@ -0,0 +1 @@
var a=function(b){b(),a()}

View File

@@ -0,0 +1 @@
a:1

View File

@@ -0,0 +1 @@
var a=0;switch(a){case 0:a++}

View File

@@ -0,0 +1 @@
label1:{label2:break label2;console.log(1)}

View File

@@ -0,0 +1 @@
(a?b:c)?d:e

View File

@@ -0,0 +1 @@
o={".5":.5},o={.5:.5},o={.5:.5}

View File

@@ -0,0 +1 @@
result=function(){return 1}()

View File

@@ -0,0 +1 @@
var a=8,b=4,c=4

View File

@@ -0,0 +1 @@
var a={};a["this"]=1,a.that=2

View File

@@ -0,0 +1 @@
var a=2e3,b=.002,c=2e-5

View File

@@ -0,0 +1 @@
var s,i;s="",i=0

View File

@@ -0,0 +1 @@
function bar(a){try{foo()}catch(b){alert("Exception caught (foo not defined)")}alert(a)}bar(10)

View File

@@ -0,0 +1 @@
x=(y,z)

View File

@@ -0,0 +1 @@
foo+"",a.toString(16),b.toString.call(c)

View File

@@ -0,0 +1 @@
function f(){function b(){}a||b()}

View File

@@ -0,0 +1 @@
[(a,b)]

View File

@@ -0,0 +1 @@
var a={a:1,b:2}

View File

@@ -0,0 +1 @@
typeof a=="string",b+""!=c+"",d<e==f<g

View File

@@ -0,0 +1 @@
var a=1,b=2

View File

@@ -0,0 +1,3 @@
new Array();
new Array(1);
new Array(1, 2, 3);

View File

@@ -0,0 +1,4 @@
(function(){
var Array = function(){};
return new Array(1, 2, 3, 4);
})();

View File

@@ -0,0 +1,4 @@
(function(){
return new Array(1, 2, 3, 4);
function Array() {};
})();

View File

@@ -0,0 +1,6 @@
(function(){
(function(){
return new Array(1, 2, 3);
})();
function Array(){};
})();

View File

@@ -0,0 +1,20 @@
a=1;
b=a;
c=1;
d=b;
e=d;
longname=2;
if (longname+1) {
x=3;
if (x) var z = 7;
}
z=1,y=1,x=1
g+=1;
h=g;
++i;
j=i;
i++;
j=i+17;

View File

@@ -0,0 +1,3 @@
var a = a + "a" + "b" + 1 + c;
var b = a + "c" + "ds" + 123 + c;
var c = a + "c" + 123 + d + "ds" + c;

View File

@@ -0,0 +1,5 @@
// test that the calculation is fold to 13
var a = 1 + 2 * 6;
// test that it isn't replaced with 0.3333 because that is more characters
var b = 1/3;

View File

@@ -0,0 +1,4 @@
var x = 5;
function bar() { return --x; }
function foo() { while (bar()); }
function mak() { for(;;); }

View File

@@ -0,0 +1,10 @@
a=func();
b=z;
for (a++; i < 10; i++) { alert(i); }
var z=1;
g=2;
for (; i < 10; i++) { alert(i); }
var a = 2;
for (var i = 1; i < 10; i++) { alert(i); }

View File

@@ -0,0 +1,6 @@
var a = 1;
if (a == 1) {
a = 2;
} else {
a = 17;
}

View File

@@ -0,0 +1,9 @@
function a(b) {
if (b == 1) {
return 2;
} else {
return 17;
}
return 3;
}

View File

@@ -0,0 +1 @@
function f() { var a; if (a = 'a') { return a; } else { return a; } }; f();

View File

@@ -0,0 +1,3 @@
new (A, B)
new (A || B)
new (X ? A : B)

View File

@@ -0,0 +1 @@
var a = /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#])(?::(\d))?)?(..?$|(?:[^?#\/]\/))([^?#]*)(?:\?([^#]))?(?:#(.))?/;

View File

@@ -0,0 +1 @@
var a = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'};

View File

@@ -0,0 +1 @@
var a = 0xC1BDCEEE;

View File

@@ -0,0 +1,4 @@
var a = function(b) {
b();
a()
}

View File

@@ -0,0 +1 @@
{a: 1}

View File

@@ -0,0 +1,6 @@
var a = 0;
switch(a) {
case 0:
a++;
break;
}

View File

@@ -0,0 +1,7 @@
label1 : {
label2 : {
break label2;
console.log(2);
}
console.log(1);
}

View File

@@ -0,0 +1 @@
(a ? b : c) ? d : e

View File

@@ -0,0 +1,3 @@
o = {'.5':.5}
o = {'0.5':.5}
o = {0.5:.5}

View File

@@ -0,0 +1 @@
result=(function(){ return 1;})()

View File

@@ -0,0 +1,3 @@
var a = 1 << 3;
var b = 8 >> 1;
var c = 8 >>> 1;

View File

@@ -0,0 +1,3 @@
var a = {};
a["this"] = 1;
a["that"] = 2;

View File

@@ -0,0 +1,3 @@
var a = 2e3;
var b = 2e-3;
var c = 2e-5;

View File

@@ -0,0 +1 @@
var s, i; s = ''; i = 0;

View File

@@ -0,0 +1,9 @@
function bar(a) {
try {
foo();
} catch(e) {
alert("Exception caught (foo not defined)");
}
alert(a); // 10 in FF, "[object Error]" in IE
}
bar(10);

View File

@@ -0,0 +1 @@
x = (y, z)

Some files were not shown because too many files have changed in this diff Show More