├── .gitignore ├── .travis.yml ├── README.md ├── example ├── gulpfile.js ├── server.js └── static │ ├── index.html │ └── style.css ├── index.js ├── package.json ├── scripts └── static.js └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | .idea 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gulp-live-server 2 | === 3 | 4 | [![status][1]][2] [![downloads][3]][4] [![tag][5]][6] [![license][7]][8] 5 | 6 | [1]: http://img.shields.io/travis/gimm/gulp-live-server/master.svg?style=flat-square 7 | [2]: https://travis-ci.org/gimm/gulp-live-server 8 | 9 | [3]: http://img.shields.io/npm/dm/gulp-live-server.svg?style=flat-square 10 | [4]: https://www.npmjs.com/package/gulp-live-server 11 | 12 | [5]: https://img.shields.io/github/tag/gimm/gulp-live-server.svg?style=flat-square 13 | [6]: https://github.com/gimm/gulp-live-server/releases 14 | 15 | [7]: http://img.shields.io/badge/license-WTFPL-blue.svg?style=flat-square 16 | [8]: http://www.wtfpl.net 17 | 18 | A handy, light-weight server you're going to love. 19 | 20 | - [Install](#install) 21 | - [Usage](#usage) 22 | - [API](#api) 23 | - [static](#staticfolder-port) 24 | - [new](#newscript) 25 | - [gls](#glsargs-options-livereload) 26 | - [start](#start) 27 | - [stop](#stop) 28 | - [notify](#notifyevent) 29 | - [livereload.js](#livereloadjs) 30 | - [Debug](#debug) 31 | 32 | Install 33 | --- 34 | [![NPM](https://nodei.co/npm/gulp-live-server.png?compact=true)](https://nodei.co/npm/gulp-live-server/) 35 | 36 | Usage 37 | --- 38 | - Serve a static folder(`gls.script`<'scripts/static.js'> is used as server script) 39 | 40 | ```js 41 | var gulp = require('gulp'); 42 | var gls = require('gulp-live-server'); 43 | gulp.task('serve', function() { 44 | //1. serve with default settings 45 | var server = gls.static(); //equals to gls.static('public', 3000); 46 | server.start(); 47 | 48 | //2. serve at custom port 49 | var server = gls.static('dist', 8888); 50 | server.start(); 51 | 52 | //3. serve multi folders 53 | var server = gls.static(['dist', '.tmp']); 54 | server.start(); 55 | 56 | //use gulp.watch to trigger server actions(notify, start or stop) 57 | gulp.watch(['static/**/*.css', 'static/**/*.html'], function (file) { 58 | server.notify.apply(server, [file]); 59 | }); 60 | }); 61 | ``` 62 | - Serve with your own script file 63 | 64 | ```js 65 | gulp.task('serve', function() { 66 | //1. run your script as a server 67 | var server = gls.new('myapp.js'); 68 | server.start(); 69 | 70 | //2. run script with cwd args, e.g. the harmony flag 71 | var server = gls.new(['--harmony', 'myapp.js']); 72 | //this will achieve `node --harmony myapp.js` 73 | //you can access cwd args in `myapp.js` via `process.argv` 74 | server.start(); 75 | 76 | //use gulp.watch to trigger server actions(notify, start or stop) 77 | gulp.watch(['static/**/*.css', 'static/**/*.html'], function (file) { 78 | server.notify.apply(server, [file]); 79 | }); 80 | gulp.watch('myapp.js', server.start.bind(server)); //restart my server 81 | 82 | // Note: try wrapping in a function if getting an error like `TypeError: Bad argument at TypeError (native) at ChildProcess.spawn` 83 | gulp.watch('myapp.js', function() { 84 | server.start.bind(server)() 85 | }); 86 | }); 87 | ``` 88 | 89 | - Customized serving with gls 90 | 91 | ```js 92 | gulp.task('serve', function() { 93 | //1. gls is the base for `static` and `new` 94 | var server = gls([gls.script, 'static', 8000]); 95 | //equals gls.new([gls.script, 'static', 8000]); 96 | //equals gls.static('static', 8000); 97 | server.start(); 98 | 99 | //2. set running options for the server, e.g. NODE_ENV 100 | var server = gls('myapp.js', {env: {NODE_ENV: 'development'}}); 101 | server.start(); 102 | 103 | //3. customize livereload server, e.g. port number 104 | var server = gls('myapp.js', undefined, 12345); 105 | var promise = server.start(); 106 | //optionally handle the server process exiting 107 | promise.then(function(result) { 108 | //log, exit, re-start, etc... 109 | }); 110 | 111 | //4. start with coffee-script executable e.g. installed with npm 112 | var server = gls('myapp.coffee'); 113 | server.start('node_modules/coffee-script/bin/coffee'); 114 | 115 | //use gulp.watch to trigger server actions(notify, start or stop) 116 | gulp.watch(['static/**/*.css', 'static/**/*.html'], function (file) { 117 | server.notify.apply(server, [file]); 118 | }); 119 | gulp.watch('myapp.js', server.start.bind(server)); //restart my server 120 | 121 | // Note: try wrapping in a function if getting an error like `TypeError: Bad argument at TypeError (native) at ChildProcess.spawn` 122 | gulp.watch('myapp.js', function() { 123 | server.start.bind(server)() 124 | }); 125 | }); 126 | ``` 127 | 128 | API 129 | --- 130 | ### static([folder][, port]) 131 | - `folder` - `String|Array` The folder(s) to serve. 132 | Use array of strings if there're multi folders to serve. 133 | If omitted, defaults to `public/`. 134 | - `port` - `Number` The port to listen on. Defaults to `3000`. 135 | - return [gls](#glsargs-options-livereload). 136 | 137 | Config new server using the [default server script](https://github.com/gimm/gulp-live-server/blob/master/scripts/static.js), to serve the given `folder` on the specified `port`. 138 | 139 | ### new(script) 140 | - `script` - `String` The script file to run. 141 | - return [gls](#glsargs-options-livereload). 142 | 143 | Config new server using the given `script`. 144 | 145 | ### gls(args[, options][, livereload]) 146 | - `args` - `String|Array` The 2nd param for [ChildProcess.spawn](http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options). 147 | - `options` - `Object` The 3rd param for [ChildProcess.spawn](http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options), 148 | will be mixin into the default value: 149 | 150 | ```js 151 | options = { 152 | cwd: undefined 153 | } 154 | options.env = process.env; 155 | options.env.NODE_ENV = 'development'; 156 | ``` 157 | - `livereload` - `Boolean|Number|Object` The option for tiny-lr server. The default value is `35729`. 158 | - `false` - will disable tiny-lr livereload server. 159 | - `number` - treated as port number of livereload server. 160 | - `object` - used to create tiny-lr server new tinylr.Server(livereload); 161 | 162 | **`gls` here is a reference of `var gls = require('gulp-live-server')`**. It aims to assemble configuration for the server child process as well as the tiny-lr server. 163 | **`static` and `new` are just shortcuts for this.** 164 | Usually, `static` and `new` will serve you well, but you can get more customized server with `gls`. 165 | 166 | ### start([execPath]) 167 | - `execPath` - `String` The executable that is used to start the server. If none is given the current node executable is used. 168 | - return [promise](https://github.com/kriskowal/q/wiki/API-Reference) from [Q](https://www.npmjs.com/package/q), resolved with the server process exits. 169 | 170 | Spawn a new child process based on the configuration. 171 | - use [`ChildProcess.spawn`](http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options) to start a node process; 172 | - use [`tiny-lr`](https://github.com/mklabs/tiny-lr) provide livereload ability; 173 | 174 | ### stop() 175 | - return [promise](https://github.com/kriskowal/q/wiki/API-Reference) from [Q](https://www.npmjs.com/package/q) 176 | 177 | Stop the server. 178 | 179 | ### notify([event]) 180 | - `event` - `Event` Event object passed along with [gulp.watch](https://github.com/gulpjs/gulp/blob/master/docs/API.md#cbevent). 181 | Optional when used with `pipe`. 182 | 183 | Tell livereload.js to reload the changed resource(s) 184 | 185 | livereload.js 186 | --- 187 | gulp-live-server comes with [tiny-lr](https://github.com/mklabs/tiny-lr/) built in, which works as a livereload server. `livereload.js` is **served** by `tiny-lr`, but in order to get it loaded with your page, you have 3 options( to **inject** `` into your page): 188 | - [LiveReload](https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei?hl=en) for Chrome; 189 | - Use [connect-livereload](https://github.com/intesso/connect-livereload) middleware; 190 | - Add [livereload.js](https://github.com/livereload/livereload-js) in your page manually; 191 | 192 | Usually, if `http://localhost:35729/livereload.js` is accessible, then your livereload server is ok, if you don't have the script tag for livereload.js in you page, you've problem with either your chrome plugin or the connect-livereload middle-ware as mentioned above. 193 | 194 | DEBUG 195 | --- 196 | If you want more output, set the `DEBUG` environment variables to `*` or `gulp-live-server`. 197 | -------------------------------------------------------------------------------- /example/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var gls = require('../index.js'); 3 | 4 | gulp.task('static', function() { 5 | var server = gls.static('static', 8000); 6 | server.start(); 7 | gulp.watch(['static/**/*.css', 'static/**/*.html'], function(file) { 8 | server.notify.apply(server, [file]); 9 | }); 10 | }); 11 | 12 | gulp.task('custom', function() { 13 | var server = gls('server.js'); 14 | server.start().then(function(result) { 15 | console.log('Server exited with result:', result); 16 | process.exit(result.code); 17 | }); 18 | gulp.watch(['static/**/*.css', 'static/**/*.html'], function(file) { 19 | server.notify.apply(server, [file]); 20 | }); 21 | gulp.watch('server.js', server.start); 22 | }); 23 | -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var app = require('connect')(); 3 | 4 | app.use(require('connect-livereload')()); 5 | app.use(require('serve-static')(path.join(__dirname, '/static'))); 6 | 7 | var server = app.listen(3000, function () { 8 | 9 | var host = server.address().address; 10 | var port = server.address().port; 11 | 12 | console.log('custom server listening at http://%s:%s', host, port); 13 | }); -------------------------------------------------------------------------------- /example/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | static content 7 | 8 | 9 |

