diff --git a/packages/subapp-react/lib/framework-lib.js b/packages/subapp-react/lib/framework-lib.js
index adc7ffb5a..df141de76 100644
--- a/packages/subapp-react/lib/framework-lib.js
+++ b/packages/subapp-react/lib/framework-lib.js
@@ -90,7 +90,7 @@ class FrameworkLib {
if (subApp.useReactRouter) {
const rrContext = {};
const rrProps = Object.assign(
- { location: request.url.pathname, context: rrContext },
+ { location: request.path || request.url.pathname, context: rrContext },
initialProps
);
// console.log("rendering", name, "for react router", rrProps);
@@ -193,7 +193,7 @@ class FrameworkLib {
`subapp ${this.ref.subApp.name} specified useReactRouter without a StartComponent, \
and can't generate it because module react-router-dom with StaticRouter is not found`
);
- return (props2) =>
+ return props2 =>
React.createElement(
ReactRouterDom.StaticRouter,
props2,
diff --git a/packages/subapp-server/lib/fastify-plugin.js b/packages/subapp-server/lib/fastify-plugin.js
index 482e65a4c..587b7d216 100644
--- a/packages/subapp-server/lib/fastify-plugin.js
+++ b/packages/subapp-server/lib/fastify-plugin.js
@@ -2,106 +2,232 @@
/* eslint-disable no-magic-numbers, max-statements */
+const Path = require("path");
+const assert = require("assert");
const _ = require("lodash");
+const xaa = require("xaa");
const HttpStatus = require("./http-status");
const subAppUtil = require("subapp-util");
const HttpStatusCodes = require("http-status-codes");
+const Fs = require("fs");
+const util = require("util");
+const readFile = util.promisify(Fs.readFile);
+const {
+ utils: { resolveChunkSelector }
+} = require("@xarc/index-page");
+
+const {
+ getSrcDir,
+ makeErrorStackResponse,
+ checkSSRMetricsReporting,
+ updateFullTemplate
+} = require("./utils");
+
+const routesFromFile = require("./routes-from-file");
+const routesFromDir = require("./routes-from-dir");
+const templateRouting = require("./template-routing");
+
+function makeRouteHandler({ path, routeRenderer, routeOptions }) {
+ const useStream = routeOptions.useStream !== false;
+
+ return async (request, reply) => {
+ try {
+ const context = await routeRenderer({
+ content: {
+ html: "",
+ status: HttpStatusCodes.OK,
+ useStream
+ },
+ mode: "",
+ request
+ });
-const { makeErrorStackResponse, checkSSRMetricsReporting } = require("./utils");
-const { getSrcDir, setupRouteRender, searchRoutesFromFile } = require("./setup-hapi-routes");
+ const data = context.result;
+ const status = data.status;
+
+ if (data instanceof Error) {
+ // rethrow to get default error behavior below with helpful errors in dev mode
+ throw data;
+ } else if (status === undefined) {
+ reply.type("text/html; charset=UTF-8").code(HttpStatusCodes.OK);
+ return reply.send(data);
+ } else if (HttpStatus.redirect[status]) {
+ return reply.redirect(status, data.path);
+ } else if (HttpStatus.displayHtml[status] || (status >= HttpStatusCodes.OK && status < 300)) {
+ reply.type("text/html; charset=UTF-8").code(status);
+ return reply.send(data.html !== undefined ? data.html : data);
+ } else {
+ reply.code(status);
+ return reply.send(data);
+ }
+ } catch (err) {
+ reply.status(HttpStatusCodes.INTERNAL_SERVER_ERROR);
+ if (process.env.NODE_ENV !== "production") {
+ const responseHtml = makeErrorStackResponse(path, err);
+ reply.type("text/html; charset=UTF-8");
+ return reply.send(responseHtml);
+ } else {
+ return reply.send("Internal Server Error");
+ }
+ }
+ };
+}
+
+function getRoutePaths(route, path = null) {
+ const defaultMethods = [].concat(route.methods || "get");
+ const paths = _.uniq([path].concat(route.path, route.paths).filter(x => x)).map(x => {
+ if (typeof x === "string") {
+ return { [x]: defaultMethods };
+ }
+ return x;
+ });
-module.exports = {
- fastifyPlugin: async (fastify, pluginOpts) => {
- const srcDir = getSrcDir(pluginOpts);
+ return paths;
+}
- // TODO:
- // const fromDir = await searchRoutesDir(srcDir, pluginOpts);
- // if (fromDir) {
- // //
- // }
-
- const { routes, topOpts } = searchRoutesFromFile(srcDir, pluginOpts);
-
- checkSSRMetricsReporting(topOpts);
-
- const subApps = await subAppUtil.scanSubAppsFromDir(srcDir);
- const subAppsByPath = subAppUtil.getSubAppByPathMap(subApps);
-
- const makeRouteHandler = (path, route) => {
- const routeOptions = Object.assign({}, topOpts, route);
-
- const routeRenderer = setupRouteRender({ subAppsByPath, srcDir, routeOptions });
- const useStream = routeOptions.useStream !== false;
-
- return async (request, reply) => {
- try {
- const context = await routeRenderer({
- content: {
- html: "",
- status: 200,
- useStream
- },
- mode: "",
- request
- });
-
- const data = context.result;
- const status = data.status;
-
- if (data instanceof Error) {
- // rethrow to get default error behavior below with helpful errors in dev mode
- throw data;
- } else if (status === undefined) {
- reply.type("text/html; charset=UTF-8").code(HttpStatusCodes.OK);
- return reply.send(data);
- } else if (HttpStatus.redirect[status]) {
- return reply.redirect(status, data.path);
- } else if (
- HttpStatus.displayHtml[status] ||
- (status >= HttpStatusCodes.OK && status < 300)
- ) {
- reply.type("text/html; charset=UTF-8").code(status);
- return reply.send(data.html !== undefined ? data.html : data);
- } else {
- reply.code(status);
- return reply.send(data);
- }
- } catch (err) {
- reply.status(HttpStatusCodes.INTERNAL_SERVER_ERROR);
- if (process.env.NODE_ENV !== "production") {
- const responseHtml = makeErrorStackResponse(path, err);
- reply.type("text/html; charset=UTF-8");
- return reply.send(responseHtml);
- } else {
- return reply.send("Internal Server Error");
- }
- }
- };
- };
+async function registerFastifyRoutesFromFile({ fastify, srcDir, routes, topOpts }) {
+ checkSSRMetricsReporting(topOpts);
- for (const path in routes) {
- const route = routes[path];
+ const subApps = await subAppUtil.scanSubAppsFromDir(srcDir);
+ const subAppsByPath = subAppUtil.getSubAppByPathMap(subApps);
+
+ for (const path in routes) {
+ const route = routes[path];
+
+ const routeOptions = Object.assign({}, topOpts, route);
- const handler = makeRouteHandler(path, route);
+ const routeRenderer = routesFromFile.setupRouteTemplate({
+ subAppsByPath,
+ srcDir,
+ routeOptions
+ });
- const defaultMethods = [].concat(route.methods || "get");
- const paths = _.uniq([path].concat(route.paths).filter(x => x)).map(x => {
- if (typeof x === "string") {
- return { [x]: defaultMethods };
- }
- return x;
+ const handler = makeRouteHandler({ path, routeRenderer, routeOptions });
+
+ getRoutePaths(route, path).forEach(pathObj => {
+ _.each(pathObj, (method, xpath) => {
+ fastify.route({
+ ...route.settings,
+ path: xpath,
+ method: method.map(x => x.toUpperCase()),
+ handler
+ });
});
+ });
+ }
+}
- paths.forEach(pathObj => {
- _.each(pathObj, (method, xpath) => {
- fastify.route({
- ...route.settings,
- path: xpath,
- method: method.map(x => x.toUpperCase()),
- handler
- });
+async function registerFastifyRoutesFromDir({ fastify, topOpts, routes }) {
+ checkSSRMetricsReporting(topOpts);
+
+ routes.forEach(routeInfo => {
+ const { route } = routeInfo;
+
+ const routeOptions = Object.assign(
+ {},
+ topOpts,
+ _.pick(route, ["pageTitle", "bundleChunkSelector", "templateFile", "selectTemplate"])
+ );
+
+ assert(
+ routeOptions.templateFile,
+ `subapp-server: route ${routeInfo.name} must define templateFile`
+ );
+ updateFullTemplate(routeInfo.dir, routeOptions);
+
+ const chunkSelector = resolveChunkSelector(routeOptions);
+
+ routeOptions.__internals = { chunkSelector };
+
+ const routeRenderer = templateRouting.makeRouteTemplateSelector(routeOptions);
+
+ const paths = getRoutePaths(route);
+
+ for (const pathObj of paths) {
+ _.each(pathObj, (method, xpath) => {
+ const routeHandler = makeRouteHandler({ path: xpath, routeRenderer, routeOptions });
+ fastify.route({
+ ...route.options,
+ path: xpath,
+ method: method.map(x => x.toUpperCase()),
+ handler: routeHandler
});
});
}
+ });
+}
+
+async function setupRoutesFromDir(fastify, srcDir, fromDir) {
+ const { routes, topOpts } = fromDir;
+
+ topOpts.routes = _.merge({}, routes, topOpts.routes);
+
+ const routesWithSetup = routes.filter(x => x.route.setup);
+
+ for (const route of routesWithSetup) {
+ await route.route.setup(fastify);
+ }
+
+ // TODO: invoke optional route intiailize hook
+
+ // in case needed, add full protocol/host/port to dev bundle base URL
+ topOpts.devBundleBase = subAppUtil.formUrl({
+ ..._.pick(topOpts.devServer, ["protocol", "host", "port"]),
+ path: topOpts.devBundleBase
+ });
+
+ await registerFastifyRoutesFromDir({ fastify, srcDir, topOpts, routes });
+}
+
+async function handleFavIcon(fastify, options) {
+ //
+ // favicon handling, turn off by setting options.favicon to false
+ //
+ if (options.favicon === false) {
+ return;
+ }
+
+ // look in CWD/static
+ let icon;
+
+ const favIcons = [options.favicon, "static/favicon.ico", "static/favicon.png"].filter(_.identity);
+ for (let i = 0; i < favIcons.length && !icon; i++) {
+ const file = Path.resolve(favIcons[i]);
+ icon = await xaa.try(() => readFile(file));
+ }
+
+ fastify.route({
+ method: "GET",
+ path: "/favicon.ico",
+ handler(request, reply) {
+ if (icon) {
+ reply.type("image/x-icon").send(icon).status(HttpStatusCodes.OK);
+ } else {
+ reply.send("").status(HttpStatusCodes.NOT_FOUND);
+ }
+ }
+ });
+}
+
+module.exports = {
+ fastifyPlugin: async (fastify, pluginOpts) => {
+ const srcDir = getSrcDir(pluginOpts);
+
+ await handleFavIcon(fastify, pluginOpts);
+
+ const fromDir = await routesFromDir.searchRoutes(srcDir, pluginOpts);
+ if (fromDir) {
+ return await setupRoutesFromDir(fastify, srcDir, fromDir);
+ }
+
+ const { routes, topOpts } = routesFromFile.searchRoutes(srcDir, pluginOpts);
+ // invoke setup callback
+ for (const path in routes) {
+ if (routes[path].setup) {
+ await routes[path].setup(fastify);
+ }
+ }
+
+ return registerFastifyRoutesFromFile({ fastify, srcDir, routes, topOpts });
}
};
diff --git a/packages/subapp-server/lib/http-status.ts b/packages/subapp-server/lib/http-status.ts
deleted file mode 100644
index 55e44d42c..000000000
--- a/packages/subapp-server/lib/http-status.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/* eslint-disable @typescript-eslint/ban-ts-ignore */
-// @ts-ignore
-import * as HttpStatusCode from "http-status-codes";
-
-export default {
- redirect: {
- [HttpStatusCode.MOVED_PERMANENTLY]: true,
- [HttpStatusCode.MOVED_TEMPORARILY]: true,
- [HttpStatusCode.PERMANENT_REDIRECT]: true,
- [HttpStatusCode.TEMPORARY_REDIRECT]: true
- }
-};
diff --git a/packages/subapp-server/lib/register-routes.js b/packages/subapp-server/lib/register-routes.js
index ea6738976..f0d91f6e6 100644
--- a/packages/subapp-server/lib/register-routes.js
+++ b/packages/subapp-server/lib/register-routes.js
@@ -5,14 +5,17 @@
const assert = require("assert");
const _ = require("lodash");
const HttpStatus = require("./http-status");
-const Routing = require("./routing");
+const templateRouting = require("./template-routing");
const { errorResponse, updateFullTemplate } = require("./utils");
const {
utils: { resolveChunkSelector }
} = require("@xarc/index-page");
const HttpStatusCodes = require("http-status-codes");
+const { checkSSRMetricsReporting } = require("./utils");
module.exports = function registerRoutes({ routes, topOpts, server }) {
+ checkSSRMetricsReporting(topOpts);
+
// register routes
routes.forEach(routeInfo => {
const { route } = routeInfo;
@@ -33,7 +36,7 @@ module.exports = function registerRoutes({ routes, topOpts, server }) {
routeOptions.__internals = { chunkSelector };
- const routeHandler = Routing.makeRouteHandler(routeOptions);
+ const routeHandler = templateRouting.makeRouteTemplateSelector(routeOptions);
const useStream = routeOptions.useStream !== false;
diff --git a/packages/subapp-server/lib/routes-from-dir.js b/packages/subapp-server/lib/routes-from-dir.js
new file mode 100644
index 000000000..8771dc255
--- /dev/null
+++ b/packages/subapp-server/lib/routes-from-dir.js
@@ -0,0 +1,70 @@
+"use strict";
+
+/* eslint-disable max-statements, complexity, global-require, no-magic-numbers, no-console */
+
+const _ = require("lodash");
+const Fs = require("fs");
+const Path = require("path");
+const assert = require("assert");
+const optionalRequire = require("optional-require")(require);
+const scanDir = require("filter-scan-dir");
+
+const { getDefaultRouteOptions, updateFullTemplate } = require("./utils");
+
+async function searchRoutes(srcDir, pluginOpts) {
+ const { loadRoutesFrom } = pluginOpts;
+
+ const routesDir = [
+ loadRoutesFrom && Path.resolve(srcDir, loadRoutesFrom),
+ Path.resolve(srcDir, "routes"),
+ Path.resolve(srcDir, "server", "routes"),
+ Path.resolve(srcDir, "server-routes")
+ ].find(x => x && Fs.existsSync(x) && Fs.statSync(x).isDirectory());
+
+ // there's no routes, server/routes, or server-routes dir
+ if (!routesDir) {
+ return undefined;
+ }
+
+ //
+ // look for routes under routesDir
+ // - each dir inside is considered to be a route with name being the dir name
+ //
+ const dirs = await scanDir({ dir: routesDir, includeRoot: true, filter: f => f === "route.js" });
+
+ //
+ // load options for all routes from routesDir/options.js[x]
+ //
+ const options = optionalRequire(Path.join(routesDir, "options"), { default: {} });
+
+ //
+ // Generate routes: load the route.js file for each route
+ //
+ const routes = dirs.map(x => {
+ const name = Path.dirname(x.substring(routesDir.length + 1));
+ const route = Object.assign({}, require(x));
+ _.defaults(route, {
+ // the route dir that's named default is /
+ path: name === "default" && "/",
+ methods: options.methods || ["get"]
+ });
+
+ assert(route.path, `subapp-server: route ${name} must define a path`);
+
+ return {
+ name,
+ dir: Path.dirname(x),
+ route
+ };
+ });
+
+ const topOpts = _.merge(getDefaultRouteOptions(), options, pluginOpts);
+
+ updateFullTemplate(routesDir, topOpts);
+
+ return { topOpts, options, dir: routesDir, routes };
+}
+
+module.exports = {
+ searchRoutes
+};
diff --git a/packages/subapp-server/lib/routes-from-file.js b/packages/subapp-server/lib/routes-from-file.js
new file mode 100644
index 000000000..4c59c0947
--- /dev/null
+++ b/packages/subapp-server/lib/routes-from-file.js
@@ -0,0 +1,82 @@
+"use strict";
+
+/* eslint-disable max-statements, complexity, global-require, no-magic-numbers, no-console */
+
+const _ = require("lodash");
+const Path = require("path");
+const optionalRequire = require("optional-require")(require);
+const templateRouting = require("./template-routing");
+const subAppUtil = require("subapp-util");
+
+const {
+ utils: { resolveChunkSelector }
+} = require("@xarc/index-page");
+
+const { getDefaultRouteOptions, updateFullTemplate } = require("./utils");
+
+function setupRouteTemplate({ subAppsByPath, srcDir, routeOptions }) {
+ updateFullTemplate(routeOptions.dir, routeOptions);
+ const chunkSelector = resolveChunkSelector(routeOptions);
+ routeOptions.__internals = { chunkSelector };
+
+ // load subapps for the route
+ if (routeOptions.subApps) {
+ routeOptions.__internals.subApps = [].concat(routeOptions.subApps).map(x => {
+ let options = {};
+ if (Array.isArray(x)) {
+ options = x[1];
+ x = x[0];
+ }
+ // absolute: use as path
+ // else: assume dir under srcDir
+ // TBD: handle it being a module
+ return {
+ subapp: subAppsByPath[Path.isAbsolute(x) ? x : Path.resolve(srcDir, x)],
+ options
+ };
+ });
+ }
+
+ // const useStream = routeOptions.useStream !== false;
+
+ const routeHandler = templateRouting.makeRouteTemplateSelector(routeOptions);
+
+ return routeHandler;
+}
+
+function searchRoutes(srcDir, pluginOpts) {
+ // there should be a src/routes.js file with routes spec
+ const { loadRoutesFrom } = pluginOpts;
+
+ const routesFile = [
+ loadRoutesFrom && Path.resolve(srcDir, loadRoutesFrom),
+ Path.resolve(srcDir, "routes")
+ ].find(x => x && optionalRequire(x));
+
+ const spec = routesFile ? require(routesFile) : {};
+
+ const topOpts = _.merge(
+ getDefaultRouteOptions(),
+ { dir: Path.resolve(srcDir) },
+ _.omit(spec, ["routes", "default"]),
+ pluginOpts
+ );
+
+ topOpts.routes = _.merge({}, spec.routes || spec.default, topOpts.routes);
+
+ // routes can either be in default (es6) or routes
+ const routes = topOpts.routes;
+
+ // in case needed, add full protocol/host/port to dev bundle base URL
+ topOpts.devBundleBase = subAppUtil.formUrl({
+ ..._.pick(topOpts.devServer, ["protocol", "host", "port"]),
+ path: topOpts.devBundleBase
+ });
+
+ return { routes, topOpts };
+}
+
+module.exports = {
+ searchRoutes,
+ setupRouteTemplate
+};
diff --git a/packages/subapp-server/lib/setup-hapi-routes.js b/packages/subapp-server/lib/setup-hapi-routes.js
index f7d4c32ec..0ca7eeda4 100644
--- a/packages/subapp-server/lib/setup-hapi-routes.js
+++ b/packages/subapp-server/lib/setup-hapi-routes.js
@@ -7,78 +7,17 @@
const _ = require("lodash");
const Fs = require("fs");
const Path = require("path");
-const assert = require("assert");
const util = require("util");
-const optionalRequire = require("optional-require")(require);
-const scanDir = require("filter-scan-dir");
const Boom = require("@hapi/boom");
const HttpStatus = require("./http-status");
const readFile = util.promisify(Fs.readFile);
const xaa = require("xaa");
-const Routing = require("./routing");
const subAppUtil = require("subapp-util");
const registerRoutes = require("./register-routes");
-const {
- utils: { resolveChunkSelector }
-} = require("@xarc/index-page");
-
-const {
- errorResponse,
- getDefaultRouteOptions,
- updateFullTemplate,
- checkSSRMetricsReporting
-} = require("./utils");
-
-async function searchRoutesDir(srcDir, pluginOpts) {
- const { loadRoutesFrom } = pluginOpts;
-
- const routesDir = [
- loadRoutesFrom && Path.resolve(srcDir, loadRoutesFrom),
- Path.resolve(srcDir, "routes"),
- Path.resolve(srcDir, "server", "routes"),
- Path.resolve(srcDir, "server-routes")
- ].find(x => x && Fs.existsSync(x) && Fs.statSync(x).isDirectory());
-
- // there's no routes, server/routes, or server-routes dir
- if (!routesDir) {
- return undefined;
- }
-
- //
- // look for routes under routesDir
- // - each dir inside is considered to be a route with name being the dir name
- //
- const dirs = await scanDir({ dir: routesDir, includeRoot: true, filter: f => f === "route.js" });
-
- //
- // load options for all routes from routesDir/options.js[x]
- //
- const options = optionalRequire(Path.join(routesDir, "options"), { default: {} });
-
- //
- // Generate routes: load the route.js file for each route
- //
- const routes = dirs.map(x => {
- const name = Path.dirname(x.substring(routesDir.length + 1));
- const route = Object.assign({}, require(x));
- _.defaults(route, {
- // the route dir that's named default is /
- path: name === "default" && "/",
- methods: options.methods || ["get"]
- });
-
- assert(route.path, `subapp-server: route ${name} must define a path`);
-
- return {
- name,
- dir: Path.dirname(x),
- route
- };
- });
-
- return { options, dir: routesDir, routes };
-}
+const routesFromFile = require("./routes-from-file");
+const routesFromDir = require("./routes-from-dir");
+const { errorResponse, getSrcDir } = require("./utils");
async function handleFavIcon(server, options) {
//
@@ -102,37 +41,7 @@ async function handleFavIcon(server, options) {
});
}
-function setupRouteRender({ subAppsByPath, srcDir, routeOptions }) {
- updateFullTemplate(routeOptions.dir, routeOptions);
- const chunkSelector = resolveChunkSelector(routeOptions);
- routeOptions.__internals = { chunkSelector };
-
- // load subapps for the route
- if (routeOptions.subApps) {
- routeOptions.__internals.subApps = [].concat(routeOptions.subApps).map(x => {
- let options = {};
- if (Array.isArray(x)) {
- options = x[1];
- x = x[0];
- }
- // absolute: use as path
- // else: assume dir under srcDir
- // TBD: handle it being a module
- return {
- subapp: subAppsByPath[Path.isAbsolute(x) ? x : Path.resolve(srcDir, x)],
- options
- };
- });
- }
-
- // const useStream = routeOptions.useStream !== false;
-
- const routeHandler = Routing.makeRouteHandler(routeOptions);
-
- return routeHandler;
-}
-
-async function registerHapiRoutes({ server, srcDir, routes, topOpts }) {
+async function registerRoutesFromFile({ server, srcDir, routes, topOpts }) {
const subApps = await subAppUtil.scanSubAppsFromDir(srcDir);
const subAppsByPath = subAppUtil.getSubAppByPathMap(subApps);
@@ -152,7 +61,13 @@ async function registerHapiRoutes({ server, srcDir, routes, topOpts }) {
const route = routes[path];
const routeOptions = Object.assign({}, topOpts, route);
- const routeRenderer = setupRouteRender({ subAppsByPath, srcDir, routeOptions });
+ // setup the template for rendering the route
+ const routeRenderer = routesFromFile.setupRouteTemplate({
+ subAppsByPath,
+ srcDir,
+ routeOptions
+ });
+
const useStream = routeOptions.useStream !== false;
const handler = async (request, h) => {
@@ -215,42 +130,8 @@ async function registerHapiRoutes({ server, srcDir, routes, topOpts }) {
}
}
-function searchRoutesFromFile(srcDir, pluginOpts) {
- // there should be a src/routes.js file with routes spec
- const { loadRoutesFrom } = pluginOpts;
-
- const routesFile = [
- loadRoutesFrom && Path.resolve(srcDir, loadRoutesFrom),
- Path.resolve(srcDir, "routes")
- ].find(x => x && optionalRequire(x));
-
- const spec = routesFile ? require(routesFile) : {};
-
- const topOpts = _.merge(
- getDefaultRouteOptions(),
- { dir: Path.resolve(srcDir) },
- _.omit(spec, ["routes", "default"]),
- pluginOpts
- );
-
- topOpts.routes = _.merge({}, spec.routes || spec.default, topOpts.routes);
-
- // routes can either be in default (es6) or routes
- const routes = topOpts.routes;
-
- // in case needed, add full protocol/host/port to dev bundle base URL
- topOpts.devBundleBase = subAppUtil.formUrl({
- ..._.pick(topOpts.devServer, ["protocol", "host", "port"]),
- path: topOpts.devBundleBase
- });
-
- return { routes, topOpts };
-}
-
async function setupRoutesFromFile(srcDir, server, pluginOpts) {
- const { routes, topOpts } = searchRoutesFromFile(srcDir, pluginOpts);
-
- checkSSRMetricsReporting(topOpts);
+ const { routes, topOpts } = routesFromFile.searchRoutes(srcDir, pluginOpts);
await handleFavIcon(server, topOpts);
@@ -261,7 +142,7 @@ async function setupRoutesFromFile(srcDir, server, pluginOpts) {
}
}
- await registerHapiRoutes({
+ await registerRoutesFromFile({
server,
routes,
topOpts,
@@ -269,17 +150,11 @@ async function setupRoutesFromFile(srcDir, server, pluginOpts) {
});
}
-async function setupRoutesFromDir(server, pluginOpts, fromDir) {
- const { routes } = fromDir;
-
- const topOpts = _.merge(getDefaultRouteOptions(), fromDir.options, pluginOpts);
-
- checkSSRMetricsReporting(topOpts);
+async function setupRoutesFromDir(server, fromDir) {
+ const { routes, topOpts } = fromDir;
topOpts.routes = _.merge({}, routes, topOpts.routes);
- updateFullTemplate(fromDir.dir, topOpts);
-
const routesWithSetup = routes.filter(x => x.route.setup);
for (const route of routesWithSetup) {
@@ -310,20 +185,12 @@ async function setupRoutesFromDir(server, pluginOpts, fromDir) {
registerRoutes({ routes, topOpts, server });
}
-function getSrcDir(pluginOpts) {
- return (
- pluginOpts.srcDir ||
- process.env.APP_SRC_DIR ||
- (process.env.NODE_ENV === "production" ? "lib" : "src")
- );
-}
-
async function setupSubAppHapiRoutes(server, pluginOpts) {
const srcDir = getSrcDir(pluginOpts);
- const fromDir = await searchRoutesDir(srcDir, pluginOpts);
+ const fromDir = await routesFromDir.searchRoutes(srcDir, pluginOpts);
if (fromDir) {
- return await setupRoutesFromDir(server, pluginOpts, fromDir);
+ return await setupRoutesFromDir(server, fromDir);
}
// no directory based routes, then they must in a JS file
@@ -331,10 +198,6 @@ async function setupSubAppHapiRoutes(server, pluginOpts) {
}
module.exports = {
- getSrcDir,
- searchRoutesDir,
- searchRoutesFromFile,
setupRoutesFromFile,
- setupSubAppHapiRoutes,
- setupRouteRender
+ setupSubAppHapiRoutes
};
diff --git a/packages/subapp-server/lib/routing.js b/packages/subapp-server/lib/template-routing.js
similarity index 97%
rename from packages/subapp-server/lib/routing.js
rename to packages/subapp-server/lib/template-routing.js
index 0581ca155..039ba1dc1 100644
--- a/packages/subapp-server/lib/routing.js
+++ b/packages/subapp-server/lib/template-routing.js
@@ -78,7 +78,7 @@ function initializeTemplate(
return (routeOptions._templateCache[cacheKey] = asyncTemplate);
}
-function makeRouteHandler(routeOptions) {
+function makeRouteTemplateSelector(routeOptions) {
routeOptions._templateCache = {};
let defaultSelection;
@@ -191,4 +191,4 @@ const setupPathOptions = (routeOptions, path) => {
);
};
-module.exports = { setupOptions, setupPathOptions, makeRouteHandler };
+module.exports = { setupOptions, setupPathOptions, makeRouteTemplateSelector };
diff --git a/packages/subapp-server/lib/utils.js b/packages/subapp-server/lib/utils.js
index 9da0b6193..24f5740cd 100644
--- a/packages/subapp-server/lib/utils.js
+++ b/packages/subapp-server/lib/utils.js
@@ -124,7 +124,16 @@ function invokeTemplateProcessor(asyncTemplate, routeOptions) {
return undefined;
}
+function getSrcDir(pluginOpts) {
+ return (
+ pluginOpts.srcDir ||
+ process.env.APP_SRC_DIR ||
+ (process.env.NODE_ENV === "production" ? "lib" : "src")
+ );
+}
+
module.exports = {
+ getSrcDir,
getDefaultRouteOptions,
updateFullTemplate,
errorResponse,
diff --git a/packages/subapp-server/src/index-page.jsx b/packages/subapp-server/src/index-page.jsx
index a124c782f..5769e0519 100644
--- a/packages/subapp-server/src/index-page.jsx
+++ b/packages/subapp-server/src/index-page.jsx
@@ -43,7 +43,6 @@ const RenderSubApps = (props, context) => {
const Template = (
Not Found
"
- }
- };
- });
+ stubRouteHandler = sinon
+ .stub(templateRouting, "makeRouteTemplateSelector")
+ .callsFake(() => async () => {
+ return {
+ result: {
+ status: 404,
+ path: "/file1",
+ html: "Not Found
"
+ }
+ };
+ });
await setupSubAppHapiRoutes(server, {});
await server.start();
const { result, statusCode } = await server.inject({
@@ -154,14 +158,16 @@ describe("setupSubAppHapiRoutes", () => {
it("should let the server reply data object if status code = 404 and no html set", async () => {
stubPathResolve = getStubResolve1();
- stubRouteHandler = sinon.stub(Routing, "makeRouteHandler").callsFake(() => async () => {
- return {
- result: {
- status: 404,
- path: "/file1"
- }
- };
- });
+ stubRouteHandler = sinon
+ .stub(templateRouting, "makeRouteTemplateSelector")
+ .callsFake(() => async () => {
+ return {
+ result: {
+ status: 404,
+ path: "/file1"
+ }
+ };
+ });
await setupSubAppHapiRoutes(server, {});
await server.start();
const { result, statusCode } = await server.inject({
@@ -174,15 +180,17 @@ describe("setupSubAppHapiRoutes", () => {
it("should let the server reply html if status code = 200", async () => {
stubPathResolve = getStubResolve1();
- stubRouteHandler = sinon.stub(Routing, "makeRouteHandler").callsFake(() => async () => {
- return {
- result: {
- status: 200,
- path: "/file1",
- html: "hello
"
- }
- };
- });
+ stubRouteHandler = sinon
+ .stub(templateRouting, "makeRouteTemplateSelector")
+ .callsFake(() => async () => {
+ return {
+ result: {
+ status: 200,
+ path: "/file1",
+ html: "hello
"
+ }
+ };
+ });
await setupSubAppHapiRoutes(server, {});
await server.start();
const { result, statusCode } = await server.inject({
@@ -195,14 +203,16 @@ describe("setupSubAppHapiRoutes", () => {
it("should let the server reply data object if status code = 200 and no html set", async () => {
stubPathResolve = getStubResolve1();
- stubRouteHandler = sinon.stub(Routing, "makeRouteHandler").callsFake(() => async () => {
- return {
- result: {
- status: 200,
- path: "/file1"
- }
- };
- });
+ stubRouteHandler = sinon
+ .stub(templateRouting, "makeRouteTemplateSelector")
+ .callsFake(() => async () => {
+ return {
+ result: {
+ status: 200,
+ path: "/file1"
+ }
+ };
+ });
await setupSubAppHapiRoutes(server, {});
await server.start();
const { result, statusCode } = await server.inject({
@@ -215,15 +225,17 @@ describe("setupSubAppHapiRoutes", () => {
it("should let the server reply data object if status code is 505", async () => {
stubPathResolve = getStubResolve1();
- stubRouteHandler = sinon.stub(Routing, "makeRouteHandler").callsFake(() => async () => {
- return {
- result: {
- status: 505,
- path: "/file1",
- html: "hello
"
- }
- };
- });
+ stubRouteHandler = sinon
+ .stub(templateRouting, "makeRouteTemplateSelector")
+ .callsFake(() => async () => {
+ return {
+ result: {
+ status: 505,
+ path: "/file1",
+ html: "hello
"
+ }
+ };
+ });
await setupSubAppHapiRoutes(server, {});
await server.start();
const { result } = await server.inject({
@@ -236,9 +248,11 @@ describe("setupSubAppHapiRoutes", () => {
it("should let the server reply error stack if routeHandler throw an error", async () => {
stubPathResolve = getStubResolve1();
- stubRouteHandler = sinon.stub(Routing, "makeRouteHandler").callsFake(() => async () => {
- throw new Error();
- });
+ stubRouteHandler = sinon
+ .stub(templateRouting, "makeRouteTemplateSelector")
+ .callsFake(() => async () => {
+ throw new Error();
+ });
const logs = [];
const stubConsoleError = sinon.stub(console, "error").callsFake(c => logs.push(c));
await setupSubAppHapiRoutes(server, {});
@@ -260,9 +274,11 @@ describe("setupSubAppHapiRoutes", () => {
it("should let the server reply error stack if routeHandler returns an error as a result", async () => {
stubPathResolve = getStubResolve1();
- stubRouteHandler = sinon.stub(Routing, "makeRouteHandler").callsFake(() => async () => ({
- result: new Error("Dev error here")
- }));
+ stubRouteHandler = sinon
+ .stub(templateRouting, "makeRouteTemplateSelector")
+ .callsFake(() => async () => ({
+ result: new Error("Dev error here")
+ }));
const logs = [];
const stubConsoleError = sinon.stub(console, "error").callsFake(c => logs.push(c));
await setupSubAppHapiRoutes(server, {});
diff --git a/packages/xarc-index-page/src/token-handlers.ts b/packages/xarc-index-page/src/token-handlers.ts
index 00b2d8138..ff0bd7ad9 100644
--- a/packages/xarc-index-page/src/token-handlers.ts
+++ b/packages/xarc-index-page/src/token-handlers.ts
@@ -102,7 +102,7 @@ export default function setup(handlerContext /*, asyncTemplate*/) {
};
if (content.useStream || isReadableStream(content.html)) {
- context.setStandardMunchyOutput();
+ context.setMunchyOutput();
}
context.setOutputTransform(transformOutput);
diff --git a/packages/xarc-render-context/package.json b/packages/xarc-render-context/package.json
index e5499e31a..0c88b05aa 100644
--- a/packages/xarc-render-context/package.json
+++ b/packages/xarc-render-context/package.json
@@ -29,6 +29,7 @@
"eslint-plugin-jsdoc": "^21.0.0",
"mocha": "^7.1.0",
"nyc": "^15.0.0",
+ "run-verify": "^1.2.5",
"sinon": "^7.2.6",
"sinon-chai": "^3.3.0",
"source-map-support": "^0.5.16",
diff --git a/packages/xarc-render-context/src/RenderContext.ts b/packages/xarc-render-context/src/RenderContext.ts
index 1591e0279..c38648bc5 100644
--- a/packages/xarc-render-context/src/RenderContext.ts
+++ b/packages/xarc-render-context/src/RenderContext.ts
@@ -6,24 +6,7 @@
import { RenderOutput } from "./RenderOutput";
import * as Munchy from "munchy";
-
-const munchyHandleStreamError = err => {
- let errMsg = (process.env.NODE_ENV !== "production" && err.stack) || err.message;
-
- if (process.cwd().length > 3) {
- errMsg = (errMsg || "").replace(new RegExp(process.cwd(), "g"), "CWD");
- }
-
- return {
- result: `
-SSR ERROR
-${errMsg}
-
+${errMsg} +`, + remit: false + }; +}; + +export const isReadableStream = x => Boolean(x && x.pipe && x.on && x._readableState); diff --git a/packages/xarc-render-context/test/spec/render-context.spec.ts b/packages/xarc-render-context/test/spec/render-context.spec.ts index 726659d05..7c7295d50 100644 --- a/packages/xarc-render-context/test/spec/render-context.spec.ts +++ b/packages/xarc-render-context/test/spec/render-context.spec.ts @@ -34,13 +34,13 @@ describe("render-context", function () { describe("munchy output", function () { it("call setDefaultMunchyOutput() with no arga", function () { const context = new RenderContext({}, {}); - context.setStandardMunchyOutput(); + context.setMunchyOutput(); expect(context.munchy).to.exist; }); it("should print output to munchy", function () { const context = new RenderContext({}, {}); - context.setStandardMunchyOutput(); + context.setMunchyOutput(); const munchyoutput = new PassThrough(); context.munchy.pipe(munchyoutput); munchyoutput.on("data", data => { @@ -51,25 +51,6 @@ describe("munchy output", function () { ro.add("foo"); ro.flush(); }); - it("should return error message", function () { - // process.env.NODE_ENV = "production"; - // context.setDefaultMunchyOutput(); - // const { result } = munchyHandleStreamError(new Error("Error1")); - // expect(result).to.contain("Error1"); - // const output = munchyHandleStreamError(new Error()); - // expect(output.result).to.contain("SSR ERROR"); - }); - it("should return stack trace on non-production", function () { - // process.env.NODE_ENV = "development"; - // const { result } = munchyHandleStreamError(new Error("e")); - // expect(result).to.contain("CWD"); - }); - it("not replace process.cwd() with CWD", function () { - // process.chdir("/"); - // process.env.NODE_ENV = "development"; - // const { result } = munchyHandleStreamError(new Error("e")); - // expect(result).to.not.contain("CWD"); - }); it("should store token handlers in a map", function () { process.env.NODE_ENV = "production"; diff --git a/packages/xarc-render-context/test/spec/render-output.spec.ts b/packages/xarc-render-context/test/spec/render-output.spec.ts index 4fdd8b8e0..767908b0c 100644 --- a/packages/xarc-render-context/test/spec/render-output.spec.ts +++ b/packages/xarc-render-context/test/spec/render-output.spec.ts @@ -5,9 +5,12 @@ import { RenderOutput } from "../../src"; import * as Munchy from "munchy"; import * as streamToArray from "stream-to-array"; +import { describe, it } from "mocha"; +import { asyncVerify, expectError } from "run-verify"; import { expect } from "chai"; import { makeDefer } from "xaa"; + describe("render-output", function () { it("should flush simple string", () => { let text; @@ -201,6 +204,7 @@ describe("render-output", function () { ro.add(item); expect(() => ro.flush()).to.throw("unable to stringify item of type object"); }); + it("should re-throw error in _finish()", async () => { const ro2 = new RenderOutput({ munchy: null, @@ -210,21 +214,26 @@ describe("render-output", function () { }); ro2._defer = makeDefer(); ro2.add("item"); - try { - await ro2._finish(); - } catch (e) { - expect(e.message).to.equal("new Error"); - } - ro2._defer = null; - ro2._context.munchy = { - munch: () => { - throw new Error("new error2"); + return asyncVerify( + expectError(() => { + setTimeout(() => ro2._finish(), 1); + return ro2._defer.promise; + }), + err => expect(err.message).to.equal("new Error"), + () => { + ro2._defer = null; + ro2._context.munchy = { + munch: () => { + throw new Error("new error2"); + } + }; + }, + expectError(() => { + return ro2._finish(); + }), + err => { + expect(err.message).to.equal("new error2"); } - }; - try { - await ro2._finish(); - } catch (e) { - expect(e.message).to.equal("new error2"); - } + ); }); }); diff --git a/packages/xarc-render-context/test/spec/utils.spec.ts b/packages/xarc-render-context/test/spec/utils.spec.ts new file mode 100644 index 000000000..5062173a5 --- /dev/null +++ b/packages/xarc-render-context/test/spec/utils.spec.ts @@ -0,0 +1,43 @@ +import { munchyHandleStreamError } from "../../src/utils"; + +import { describe, it } from "mocha"; +import { expect } from "chai"; + +describe("utils munchyHandleStreamError", function () { + let saveEnv; + + before(() => { + saveEnv = process.env.NODE_ENV; + }); + + afterEach(() => { + if (saveEnv) { + process.env.NODE_ENV = saveEnv; + } else { + delete process.env.NODE_ENV; + } + }); + + it("should return only error message in production", function () { + process.env.NODE_ENV = "production"; + const { result } = munchyHandleStreamError(new Error("Error1")); + expect(result).contains(`Error1 +`); + }); + + it("should return stack trace on non-production", function () { + process.env.NODE_ENV = "development"; + const { result } = munchyHandleStreamError(new Error("e")); + expect(result).contains("test/spec/utils.spec.ts"); // stack + }); + + it("should not replace with CWD if it's less than 3 chars", function () { + const { result } = munchyHandleStreamError(new Error("1"), "/a"); + expect(result).to.not.contain("CWD"); + }); + + it("should handle empty error message", () => { + const { result } = munchyHandleStreamError(new Error("")); + expect(result).to.not.contain("CWD"); + }); +}); diff --git a/packages/xarc-tag-renderer/package.json b/packages/xarc-tag-renderer/package.json index 82a091cb6..b164d9551 100644 --- a/packages/xarc-tag-renderer/package.json +++ b/packages/xarc-tag-renderer/package.json @@ -49,7 +49,7 @@ }, "files": [ "dist", - "lib" + "src" ], "dependencies": { "ts-node": "^8.10.2"