Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: live reload port fallback if port is used #899

Merged
merged 16 commits into from
Sep 12, 2018
13 changes: 5 additions & 8 deletions lib/core/Site.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const Head = require('./Head.js');

const Footer = require(`${process.cwd()}/core/Footer.js`);
const translation = require('../server/translation.js');
const constants = require('./constants');
const liveReloadServer = require('../server/liveReloadServer.js');
const {idx} = require('./utils.js');

const CWD = process.cwd();
Expand All @@ -36,6 +36,8 @@ class Site extends React.Component {
(this.props.url || 'index.html');
let docsVersion = this.props.version;

const liveReloadScriptUrl = liveReloadServer.getReloadScriptUrl();

if (!docsVersion && fs.existsSync(`${CWD}/versions.json`)) {
const latestVersion = require(`${CWD}/versions.json`)[0];
docsVersion = latestVersion;
Expand Down Expand Up @@ -147,13 +149,8 @@ class Site extends React.Component {
/>
))}

{process.env.NODE_ENV === 'development' && (
<script
src={`http://localhost:${
constants.LIVE_RELOAD_PORT
}/livereload.js`}
/>
)}
{process.env.NODE_ENV === 'development' &&
liveReloadScriptUrl && <script src={liveReloadScriptUrl} />}
</body>
</html>
);
Expand Down
6 changes: 4 additions & 2 deletions lib/core/constants.js → lib/server/__mocks__/tiny-lr.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/