this is serving from the static

10 |

hello

11 | 12 | 13 | -------------------------------------------------------------------------------- /example/static/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-color: tomato; 3 | color: whitesmoke; 4 | font-family: source_sans, "Lucida Grande", "Lucida Sans", sans-serif; 5 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Created by gimm on 3/13/2015. 4 | */ 5 | 6 | var util = require('util'), 7 | path = require('path'), 8 | assert = require('assert'), 9 | spawn = require('child_process').spawn, 10 | merge = require('deepmerge'), 11 | tinylr = require('tiny-lr'), 12 | es = require('event-stream'), 13 | Q = require('q'), 14 | chalk = require('chalk'), 15 | debug = require('debug')('gulp-live-server'); 16 | 17 | var info = chalk.gray, 18 | error = chalk.bold.red; 19 | 20 | var glsInstanceCounter = 0; 21 | 22 | var callback = { 23 | processExit: function (code, sig, server) { 24 | glsInstanceCounter--; 25 | debug(info('Main process exited with [code => %s | sig => %s]'), code, sig); 26 | server && server.kill(); 27 | }, 28 | 29 | serverExit: function (code, sig) { 30 | debug(info('server process exited with [code => %s | sig => %s]'), code, sig); 31 | if(sig !== 'SIGKILL'){ 32 | //server stopped unexpectedly 33 | process.exit(0); 34 | } 35 | }, 36 | 37 | lrServerReady: function () { 38 | console.log(info('livereload[tiny-lr] listening on %s ...'), this.config.livereload.port); 39 | }, 40 | 41 | serverLog: function (data) { 42 | console.log(data.replace(/\s+$/, '')); 43 | }, 44 | 45 | serverError: function (data) { 46 | console.log(error(data.replace(/\s+$/, ''))); 47 | } 48 | }; 49 | 50 | /** 51 | * set config data for the new server child process 52 | * @type {Function} 53 | */ 54 | module.exports = exports = (function() { 55 | var defaults = { 56 | options: { 57 | cwd: undefined 58 | }, 59 | livereload: { 60 | port: 35729 61 | } 62 | }; 63 | defaults.options.env = JSON.parse(JSON.stringify(process.env)); 64 | defaults.options.env.NODE_ENV = 'development'; 65 | 66 | return function(args, options, livereload){ 67 | var config = {} 68 | config.args = util.isArray(args) ? args : [args]; 69 | //deal with options 70 | config.options = merge(defaults.options, options || {}); 71 | //deal with livereload 72 | if (livereload) { 73 | config.livereload = (typeof livereload === 'object' ? livereload : {port: livereload}); 74 | }else{ 75 | config.livereload = (livereload === false ? false : defaults.livereload); 76 | } 77 | // return exports with its state, the server and livereload instance 78 | // this allows multiple servers at once 79 | return merge({ 80 | config: config, 81 | server: undefined, // the server child process 82 | lr: undefined // tiny-lr serverexports; 83 | }, exports); 84 | }; 85 | })(); 86 | 87 | /** 88 | * default server script, the static server 89 | */ 90 | exports.script = path.join(__dirname, 'scripts/static.js'); 91 | 92 | /** 93 | * create a server child process with the script file 94 | */ 95 | exports.new = function (script) { 96 | if(!script){ 97 | return console.log(error('script file not specified.')); 98 | } 99 | var args = util.isArray(script) ? script : [script]; 100 | return this(args); 101 | }; 102 | 103 | /** 104 | * create a server child process with the static server script 105 | */ 106 | exports.static = function (folder, port) { 107 | var script = this.script; 108 | folder = folder || process.cwd(); 109 | util.isArray(folder) && (folder = folder.join(',')); 110 | port = port || 3000; 111 | return this([script, folder, port]); 112 | }; 113 | 114 | /** 115 | * start/restart the server 116 | */ 117 | exports.start = function (execPath) { 118 | if (this.server) { // server already running 119 | debug(info('kill server')); 120 | this.server.kill('SIGKILL'); 121 | //server.removeListener('exit', callback.serverExit); 122 | this.server = undefined; 123 | } else { 124 | if(this.config.livereload){ 125 | this.lr = tinylr(this.config.livereload); 126 | this.lr.listen(this.config.livereload.port, callback.lrServerReady.bind(this)); 127 | } 128 | } 129 | 130 | // if a executable is specified use that to start the server (e.g. coffeescript) 131 | // otherwise use the currents process executable 132 | this.config.execPath = execPath || this.config.execPath || process.execPath; 133 | var deferred = Q.defer(); 134 | this.server = spawn(this.config.execPath, this.config.args, this.config.options); 135 | 136 | //stdout and stderr will not be set if using the { stdio: 'inherit' } options for spawn 137 | if (this.server.stdout) { 138 | this.server.stdout.setEncoding('utf8'); 139 | this.server.stdout.on('data', function(data) { 140 | deferred.notify(data); 141 | callback.serverLog(data); 142 | }); 143 | } 144 | if (this.server.stderr) { 145 | this.server.stderr.setEncoding('utf8'); 146 | this.server.stderr.on('data', function (data) { 147 | deferred.notify(data); 148 | callback.serverError(data); 149 | }); 150 | } 151 | 152 | this.server.once('exit', function (code, sig) { 153 | setTimeout(function() { // yield event loop for stdout/stderr 154 | deferred.resolve({ 155 | code: code, 156 | signal: sig 157 | }); 158 | if (glsInstanceCounter == 0) 159 | callback.serverExit(code, sig); 160 | }, 0) 161 | }); 162 | 163 | var exit = function(code, sig) { 164 | callback.processExit(code,sig,server); 165 | } 166 | process.listeners('exit') || process.once('exit', exit); 167 | 168 | glsInstanceCounter++; 169 | return deferred.promise; 170 | }; 171 | 172 | /** 173 | * stop the server 174 | */ 175 | exports.stop = function () { 176 | var deferred = Q.defer(); 177 | if (this.server) { 178 | this.server.once('exit', function (code) { 179 | deferred.resolve(code); 180 | }); 181 | 182 | debug(info('kill server')); 183 | //use SIGHUP instead of SIGKILL, see issue #34 184 | this.server.kill('SIGKILL'); 185 | //server.removeListener('exit', callback.serverExit); 186 | this.server = undefined; 187 | }else{ 188 | deferred.resolve(0); 189 | } 190 | if(this.lr){ 191 | debug(info('close livereload server')); 192 | this.lr.close(); 193 | //TODO how to stop tiny-lr from hanging the terminal 194 | this.lr = undefined; 195 | } 196 | 197 | return deferred.promise; 198 | }; 199 | 200 | /** 201 | * tell livereload.js to reload the changed resource(s) 202 | */ 203 | exports.notify = function (event) { 204 | var lr = this.lr; 205 | if(event && event.path){ 206 | var filepath = path.relative(__dirname, event.path); 207 | debug(info('file(s) changed: %s'), event.path); 208 | lr.changed({body: {files: [filepath]}}); 209 | } 210 | 211 | return es.map(function(file, done) { 212 | var filepath = path.relative(__dirname, file.path); 213 | debug(info('file(s) changed: %s'), filepath); 214 | lr.changed({body: {files: [filepath]}}); 215 | done(null, file); 216 | }.bind(this)); 217 | }; 218 | 219 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-live-server", 3 | "version": "0.0.31", 4 | "description": "easy light weight server with livereload", 5 | "homepage": "https://github.com/gimm/gulp-live-server", 6 | "main": "./index.js", 7 | "scripts": { 8 | "test": "mocha" 9 | }, 10 | "keywords": [ 11 | "gulpplugin", 12 | "server", 13 | "static", 14 | "live", 15 | "livereload", 16 | "connect", 17 | "express" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/gimm/gulp-live-server" 22 | }, 23 | "author": { 24 | "name": "yucc2008@gmail.com" 25 | }, 26 | "license": "WTFPL", 27 | "dependencies": { 28 | "chalk": "^1.0.0", 29 | "connect": "^3.3.4", 30 | "connect-livereload": "^0.5.3", 31 | "debug": "^2.1.1", 32 | "deepmerge": "~0.2.7", 33 | "event-stream": "~3.2.1", 34 | "q": "^1.2.0", 35 | "serve-static": "^1.9.1", 36 | "tiny-lr": "^1.0.3" 37 | }, 38 | "readmeFilename": "README.md", 39 | "bugs": { 40 | "url": "https://github.com/gimm/gulp-live-server/issues" 41 | }, 42 | "devDependencies": { 43 | "gulp": "^3.8.11", 44 | "mocha": "^2.0.1", 45 | "should": "^5.2.0", 46 | "supertest": "^0.15.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /scripts/static.js: -------------------------------------------------------------------------------- 1 | var app = require('connect')(); 2 | var path = require('path'); 3 | var connect_livereload = require('connect-livereload'); 4 | var serve_static = require('serve-static'); 5 | 6 | var args = Array.prototype.slice.call(process.argv, 2, 4); 7 | var root = args[0] || 'public/'; 8 | var port = args[1] || 3000; 9 | 10 | app.use(connect_livereload()); 11 | 12 | root.split(",").forEach(function(r){ 13 | app.use(serve_static(path.join(process.cwd(), r))); 14 | }); 15 | 16 | app.listen(port, function () { 17 | 18 | var host = 'localhost'; 19 | 20 | console.log('folder "%s" serving at http://%s:%s', root, host, port); 21 | }); -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var request = require('supertest'); 4 | var should = require('should'); 5 | var gls = require('../index.js'); 6 | 7 | describe('gulp-live-server', function () { 8 | describe('default static server', function () { 9 | var server = undefined; 10 | var req = request('http://localhost:3000'); 11 | before('start server', function (done) { 12 | server = gls.static(); 13 | server.start().then(null, null, function(code){ 14 | done(); 15 | }).done(); 16 | }); 17 | after('stop server', function (done) { 18 | server.stop().then(function () { 19 | done(); 20 | }).done(); 21 | }); 22 | 23 | it('should server listening', function (done) { 24 | req.get('/').expect(404, done); 25 | }); 26 | 27 | it('should livereload listening', function (done) { 28 | request('http://localhost:35729').get('/').expect(200, done); 29 | }); 30 | }); 31 | 32 | describe('customized static server', function () { 33 | var server = undefined; 34 | var req = request('http://localhost:8000'); 35 | before('start server', function (done) { 36 | server = gls.static('example', 8000); 37 | server.start().then(null, null, function(code){ 38 | done(); 39 | }).done(); 40 | }); 41 | 42 | after('stop server', function (done) { 43 | server.stop().then(function () { 44 | done(); 45 | }).done(); 46 | }); 47 | 48 | it('should server listening', function (done) { 49 | req.get('/') 50 | .expect(404, done); 51 | }); 52 | 53 | it('should livereload listening', function (done) { 54 | request('http://localhost:35729') 55 | .get('/') 56 | .end(function (err, res) { 57 | should.equal(null); 58 | should.exist(res); 59 | done(); 60 | }); 61 | }); 62 | 63 | it('should stop the server', function (done) { 64 | server.stop().then(function () { 65 | req 66 | .get('/') 67 | .end(function (err, res) { 68 | err.should.have.property('code', 'ECONNREFUSED'); 69 | done(); 70 | }); 71 | }).done(); 72 | }); 73 | }); 74 | 75 | describe('simple new server', function(){ 76 | var server; 77 | var req = request('http://localhost:3000'); 78 | before('start server', function(done){ 79 | server = gls.new(gls.script); 80 | server.start().then(null, null, function(){ 81 | done(); 82 | }).done(); 83 | }); 84 | after('stop server', function (done) { 85 | server.stop().then(function () { 86 | done(); 87 | }).done(); 88 | }); 89 | 90 | it('should listening', function(done){ 91 | req.get('/') 92 | .expect(404, done); 93 | }); 94 | }); 95 | 96 | describe('spawn options', function() { 97 | var server; 98 | afterEach(function(done) { 99 | if (!server) { 100 | done(); 101 | return; 102 | } 103 | 104 | server.stop().then(function() { 105 | done(); 106 | }).done(); 107 | }); 108 | 109 | it('should not explode if stdio is set to "inherit"', function(done) { 110 | var options = { 111 | stdio: 'inherit' 112 | }; 113 | server = gls(gls.script, options, false); 114 | server.start(); 115 | done(); 116 | }); 117 | }); 118 | 119 | }); 120 | --------------------------------------------------------------------------------