diff --git a/packages/react-dev-utils/README.md b/packages/react-dev-utils/README.md index 5f64db3bde4abb4d4a84bc5e40e7b174386a329f..46cdecf188c970cfac1aaa592417d00469eab33d 100644 --- a/packages/react-dev-utils/README.md +++ b/packages/react-dev-utils/README.md @@ -291,10 +291,10 @@ On macOS, tries to find a known running editor process and opens the file in it. Returns Express middleware that serves a `${servedPath}/service-worker.js` that resets any previously set service worker configuration. Useful for development. -#### `redirectServedPathMiddleware(servedPath: string): ExpressMiddleware` +#### `redirectServedPathMiddleware(servedPath: string, appPublic: string): ExpressMiddleware` Returns Express middleware that redirects to `${servedPath}/${req.path}`, if `req.url` -does not start with `servedPath`. Useful for development. +does not start with `servedPath` or is not a proxy like request. Useful for development. #### `openBrowser(url: string): boolean` @@ -345,10 +345,14 @@ The `args` object accepts a number of properties: - **tscCompileOnError** `boolean`: If `true`, errors in TypeScript type checking will not prevent start script from running app, and will not cause build script to exit unsuccessfully. Also downgrades all TypeScript type checking error messages to warning messages. - **webpack** `function`: A reference to the webpack constructor. -##### `prepareProxy(proxySetting: string, appPublicFolder: string): Object` +##### `prepareProxy(proxySetting: string, appPublicFolder: string, servedPathname: string): Object` Creates a WebpackDevServer `proxy` configuration object from the `proxy` setting in `package.json`. +##### `mayProxy(requestMethod: string, acceptHeader: string | undefined, pathname: string, appPublicFolder: string, servedPathname: string): boolean` + +Will return false if is not a development resource like, a static file, or WDS socket + ##### `prepareUrls(protocol: string, host: string, port: number, pathname: string = '/'): Object` Returns an object with local and remote URLs for the development server. Pass this object to `createCompiler()` described above. diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js index 26b36fca3950340cf38011280d2ad6186224965f..f6b0d1ac0f1ceda0c28a5afd87f347f3ea32a75a 100644 --- a/packages/react-dev-utils/WebpackDevServerUtils.js +++ b/packages/react-dev-utils/WebpackDevServerUtils.js @@ -356,7 +356,39 @@ function onProxyError(proxy) { }; } -function prepareProxy(proxy, appPublicFolder) { +function mayProxy( + requestMethod, + acceptHeader, + pathname, + appPublicFolder, + servedPathname +) { + // If proxy is specified, let it handle any request except for + // files in the public folder and requests to the WebpackDevServer socket endpoint. + // https://github.com/facebook/create-react-app/issues/6720 + const sockPath = process.env.WDS_SOCKET_PATH || '/sockjs-node'; + const isDefaultSockHost = !process.env.WDS_SOCKET_HOST; + function isProxy(pathname) { + const maybePublicPath = path.resolve( + appPublicFolder, + pathname.replace(new RegExp('^' + servedPathname), '') + ); + const isPublicFileRequest = fs.existsSync(maybePublicPath); + // used by webpackHotDevClient + const isWdsEndpointRequest = + isDefaultSockHost && pathname.startsWith(sockPath); + return !(isPublicFileRequest || isWdsEndpointRequest); + } + + return ( + requestMethod !== 'GET' || + (isProxy(pathname) && + acceptHeader && + acceptHeader.indexOf('text/html') === -1) + ); +} + +function prepareProxy(proxy, appPublicFolder, servedPathname) { // `proxy` lets you specify alternate servers for specific requests. if (!proxy) { return undefined; @@ -374,20 +406,6 @@ function prepareProxy(proxy, appPublicFolder) { process.exit(1); } - // If proxy is specified, let it handle any request except for - // files in the public folder and requests to the WebpackDevServer socket endpoint. - // https://github.com/facebook/create-react-app/issues/6720 - const sockPath = process.env.WDS_SOCKET_PATH || '/sockjs-node'; - const isDefaultSockHost = !process.env.WDS_SOCKET_HOST; - function mayProxy(pathname) { - const maybePublicPath = path.resolve(appPublicFolder, pathname.slice(1)); - const isPublicFileRequest = fs.existsSync(maybePublicPath); - // used by webpackHotDevClient - const isWdsEndpointRequest = - isDefaultSockHost && pathname.startsWith(sockPath); - return !(isPublicFileRequest || isWdsEndpointRequest); - } - if (!/^http(s)?:\/\//.test(proxy)) { console.log( chalk.red( @@ -418,11 +436,15 @@ function prepareProxy(proxy, appPublicFolder) { // However API calls like `fetch()` won’t generally accept text/html. // If this heuristic doesn’t work well for you, use `src/setupProxy.js`. context: function(pathname, req) { - return ( - req.method !== 'GET' || - (mayProxy(pathname) && - req.headers.accept && - req.headers.accept.indexOf('text/html') === -1) + // If proxy is specified, let it handle any request except for + // files in the public folder and requests to the WebpackDevServer socket endpoint. + // https://github.com/facebook/create-react-app/issues/6720 + return mayProxy( + req.method, + req.headers.accept, + pathname, + appPublicFolder, + servedPathname ); }, onProxyReq: proxyReq => { @@ -492,6 +514,7 @@ function choosePort(host, defaultPort) { module.exports = { choosePort, createCompiler, + mayProxy, prepareProxy, prepareUrls, }; diff --git a/packages/react-dev-utils/redirectServedPathMiddleware.js b/packages/react-dev-utils/redirectServedPathMiddleware.js index a71578f0a9f3298d9d4b57dbf69860e67c66e0b7..3a0d315f50895b8007e01e0b358bb9535967f349 100644 --- a/packages/react-dev-utils/redirectServedPathMiddleware.js +++ b/packages/react-dev-utils/redirectServedPathMiddleware.js @@ -7,19 +7,34 @@ 'use strict'; const path = require('path'); +const { mayProxy } = require('react-dev-utils/WebpackDevServerUtils'); -module.exports = function createRedirectServedPathMiddleware(servedPath) { +module.exports = function createRedirectServedPathMiddleware( + servedPath, + appPublic +) { // remove end slash so user can land on `/test` instead of `/test/` - servedPath = servedPath.slice(0, -1); + const servedPathSlashTrimmed = servedPath.slice(0, -1); return function redirectServedPathMiddleware(req, res, next) { + const pathname = new URL(req.url, 'https://stub-domain').pathname; if ( - servedPath === '' || - req.url === servedPath || - req.url.startsWith(servedPath) + mayProxy(req.method, req.headers.accept, pathname, appPublic, servedPath) + ) { + next(); + return; + } + + if ( + servedPathSlashTrimmed === '' || + req.url === servedPathSlashTrimmed || + req.url.startsWith(servedPathSlashTrimmed) ) { next(); } else { - const newPath = path.join(servedPath, req.path !== '/' ? req.path : ''); + const newPath = path.join( + servedPathSlashTrimmed, + req.path !== '/' ? req.path : '' + ); res.redirect(newPath); } }; diff --git a/packages/react-scripts/config/webpackDevServer.config.js b/packages/react-scripts/config/webpackDevServer.config.js index 82e78c09f79bf2f3bfd72f8d8ba648206946ed9d..ad42332e1a86e4ad7f67b56311b1de8ed8d9928e 100644 --- a/packages/react-scripts/config/webpackDevServer.config.js +++ b/packages/react-scripts/config/webpackDevServer.config.js @@ -108,6 +108,7 @@ module.exports = function(proxy, allowedHost) { index: paths.publicUrlOrPath, }, public: allowedHost, + // `proxy` is run between `before` and `after` `webpack-dev-server` hooks proxy, before(app, server) { // Keep `evalSourceMapMiddleware` and `errorOverlayMiddleware` @@ -117,14 +118,14 @@ module.exports = function(proxy, allowedHost) { // This lets us open files from the runtime error overlay. app.use(errorOverlayMiddleware()); - // Redirect to `PUBLIC_URL` or `homepage` from `package.json` if url not match - app.use(redirectServedPath(paths.publicUrlOrPath)); - if (fs.existsSync(paths.proxySetup)) { // This registers user provided middleware for proxy reasons require(paths.proxySetup)(app); } + // Redirect to `PUBLIC_URL` or `homepage` from `package.json` if url not match + app.use(redirectServedPath(paths.publicUrlOrPath, paths.appPublic)); + // This service worker file is effectively a 'no-op' that will reset any // previous service worker registered for the same host:port combination. // We do this in development to avoid hitting the production cache if diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index 094f6e804095f855801be9c1789498b13967fa20..97ad10fe001061e0f3975c6212b66c0a5d045274 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -122,7 +122,11 @@ checkBrowsers(paths.appPath, isInteractive) }); // Load proxy config const proxySetting = require(paths.appPackageJson).proxy; - const proxyConfig = prepareProxy(proxySetting, paths.appPublic); + const proxyConfig = prepareProxy( + proxySetting, + paths.appPublic, + paths.publicUrlOrPath + ); // Serve webpack assets generated by the compiler over a web server. const serverConfig = createDevServerConfig( proxyConfig,