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

Resolve localhost when offline on Windows #1839

Merged
merged 9 commits into from
Apr 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 24 additions & 16 deletions packages/react-scripts/scripts/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,30 @@ function run(port) {
const devServer = new WebpackDevServer(compiler, devServerConfig);

// Our custom middleware proxies requests to /index.html or a remote API.
addWebpackMiddleware(devServer);

// Launch WebpackDevServer.
devServer.listen(port, host, err => {
if (err) {
return console.log(err);
}

if (isInteractive) {
clearConsole();
}
console.log(chalk.cyan('Starting the development server...'));
console.log();

openBrowser(`${protocol}://${host}:${port}/`);
});
addWebpackMiddleware(devServer)
.then(() => {
// Launch WebpackDevServer.
devServer.listen(port, host, err => {
if (err) {
return console.log(err);
}

if (isInteractive) {
clearConsole();
}
console.log(chalk.cyan('Starting the development server...'));
console.log();

openBrowser(`${protocol}://${host}:${port}/`);
});
})
.catch(e => {
console.log(
chalk.red('Failed to setup middleware, please report this error:')
);
console.log(e);
process.exit(1);
});
}

// We attempt to use the default port but if it is busy, we offer the user to
Expand Down
126 changes: 80 additions & 46 deletions packages/react-scripts/scripts/utils/addWebpackMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
'use strict';

const chalk = require('chalk');
const dns = require('dns');
const historyApiFallback = require('connect-history-api-fallback');
const httpProxyMiddleware = require('http-proxy-middleware');
const url = require('url');
const paths = require('../../config/paths');

// We need to provide a custom onError function for httpProxyMiddleware.
Expand Down Expand Up @@ -56,49 +58,57 @@ function onProxyError(proxy) {
};
}

module.exports = function addWebpackMiddleware(devServer) {
// `proxy` lets you to specify a fallback server during development.
// Every unrecognized request will be forwarded to it.
const proxy = require(paths.appPackageJson).proxy;
devServer.use(
historyApiFallback({
// Paths with dots should still use the history fallback.
// See https://github.com/facebookincubator/create-react-app/issues/387.
disableDotRule: true,
// For single page apps, we generally want to fallback to /index.html.
// However we also want to respect `proxy` for API calls.
// So if `proxy` is specified, we need to decide which fallback to use.
// We use a heuristic: if request `accept`s text/html, we pick /index.html.
// Modern browsers include text/html into `accept` header when navigating.
// However API calls like `fetch()` won’t generally accept text/html.
// If this heuristic doesn’t work well for you, don’t use `proxy`.
htmlAcceptHeaders: proxy ? ['text/html'] : ['text/html', '*/*'],
})
);
if (proxy) {
if (typeof proxy !== 'string') {
console.log(
chalk.red('When specified, "proxy" in package.json must be a string.')
);
console.log(
chalk.red('Instead, the type of "proxy" was "' + typeof proxy + '".')
);
console.log(
chalk.red(
'Either remove "proxy" from package.json, or make it a string.'
)
);
process.exit(1);
// Test that proxy url specified starts with http:// or https://
} else if (!/^http(s)?:\/\//.test(proxy)) {
console.log(
chalk.red(
'When "proxy" is specified in package.json it must start with either http:// or https://'
)
);
process.exit(1);
}
function resolveProxy(proxy) {
const p = url.parse(proxy);
const hostname = p.hostname;
if (hostname !== 'localhost') {
return Promise.resolve(proxy);
}
p.host = undefined; // Remove the host; we don't care about it
return new Promise(resolve => {
dns.lookup(hostname, { hints: 0, all: false }, (err, address) => {
if (err) {
console.log(
chalk.red(
'"proxy" in package.json is set to localhost and cannot be resolved.'
)
);
console.log(
chalk.red('Try setting "proxy" to 127.0.0.1 instead of localhost.')
);
process.exit(1);
}
p.hostname = address;
resolve(url.format(p));
});
});
}

function registerProxy(devServer, _proxy) {
if (typeof _proxy !== 'string') {
console.log(
chalk.red('When specified, "proxy" in package.json must be a string.')
);
console.log(
chalk.red('Instead, the type of "proxy" was "' + typeof _proxy + '".')
);
console.log(
chalk.red('Either remove "proxy" from package.json, or make it a string.')
);
process.exit(1);
// Test that proxy url specified starts with http:// or https://
} else if (!/^http(s)?:\/\//.test(_proxy)) {
console.log(
chalk.red(
'When "proxy" is specified in package.json it must start with either http:// or https://'
)
);
process.exit(1);
}

return (process.platform === 'win32'
? resolveProxy(_proxy)
: Promise.resolve(_proxy)).then(proxy => {
// Otherwise, if proxy is specified, we will let it handle any request.
// There are a few exceptions which we won't send to the proxy:
// - /index.html (served as HTML5 history API fallback)
Expand Down Expand Up @@ -132,9 +142,33 @@ module.exports = function addWebpackMiddleware(devServer) {
// If this is not done, httpProxyMiddleware will not try to upgrade until
// an initial plain HTTP request is made.
devServer.listeningApp.on('upgrade', hpm.upgrade);
}
});
}

// Finally, by now we have certainly resolved the URL.
// It may be /index.html, so let the dev server try serving it again.
devServer.use(devServer.middleware);
module.exports = function addWebpackMiddleware(devServer) {
// `proxy` lets you to specify a fallback server during development.
// Every unrecognized request will be forwarded to it.
const proxy = require(paths.appPackageJson).proxy;
devServer.use(
historyApiFallback({
// Paths with dots should still use the history fallback.
// See https://github.com/facebookincubator/create-react-app/issues/387.
disableDotRule: true,
// For single page apps, we generally want to fallback to /index.html.
// However we also want to respect `proxy` for API calls.
// So if `proxy` is specified, we need to decide which fallback to use.
// We use a heuristic: if request `accept`s text/html, we pick /index.html.
// Modern browsers include text/html into `accept` header when navigating.
// However API calls like `fetch()` won’t generally accept text/html.
// If this heuristic doesn’t work well for you, don’t use `proxy`.
htmlAcceptHeaders: proxy ? ['text/html'] : ['text/html', '*/*'],
})
);
return (proxy
? registerProxy(devServer, proxy)
: Promise.resolve()).then(() => {
// Finally, by now we have certainly resolved the URL.
// It may be /index.html, so let the dev server try serving it again.
devServer.use(devServer.middleware);
});
};