UNPKG

subapp-server

Version:

Electrode SubApp app server support

205 lines (172 loc) 6.25 kB
"use strict"; const _ = require("lodash"); const Path = require("path"); const { TagRenderer } = require("@xarc/tag-renderer"); const { JsxRenderer, xarcJsxElement } = require("@xarc/jsx-renderer"); const { loadTokenModuleHandler } = require("@xarc/render-context"); const { utils: { resolvePath, getOtherStats, getOtherAssets, resolveChunkSelector, loadAssetsFromStats, getStatsPath, makeDevBundleBase } } = require("@xarc/index-page"); const assert = require("assert"); const otherStats = getOtherStats(); /*eslint-disable max-statements*/ function initializeTemplate( { templateFile, tokenHandlers, cacheId, cacheKey, options }, routeOptions ) { cacheKey = cacheKey || `${templateFile}#${cacheId}#${routeOptions.path}`; let asyncTemplate = routeOptions._templateCache[cacheKey]; if (asyncTemplate) { return asyncTemplate; } if (options) { routeOptions = Object.assign({}, routeOptions, options); } const userTokenHandlers = [] .concat(tokenHandlers, routeOptions.tokenHandler, routeOptions.tokenHandlers) .filter(x => x); let finalTokenHandlers = userTokenHandlers; // Inject the built-in react/token-handlers if it is not in user's handlers // and replaceTokenHandlers option is false if (!routeOptions.replaceTokenHandlers) { const reactTokenHandlers = require.resolve("@xarc/index-page"); finalTokenHandlers = userTokenHandlers.indexOf(reactTokenHandlers) < 0 ? [reactTokenHandlers].concat(userTokenHandlers) : userTokenHandlers; } const templateFullPath = resolvePath(templateFile); const templateModule = require(templateFullPath); // eslint-disable-line const templateExp = templateModule.templateTags || templateModule.default || templateModule; const template = typeof templateExp === "function" ? templateExp(routeOptions) : templateExp; if (template.$$typeof === xarcJsxElement || template.children) { // JSX asyncTemplate = new JsxRenderer({ templateFullPath: Path.dirname(templateFullPath), template, tokenHandlers: finalTokenHandlers.filter(x => x), insertTokenIds: routeOptions.insertTokenIds, routeOptions }); } else { // Tag asyncTemplate = new TagRenderer({ templateTags: template, tokenHandlers: finalTokenHandlers.map(x => loadTokenModuleHandler(x)), insertTokenIds: routeOptions.insertTokenIds, routeOptions }); } // asyncTemplate.initializeRenderer(); return (routeOptions._templateCache[cacheKey] = asyncTemplate); } function makeRouteTemplateSelector(routeOptions) { routeOptions._templateCache = {}; let defaultSelection; if (routeOptions.templateFile) { defaultSelection = { templateFile: typeof routeOptions.templateFile === "string" ? Path.resolve(routeOptions.templateFile) : Path.join(__dirname, "../template/index") }; } else { defaultSelection = { htmlFile: routeOptions.htmlFile }; } const render = (options, templateSelection) => { let selection = templateSelection || defaultSelection; if (templateSelection && !templateSelection.templateFile && !templateSelection.htmlFile) { selection = Object.assign({}, templateSelection, defaultSelection); } const asyncTemplate = initializeTemplate(selection, routeOptions, options.request); return asyncTemplate.render(options); }; return options => { if (routeOptions.selectTemplate) { const selection = routeOptions.selectTemplate(options.request, routeOptions); if (selection && selection.then) { return selection.then(x => render(options, x)); } return render(options, selection); } assert(!defaultSelection.htmlFile, `subapp-server doesn't support htmlFile templates`); const asyncTemplate = initializeTemplate(defaultSelection, routeOptions, options.request); return asyncTemplate.render(options); }; } const setupOptions = options => { const https = process.env.WEBPACK_DEV_HTTPS && process.env.WEBPACK_DEV_HTTPS !== "false"; const pluginOptionsDefaults = { pageTitle: "Untitled Electrode Web Application", webpackDev: process.env.WEBPACK_DEV === "true", renderJS: true, serverSideRendering: true, htmlFile: Path.join(__dirname, "index.html"), devServer: { protocol: https ? "https" : "http", host: process.env.WEBPACK_DEV_HOST || process.env.WEBPACK_HOST || "localhost", port: process.env.WEBPACK_DEV_PORT || "2992", https }, unbundledJS: { enterHead: [], preBundle: [], postBundle: [] }, paths: {}, stats: "dist/server/stats.json", otherStats, iconStats: "dist/server/iconstats.json", criticalCSS: "dist/js/critical.css", buildArtifacts: ".build", prodBundleBase: "/js/", cspNonceValue: undefined }; const pluginOptions = _.defaultsDeep({}, options, pluginOptionsDefaults); const chunkSelector = resolveChunkSelector(pluginOptions); const devBundleBase = makeDevBundleBase(pluginOptions.devServer); const statsPath = getStatsPath(pluginOptions.stats, pluginOptions.buildArtifacts); const assets = loadAssetsFromStats(statsPath); const otherAssets = getOtherAssets(pluginOptions); pluginOptions.__internals = _.defaultsDeep({}, pluginOptions.__internals, { assets, otherAssets, chunkSelector, devBundleBase }); return pluginOptions; }; const pathSpecificOptions = [ "htmlFile", "templateFile", "insertTokenIds", "pageTitle", "selectTemplate", "responseForBadStatus", "responseForError" ]; const setupPathOptions = (routeOptions, path) => { const pathData = _.get(routeOptions, ["paths", path], {}); const pathOverride = _.get(routeOptions, ["paths", path, "overrideOptions"], {}); const pathOptions = pathData.options; return _.defaultsDeep( _.pick(pathData, pathSpecificOptions), { tokenHandler: [].concat(routeOptions.tokenHandler, pathData.tokenHandler), tokenHandlers: [].concat(routeOptions.tokenHandlers, pathData.tokenHandlers) }, pathOptions, _.omit(pathOverride, "paths"), routeOptions ); }; module.exports = { setupOptions, setupPathOptions, makeRouteTemplateSelector };