UNPKG

infrastructure-components

Version:

Infrastructure-Components configure the infrastructure of your React-App as part of your React-Components.

292 lines (289 loc) • 15.3 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); // this must be imported to allow async-functions within an AWS lambda environment // see: https://github.com/babel/babel/issues/5085 require("@babel/polyfill"); const server_1 = __importDefault(require("react-dom/server")); const express_1 = __importDefault(require("express")); const serverless_http_1 = __importDefault(require("serverless-http")); const connect_sequence_1 = __importDefault(require("connect-sequence")); // make the styled-components work with server-side rendering const styled_components_1 = require("styled-components"); // <-- importing ServerStyleSheet const react_router_1 = require("react-router"); const react_helmet_1 = __importDefault(require("react-helmet")); const routed_app_1 = require("./routed-app"); const iso_libs_1 = require("../libs/iso-libs"); const attach_data_layer_1 = require("./attach-data-layer"); const types_1 = __importDefault(require("../types")); const loader_1 = require("../libs/loader"); const datalayer_integration_1 = require("./datalayer-integration"); const graphql_1 = require("graphql"); const server_libs_1 = require("../libs/server-libs"); //import {loadIsoConfigFromComponent, applyCustomComponents} from "../isolib"; //import { applyAppClientModules } from '../types/client-app-config'; const createServer = (assetsDir, resolvedAssetsPath, isomorphicId) => { // express is the web-framework that lets us configure the endpoints const app = express_1.default(); // in production, API-Gateway proxy redirects the request to S3 // serve static files - the async components of the server - only used of localhost app.use('/' + assetsDir, express_1.default.static(resolvedAssetsPath)); // load the IsomorphicComponent // we must load it directly from the module here, to enable the aliad of the config_file_path const isoConfig = loader_1.loadConfigurationFromModule(require('__CONFIG_FILE_PATH__'), loader_1.INFRASTRUCTURE_MODES.RUNTIME); // let's extract it from the root configuration const isoApp = loader_1.extractObject(isoConfig, types_1.default.INFRASTRUCTURE_TYPE_CONFIGURATION, isomorphicId); // connect the middlewares isoApp.middlewares.map(mw => app.use(mw.callback)); // let's extract it from the root configuration const dataLayer = loader_1.extractObject(isoConfig, types_1.default.INFRASTRUCTURE_TYPE_COMPONENT, __DATALAYER_ID__); if (dataLayer) { console.log("Datalayer Active: ", dataLayer.id); app.use('/query', (req, res, next) => __awaiter(this, void 0, void 0, function* () { console.log(req.body); const parsedBody = JSON.parse(req.body); console.log(parsedBody); yield graphql_1.graphql(dataLayer.getSchema(false), parsedBody.query).then(result_type => { const entryQueryName = Object.keys(result_type.data)[0]; // when the query resolves, we get back console.log("pre-resolve | found entry: ", entryQueryName); new connect_sequence_1.default(req, res, next) .append(...dataLayer.entries.filter(entry => entry.providesQuery(entryQueryName)).map(entry => entry.middleware.callback)) .append((req, res, next) => __awaiter(this, void 0, void 0, function* () { //console.log("DL-mw: req: "); //const parsedBody = JSON.parse(req.body); //console.log("parsedBody: ", parsedBody); // now we let the schema resolve with data yield graphql_1.graphql(dataLayer.getSchema(true), parsedBody.query).then(result => res.status(200).set({ "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": true // Required for cookies, authorization headers with HTTPS }).send(JSON.stringify(result)), err => res.set({ "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": true // Required for cookies, authorization headers with HTTPS }).status(500).send(err)); })) .run(); }, err => res.set({ "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": true // Required for cookies, authorization headers with HTTPS }).status(500).send(err)); })); } ; // flattens the callbacks const unpackMiddlewares = (middlewares) => { // always returns the list of callbacks const cbList = (mw) => Array.isArray(mw.callback) ? mw.callback : [mw.callback]; return middlewares.reduce((res, mw) => res.concat(...cbList(mw)), dataLayer ? [ // when we have a dataLayer, let's attach it to the request attach_data_layer_1.serviceAttachDataLayer(dataLayer) ] : []); }; // split the clientApps here and define a function for each of the clientApps, with the right middleware isoApp.services.map(service => { console.log("found service: ", service); if (service.method.toUpperCase() == "GET") { app.get(service.path, ...unpackMiddlewares(service.middlewares)); } else if (service.method.toUpperCase() == "POST") { app.post(service.path, ...unpackMiddlewares(service.middlewares)); } else if (service.method.toUpperCase() == "PUT") { app.put(service.path, ...unpackMiddlewares(service.middlewares)); } else if (service.method.toUpperCase() == "DELETE") { app.delete(service.path, ...unpackMiddlewares(service.middlewares)); } return service; }); console.log("webApps: ", isoApp.webApps.length, " -> ", isoApp.webApps); // split the clientApps here and define a function for each of the clientApps, with the right middleware isoApp.webApps //.filter(clientApp => clientApp.middlewares !== undefined) .map(clientApp => { const serveMiddleware = (req, res, next) => serve(req, res, next, clientApp, assetsDir, isoConfig); const routes = clientApp.routes.filter(route => route.middlewares !== undefined && route.middlewares.length > 0); if (clientApp.method.toUpperCase() == "GET") { app.get(clientApp.path, ...unpackMiddlewares(clientApp.middlewares)); routes.forEach(route => app.get(route.path, ...unpackMiddlewares(route.middlewares))); app.get(clientApp.path, serveMiddleware); } else if (clientApp.method.toUpperCase() == "POST") { app.post(clientApp.path, ...unpackMiddlewares(clientApp.middlewares)); routes.forEach(route => app.post(route.path, ...unpackMiddlewares(route.middlewares))); app.post(clientApp.path, serveMiddleware); } else if (clientApp.method.toUpperCase() == "PUT") { app.put(clientApp.path, ...unpackMiddlewares(clientApp.middlewares)); routes.forEach(route => app.put(route.path, ...unpackMiddlewares(route.middlewares))); app.put(clientApp.path, serveMiddleware); } else if (clientApp.method.toUpperCase() == "DELETE") { app.delete(clientApp.path, ...unpackMiddlewares(clientApp.middlewares)); routes.forEach(route => app.delete(route.path, ...unpackMiddlewares(route.middlewares))); app.delete(clientApp.path, serveMiddleware); } return clientApp; }); return app; }; function serve(req, res, next, clientApp, assetsDir, isoConfig) { return __awaiter(this, void 0, void 0, function* () { //TODO use try catch depending on the environment //try { //context is used by react router, empty by default let context = {}; const basename = iso_libs_1.getBasename(); // creating the stylesheet const sheet = new styled_components_1.ServerStyleSheet(); const parsedUrl = req.url.indexOf("?") >= 0 ? req.url.substring(0, req.url.indexOf("?")) : req.url; console.log("parsedUrl: ", parsedUrl); ////////// TODO refactor var foundPath = undefined; // match request url to our React Router paths and grab the path-params let matchResult = clientApp.routes.find(({ path, exact }) => { foundPath = react_router_1.matchPath(parsedUrl, { path, exact, strict: false }); return foundPath; }) || {}; let { path } = matchResult; //console.log("found: ", foundPath); console.log("server: path params: ", foundPath ? foundPath.params : "---"); const routePath = foundPath ? (foundPath.path.indexOf("/:") > 0 ? foundPath.path.substring(0, foundPath.path.indexOf("/:")) : foundPath.path) : ""; //console.log("routePath: ", routePath); ////////// END OF REFACTORING required //console.log("app data layer id: ", clientApp.dataLayerId); const fConnectWithDataLayer = clientApp.dataLayerId !== undefined ? datalayer_integration_1.connectWithDataLayer(clientApp.dataLayerId, req) : function (app) { return __awaiter(this, void 0, void 0, function* () { console.log("default dummy data layer"); return { connectedApp: app, getState: () => "" }; }); }; // create the app and connect it with the DataAbstractionLayer yield fConnectWithDataLayer(routed_app_1.createServerApp(clientApp.routes, clientApp.redirects, basename, req.url, context, req, require('infrastructure-components').getAuthCallback(isoConfig, clientApp.authenticationId))).then(({ connectedApp, getState }) => { //console.log("resolved...") // collect the styles from the connected app const htmlData = server_1.default.renderToString(sheet.collectStyles(connectedApp)); // getting all the tags from the sheet const styles = sheet.getStyleTags(); //render helmet data aka meta data in <head></head> const helmetData = react_helmet_1.default.renderStatic(); const fRender = clientApp.renderHtmlPage ? clientApp.renderHtmlPage : renderHtmlPage; // render a page with the state and return it in the response res.status(200).send(fRender(htmlData, styles, getState(), helmetData, basename, routePath, clientApp, assetsDir)).end(); }); }); } /** * * This functions puts together the whole Html * * - style as collected from the styled-components * - head-meta data: from helmet * - data state: from the DAL * - basename: to let the client know * * The html loads the script from the path where we find the assets. This is part of the config, thus load it * using `require('../config').pathToAssets(process.env.STAGE_PATH)` * * The script loading the app.bundle.js uses the window location in order to find out whether there is a slash * * //TODO the app.bundle.js depends on the name "app". Paramterize this! * * //TODO: apply the same base style as the client does * * when we are in a sub-route, e.g. when we have path parameters, we need to add ../ to the path to the assets * * Routing to the Assets * * entered url | basename==/ | basename==/dev * --------------------------------------------- * (none) / /dev * / / /dev * (dev)/ / / * (dev)/route / / * (dev)/route/ ../ ../ * (dev)/route/:var ../ ../ * TODO what happens with more than one path parameter? * * * @param host the host of the request * @param html * @param styles * @param preloadedState the state in form of a script * @param helmet */ function renderHtmlPage(html, styles, preloadedState, helmet, basename, routePath, clientApp, assetsDir) { //<link rel="icon" href="/assets/favicon.ico" type="image/ico" /> console.log(preloadedState); const path = require('path'); const calcBase = () => { return path.join(basename, routePath); }; //For each"/" in the entered path after the basename, we need to add "../" to the assets-path //when there is a basename, it must be added console.log("calcBase: ", calcBase()); return `<!doctype html> <html> <head> <meta charset="utf-8" /> ${helmet.title.toString()} ${helmet.meta.toString()} ${helmet.link.toString()} <meta name="viewport" content="width=device-width, initial-scale=1.0"> ${styles} <style> body { display: block; margin: 0px; } </style> </head> <body> <div id="root">${html.trim()}</div> <script> ${preloadedState} window.__BASENAME__ = "${basename}"; </script> <script> var loadscript = document.createElement('script'); function getPath() { console.log( window.location.pathname); const basePath = ${basename !== "/" ? "window.location.pathname.startsWith(\"" + basename + "\") ? \"\": \"/\" " : "\"\""}; const routePath= "${routePath !== "/" ? routePath : ""}"; const pre = window.location.pathname.startsWith(basePath+routePath+"/") ? ".." : ""; return pre+"${path.join(basename, assetsDir, server_libs_1.getClientFilename(clientApp.id))}"; } loadscript.setAttribute('src',getPath()); document.body.appendChild(loadscript); </script> </body> </html>`; } // we're exporting the handler-function as default, must match the sls-config! //export default (assetsDir, resolvedAssetsPath) => serverless(createServer(assetsDir, resolvedAssetsPath)); /* const serverIndexPath = path.join(serverPath, "index.js"); fs.writeFileSync(serverIndexPath, `const lib = require ('./server'); const server = lib.default('${ssrConfig.assetsPath}', '${resolveAssetsPath(ssrConfig)}'); exports.default = server;*/ // these variables are replaced during compilation exports.default = serverless_http_1.default(createServer(__ASSETS_PATH__, __RESOLVED_ASSETS_PATH__, __ISOMORPHIC_ID__)); //# sourceMappingURL=server.js.map