UNPKG

9.25 kBJavaScriptView Raw
1"use strict";
2
3var _ = require('lodash'),
4 through = require('through2'),
5 gutil = require('gulp-util'),
6 glogg = require('glogg'),
7 http = require('http'),
8 https = require('https'),
9 inject = require('connect-inject'),
10 connect = require('connect'),
11 proxy = require('proxy-middleware'),
12 watch = require('node-watch'),
13 fs = require('fs'),
14 serveIndex = require('serve-index'),
15 serveStatic = require('serve-static'),
16 path = require('path'),
17 open = require('open'),
18 enableMiddlewareShorthand = require('./enableMiddlewareShorthand'),
19 socket = require('socket.io'),
20 url = require('url'),
21 extend = require('node.extend');
22
23var BROWSER_SCIPTS_DIR = path.join(__dirname, 'browser-scripts');
24
25var levels = [
26 'error',
27 'warn',
28 'info',
29 'debug',
30];
31var instanceNumber = 0;
32
33function bindLogger(logLevel, kind) {
34 var logger = glogg('gulp-server-livereload-' + kind + '-' + instanceNumber);
35
36 logLevel = levels.indexOf(logLevel) + 1;
37
38 if (!logLevel) {
39 throw 'Logging level "' + logLevel + '" does not exist!';
40 }
41
42 levels
43 .filter(function (item, i) {
44 return i < logLevel;
45 })
46 .forEach(function (level) {
47 logger.on(level, function () {
48 gutil.log.apply(gutil.log, arguments);
49 });
50 });
51
52 return logger;
53}
54
55module.exports = function(options) {
56 var defaults = {
57 /**
58 *
59 * BASIC DEFAULTS
60 *
61 **/
62 host: 'localhost',
63 port: 8000,
64 defaultFile: 'index.html',
65 fallback: null,
66 fallbackLogic: function(req, res, fallbackFile) {
67 res.setHeader('Content-Type', 'text/html; charset=UTF-8');
68 fs.createReadStream(fallbackFile).pipe(res);
69 },
70 https: false,
71 open: false,
72 log: 'info',
73 clientLog: 'debug',
74
75 /**
76 *
77 * MIDDLEWARE DEFAULTS
78 *
79 * NOTE:
80 * All middleware should defaults should have the 'enable'
81 * property if you want to support shorthand syntax like:
82 *
83 * webserver({
84 * livereload: true
85 * });
86 *
87 */
88
89 // Middleware: Livereload
90 livereload: {
91 enable: false,
92 markupHost: null,
93 port: 35729,
94 filter: function(filename, cb) {
95 cb( !(/node_modules/.test(filename)) );
96 },
97 clientConsole: false,
98 },
99
100 // Middleware: Directory listing
101 // For possible options, see:
102 // https://github.com/expressjs/serve-index
103 directoryListing: {
104 enable: false,
105 path: './',
106 options: undefined
107 },
108
109 // Middleware: Proxy
110 // For possible options, see:
111 // https://github.com/andrewrk/connect-proxy
112 proxies: []
113
114 };
115
116 // Deep extend user provided options over the all of the defaults
117 // Allow shorthand syntax, using the enable property as a flag
118 var config = enableMiddlewareShorthand(defaults, options, ['directoryListing', 'livereload']);
119
120 var logger = bindLogger(config.log, 'server');
121 var clientLogger = bindLogger(config.clientLog, 'client');
122
123 instanceNumber += 1;
124
125 var httpsOptions = {
126 key: fs.readFileSync(config.https.key || __dirname + '/../ssl/dev-key.pem'),
127 cert: fs.readFileSync(config.https.cert || __dirname + '/../ssl/dev-cert.pem')
128 };
129
130 var openInBrowser = function () {
131 if (config.open === false) return;
132 open('http' + (config.https ? 's' : '') + '://' + config.host + ':' + config.port);
133 openInBrowser = undefined;
134 };
135
136 // connect app
137 var app = connect();
138
139 // Disable browser cache(fix #15)
140 app.use(function (req, res, next) {
141 res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
142 res.setHeader("Pragma", "no-cache");
143 res.setHeader("Expires", 0);
144
145 next();
146 });
147
148 // Proxy requests
149 for (var i = 0, len = config.proxies.length; i < len; i++) {
150 var proxyoptions = url.parse(config.proxies[i].target);
151 if (config.proxies[i].hasOwnProperty('options')) {
152 extend(proxyoptions, config.proxies[i].options);
153 }
154
155 proxyoptions.route = config.proxies[i].source;
156 app.use(proxy(proxyoptions));
157
158 logger.debug(config.proxies[i].source + ' is proxied.');
159 }
160 // directory listing
161 if (config.directoryListing.enable) {
162 app.use(serveIndex(path.resolve(config.directoryListing.path), config.directoryListing.options));
163 }
164
165 // socket.io
166 if (config.livereload.enable) {
167 var snippetParams = [];
168
169 if (config.livereload.clientConsole) {
170 snippetParams.push("extra=capture-console");
171 }
172
173 // If it wasn't provided, use the server host:
174 var markupHost = !!_.get(config.livereload.markupHost, 'length')
175 ? "'" + config.livereload.markupHost + "'"
176 : null;
177
178 var snippet =
179 "<script type=\"text/javascript\">"
180 + "var _lrscript = document.createElement('script');"
181 + "_lrscript.type = 'text/javascript';"
182 + "_lrscript.defer = _lrscript.async = true;"
183 + "_lrscript.src = '//' + ((" + markupHost + "||location.host).split(':')[0]) + ':"+config.livereload.port+"/livereload.js?"+snippetParams.join('&')+"';"
184 + "document.body.appendChild(_lrscript);"
185 + "</script>";
186
187 var prepend = function(w, s) {
188 return s + w;
189 };
190
191 var append = function(w, s) {
192 return w + s;
193 }
194
195 app.use(inject({
196 snippet: snippet,
197 rules: [{
198 match: /<\/body>/,
199 fn: prepend
200 }, {
201 match: /<\/html>/,
202 fn: prepend
203 }, {
204 match: /<\!DOCTYPE.+>/,
205 fn: append
206 }]
207 }));
208
209 var io = config.livereload.io = socket();
210 io.serveClient(true);
211 io.path("");
212 io.on('connection', function(socket){
213 logger.info('Livereload client connected');
214
215 socket.on('console', function(params){
216 var method = params.method,
217 data = params.data,
218 methodLabel = gutil.colors.green(method.toUpperCase()),
219 translatedMethod = 'info';
220
221 switch (method) {
222 case 'error':
223 methodLabel = gutil.colors.red('ERROR');
224 translatedMethod = 'error';
225 break;
226 case 'warn':
227 methodLabel = gutil.colors.yellow('WARN');
228 translatedMethod = 'warn';
229 break;
230 case 'info':
231 methodLabel = gutil.colors.cyan('INFO');
232 translatedMethod = 'info';
233 break;
234 case 'debug':
235 case 'trace':
236 methodLabel = gutil.colors.blue('DEBUG');
237 translatedMethod = 'debug';
238 break;
239 }
240 var args = ['[Client:' + methodLabel + ']'];
241
242 for (var i in data) {
243 args.push(data[i]);
244 }
245
246 clientLogger[translatedMethod].apply(clientLogger, args);
247 });
248 });
249
250 var ioApp = connect();
251
252 ioApp.use(serveStatic(BROWSER_SCIPTS_DIR, { index: false }));
253
254 var ioServerBase = config.https
255 ? https.createServer(httpsOptions, ioApp)
256 : http.createServer(ioApp);
257
258 var ioServer = config.livereload.ioServer =
259 ioServerBase.listen(config.livereload.port, config.host);
260
261 io.attach(ioServer, {
262 path: '/socket.io'
263 });
264
265 logger.debug('Livereload started at', gutil.colors.gray('http' + (config.https ? 's' : '') + '://' + config.host + ':' + config.livereload.port));
266 }
267
268 // http server
269 var webserver = null;
270 if (config.https) {
271 webserver = https.createServer(httpsOptions, app);
272 }
273 else {
274 webserver = http.createServer(app);
275 }
276
277 var files = [];
278
279 // Create server
280 var stream = through.obj(function(file, enc, callback) {
281 if ('debug' === config.log) {
282 app.use(function(req, res, next) {
283 logger.debug(req.method + ' ' + req.url);
284
285 next();
286 });
287 }
288
289
290 app.use(serveStatic(file.path, {
291 index: (config.directoryListing.enable ? false : config.defaultFile)
292 }));
293
294 if (config.livereload.enable) {
295 watch(file.path, function(filename) {
296 config.livereload.filter(filename, function(shouldReload) {
297 if (shouldReload) {
298 logger.debug('Livereload: file changed: ' + filename);
299
300 config.livereload.io.sockets.emit('reload');
301 // Treat changes to sourcemaps as changes to the original files.
302 filename = filename.replace(/\.map$/, '');
303
304 config.livereload.io.sockets.emit('file_changed', {
305 path: filename,
306 name: path.basename(filename),
307 ext: path.extname(filename),
308 });
309 }
310 });
311 });
312 }
313
314 this.push(file);
315
316 callback();
317 })
318 .on('data', function(f) {
319 files.push(f);
320
321 // start the web server
322 webserver.listen(config.port, config.host, openInBrowser);
323
324 logger.info('Webserver started at', gutil.colors.cyan('http' + (config.https ? 's' : '') + '://' + config.host + ':' + config.port));
325 })
326 .on('end', function(){
327 if (config.fallback) {
328 files.forEach(function(file){
329 var fallbackFile = file.path + '/' + config.fallback;
330 if (fs.existsSync(fallbackFile)) {
331 app.use(function(req, res) {
332 return config.fallbackLogic(req, res, fallbackFile);
333 });
334 }
335 });
336 }
337 });
338
339
340 // once stream killed
341 stream.on('kill', function() {
342 webserver.close();
343
344 if (config.livereload.enable) {
345 config.livereload.ioServer.close();
346 }
347 });
348
349 return stream;
350};