Skip to content

Commit

Permalink
feat(dev-server): provide custom request listener
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdbradley committed Jan 30, 2021
1 parent c7ccc88 commit eec7651
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 57 deletions.
25 changes: 25 additions & 0 deletions src/declarations/stencil-public-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,31 @@ export interface StencilDevServerConfig {
* To disable any reloading, use `null`. Defaults to `hmr`.
*/
reloadStrategy?: PageReloadStrategy;
/**
* Local path to a NodeJs file with a dev server request listener as the default export.
* The user's request listener is given the first chance to handle every request the dev server
* receives, and can choose to handle it or instead pass it on to the default dev server
* by calling `next()`.
*
* Below is an example of a NodeJs file the `requestListenerPath` config is using.
* The request and response arguments are the same as Node's `http` module and `RequestListener`
* callback. https://nodejs.org/api/http.html#http_http_createserver_options_requestlistener
*
* ```js
* module.exports = function (req, res, next) {
* if (req.url === '/ping') {
* // custom response overriding the dev server
* res.setHeader('Content-Type', 'text/plain');
* res.writeHead(200);
* res.end('pong');
* } else {
* // pass request on to the default dev server
* next();
* }
* };
* ```
*/
requestListenerPath?: string;
/**
* The root directory to serve the files from.
*/
Expand Down
114 changes: 64 additions & 50 deletions src/dev-server/request-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,71 +10,85 @@ import { ssrPageRequest, ssrStaticDataRequest } from './ssr-request';
import path from 'path';

export function createRequestHandler(devServerConfig: d.DevServerConfig, serverCtx: d.DevServerContext) {
return async function (incomingReq: IncomingMessage, res: ServerResponse) {
try {
const req = normalizeHttpRequest(devServerConfig, incomingReq);

if (!req.url) {
return serverCtx.serve302(req, res);
}
let userRequestHandler: (req: IncomingMessage, res: ServerResponse, next: () => void) => void = null;

if (isDevClient(req.pathname) && devServerConfig.websocket) {
return serveDevClient(devServerConfig, serverCtx, req, res);
}
if (typeof devServerConfig.requestListenerPath === 'string') {
userRequestHandler = require(devServerConfig.requestListenerPath);
}

if (isDevModule(req.pathname)) {
return serveDevNodeModule(serverCtx, req, res);
}
return async function (incomingReq: IncomingMessage, res: ServerResponse) {
async function defaultHandler() {
try {
const req = normalizeHttpRequest(devServerConfig, incomingReq);

if (!isValidUrlBasePath(devServerConfig.basePath, req.url)) {
return serverCtx.serve404(
req,
res,
`invalid basePath`,
`404 File Not Found, base path: ${devServerConfig.basePath}`,
);
}
if (!req.url) {
return serverCtx.serve302(req, res);
}

if (devServerConfig.ssr) {
if (isExtensionLessPath(req.url.pathname)) {
return ssrPageRequest(devServerConfig, serverCtx, req, res);
if (isDevClient(req.pathname) && devServerConfig.websocket) {
return serveDevClient(devServerConfig, serverCtx, req, res);
}
if (isSsrStaticDataPath(req.url.pathname)) {
return ssrStaticDataRequest(devServerConfig, serverCtx, req, res);

if (isDevModule(req.pathname)) {
return serveDevNodeModule(serverCtx, req, res);
}
}

req.stats = await serverCtx.sys.stat(req.filePath);
if (req.stats.isFile) {
return serveFile(devServerConfig, serverCtx, req, res);
}
if (!isValidUrlBasePath(devServerConfig.basePath, req.url)) {
return serverCtx.serve404(
req,
res,
`invalid basePath`,
`404 File Not Found, base path: ${devServerConfig.basePath}`,
);
}

if (req.stats.isDirectory) {
return serveDirectoryIndex(devServerConfig, serverCtx, req, res);
}
if (devServerConfig.ssr) {
if (isExtensionLessPath(req.url.pathname)) {
return ssrPageRequest(devServerConfig, serverCtx, req, res);
}
if (isSsrStaticDataPath(req.url.pathname)) {
return ssrStaticDataRequest(devServerConfig, serverCtx, req, res);
}
}

const xSource = ['notfound'];
const validHistoryApi = isValidHistoryApi(devServerConfig, req);
xSource.push(`validHistoryApi: ${validHistoryApi}`);
req.stats = await serverCtx.sys.stat(req.filePath);
if (req.stats.isFile) {
return serveFile(devServerConfig, serverCtx, req, res);
}

if (validHistoryApi) {
try {
const indexFilePath = path.join(devServerConfig.root, devServerConfig.historyApiFallback.index);
xSource.push(`indexFilePath: ${indexFilePath}`);
if (req.stats.isDirectory) {
return serveDirectoryIndex(devServerConfig, serverCtx, req, res);
}

req.stats = await serverCtx.sys.stat(indexFilePath);
if (req.stats.isFile) {
req.filePath = indexFilePath;
return serveFile(devServerConfig, serverCtx, req, res);
const xSource = ['notfound'];
const validHistoryApi = isValidHistoryApi(devServerConfig, req);
xSource.push(`validHistoryApi: ${validHistoryApi}`);

if (validHistoryApi) {
try {
const indexFilePath = path.join(devServerConfig.root, devServerConfig.historyApiFallback.index);
xSource.push(`indexFilePath: ${indexFilePath}`);

req.stats = await serverCtx.sys.stat(indexFilePath);
if (req.stats.isFile) {
req.filePath = indexFilePath;
return serveFile(devServerConfig, serverCtx, req, res);
}
} catch (e) {
xSource.push(`notfound error: ${e}`);
}
} catch (e) {
xSource.push(`notfound error: ${e}`);
}

return serverCtx.serve404(req, res, xSource.join(', '));
} catch (e) {
return serverCtx.serve500(incomingReq, res, e, `not found error`);
}
}

return serverCtx.serve404(req, res, xSource.join(', '));
} catch (e) {
return serverCtx.serve500(incomingReq, res, e, `not found error`);
if (typeof userRequestHandler === 'function') {
userRequestHandler(incomingReq, res, defaultHandler);
} else {
defaultHandler();
}
};
}
Expand Down
8 changes: 1 addition & 7 deletions test/hello-world/src/components/hello-world.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ import { Component, h } from '@stencil/core';
})
export class HelloWorld {
render() {
return <div>{getData()}</div>;
return <div>Hello World</div>;
}
}

const getData = async () => {
const rsp = await fetch('http://ionic.io/');
const txt = await rsp.text();
return txt;
};

0 comments on commit eec7651

Please sign in to comment.