From 2bf1dff0519d2eeaad401923d8a71874ded1fd77 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Fri, 6 Mar 2015 17:42:07 -0800 Subject: [PATCH] Minor packager updates Splitting this out from the next commit for clarity. --- .../src/Server/__tests__/Server-test.js | 54 ++++++++++++++----- packager/react-packager/src/Server/index.js | 45 +++++++++++++++- 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/packager/react-packager/src/Server/__tests__/Server-test.js b/packager/react-packager/src/Server/__tests__/Server-test.js index f55df7c28..71b7e400f 100644 --- a/packager/react-packager/src/Server/__tests__/Server-test.js +++ b/packager/react-packager/src/Server/__tests__/Server-test.js @@ -45,6 +45,7 @@ describe('processRequest', function() { var invalidatorFunc = jest.genMockFunction(); var watcherFunc = jest.genMockFunction(); var requestHandler; + var triggerFileChange; beforeEach(function() { Packager = require('../../Packager'); @@ -61,7 +62,15 @@ describe('processRequest', function() { }); }; - FileWatcher.prototype.on = watcherFunc; + + FileWatcher.prototype.on = function(eventType, callback) { + if (eventType !== 'all') { + throw new Error('Can only handle "all" event in watcher.'); + } + watcherFunc.apply(this, arguments); + triggerFileChange = callback; + return this; + }; Packager.prototype.invalidateFile = invalidatorFunc; @@ -109,17 +118,6 @@ describe('processRequest', function() { describe('file changes', function() { - var triggerFileChange; - beforeEach(function() { - FileWatcher.prototype.on = function(eventType, callback) { - if (eventType !== 'all') { - throw new Error('Can only handle "all" event in watcher.'); - } - triggerFileChange = callback; - return this; - }; - }); - pit('invalides files in package when file is updated', function() { return makeRequest( requestHandler, @@ -175,4 +173,36 @@ describe('processRequest', function() { }); }); }); + + describe('/onchange endpoint', function() { + var EventEmitter; + var req; + var res; + + beforeEach(function() { + EventEmitter = require.requireActual('events').EventEmitter; + req = new EventEmitter(); + req.url = '/onchange'; + res = { + writeHead: jest.genMockFn(), + end: jest.genMockFn() + }; + }); + + it('should hold on to request and inform on change', function() { + server.processRequest(req, res); + triggerFileChange('all', 'path/file.js', options.projectRoots[0]); + jest.runAllTimers(); + expect(res.end).toBeCalledWith(JSON.stringify({changed: true})); + }); + + it('should not inform changes on disconnected clients', function() { + server.processRequest(req, res); + req.emit('close'); + jest.runAllTimers(); + triggerFileChange('all', 'path/file.js', options.projectRoots[0]); + jest.runAllTimers(); + expect(res.end).not.toBeCalled(); + }); + }); }); diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 053aa0333..8982bc916 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -50,6 +50,7 @@ function Server(options) { this._projectRoots = opts.projectRoots; this._packages = Object.create(null); this._packager = new Packager(opts); + this._changeWatchers = []; this._fileWatcher = options.nonPersistent ? FileWatcher.createDummyWatcher() @@ -65,6 +66,7 @@ Server.prototype._onFileChange = function(type, filepath, root) { // Make sure the file watcher event runs through the system before // we rebuild the packages. setImmediate(this._rebuildPackages.bind(this, absPath)); + setImmediate(this._informChangeWatchers.bind(this)); }; Server.prototype._rebuildPackages = function() { @@ -83,6 +85,20 @@ Server.prototype._rebuildPackages = function() { }); }; +Server.prototype._informChangeWatchers = function() { + var watchers = this._changeWatchers; + var headers = { + 'Content-Type': 'application/json; charset=UTF-8', + }; + + watchers.forEach(function(w) { + w.res.writeHead(205, headers); + w.res.end(JSON.stringify({ changed: true })); + }); + + this._changeWatchers = []; +}; + Server.prototype.end = function() { q.all([ this._fileWatcher.end(), @@ -142,6 +158,24 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { } }; +Server.prototype._processOnChangeRequest = function(req, res) { + var watchers = this._changeWatchers; + + watchers.push({ + req: req, + res: res, + }); + + req.on('close', function() { + for (var i = 0; i < watchers.length; i++) { + if (watchers[i] && watchers[i].req === req) { + watchers.splice(i, 1); + break; + } + } + }); +}; + Server.prototype.processRequest = function(req, res, next) { var urlObj = url.parse(req.url, true); var pathname = urlObj.pathname; @@ -154,6 +188,9 @@ Server.prototype.processRequest = function(req, res, next) { } else if (pathname.match(/^\/debug/)) { this._processDebugRequest(req.url, res); return; + } else if (pathname.match(/^\/onchange\/?$/)) { + this._processOnChangeRequest(req, res); + return; } else { next(); return; @@ -196,7 +233,7 @@ function getOptionsFromUrl(reqUrl) { return { sourceMapUrl: urlObj.pathname.replace(/\.bundle$/, '.map'), main: main, - dev: getBoolOptionFromQuery(urlObj.query, 'dev'), + dev: getBoolOptionFromQuery(urlObj.query, 'dev', true), minify: getBoolOptionFromQuery(urlObj.query, 'minify'), runModule: getBoolOptionFromQuery(urlObj.query, 'runModule') || // Backwards compatibility. @@ -206,7 +243,11 @@ function getOptionsFromUrl(reqUrl) { }; } -function getBoolOptionFromQuery(query, opt) { +function getBoolOptionFromQuery(query, opt, defaultVal) { + if (query[opt] == null && defaultVal != null) { + return defaultVal; + } + return query[opt] === 'true' || query[opt] === '1'; }