UNPKG

7.7 kBJavaScriptView Raw
1/*!
2 * socket.io-node
3 * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
4 * MIT Licensed
5 */
6
7/**
8 * Module dependencies.
9 */
10
11var fs = require('fs')
12 , socket = require('../lib/io')
13 , uglify = require('uglify-js');
14
15/**
16 * License headers.
17 *
18 * @api private
19 */
20
21var template = '/*! Socket.IO.%ext% build:' + socket.version + ', %type%. Copyright(c) 2011 LearnBoost <dev@learnboost.com> MIT Licensed */\n'
22 , development = template.replace('%type%', 'development').replace('%ext%', 'js')
23 , production = template.replace('%type%', 'production').replace('%ext%', 'min.js');
24
25/**
26 * If statements, these allows you to create serveride & client side compatible
27 * code using specially designed `if` statements that remove serverside
28 * designed code from the source files
29 *
30 * @api private
31 */
32
33var starttagIF = '// if node'
34 , endtagIF = '// end node';
35
36/**
37 * The modules that are required to create a base build of Socket.IO.
38 *
39 * @const
40 * @type {Array}
41 * @api private
42 */
43
44var base = [
45 'io.js'
46 , 'util.js'
47 , 'events.js'
48 , 'json.js'
49 , 'parser.js'
50 , 'transport.js'
51 , 'socket.js'
52 , 'namespace.js'
53 ];
54
55/**
56 * The available transports for Socket.IO. These are mapped as:
57 *
58 * - `key` the name of the transport
59 * - `value` the dependencies for the transport
60 *
61 * @const
62 * @type {Object}
63 * @api public
64 */
65
66var baseTransports = {
67 'websocket': ['transports/websocket.js']
68 , 'flashsocket': [
69 'transports/websocket.js'
70 , 'transports/flashsocket.js'
71 , 'vendor/web-socket-js/swfobject.js'
72 , 'vendor/web-socket-js/web_socket.js'
73 ]
74 , 'htmlfile': ['transports/xhr.js', 'transports/htmlfile.js']
75 /* FIXME: re-enable me once we have multi-part support
76 , 'xhr-multipart': ['transports/xhr.js', 'transports/xhr-multipart.js'] */
77 , 'xhr-polling': ['transports/xhr.js', 'transports/xhr-polling.js']
78 , 'jsonp-polling': [
79 'transports/xhr.js'
80 , 'transports/xhr-polling.js'
81 , 'transports/jsonp-polling.js'
82 ]
83};
84
85/**
86 * Builds a custom Socket.IO distribution based on the transports that you
87 * need. You can configure the build to create development build or production
88 * build (minified).
89 *
90 * @param {Array} transports The transports that needs to be bundled.
91 * @param {Object} [options] Options to configure the building process.
92 * @param {Function} callback Last argument should always be the callback
93 * @callback {String|Boolean} err An optional argument, if it exists than an error
94 * occurred during the build process.
95 * @callback {String} result The result of the build process.
96 * @api public
97 */
98
99var builder = module.exports = function () {
100 var transports, options, callback, error = null
101 , args = Array.prototype.slice.call(arguments, 0)
102 , settings = {
103 minify: true
104 , node: false
105 , custom: []
106 };
107
108 // Fancy pancy argument support this makes any pattern possible mainly
109 // because we require only one of each type
110 args.forEach(function (arg) {
111 var type = Object.prototype.toString.call(arg)
112 .replace(/\[object\s(\w+)\]/gi , '$1' ).toLowerCase();
113
114 switch (type) {
115 case 'array':
116 return transports = arg;
117 case 'object':
118 return options = arg;
119 case 'function':
120 return callback = arg;
121 }
122 });
123
124 // Add defaults
125 options = options || {};
126 transports = transports || Object.keys(baseTransports);
127
128 // Merge the data
129 for(var option in options) {
130 settings[option] = options[option];
131 }
132
133 // Start creating a dependencies chain with all the required files for the
134 // custom Socket.IO bundle.
135 var files = [];
136 base.forEach(function (file) {
137 files.push(__dirname + '/../lib/' + file);
138 });
139
140 transports.forEach(function (transport) {
141 var dependencies = baseTransports[transport];
142 if (!dependencies) {
143 error = 'Unsupported transport `' + transport + '` supplied as argument.';
144 return;
145 }
146
147 // Add the files to the files list, but only if they are not added before
148 dependencies.forEach(function (file) {
149 var path = __dirname + '/../lib/' + file;
150 if (!~files.indexOf(path)) files.push(path);
151 })
152 });
153
154 // check to see if the files tree compilation generated any errors.
155 if (error) return callback(error);
156
157 var results = {};
158 files.forEach(function (file) {
159 fs.readFile(file, function (err, content) {
160 if (err) error = err;
161 results[file] = content;
162
163 // check if we are done yet, or not.. Just by checking the size of the result
164 // object.
165 if (Object.keys(results).length !== files.length) return;
166
167 // we are done, did we error?
168 if (error) return callback(error);
169
170 // concatinate the file contents in order
171 var code = development
172 , ignore = 0;
173
174 files.forEach(function (file) {
175 code += results[file];
176 });
177
178 // check if we need to add custom code
179 if (settings.custom.length) {
180 settings.custom.forEach(function (content) {
181 code += content;
182 });
183 }
184
185 // Search for conditional code blocks that need to be removed as they
186 // where designed for a server side env. but only if we don't want to
187 // make this build node compatible.
188 if (!settings.node) {
189 code = code.split('\n').filter(function (line) {
190 // check if there are tags in here
191 var start = line.indexOf(starttagIF) >= 0
192 , end = line.indexOf(endtagIF) >= 0
193 , ret = ignore;
194
195 // ignore the current line
196 if (start) {
197 ignore++;
198 ret = ignore;
199 }
200
201 // stop ignoring the next line
202 if (end) {
203 ignore--;
204 }
205
206 return ret == 0;
207 }).join('\n');
208 }
209
210 // check if we need to process it any further
211 if (settings.minify) {
212 // uglify hate unicode chars...
213 var separator = '@@OMGYUCHANGEME@@@';
214 code = code.replace(/(\\ufffd)/g, separator);
215
216 var ast = uglify.parser.parse(code);
217 ast = uglify.uglify.ast_mangle(ast);
218 ast = uglify.uglify.ast_squeeze(ast);
219
220 code = production + uglify.uglify.gen_code(ast);
221
222 // restore the code
223 code = code.replace(new RegExp('('+ separator + ')', 'g'), '\\ufffd');
224 }
225
226 callback(error, code);
227 })
228 })
229};
230
231/**
232 * Builder version is also the current client version
233 * this way we don't have to do another include for the
234 * clients version number and we can just include the builder.
235 *
236 * @type {String}
237 * @api public
238 */
239
240builder.version = socket.version;
241
242/**
243 * A list of all build in transport types.
244 *
245 * @type {Object}
246 * @api public
247 */
248
249builder.transports = baseTransports;
250
251/**
252 * Command line support, this allows us to generate builds without having
253 * to load it as module.
254 */
255
256if (!module.parent){
257 // the first 2 are `node` and the path to this file, we don't need them
258 var args = process.argv.slice(2);
259
260 // build a development build
261 builder(args.length ? args : false, { minify:false }, function (err, content) {
262 if (err) return console.error(err);
263
264 fs.write(
265 fs.openSync(__dirname + '/../dist/socket.io.js', 'w')
266 , content
267 , 0
268 , 'utf8'
269 );
270 console.log('Successfully generated the development build: socket.io.js');
271 });
272
273 // and build a production build
274 builder(args.length ? args : false, function (err, content) {
275 if (err) return console.error(err);
276
277 fs.write(
278 fs.openSync(__dirname + '/../dist/socket.io.min.js', 'w')
279 , content
280 , 0
281 , 'utf8'
282 );
283 console.log('Successfully generated the production build: socket.io.min.js');
284 });
285}