module.exports = {
LIVE_RELOAD_PORT: 35729,
const tinylrServer = {
listen: jest.fn(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file has no relationship to the constants file, right? You just changed the constants file and then made a file move. I am just wondering why GitHub thinks this is a file move.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right, the two files are unrelated. I actually deleted the constants file in a separate commit, and added the tiny-lr mock in a later one, so I'm not sure why GitHub thinks this was a file move 😕

};

module.exports = () => tinylrServer;
26 changes: 26 additions & 0 deletions lib/server/__tests__/liveReloadServer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

jest.mock('gaze');
jest.mock('../readMetadata.js');
jest.mock('tiny-lr');

// When running Jest the siteConfig import fails because siteConfig doesn't exist
// relative to the cwd of the tests. Rather than mocking out cwd just mock
// siteConfig virtually.
jest.mock(`${process.cwd()}/siteConfig.js`, () => jest.fn(), {virtual: true});

const liveReloadServer = require('../liveReloadServer.js');

describe('get reload script', () => {
test('when server started, returns url with correct port', () => {
const port = 1234;
liveReloadServer.start(port);
const expectedUrl = `http://localhost:${port}/livereload.js`;
expect(liveReloadServer.getReloadScriptUrl()).toBe(expectedUrl);
});
});
138 changes: 138 additions & 0 deletions lib/server/__tests__/start.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

const program = require('commander');
const openBrowser = require('react-dev-utils/openBrowser');
const portFinder = require('portfinder');
const liveReloadServer = require('../liveReloadServer.js');
const server = require('../server.js');

const siteConfig = require(`${process.cwd()}/siteConfig.js`);

// When running Jest the siteConfig import fails because siteConfig doesn't exist
// relative to the cwd of the tests. Rather than mocking out cwd just mock
// siteConfig virtually.
jest.mock(`${process.cwd()}/siteConfig.js`, () => jest.fn(), {virtual: true});

jest.mock('commander');
jest.mock('react-dev-utils/openBrowser');
jest.mock('portfinder');
jest.mock('../liveReloadServer.js');
jest.mock('../server.js');
jest.mock('process');

console.log = jest.fn();

const start = require('../start.js');

beforeEach(() => jest.resetAllMocks());

describe('start live reload', () => {
test('uses inital port 35729', () => {
portFinder.getPortPromise.mockResolvedValue();
start.startLiveReloadServer();
expect(portFinder.getPortPromise).toHaveBeenCalledWith({port: 35729});
});

test('when an unused port is found, starts the live reload server on that port', () => {
expect.assertions(1);
const unusedPort = 1234;
portFinder.getPortPromise.mockResolvedValue(unusedPort);
return start.startLiveReloadServer().then(() => {
expect(liveReloadServer.start).toHaveBeenCalledWith(unusedPort);
});
});

test('when no unused port found, returns error', () => {
expect.assertions(1);
const unusedPortError = new Error('no unused port');
portFinder.getPortPromise.mockRejectedValue(unusedPortError);
return expect(start.startLiveReloadServer()).rejects.toEqual(
unusedPortError
);
});
});

describe('start server', () => {
test('when custom port provided as parameter, uses as inital port', () => {
const customPort = 1234;
program.port = customPort;
portFinder.getPortPromise.mockResolvedValue();
start.startServer();
expect(portFinder.getPortPromise).toBeCalledWith({port: customPort});
delete program.port;
});

test('when port environment variable set and no custom port, used as inital port', () => {
const customPort = '4321';
process.env.PORT = customPort;
portFinder.getPortPromise.mockResolvedValue();
start.startServer();
expect(portFinder.getPortPromise).toBeCalledWith({port: customPort});
delete process.env.PORT;
});

test('when no custom port specified, uses port 3000', () => {
portFinder.getPortPromise.mockResolvedValue();
start.startServer();
expect(portFinder.getPortPromise).toBeCalledWith({port: 3000});
});

test('when unused port found, starts server on that port', () => {
expect.assertions(1);
const port = 1357;
portFinder.getPortPromise.mockResolvedValue(port);
return start.startServer().then(() => {
expect(server).toHaveBeenCalledWith(port);
});
});

test('when unused port found, opens browser to server address', () => {
expect.assertions(1);
const baseUrl = '/base_url';
siteConfig.baseUrl = baseUrl;
const port = 2468;
portFinder.getPortPromise.mockResolvedValue(port);
const expectedServerAddress = `http://localhost:${port}${baseUrl}`;
return start.startServer().then(() => {
expect(openBrowser).toHaveBeenCalledWith(expectedServerAddress);
});
});
});

describe('start docusaurus', () => {
test('when watch enabled, starts live reload server', () => {
expect.assertions(1);
program.watch = true;
portFinder.getPortPromise.mockResolvedValue();
return start.startDocusaurus().then(() => {
expect(liveReloadServer.start).toBeCalled();
});
});

test('when live reload fails to start, server still started', () => {
expect.assertions(1);
program.watch = true;
console.warn = jest.fn();
portFinder.getPortPromise
.mockRejectedValueOnce('could not find live reload port')
.mockResolvedValueOnce();
return start.startDocusaurus().then(() => {
expect(server).toBeCalled();
});
});

test('live reload disabled, only starts docusarus server', () => {
expect.assertions(2);
program.watch = false;
portFinder.getPortPromise.mockResolvedValue();
return start.startDocusaurus().then(() => {
expect(liveReloadServer.start).not.toBeCalled();
expect(server).toBeCalled();
});
});
});
38 changes: 38 additions & 0 deletions lib/server/liveReloadServer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

const gaze = require('gaze');
const tinylr = require('tiny-lr');
const readMetadata = require('./readMetadata.js');

let reloadScriptUrl;

function start(port) {
process.env.NODE_ENV = 'development';
const server = tinylr();
server.listen(port, () => {
console.log('LiveReload server started on port %d', port);
});

gaze(
[`../${readMetadata.getDocsPath()}/**/*`, '**/*', '!node_modules/**/*'],
function() {
this.on('all', () => {
server.notifyClients(['/']);
});
}
);

reloadScriptUrl = `http://localhost:${port}/livereload.js`;
}

const getReloadScriptUrl = () => reloadScriptUrl;

module.exports = {
start,
getReloadScriptUrl,
};
26 changes: 1 addition & 25 deletions lib/server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

/* eslint-disable no-cond-assign */

function execute(port, options) {
function execute(port) {
const extractTranslations = require('../write-translations');
const metadataUtils = require('./metadataUtils');
const blog = require('./blog');
Expand All @@ -22,9 +22,6 @@ function execute(port, options) {
const mkdirp = require('mkdirp');
const glob = require('glob');
const chalk = require('chalk');
const gaze = require('gaze');
const tinylr = require('tiny-lr');
const constants = require('../core/constants');
const translate = require('./translate');
const {renderToStaticMarkupWithDoctype} = require('./renderUtils');
const feed = require('./feed');
Expand Down Expand Up @@ -105,26 +102,6 @@ function execute(port, options) {
});
}

function startLiveReload() {
process.env.NODE_ENV = 'development';
const server = tinylr();
server.listen(constants.LIVE_RELOAD_PORT, () => {
console.log(
'LiveReload server started on port %d',
constants.LIVE_RELOAD_PORT
);
});

gaze(
[`../${readMetadata.getDocsPath()}/**/*`, '**/*', '!node_modules/**/*'],
function() {
this.on('all', () => {
server.notifyClients(['/']);
});
}
);
}

reloadMetadata();
reloadMetadataBlog();
extractTranslations();
Expand Down Expand Up @@ -398,7 +375,6 @@ function execute(port, options) {
requestFile(`http://localhost:${port}${req.path}.html`, res, next);
});

if (options.watch) startLiveReload();
app.listen(port);
}

Expand Down
51 changes: 51 additions & 0 deletions lib/server/start.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

const program = require('commander');
const openBrowser = require('react-dev-utils/openBrowser');
const portFinder = require('portfinder');
const liveReloadServer = require('./liveReloadServer.js');
const server = require('./server.js');

const CWD = process.cwd();

function startLiveReloadServer() {
const promise = portFinder.getPortPromise({port: 35729}).then(port => {
liveReloadServer.start(port);
});
return promise;
}

function startServer() {
const initialServerPort =
parseInt(program.port, 10) || process.env.PORT || 3000;
const promise = portFinder
.getPortPromise({port: initialServerPort})
.then(port => {
server(port);
const {baseUrl} = require(`${CWD}/siteConfig.js`);
const serverAddress = `http://localhost:${port}${baseUrl}`;
console.log('Docusaurus server started on port %d', port);
openBrowser(serverAddress);
});
return promise;
}

function startDocusaurus() {
if (program.watch) {
return startLiveReloadServer()
.catch(ex => console.warn(`Failed to start live reload server: ${ex}`))
.then(() => startServer());
}
return startServer();
}

module.exports = {
startDocusaurus,
startServer,
startLiveReloadServer,
};
Loading