1 | "use strict";
|
2 |
|
3 | var _ = 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 |
|
23 | var BROWSER_SCIPTS_DIR = path.join(__dirname, 'browser-scripts');
|
24 |
|
25 | var levels = [
|
26 | 'error',
|
27 | 'warn',
|
28 | 'info',
|
29 | 'debug',
|
30 | ];
|
31 | var instanceNumber = 0;
|
32 |
|
33 | function 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 |
|
55 | module.exports = function(options) {
|
56 | var defaults = {
|
57 | |
58 |
|
59 |
|
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 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
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 |
|
101 |
|
102 |
|
103 | directoryListing: {
|
104 | enable: false,
|
105 | path: './',
|
106 | options: undefined
|
107 | },
|
108 |
|
109 |
|
110 |
|
111 |
|
112 | proxies: []
|
113 |
|
114 | };
|
115 |
|
116 |
|
117 |
|
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 |
|
137 | var app = connect();
|
138 |
|
139 |
|
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 |
|
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 |
|
161 | if (config.directoryListing.enable) {
|
162 | app.use(serveIndex(path.resolve(config.directoryListing.path), config.directoryListing.options));
|
163 | }
|
164 |
|
165 |
|
166 | if (config.livereload.enable) {
|
167 | var snippetParams = [];
|
168 |
|
169 | if (config.livereload.clientConsole) {
|
170 | snippetParams.push("extra=capture-console");
|
171 | }
|
172 |
|
173 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 | };
|