├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── lib └── util.js ├── package.json ├── tasks └── express.js └── test ├── express_test.js ├── fixtures ├── defaults │ └── Gruntfile.js ├── express │ ├── Gruntfile.js │ └── lib │ │ └── server.js ├── serverrl │ ├── Gruntfile.js │ └── lib │ │ ├── server.js │ │ └── test.js └── statics │ ├── Gruntfile.js │ ├── public │ └── test.html │ └── public2 │ └── test2.html ├── helper.js └── tasks ├── delay.js ├── kill.js ├── request.js └── write.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tmp 4 | .idea 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "node": true 13 | } 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | .editorconfig 3 | .jshintrc 4 | .jshintignore 5 | .gitattributes 6 | test 7 | docs -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | before_script: 5 | - npm install -g grunt-cli 6 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | 4 | module.exports = function(grunt) { 5 | 6 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 7 | 8 | grunt.initConfig({ 9 | jshint: { 10 | all: [ 11 | 'Gruntfile.js', 12 | 'tasks/*.js', 13 | '<%= nodeunit.tests %>' 14 | ], 15 | options: { 16 | jshintrc: '.jshintrc' 17 | } 18 | }, 19 | 20 | nodeunit: { 21 | tests: ['test/*_test.js'] 22 | }, 23 | 24 | watch: { 25 | options: {}, 26 | express: {} 27 | }, 28 | 29 | express: { 30 | defaults: { 31 | options: { 32 | server: path.resolve('./test/fixtures/express/lib/server'), 33 | serverreload: true 34 | } 35 | } 36 | } 37 | }); 38 | 39 | grunt.loadTasks('test/tasks'); 40 | grunt.loadTasks('tasks'); 41 | 42 | grunt.registerTask('test', ['nodeunit']); 43 | grunt.registerTask('default', ['jshint', 'test']); 44 | }; -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 "blai" Brian Lai, contributors 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grunt-express [](http://travis-ci.org/blai/grunt-express) [](https://gemnasium.com/blai/grunt-express) 2 | 3 | ## grunt-express v1.0 4 | v1.0 is nearly a complete re-done, it acts as a higher-level grunt task that depends on (and consumes) `grunt-contrib-watch`. It will dynamically configure `watch` tasks based on your `express` task setup at runtime, and it will run `watch` if necessary. Here's the list of high level changes 5 | 6 | 1. use `grunt-contrib-watch` to manage reloading express server, instead of `forever-monitor` 7 | 2. support both `livereload` and `serverreload` (pre-v1.0 users: `grunt-express` will no longer manage to restart your server by default, you would have to set `serverreload` to `true` to regain the old behavior) 8 | 3. if `serverreload` is set to `false` in `options`, then the following are true: 9 | * server will be started in the same process as your `grunt` (so developers can run debugger using Webstorm or other tools) 10 | * server will be run WITHOUT the `this.async()` call (you can optionally append the task `express-keepalive` to keep the server running), this allows you to run tests using grunt-express 11 | 4. continue to support `socket.io` + `express` use cases 12 | 5. discontinue support of `express-stop` 13 | 14 | > I am in process to add more test cases to cover all use cases 15 | 16 | 17 | 18 | ## Sample app 19 | [grunt-express-angular-example](https://github.com/blai/grunt-express-angular-example) is a minimal example that shows how you can use `grunt-express` to run a basic `express` server that hosts an Angular app, it is based on @bford's Yeoman generator `generator-angular`. 20 | 21 | 22 | 23 | ## Getting Started 24 | This plugin requires Grunt `~0.4.0` 25 | 26 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: 27 | 28 | ```shell 29 | npm install grunt-express --save-dev 30 | ``` 31 | 32 | Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: 33 | 34 | ```js 35 | grunt.loadNpmTasks('grunt-express'); 36 | ``` 37 | 38 | 39 | ## Express task 40 | 41 | ### express (main task, Multi Tasks) 42 | _Run this task with the `grunt express` command._ 43 | 44 | Configure one or more servers for grunt to start, the minimal config would be: 45 | 46 | ```javascript 47 | grunt.initConfig({ 48 | express: { 49 | default_option: {} 50 | } 51 | }); 52 | 53 | grunt.loadNpmTasks('grunt-express'); 54 | 55 | grunt.registerTask('default', ['express']); 56 | ``` 57 | 58 | ### express-start 59 | ### express-restart 60 | 61 | Start your express server (or restart a server if it is already started). 62 | 63 | ### express-keepalive 64 | 65 | Note that when `serverreload` is false, this server only runs as long as grunt is running. Once grunt's tasks have completed, the web server stops. This behavior can be changed by appending a `express-keepalive` task at the end of your task list like so 66 | 67 | ```javascript 68 | grunt.registerTask('myServer', ['express', 'express-keepalive']); 69 | ``` 70 | Now when you run `grunt myServer`, your express server will be kept alive until you manually terminate it. 71 | 72 | Such feature can also be enabled ad-hoc by running the command like `grunt express express-keepalive`. 73 | 74 | This design gives you the flexibility to use `grunt-express` in conjunction with another task that is run immediately afterwards, like the [grunt-contrib-qunit plugin](https://github.com/gruntjs/grunt-contrib-qunit) `qunit` task. If we force `express` task to be always async, such use case can no longer happen. 75 | 76 | 77 | 78 | ## Options 79 | 80 | All options of `grunt-express` are optional, if you specify nothing, it will start a `connect` server using port 3000 (which serves nothing). 81 | 82 | #### port 83 | Type: `Integer` 84 | Default: `3000` 85 | 86 | The port on which the webserver will respond. The task will fail if the specified port is already in use. 87 | 88 | #### hostname 89 | Type: `String` 90 | Default: `'localhost'` 91 | 92 | The hostname the webserver will use. If set to `'*'`, server could be accessed from ip (e.g. 127.0.0.1) as well as `localhost` 93 | 94 | #### bases 95 | Type: `String|Array` 96 | Default: `null` 97 | 98 | The bases (or root) directories from which static files will be served. A `connect.static()` will be generated for each entry of `bases`. When `livereload` is set to `true` (or set to a specific port number), a `watch` task will be created for you (at runtime) to watch your `basePath/**/*.*`. 99 | 100 | You may optionally define a placeholder middleware named `staticsPlaceholder` in your server's list of middlewares, and when one is defined, every `connect.static()` middleware generated from your `bases` will be inserted before your `staticsPlaceholder` middleware. If you do not define a `staticsPlaceholder`, your `connect.static()` will be appended at the end of the middleware stack. 101 | 102 | ##### `staticsPlaceholder` example 103 | ```js 104 | app.use(function staticsPlaceholder(req, res, next) { 105 | return next(); 106 | }); 107 | ``` 108 | 109 | #### server 110 | Type: `String` 111 | Default: null 112 | 113 | This option allows you to specify a path to a Node.js module that exports a "connect-like" object. Such object should have the following two functions: 114 | 115 | 1. `use(route, fn)` (https://github.com/senchalabs/connect/blob/master/lib/proto.js#L62) 116 | 2. `listen()` (https://github.com/senchalabs/connect/blob/master/lib/proto.js#L227) 117 | _note: you DO NOT want to call the listen() from within your server module, `express` task will take care of that for you_ 118 | 119 | The simplest example would be: 120 | ```js 121 | var connect = require('connect'); 122 | module.exports = connect(); 123 | ``` 124 | 125 | or if you prefer express.js 126 | ```js 127 | var express = require('express'); 128 | var app = express(); 129 | app.get('/', function(req, res) { 130 | res.send('hello!'); 131 | }); 132 | module.exports = app; 133 | ``` 134 | 135 | or if you want to use both express and socket.io 136 | ```js 137 | var app = require('express')() 138 | , server = require('http').createServer(app) 139 | , io = require('socket.io').listen(server); 140 | 141 | app.get('/', function (req, res) { 142 | res.sendfile(__dirname + '/index.html'); 143 | }); 144 | 145 | io.sockets.on('connection', function (socket) { 146 | socket.emit('news', { hello: 'world' }); 147 | socket.on('my other event', function (data) { 148 | console.log(data); 149 | }); 150 | }); 151 | 152 | exports = module.exports = server; 153 | // delegates use() function 154 | exports.use = function() { 155 | app.use.apply(app, arguments); 156 | }; 157 | ``` 158 | 159 | When `server` option is not set, `express` task will generate a plain `connect` object for you. 160 | 161 | _note: `express` task will generate `static` middleware for each of the `bases` you specified, and load them onto your server (or the generated server) object by calling `use()` function_ 162 | 163 | #### livereload 164 | Type: `Boolean|Number` 165 | Default: `false` 166 | 167 | This options allows you to define the livereload port (or if you set it to `true`, it will use the default livereload port: `35729`), and when you also define `bases` options, then the livereload server will be watching all contents under your `bases` folder, and perform livereload when those contents change. 168 | 169 | When livereload is set, a [connect-livereload](https://github.com/intesso/connect-livereload) middleware will be inserted at the top of your server's middleware stack (so you don't have to do the extra step as intructed by [grunt-contrib-connnect's documentation](https://github.com/gruntjs/grunt-contrib-watch#enabling-live-reload-in-your-html)) 170 | 171 | #### serverreload 172 | Type: `Boolean` 173 | Default: `false` 174 | 175 | Setting this option to `true` will tell `express` task to start a forever running server in a child process, and if any of your server scripts change, the server will be restarted (using a dynamically generated `watch` task) 176 | 177 | When this options is not set (or set to `false`), the server will be running in the same process as grunt, and will only live as long as the grunt process is running. You may optionally use `express-keepalive` task to keep it alive. 178 | 179 | #### showStack 180 | Type: `Boolean` 181 | Default: `false` 182 | 183 | Setting this option to `true` will tell `express` task to show the full error stack, if an error occurs in your `express` server. 184 | 185 | #### watch (experimental) 186 | Type: `Object` 187 | 188 | If you set `serverreload` (to `true`), a `grunt-contrib-watch` task config would be generated for you to manage the express server. In which case, you can optionally define a `watch` option to control the configuration of such `watch` task. There are, however, a few settings (that `grunt-express` is relying on) you may not change, they are as follow: 189 | 190 | ```js 191 | var watcherOptions = { 192 | interrupt: true, 193 | atBegin: true, 194 | event: ['added', 'changed'] 195 | } 196 | ``` 197 | 198 | #### middleware (experimental) 199 | Type: `Array` 200 | Default: `null` 201 | 202 | Try to mimic `grunt-contrib-connect`'s `middleware` options (and should work the same way). Like `bases` options, you can control the insertion point of your `middleware` by adding a `middlewarePlaceholder`, like so: 203 | 204 | ```js 205 | app.use(function middlewarePlaceholder(req, res, next) { 206 | return next(); 207 | }); 208 | ``` 209 | 210 | #### open (mimics [grunt-contrib-connect#open](https://github.com/gruntjs/grunt-contrib-connect#open)) 211 | Type: `Boolean` or `String` 212 | Default: `false` 213 | 214 | Open the served page in your default browser. Specifying `true` opens the default server URL, while specifying a URL opens that URL. 215 | 216 | #### monitor (WARN: no longer availabe in 1.0+) 217 | #### Please use a trailing `serverreload` option instead 218 | Type: `Object` 219 | Default: `null` 220 | 221 | Under the hood `grunt-express` uses [forever-monitor](https://github.com/nodejitsu/forever-monitor) to manage individual servers in separate child processes. This makes restarting the server automatically possible. This property allow you to pass in the `forever-monitor` options. When specified, the object will be used as the options hash when creating the forever monitor to manage the server in child process. 222 | 223 | #### keepalive (WARN: no longer availabe in 0.20+) 224 | #### Please use a trailing `express-keepalive` task instead 225 | Type: `Boolean` 226 | Default: `false` 227 | 228 | Keep the server alive indefinitely. Note that if this option is enabled, any tasks specified after this task will _never run_. By default, once grunt's tasks have completed, the web server stops. This option changes that behavior. 229 | 230 | #### monitor (WARN: no longer availabe in 1.0+) 231 | #### debug (WARN: no longer availabe in 1.0+) 232 | Type: `Boolean` 233 | Default: `false` 234 | 235 | Turning this option on will make the "supervised" express|connect instance output more debugging messages. 236 | 237 | 238 | 239 | ### Usage examples 240 | 241 | #### Basic Use 242 | In this example, `grunt express` (or more verbosely, `grunt express:server`) will start a static web server at `http://localhost:9000/`, with its base path set to the `public` directory relative to the gruntfile, and any tasks run afterwards will be able to access it. 243 | 244 | ```javascript 245 | // Project configuration. 246 | grunt.initConfig({ 247 | express: { 248 | server: { 249 | options: { 250 | port: 9000, 251 | bases: 'public' 252 | } 253 | } 254 | } 255 | }); 256 | ``` 257 | 258 | You may specify more than one `bases` like so. Enhancing the above example, now your server will server static content from both `public` folder and `dist` folder (both are relative path to the `Gruntfile.js`) 259 | 260 | ```javascript 261 | // Project configuration. 262 | grunt.initConfig({ 263 | express: { 264 | server: { 265 | options: { 266 | port: 9001, 267 | bases: ['public', 'dist'] 268 | } 269 | } 270 | } 271 | }); 272 | ``` 273 | 274 | If you want your web server to use the default options, just omit the `options` object. You still need to specify a target (`uses_defaults` in this example), but the target's configuration object can otherwise be empty or nonexistent. In this example, `grunt express` (or more verbosely, `grunt express:uses_defaults`) will start a static web server using the default options. 275 | 276 | ```javascript 277 | // Project configuration. 278 | grunt.initConfig({ 279 | express: { 280 | uses_defaults: {} 281 | } 282 | }); 283 | ``` 284 | 285 | But usually, you want to start an express server using your own express application script, like so: 286 | 287 | ```javascript 288 | // Project configuration. 289 | grunt.initConfig({ 290 | express: { 291 | myServer: { 292 | server: path.resolve(__dirname, 'server.js') 293 | // if you do not define a port it will start your server at port 3000 294 | } 295 | } 296 | }); 297 | ``` 298 | 299 | #### Livereload (both server and browser) 300 | 301 | `grunt-express` leaves the control on your hands to perform livereload for your express server and the browser contents (e.g. html, javascript, css). You can set the `livereload` and `serverreload` respectively like following: 302 | 303 | ```js 304 | grunt.initConfig({ 305 | express: { 306 | livereloadServer: { 307 | server: path.resolve(__dirname, 'server'), 308 | bases: path.resolve(__dirname, 'public'), 309 | livereload: true, // if you just specify `true`, default port `35729` will be used 310 | serverreload: true 311 | } 312 | } 313 | }); 314 | ``` 315 | 316 | If all you have are the browser side static contents, you can omit the `server` option (and of course, you would not set `serverreload` to `true` in this case, although it would not hurt to set it): 317 | 318 | ```js 319 | grunt.initConfig({ 320 | express: { 321 | myLivereloadServer: { 322 | bases: path.resolve(__dirname, 'public'), 323 | livereload: true 324 | } 325 | } 326 | }); 327 | ``` 328 | The above example is equivalent to the following: 329 | 330 | ```js 331 | var LIVERELOAD_PORT = 35729; 332 | var lrSnippet = require('connect-livereload')({ port: LIVERELOAD_PORT }); 333 | var mountFolder = function (connect, dir) { 334 | return connect.static(require('path').resolve(dir)); 335 | }; 336 | 337 | grunt.initConfig({ 338 | watch: { 339 | options: { 340 | livereload: LIVERELOAD_PORT 341 | }, 342 | files: [ 343 | path.resolve(__dirname, 'public') + '/{,*/}*.*' 344 | ] 345 | }, 346 | connect: { 347 | livereload: { 348 | options: { 349 | port: 3000, 350 | middleware: function (connect) { 351 | return [ 352 | lrSnippet, 353 | mountFolder(connect, path.resolve(__dirname, 'public')), 354 | mountFolder(connect, yeomanConfig.app) 355 | ]; 356 | } 357 | } 358 | } 359 | } 360 | }); 361 | ``` 362 | 363 | #### Managing static content and dynamic middlewares 364 | 365 | Noted that `grunt-exress` translates each of your static folders (defined as `bases` option) into an instance of express.static() middleware. And in the case of `livereload` is `true` (or a port number), `grunt-express` will also insert a [connect-livereload](https://github.com/intesso/connect-livereload) middleware for you. This is unlike `grunt-contrib-connect`, where you have to define your own middleware to do so (which has the up side of having full flexibility). Also noted that `grunt-express` will rearrange your middlewares (at runtime) to make sure `connect-livereload` is at the top of your server's middleware stack (like `connect-livereload`'s documentation has suggested). 366 | 367 | Starting v1.0, `grunt-express` also allow a dynamic list of middlewares to be passed in as option `middleware`, this is to mimic the popular `grunt-contrib-connect` [middleware feature](https://github.com/gruntjs/grunt-contrib-connect#middleware). There are some limitations on enabling this, and may not be fully funtional in all cases. 368 | 369 | Usually, we also want to control the order of loading express middlewares, because sometimes they would only function with a particular loading order. `grunt-express` tries to give you such freedom with the introduction of `placeholder` middleware. Let's see an example. Say, you have the following express script: 370 | 371 | ```js 372 | var express = require('express'); 373 | var passport = require('passport'); 374 | var app = express(); 375 | 376 | 377 | app.use(express.logger('dev')); 378 | 379 | // I want to place any static content here 380 | // but I want to define the location of these static content in `grunt-express` options like so: 381 | // 382 | // grunt.initConfig({ 383 | // express: { 384 | // livereloadServer: { 385 | // server: path.resolve(__dirname, 'server'), 386 | // bases: [path.resolve(__dirname, 'public'), path.resolve(__dirname, '.tmp')], 387 | // livereload: true, 388 | // serverreload: true 389 | // }, 390 | // productionServer: { 391 | // server: path.resolve(__dirname, 'server'), 392 | // bases: path.resolve(__dirname, 'dist') 393 | // } 394 | // } 395 | // }); 396 | // Notice the name of the following middleware function 397 | app.use(function staticsPlaceholder(req, res, next) { 398 | return next(); 399 | }); 400 | 401 | app.use(express.cookieParser()); 402 | app.use(express.session({ secret: 'i am not telling you' })); 403 | app.use(express.bodyParser()); 404 | 405 | app.use(passport.initialize()); 406 | app.use(passport.session()); 407 | 408 | // here is where I want my dynamic middlewares be loaded 409 | app.use(function middlewarePlaceholder(req, res, next) { 410 | return next(); 411 | }); 412 | 413 | app.use(myOtherMiddlewares); 414 | 415 | … 416 | 417 | ``` 418 | 419 | `grunt-exress` also support an edge case where you put the `staticsPlaceholder` middleware as part of the `middlewares` being passed in as part of the options. 420 | 421 | 422 | #### Multiple Servers 423 | You can specify multiple servers to be run alone or simultaneously by creating a target for each server. In this example, running either `grunt express:site1` or `grunt express:site2` will start the appropriate web server, but running `grunt connect` will run _both_. Note that any server for which the [keepalive](#keepalive) option is specified will prevent _any_ task or target from running after it. 424 | 425 | ```javascript 426 | // Project configuration. 427 | grunt.initConfig({ 428 | express: { 429 | site1: { 430 | options: { 431 | port: 9000, 432 | bases: 'www-roots/site1' 433 | } 434 | }, 435 | site2: { 436 | options: { 437 | port: 9001, 438 | bases: 'www-roots/site2' 439 | } 440 | } 441 | } 442 | }); 443 | ``` 444 | 445 | #### Custom express 446 | Like the [Basic Use](#basic-use) example, this example will start a static web server at `http://localhost:9001/`, with its base path set to the `www-root` directory relative to the gruntfile. Unlike the other example, this will use your custom server script as referred to by `server`! We also set `keepalive` and `watchChanges` to true, so the server will run forever (until you terminate the command), also the server will restart when you apply any changes to your server script. 447 | 448 | ```javascript 449 | // Project configuration. 450 | grunt.initConfig({ 451 | express: { 452 | custom: { 453 | options: { 454 | port: 9001, 455 | bases: 'www-root', 456 | server: path.resolve('./server/main') 457 | } 458 | } 459 | } 460 | }); 461 | ``` 462 | 463 | any in your `server/main.js`, we reuse the previous sample server script 464 | ```javascript 465 | var express = require('express'); 466 | var app = express(); 467 | app.get('/', function(req, res) { 468 | res.send('hello!'); 469 | }); 470 | module.exports = app; 471 | ``` 472 | (open [localhost:9001](http://localhost:9001) and you should see "hello!") 473 | 474 | Now let's change `server/main.js`'s content to: 475 | ```javascript 476 | var express = require('express'); 477 | var app = express(); 478 | app.get('/', function(req, res) { 479 | res.send('bonjour!'); 480 | }); 481 | module.exports = app; 482 | ``` 483 | (refresh browser and you should see "bonjour!") 484 | 485 | 486 | ## Release History 487 | * 2014-05-09 `v1.3.5` #58 488 | * 2014-05-04 `v1.3.4` bump npm dependencies 489 | * 2014-04-30 `v1.3.0` fixes server reload 490 | * 2013-07-16 `v1.0.0-beta` use grunt-contrib-watch, support both serverreload and livereload 491 | * 2013-04-25 `v0.3.3` use forever-monitor npm v1.2.1 492 | * 2013-03-24 `v0.3.2` fixed npm v1.2.15 compatibility issue 493 | * 2013-03-14 `v0.3.0` support 'debug-brk' option for launching server in child process (so it can be linked to a remote debugger); also point forever-monitor dependency to its github verion (has fix for accepting 'debug-brk' options) 494 | * 2013-03-13 `v0.2.2` do not defalt hostname to "localhost" when none is provided as that will prevent access to the server through IP addres 495 | * 2013-03-11 `v0.2.1` Make static directories not browsable as it breaks twitter bootstrap (suggested by @hmalphettes) 496 | * 2013-02-28 `v0.2.0` Switch to use forever-monitor (instead of node-supervisor). Removed "keepalive" option, instead enable the feature using "express-keepalive" task. 497 | * 2013-02-25 `v0.1.3` Fixes #1, changing option "watchChanges" to "supervisor". 498 | * 2013-02-24 `v0.1.1` Added missing "connect" dependency, factored out some logic to util.js. 499 | * 2013-02-23 `v0.1.0` first draft. 500 | 501 | ## [License-MIT](https://github.com/blai/grunt-express/blob/master/LICENSE-MIT) 502 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var touch = require('touch'); 6 | var connect = require('connect'); 7 | 8 | 9 | exports.touchFile = function touchFile(path) { 10 | touch.sync(path); 11 | } 12 | 13 | exports.makeServerTaskName = function makeServerTaskName(serverName, kind) { 14 | return 'express_' + serverName + '_' + kind; 15 | } 16 | 17 | exports.watchModule = function watchModule(watcher) { 18 | // hijack each module extension handler, and watch the file 19 | function injectWatcher(handler) { 20 | return function(module, filename) { 21 | fs.watchFile(filename, watcher); 22 | handler(module, filename); 23 | }; 24 | } 25 | 26 | for (var ext in require.extensions) { 27 | var handler = require.extensions[ext]; 28 | require.extensions[ext] = injectWatcher(handler); 29 | } 30 | } 31 | 32 | function insertMiddleware(server, index, middlewares) { 33 | if (middlewares.length == 0) { 34 | return; // nothing to do 35 | } 36 | var stackHolder = server._router || server; 37 | var stackCount = stackHolder.stack.length; 38 | middlewares.each(function (mw) { 39 | server.use(mw); 40 | }); 41 | if (0 <= index && index < stackCount) { // need to reposition inserted items 42 | var insertedMiddleware = stackHolder.stack.splice(stackCount); 43 | [].splice.apply(stackHolder.stack, [index, 0].concat(insertedMiddleware)); 44 | } 45 | } 46 | 47 | function findPlaceholder(server, placeholderName) { 48 | var stackHolder = server._router || server; 49 | return stackHolder.stack.findIndex(function (mw) { 50 | return mw.handle.name === placeholderName; 51 | }); 52 | } 53 | 54 | function dumpStack(label, server) { 55 | var stackHolder = server._router || server; 56 | console.log(label); 57 | var items = []; 58 | stackHolder.stack.each(function (item) { 59 | var obj = { 60 | handle: item.handle, 61 | path: item.route && item.route.path 62 | }; 63 | items.push(obj); 64 | }); 65 | console.log(items); 66 | } 67 | 68 | exports.runServer = function runServer(grunt, options) { 69 | options = Object.merge({ 70 | port: 3000, 71 | // hostname: 'localhost', 72 | bases: null, // string|array of each static folders 73 | server: null, 74 | showStack: false 75 | // (optional) filepath that points to a module that exportss a 'server' object that provides 76 | // 1. a 'listen' function act like http.Server.listen (which connect.listen does) 77 | // 2. a 'use' function act like connect.use 78 | }, options); 79 | 80 | var middlewares = options.middleware || []; 81 | 82 | var statics = []; 83 | if (options.bases) { 84 | // wrap each path in connect.static middleware 85 | options.bases.each(function(b) { 86 | statics.push(connect.static(b)); 87 | }); 88 | } 89 | 90 | var server; 91 | if (options.server) { 92 | try { 93 | server = require(path.resolve(options.server)); 94 | if (typeof server.listen !== 'function') { 95 | grunt.fatal('Server should provide a function called "listen" that acts as http.Server.listen'); 96 | } 97 | if (typeof server.use !== 'function') { 98 | grunt.fatal('Server should provide a function called "use" that acts as connect.use'); 99 | } 100 | } catch (e) { 101 | var errorMessage = options.showStack ? '\n' + e.stack : e; 102 | grunt.fatal('Server ["' + options.server + '"] - ' + errorMessage); 103 | } 104 | } else { 105 | server = connect(); 106 | } 107 | 108 | // dumpStack('BEFORE', server); 109 | insertMiddleware(server, findPlaceholder(server, 'middlewarePlaceholder'), middlewares); 110 | // dumpStack('AFTER middleware', server); 111 | insertMiddleware(server, findPlaceholder(server, 'staticsPlaceholder'), statics); 112 | // dumpStack('AFTER statics', server); 113 | 114 | if (options.livereload && options.insertConnectLivereload !== false) { 115 | var lr = require('connect-livereload')({ 116 | port: options.livereload 117 | }); 118 | insertMiddleware(server, 0, [lr]); 119 | // dumpStack('AFTER livereload', server); 120 | } 121 | 122 | if (options.hostname === '*') { 123 | delete options.hostname; 124 | } 125 | 126 | // Start server. 127 | (function startServer (port) { 128 | 129 | // grunt.config.set( 130 | var args = [port, 131 | function() { 132 | server.emit('startListening', this); 133 | grunt.log.writeln('Web server started on port:' + port + (options.hostname ? ', hostname: ' + options.hostname : ', no hostname specified') + ' [pid: ' + process.pid + ']'); 134 | } 135 | ]; 136 | 137 | // always default hostname to 'localhost' would prevent access using IP address 138 | if (options.hostname) { 139 | args.splice(1, 0, options.hostname); 140 | } 141 | 142 | server.listen.apply(server, args) 143 | .on('error', function(err) { 144 | if (err.code === 'EADDRINUSE') { 145 | grunt.log.writeln('Port ' + port + ' in use'); 146 | startServer(++port); 147 | } else { 148 | grunt.fatal(err); 149 | } 150 | }); 151 | })(options.port); 152 | 153 | return server; 154 | } 155 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-express", 3 | "description": "Start (and supervise) an Express.js web server using grunt.js, works well with socket.io", 4 | "version": "1.4.0", 5 | "homepage": "https://github.com/blai/grunt-express", 6 | "author": { 7 | "name": "Brian Lai" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/blai/grunt-express.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/blai/grunt-express/issues" 15 | }, 16 | "licenses": [ 17 | { 18 | "type": "MIT" 19 | } 20 | ], 21 | "main": "gruntfile.js", 22 | "engines": { 23 | "node": ">= 0.8.0" 24 | }, 25 | "scripts": { 26 | "test": "grunt test" 27 | }, 28 | "dependencies": { 29 | "connect": "^2.15.0", 30 | "temp": "^0.7.0", 31 | "touch": "0.0.3", 32 | "connect-livereload": "^0.4.0", 33 | "grunt-contrib-watch": "^0.6.1", 34 | "open": "0.0.5", 35 | "grunt-parallel": "^0.3.1", 36 | "sugar": "^1.4.1" 37 | }, 38 | "devDependencies": { 39 | "grunt-contrib-jshint": "^0.10.0", 40 | "express": "^4.1.1", 41 | "grunt-contrib-nodeunit": "^0.3.3", 42 | "grunt": "^0.4.4", 43 | "matchdep": "^0.3.0", 44 | "request": "^2.34.0", 45 | "lodash": "~2.4.1" 46 | }, 47 | "peerDependencies": { 48 | "grunt": "~0.4.0" 49 | }, 50 | "keywords": [ 51 | "gruntplugin", 52 | "server", 53 | "express", 54 | "connect", 55 | "socket.io", 56 | "livereload", 57 | "keepalive", 58 | "watch", 59 | "http" 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /tasks/express.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var temp = require('temp'); 5 | var open = require('open'); 6 | 7 | var util = require('../lib/util'); 8 | 9 | require('sugar'); 10 | 11 | module.exports = function (grunt) { 12 | var DefaultLiveReloadPort = 35729; 13 | var watchDir = temp.mkdirSync('express'); 14 | var serverMap = {}; 15 | var parentcwd = process.cwd(); 16 | 17 | // get npmTasks from grunt-express, not the parent Gruntfile 18 | process.chdir(path.join(__dirname, '../')); 19 | 20 | if (!grunt.task._tasks['watch']) { 21 | grunt.loadNpmTasks('grunt-contrib-watch'); 22 | } 23 | 24 | if (!grunt.task._tasks['parallel']) { 25 | grunt.loadNpmTasks('grunt-parallel'); 26 | } 27 | 28 | process.chdir(parentcwd); 29 | 30 | grunt.registerMultiTask('express', function () { 31 | var thisTarget = this.target; 32 | var options = this.options({ 33 | serverreload: false, 34 | livereload: false, 35 | open: false 36 | }); 37 | 38 | serverMap[thisTarget] = options.serverKey = path.resolve(watchDir, thisTarget + '.server'); 39 | util.touchFile(options.serverKey); 40 | 41 | if (options.bases) { 42 | if (!Array.isArray(options.bases)) { 43 | grunt.config.set('express.' + thisTarget + '.options.bases', [options.bases]); 44 | options.bases = [options.bases]; 45 | } 46 | 47 | // wrap each path in connect.static middleware 48 | options.bases = options.bases.map(function (b) { 49 | return path.resolve(b); 50 | }); 51 | } 52 | 53 | if (options.livereload === true) { 54 | options.livereload = DefaultLiveReloadPort; 55 | } 56 | if (options.livereload) { 57 | // dynamically add `grunt-contrib-watch` task to manage livereload of static `bases` 58 | grunt.config.set('watch.' + util.makeServerTaskName(thisTarget, 'livereload'), { 59 | files: options.bases.map(function (base) { 60 | return base + '/**/*.*'; 61 | }), 62 | options: { 63 | livereload: options.livereload 64 | } 65 | }); 66 | } 67 | 68 | if (options.serverreload) { 69 | var watcherOptions = { 70 | interrupt: true, 71 | atBegin: true, 72 | event: ['added', 'changed'] 73 | }; 74 | 75 | // dynamically add `grunt-contrib-watch` task to manage `grunt-express` sub task 76 | grunt.config.set('watch.' + util.makeServerTaskName(thisTarget, 'server'), { 77 | files: options.serverKey, 78 | tasks: [ 79 | ['express-server', thisTarget, options.serverKey].join(':'), 'express-keepalive' 80 | ], 81 | options: Object.merge(options.watch || {}, watcherOptions) 82 | }); 83 | 84 | if (grunt.task._queue.filter(function (task) { 85 | return !task.placeholder && task.task.name === 'watch'; 86 | }).length === 0) { 87 | grunt.task.run('watch'); 88 | } 89 | } else { 90 | grunt.task.run(['express-server', thisTarget].join(':')); 91 | } 92 | }); 93 | 94 | 95 | grunt.registerTask('express-start', 'Start the server (or restart if already started)', function (target) { 96 | util.touchFile(serverMap[target]); 97 | }); 98 | // alias, backward compatibility 99 | grunt.registerTask('express-restart', 'Restart the server (or start if not already started)', ['express-start']); 100 | 101 | grunt.registerTask('express-server', function (target) { 102 | var self = this; 103 | var options = Object.merge(grunt.config.get('express.options') || {}, grunt.config.get('express.' + target + '.options')); 104 | if (options.livereload === true) { 105 | options.livereload = DefaultLiveReloadPort; 106 | } 107 | 108 | if (options.serverreload) { 109 | util.watchModule(function (oldStat, newStat) { 110 | if (newStat.mtime.getTime() !== oldStat.mtime.getTime()) { 111 | util.touchFile(self.args[1]); 112 | } 113 | }); 114 | } 115 | 116 | var done = this.async(); 117 | 118 | util.runServer(grunt, options).on('startListening', function (server) { 119 | var address = server.address(); 120 | var serverPort = address.port; 121 | if (serverPort !== options.port) { 122 | grunt.config.set('express.' + target + '.options.port', serverPort); 123 | } 124 | 125 | if (options.open === true) { 126 | // https://github.com/joyent/node/blob/master/lib/_tls_wrap.js#L464 127 | var protocol = (!server.pfx && (!server.cert || !server.key)) ? 'http' : 'https'; 128 | var hostname = address.address || 'localhost'; 129 | if (hostname === '0.0.0.0') { 130 | hostname = 'localhost'; 131 | } 132 | open(protocol + '://' + hostname + ':' + address.port); 133 | } else if (typeof options.open === 'string') { 134 | open(options.open); 135 | } 136 | 137 | grunt.event.emit('express:' + target + ':started'); 138 | done(); 139 | }); 140 | }); 141 | 142 | grunt.registerTask('express-keepalive', 'Keep grunt running', function () { 143 | this.async(); 144 | }); 145 | }; 146 | -------------------------------------------------------------------------------- /test/express_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var grunt = require('grunt'); 6 | var request = require('request'); 7 | var http = require('http'); 8 | var helper = require('./helper'); 9 | var fixtures = path.join(__dirname, 'fixtures'); 10 | 11 | var useFixtures = ['defaults', 'statics', 'express', 'serverrl']; 12 | 13 | function cleanUp() { 14 | useFixtures.forEach(function (fixture) { 15 | var files = fixture + '/node_modules'; 16 | if (typeof files === 'string') { 17 | files = [files]; 18 | } 19 | files.forEach(function (filepath) { 20 | filepath = path.join(fixtures, filepath); 21 | if (grunt.file.exists(filepath)) { 22 | grunt.file.delete(filepath); 23 | } 24 | }); 25 | }); 26 | } 27 | 28 | function setupFixture(fixture) { 29 | fs.symlinkSync(path.join(__dirname, '../node_modules'), path.join(fixtures, fixture, 'node_modules')); 30 | } 31 | 32 | exports.simple = { 33 | setUp: function (done) { 34 | cleanUp(); 35 | useFixtures.forEach(setupFixture); 36 | done(); 37 | }, 38 | tearDown: function (done) { 39 | cleanUp(); 40 | done(); 41 | }, 42 | 43 | defaults: function (test) { 44 | test.expect(1); 45 | var cwd = path.resolve(fixtures, 'defaults'); 46 | var assertExpress = helper.assertTask([ 47 | 'express', 48 | 'request:http://localhost:3000' 49 | ], { 50 | cwd: cwd 51 | }); 52 | 53 | assertExpress(null, 54 | function (result) { 55 | helper.verboseLog(result); 56 | test.ok(result.match(/Web server started on port:3000/), 'Default server should start'); 57 | test.done(); 58 | }); 59 | }, 60 | 61 | statics: function (test) { 62 | test.expect(1); 63 | var cwd = path.resolve(fixtures, 'statics'); 64 | var assertExpress = helper.assertTask([ 65 | 'express:statics', 66 | 'request:http://localhost:3000/test.html' 67 | ], { 68 | cwd: cwd 69 | }); 70 | 71 | assertExpress(null, 72 | function (result) { 73 | helper.verboseLog(result); 74 | test.ok(result.match(/Test Static/gm), 'Server should serve static folder'); 75 | test.done(); 76 | }); 77 | }, 78 | 79 | multipleStatics: function (test) { 80 | test.expect(2); 81 | var cwd = path.resolve(fixtures, 'statics'); 82 | var assertExpress = helper.assertTask([ 83 | 'express:multiStatics', 84 | 'request:http://localhost:4000/test.html', 85 | 'request:http://localhost:4000/test2.html' 86 | ], { 87 | cwd: cwd 88 | }); 89 | 90 | assertExpress(null, 91 | function (result) { 92 | helper.verboseLog(result); 93 | test.ok(result.match(/Test Static/gm).length === 2, 'Server should serve static folder'); 94 | test.ok(result.match(/Test Static 2/gm).length === 1, 'Server should serve more than one static folder'); 95 | test.done(); 96 | }); 97 | }, 98 | 99 | express: function (test) { 100 | test.expect(1); 101 | var cwd = path.resolve(fixtures, 'express'); 102 | var assertExpress = helper.assertTask([ 103 | 'express:express', 104 | 'request:http://localhost:3000/test' 105 | ], { 106 | cwd: cwd 107 | }); 108 | 109 | assertExpress(null, 110 | function (result) { 111 | helper.verboseLog(result); 112 | test.ok(result.match(/Path: \/test/gm).length === 1, 'Express server should work'); 113 | test.done(); 114 | }); 115 | }, 116 | 117 | serverrl: function (test) { 118 | test.expect(2); 119 | var cwd = path.resolve(fixtures, 'serverrl'); 120 | var assertExpress = helper.assertTask([ 121 | 'express', 122 | 'watch' 123 | ], { 124 | cwd: cwd, 125 | trigger: 'Web server started on port:3000' 126 | }); 127 | 128 | grunt.file.write(path.resolve(cwd, 'lib', 'test.js'), 'module.exports = 1;'); 129 | 130 | assertExpress([ 131 | function () { 132 | request.get('http://localhost:3000/test', function (err, res, body) { 133 | test.ok(body.match(/test: 1/gm).length === 1, 'Express server should start'); 134 | grunt.file.write(path.resolve(cwd, 'lib', 'test.js'), 'module.exports = 2;'); 135 | }); 136 | }, 137 | function () { 138 | request.get('http://localhost:3000/test', function (err, res, body) { 139 | test.ok(body.match(/test: 2/gm).length === 1, 'Express server should reload'); 140 | grunt.file.write(path.resolve(cwd, 'lib', 'test.js'), 'module.exports = 3;'); 141 | }); 142 | } 143 | ], 144 | function (result) { 145 | helper.verboseLog(result); 146 | test.done(); 147 | }); 148 | } 149 | }; 150 | -------------------------------------------------------------------------------- /test/fixtures/defaults/Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.initConfig({ 5 | express: { 6 | defaults: {} 7 | } 8 | }); 9 | 10 | // Load helper tasks 11 | grunt.loadTasks('../../tasks'); 12 | // Load this express task 13 | grunt.loadTasks('../../../tasks'); 14 | grunt.registerTask('default', ['express']); 15 | }; -------------------------------------------------------------------------------- /test/fixtures/express/Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | 5 | module.exports = function(grunt) { 6 | grunt.initConfig({ 7 | express: { 8 | express: { 9 | options: { 10 | server: path.resolve('./lib/server') 11 | } 12 | } 13 | } 14 | }); 15 | 16 | // Load helper tasks 17 | grunt.loadTasks('../../tasks'); 18 | // Load this express task 19 | grunt.loadTasks('../../../tasks'); 20 | grunt.registerTask('default', ['express']); 21 | }; -------------------------------------------------------------------------------- /test/fixtures/express/lib/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | app.get('*', function(req, res) { 5 | return res.send('Path: ' + req.path); 6 | }); 7 | 8 | module.exports = app; -------------------------------------------------------------------------------- /test/fixtures/serverrl/Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | 5 | module.exports = function(grunt) { 6 | grunt.initConfig({ 7 | watch: { 8 | options: {}, 9 | express: { 10 | files: __filename 11 | } 12 | }, 13 | express: { 14 | serverrl: { 15 | options: { 16 | server: path.resolve('./lib/server'), 17 | serverreload: true 18 | } 19 | } 20 | } 21 | }); 22 | 23 | // Load helper tasks 24 | grunt.loadTasks('../../tasks'); 25 | // Load this express task 26 | grunt.loadTasks('../../../tasks'); 27 | grunt.registerTask('default', ['express', 'watch']); 28 | }; -------------------------------------------------------------------------------- /test/fixtures/serverrl/lib/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | app.get('/test', function(req, res) { 5 | return res.send('test: ' + require('./test')); 6 | }) 7 | 8 | module.exports = app; -------------------------------------------------------------------------------- /test/fixtures/serverrl/lib/test.js: -------------------------------------------------------------------------------- 1 | module.exports = 3; -------------------------------------------------------------------------------- /test/fixtures/statics/Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | 5 | module.exports = function(grunt) { 6 | grunt.initConfig({ 7 | express: { 8 | statics: { 9 | options: { 10 | bases: path.resolve('./public') 11 | } 12 | }, 13 | multiStatics: { 14 | options: { 15 | bases: [path.resolve('./public'), path.resolve('./public2')], 16 | port: 4000 17 | } 18 | } 19 | } 20 | }); 21 | 22 | // Load helper tasks 23 | grunt.loadTasks('../../tasks'); 24 | // Load this express task 25 | grunt.loadTasks('../../../tasks'); 26 | grunt.registerTask('default', ['express']); 27 | }; -------------------------------------------------------------------------------- /test/fixtures/statics/public/test.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |