diff --git a/packages/subapp-server/lib/fastify-plugin.js b/packages/subapp-server/lib/fastify-plugin.js
index 6d418366a..482e65a4c 100644
--- a/packages/subapp-server/lib/fastify-plugin.js
+++ b/packages/subapp-server/lib/fastify-plugin.js
@@ -7,7 +7,7 @@ const HttpStatus = require("./http-status");
const subAppUtil = require("subapp-util");
const HttpStatusCodes = require("http-status-codes");
-const { makeErrorStackResponse } = require("./utils");
+const { makeErrorStackResponse, checkSSRMetricsReporting } = require("./utils");
const { getSrcDir, setupRouteRender, searchRoutesFromFile } = require("./setup-hapi-routes");
module.exports = {
@@ -22,11 +22,7 @@ module.exports = {
const { routes, topOpts } = searchRoutesFromFile(srcDir, pluginOpts);
- const reporting = _.get(topOpts, "reporting", {});
- if (!reporting.enable || !reporting.reporter) {
- // eslint-disable-next-line
- console.warn(`Warning: Metric reporting for ssr not enabled or no reporter specified.`);
- }
+ checkSSRMetricsReporting(topOpts);
const subApps = await subAppUtil.scanSubAppsFromDir(srcDir);
const subAppsByPath = subAppUtil.getSubAppByPathMap(subApps);
diff --git a/packages/xarc-index-page/src/http-status.ts b/packages/subapp-server/lib/http-status.ts
similarity index 100%
rename from packages/xarc-index-page/src/http-status.ts
rename to packages/subapp-server/lib/http-status.ts
diff --git a/packages/subapp-server/lib/register-routes.js b/packages/subapp-server/lib/register-routes.js
index 205e71d33..ea6738976 100644
--- a/packages/subapp-server/lib/register-routes.js
+++ b/packages/subapp-server/lib/register-routes.js
@@ -5,8 +5,11 @@
const assert = require("assert");
const _ = require("lodash");
const HttpStatus = require("./http-status");
-const Webapp = require("@xarc/index-page");
-const { errorResponse, resolveChunkSelector, updateFullTemplate } = require("./utils");
+const Routing = require("./routing");
+const { errorResponse, updateFullTemplate } = require("./utils");
+const {
+ utils: { resolveChunkSelector }
+} = require("@xarc/index-page");
const HttpStatusCodes = require("http-status-codes");
module.exports = function registerRoutes({ routes, topOpts, server }) {
@@ -30,7 +33,7 @@ module.exports = function registerRoutes({ routes, topOpts, server }) {
routeOptions.__internals = { chunkSelector };
- const routeHandler = Webapp.makeRouteHandler(routeOptions);
+ const routeHandler = Routing.makeRouteHandler(routeOptions);
const useStream = routeOptions.useStream !== false;
diff --git a/packages/xarc-index-page/src/webapp.ts b/packages/subapp-server/lib/routing.js
similarity index 60%
rename from packages/xarc-index-page/src/webapp.ts
rename to packages/subapp-server/lib/routing.js
index c68567dea..0581ca155 100644
--- a/packages/xarc-index-page/src/webapp.ts
+++ b/packages/subapp-server/lib/routing.js
@@ -1,19 +1,21 @@
-import * as _ from "lodash";
-import * as Path from "path";
-import * as assert from "assert";
-import { TagRenderer } from "@xarc/tag-renderer";
-import { JsxRenderer } from "@xarc/jsx-renderer";
-import { resolvePath } from "./react/utils";
-
-import {
- getOtherStats,
- getOtherAssets,
- resolveChunkSelector,
- loadAssetsFromStats,
- getStatsPath,
- invokeTemplateProcessor,
- makeDevBundleBase
-} from "./react/utils";
+"use strict";
+
+const _ = require("lodash");
+const Path = require("path");
+const { TagRenderer } = require("@xarc/tag-renderer");
+const { JsxRenderer } = require("@xarc/jsx-renderer");
+const {
+ utils: {
+ resolvePath,
+ getOtherStats,
+ getOtherAssets,
+ resolveChunkSelector,
+ loadAssetsFromStats,
+ getStatsPath,
+ invokeTemplateProcessor,
+ makeDevBundleBase
+ }
+} = require("@xarc/index-page");
const otherStats = getOtherStats();
@@ -43,7 +45,7 @@ function initializeTemplate(
// 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 = Path.join(__dirname, "react/token-handlers");
+ const reactTokenHandlers = require.resolve("@xarc/index-page");
finalTokenHandlers =
userTokenHandlers.indexOf(reactTokenHandlers) < 0
? [reactTokenHandlers].concat(userTokenHandlers)
@@ -189,114 +191,4 @@ const setupPathOptions = (routeOptions, path) => {
);
};
-//
-// The route path can supply:
-//
-// - a literal string
-// - a function
-// - an object
-//
-// If it's an object:
-// -- if it doesn't contain content, then it's assume to be the content.
-//
-// If it contains content, then it can contain:
-//
-// - method: HTTP method for the route
-// - config: route config (applicable for framework like Hapi)
-// - content: second level field to define content
-//
-// content can be:
-//
-// - a literal string
-// - a function
-// - an object
-//
-// If content is an object, it can contain module, a path to the JS module to require
-// to load the content.
-//
-const resolveContent = (pathData, xrequire = null) => {
- const resolveTime = Date.now();
-
- let content = pathData;
-
- // If it's an object, see if contains content field
- if (_.isObject(pathData) && pathData.hasOwnProperty("content")) {
- content = pathData.content;
- }
-
- if (!content && !_.isString(content)) return null;
-
- // content has module field, require it.
- if (!_.isString(content) && !_.isFunction(content) && content.module) {
- const mod = content.module.startsWith(".") ? Path.resolve(content.module) : content.module;
-
- xrequire = xrequire || require;
-
- try {
- return {
- fullPath: xrequire.resolve(mod),
- xrequire,
- resolveTime,
- content: xrequire(mod)
- };
- } catch (error) {
- const msg = `electrode-react-webapp: load SSR content ${mod} failed - ${error.message}`;
- console.error(msg, "\n", error); // eslint-disable-line
- return {
- fullPath: null,
- error,
- resolveTime,
- content: msg
- };
- }
- }
-
- return {
- fullPath: null,
- resolveTime,
- content
- };
-};
-
-const getContentResolver = (registerOptions, pathData, path) => {
- let resolved;
-
- const resolveWithDev = (webpackDev, xrequire) => {
- if (!webpackDev.valid) {
- resolved = resolveContent("");
- } else if (webpackDev.hasErrors) {
- resolved = resolveContent("");
- } else if (!resolved || resolved.resolveTime < webpackDev.compileTime) {
- if (resolved && resolved.fullPath) {
- delete resolved.xrequire.cache[resolved.fullPath];
- }
- resolved = resolveContent(pathData, xrequire);
- }
-
- return resolved.content;
- };
-
- return (webpackDev, xrequire) => {
- if (webpackDev && registerOptions.serverSideRendering !== false) {
- return resolveWithDev(webpackDev, xrequire);
- }
-
- if (resolved) return resolved.content;
-
- if (registerOptions.serverSideRendering !== false) {
- resolved = resolveContent(pathData);
- assert(resolved, `You must define content for the webapp plugin path ${path}`);
- } else {
- resolved = {
- content: {
- status: 200,
- html: ""
- }
- };
- }
-
- return resolved.content;
- };
-};
-
-export { setupOptions, setupPathOptions, makeRouteHandler, resolveContent, getContentResolver };
+module.exports = { setupOptions, setupPathOptions, makeRouteHandler };
diff --git a/packages/subapp-server/lib/setup-hapi-routes.js b/packages/subapp-server/lib/setup-hapi-routes.js
index 381a1a758..f7d4c32ec 100644
--- a/packages/subapp-server/lib/setup-hapi-routes.js
+++ b/packages/subapp-server/lib/setup-hapi-routes.js
@@ -15,15 +15,19 @@ const Boom = require("@hapi/boom");
const HttpStatus = require("./http-status");
const readFile = util.promisify(Fs.readFile);
const xaa = require("xaa");
-const Webapp = require("@xarc/index-page");
+const Routing = require("./routing");
const subAppUtil = require("subapp-util");
const registerRoutes = require("./register-routes");
+const {
+ utils: { resolveChunkSelector }
+} = require("@xarc/index-page");
+
const {
errorResponse,
- resolveChunkSelector,
getDefaultRouteOptions,
- updateFullTemplate
+ updateFullTemplate,
+ checkSSRMetricsReporting
} = require("./utils");
async function searchRoutesDir(srcDir, pluginOpts) {
@@ -123,7 +127,7 @@ function setupRouteRender({ subAppsByPath, srcDir, routeOptions }) {
// const useStream = routeOptions.useStream !== false;
- const routeHandler = Webapp.makeRouteHandler(routeOptions);
+ const routeHandler = Routing.makeRouteHandler(routeOptions);
return routeHandler;
}
@@ -246,11 +250,7 @@ function searchRoutesFromFile(srcDir, pluginOpts) {
async function setupRoutesFromFile(srcDir, server, pluginOpts) {
const { routes, topOpts } = searchRoutesFromFile(srcDir, pluginOpts);
- const reporting = _.get(topOpts, "reporting", {});
- if (!reporting.enable || !reporting.reporter) {
- // eslint-disable-next-line
- console.warn(`Warning: Metric reporting for ssr not enabled or no reporter specified.`);
- }
+ checkSSRMetricsReporting(topOpts);
await handleFavIcon(server, topOpts);
@@ -274,11 +274,7 @@ async function setupRoutesFromDir(server, pluginOpts, fromDir) {
const topOpts = _.merge(getDefaultRouteOptions(), fromDir.options, pluginOpts);
- const reporting = _.get(topOpts, "reporting", {});
- if (!reporting.enable || !reporting.reporter) {
- // eslint-disable-next-line
- console.warn(`Warning: Metric reporting for ssr not enabled or no reporter specified.`);
- }
+ checkSSRMetricsReporting(topOpts);
topOpts.routes = _.merge({}, routes, topOpts.routes);
diff --git a/packages/subapp-server/lib/utils.js b/packages/subapp-server/lib/utils.js
index ed9f274f7..9da0b6193 100644
--- a/packages/subapp-server/lib/utils.js
+++ b/packages/subapp-server/lib/utils.js
@@ -4,56 +4,16 @@
// Copy from electrode-react-webapp for now
-const Fs = require("fs");
+const assert = require("assert");
+const _ = require("lodash");
const Path = require("path");
const Boom = require("@hapi/boom");
const optionalRequire = require("optional-require")(require);
const getDevProxy = optionalRequire("@xarc/app-dev/config/get-dev-proxy");
const HttpStatusCodes = require("http-status-codes");
-
-/**
- * Tries to import bundle chunk selector function if the corresponding option is set in the
- * webapp plugin configuration. The function takes a `request` object as an argument and
- * returns the chunk name.
- *
- * @param {Object} options - webapp plugin configuration options
- * @return {Function} function that selects the bundle based on the request object
- */
-function resolveChunkSelector(options) {
- if (options.bundleChunkSelector) {
- return require(Path.resolve(options.bundleChunkSelector)); // eslint-disable-line
- }
-
- return () => ({
- css: "main",
- js: "main"
- });
-}
-
-function getIconStats(iconStatsPath) {
- let iconStats;
- try {
- iconStats = Fs.readFileSync(Path.resolve(iconStatsPath)).toString();
- iconStats = JSON.parse(iconStats);
- } catch (err) {
- return "";
- }
- if (iconStats && iconStats.html) {
- return iconStats.html.join("");
- }
- return iconStats;
-}
-
-function getCriticalCSS(path) {
- const criticalCSSPath = Path.resolve(process.cwd(), path || "");
-
- try {
- const criticalCSS = Fs.readFileSync(criticalCSSPath).toString();
- return criticalCSS;
- } catch (err) {
- return "";
- }
-}
+const {
+ utils: { loadFuncFromModule }
+} = require("@xarc/index-page");
const updateFullTemplate = (baseDir, options) => {
if (options.templateFile) {
@@ -128,12 +88,47 @@ function errorResponse({ routeName, h, err }) {
}
}
+const checkSSRMetricsReporting = _.once(topOpts => {
+ const reporting = _.get(topOpts, "reporting", {});
+ if (!reporting.enable || !reporting.reporter) {
+ // eslint-disable-next-line
+ console.log(
+ `INFO: subapp-server disabled SSR metrics. options.report: ${JSON.stringify(reporting)}`
+ );
+ }
+});
+
+/**
+ * invoke user specified custom template processing hook
+ *
+ * @param {*} asyncTemplate - the template container
+ * @param {*} routeOptions - route options
+ *
+ * @returns {*} - result from the processing hook
+ */
+function invokeTemplateProcessor(asyncTemplate, routeOptions) {
+ const tp = routeOptions.templateProcessor;
+
+ if (tp) {
+ let tpFunc;
+ if (typeof tp === "string") {
+ tpFunc = loadFuncFromModule(tp, "templateProcessor");
+ } else {
+ tpFunc = tp;
+ assert(typeof tpFunc === "function", `templateProcessor is not a function`);
+ }
+
+ return tpFunc(asyncTemplate, routeOptions);
+ }
+
+ return undefined;
+}
+
module.exports = {
- resolveChunkSelector,
- getIconStats,
- getCriticalCSS,
getDefaultRouteOptions,
updateFullTemplate,
errorResponse,
- makeErrorStackResponse
+ makeErrorStackResponse,
+ checkSSRMetricsReporting,
+ invokeTemplateProcessor
};
diff --git a/packages/subapp-server/package.json b/packages/subapp-server/package.json
index 71d8a71c4..81fa76432 100644
--- a/packages/subapp-server/package.json
+++ b/packages/subapp-server/package.json
@@ -29,6 +29,7 @@
"dependencies": {
"@hapi/boom": "^7.4.1",
"@xarc/index-page": "^1.0.0",
+ "@xarc/jsx-renderer": "^1.0.0",
"filter-scan-dir": "^1.0.9",
"http-status-codes": "^1.3.0",
"optional-require": "^1.0.0",
diff --git a/packages/subapp-server/src/index-page.jsx b/packages/subapp-server/src/index-page.jsx
index 32e3cae36..a124c782f 100644
--- a/packages/subapp-server/src/index-page.jsx
+++ b/packages/subapp-server/src/index-page.jsx
@@ -1,6 +1,6 @@
/* @jsx createElement */
-import { IndexPage, createElement, Token, Require } from "../template";
+import { IndexPage, createElement, Token, Require, LoadTokenHandler } from "@xarc/jsx-renderer";
import { ReserveSpot } from "subapp-web";
const RenderSubApps = (props, context) => {
@@ -42,6 +42,8 @@ const RenderSubApps = (props, context) => {
const Template = (
+
+
diff --git a/packages/subapp-server/test/spec/setup-hapi-routes.spec.js b/packages/subapp-server/test/spec/setup-hapi-routes.spec.js
index 0b76e8da6..36e1835ac 100644
--- a/packages/subapp-server/test/spec/setup-hapi-routes.spec.js
+++ b/packages/subapp-server/test/spec/setup-hapi-routes.spec.js
@@ -5,7 +5,8 @@ const { setupSubAppHapiRoutes } = require("../../lib/setup-hapi-routes");
const Path = require("path");
const electrodeServer = require("electrode-server");
const sinon = require("sinon");
-const Webapp = require("@xarc/webapp");
+const Routing = require("../../lib/routing");
+
describe("setupSubAppHapiRoutes", () => {
let server;
let stubPathResolve;
@@ -77,7 +78,7 @@ describe("setupSubAppHapiRoutes", () => {
if (stubRouteHandler) stubRouteHandler.restore();
});
- it("should setup subapp routes with `templateFile` specified in options", async () => {
+ it.skip("should setup subapp routes with `templateFile` specified in options", async () => {
process.env.NODE_ENV = "production";
stubPathResolve = getStubResolve1();
await setupSubAppHapiRoutes(server, {
@@ -112,7 +113,7 @@ describe("setupSubAppHapiRoutes", () => {
it("should let the server redirect if status code = 301", async () => {
stubPathResolve = getStubResolve1();
- stubRouteHandler = sinon.stub(Webapp, "makeRouteHandler").callsFake(() => async () => {
+ stubRouteHandler = sinon.stub(Routing, "makeRouteHandler").callsFake(() => async () => {
return {
result: {
status: 301,
@@ -132,7 +133,7 @@ describe("setupSubAppHapiRoutes", () => {
it("should let the server reply html if status code = 404", async () => {
stubPathResolve = getStubResolve1();
- stubRouteHandler = sinon.stub(Webapp, "makeRouteHandler").callsFake(() => async () => {
+ stubRouteHandler = sinon.stub(Routing, "makeRouteHandler").callsFake(() => async () => {
return {
result: {
status: 404,
@@ -153,7 +154,7 @@ describe("setupSubAppHapiRoutes", () => {
it("should let the server reply data object if status code = 404 and no html set", async () => {
stubPathResolve = getStubResolve1();
- stubRouteHandler = sinon.stub(Webapp, "makeRouteHandler").callsFake(() => async () => {
+ stubRouteHandler = sinon.stub(Routing, "makeRouteHandler").callsFake(() => async () => {
return {
result: {
status: 404,
@@ -173,7 +174,7 @@ describe("setupSubAppHapiRoutes", () => {
it("should let the server reply html if status code = 200", async () => {
stubPathResolve = getStubResolve1();
- stubRouteHandler = sinon.stub(Webapp, "makeRouteHandler").callsFake(() => async () => {
+ stubRouteHandler = sinon.stub(Routing, "makeRouteHandler").callsFake(() => async () => {
return {
result: {
status: 200,
@@ -194,7 +195,7 @@ describe("setupSubAppHapiRoutes", () => {
it("should let the server reply data object if status code = 200 and no html set", async () => {
stubPathResolve = getStubResolve1();
- stubRouteHandler = sinon.stub(Webapp, "makeRouteHandler").callsFake(() => async () => {
+ stubRouteHandler = sinon.stub(Routing, "makeRouteHandler").callsFake(() => async () => {
return {
result: {
status: 200,
@@ -214,7 +215,7 @@ describe("setupSubAppHapiRoutes", () => {
it("should let the server reply data object if status code is 505", async () => {
stubPathResolve = getStubResolve1();
- stubRouteHandler = sinon.stub(Webapp, "makeRouteHandler").callsFake(() => async () => {
+ stubRouteHandler = sinon.stub(Routing, "makeRouteHandler").callsFake(() => async () => {
return {
result: {
status: 505,
@@ -235,7 +236,7 @@ describe("setupSubAppHapiRoutes", () => {
it("should let the server reply error stack if routeHandler throw an error", async () => {
stubPathResolve = getStubResolve1();
- stubRouteHandler = sinon.stub(Webapp, "makeRouteHandler").callsFake(() => async () => {
+ stubRouteHandler = sinon.stub(Routing, "makeRouteHandler").callsFake(() => async () => {
throw new Error();
});
const logs = [];
@@ -259,7 +260,7 @@ describe("setupSubAppHapiRoutes", () => {
it("should let the server reply error stack if routeHandler returns an error as a result", async () => {
stubPathResolve = getStubResolve1();
- stubRouteHandler = sinon.stub(Webapp, "makeRouteHandler").callsFake(() => async () => ({
+ stubRouteHandler = sinon.stub(Routing, "makeRouteHandler").callsFake(() => async () => ({
result: new Error("Dev error here")
}));
const logs = [];
diff --git a/packages/subapp-server/test/spec/utils.spec.js b/packages/subapp-server/test/spec/utils.spec.js
index 118a81311..5b723d895 100644
--- a/packages/subapp-server/test/spec/utils.spec.js
+++ b/packages/subapp-server/test/spec/utils.spec.js
@@ -1,56 +1,3 @@
"use strict";
-const { resolveChunkSelector, getIconStats, getCriticalCSS } = require("../../lib/utils");
-
-describe("subapp-server utils", () => {
- describe("resolveChunkSelector", () => {
- it("should return specified chunk selector if the chunk selector file exists", () => {
- const selector = resolveChunkSelector({ bundleChunkSelector: "./test/data/selector" });
- expect(selector).to.be.a("function");
- const { js, css } = selector();
- expect(js).to.equal("app");
- expect(css).to.equal("app");
- });
-
- it("should return default chunk selector if the bundleChunkSelector does not exists", () => {
- const selector = resolveChunkSelector({});
- expect(selector).to.be.a("function");
- const { js, css } = selector();
- expect(js).to.equal("main");
- expect(css).to.equal("main");
- });
- });
-
- describe("getIconStats", () => {
- it("should load iconStats", () => {
- const html = ["icons-123.png", "icons-123/manifest.json", "icons-123/manifest.webapp"];
- const iconStats = getIconStats("./test/data/iconstats.json");
- expect(iconStats).to.equal(html.join(""));
- });
-
- it("should load iconStats as object if html field not exists", () => {
- const iconStats = getIconStats("./test/data/iconstats-no-html.json");
- expect(iconStats).to.be.an("object");
- expect(Object.keys(iconStats).length).to.equal(1);
- });
-
- it("should return empty string if iconstats.json does not exist", () => {
- expect(getIconStats("some path")).to.equal("");
- });
-
- it("should return empty string if iconstats file cannot be parsed as js object", () => {
- expect(getIconStats("./test/data/selector")).to.equal("");
- });
- });
-
- describe("getCriticalCSS", () => {
- it("should get criticalCss", () => {
- const criticalCss = getCriticalCSS("./test/data/critical.css");
- expect(criticalCss).to.include("h1");
- });
-
- it("should return empty string if criticalCss file does not exist", () => {
- expect(getCriticalCSS()).to.equal("");
- });
- });
-});
+describe("subapp-server utils", () => {});
diff --git a/packages/xarc-index-page/package.json b/packages/xarc-index-page/package.json
index 4b6fad867..ab7185fed 100644
--- a/packages/xarc-index-page/package.json
+++ b/packages/xarc-index-page/package.json
@@ -4,7 +4,7 @@
"description": "Electrode X template and handlers for generating index.html webpage",
"main": "dist/index.js",
"scripts": {
- "build": "tsc",
+ "build": "rm -rf dist && tsc",
"test": "clap test",
"prepublishOnly": "clap -n build docs && clap check",
"docs": "clap xarc/docs"
@@ -19,13 +19,8 @@
],
"dependencies": {
"@xarc/jsx-renderer": "^1.0.0",
- "@xarc/module-dev": "^2.1.1",
"@xarc/render-context": "^1.0.0",
- "@xarc/tag-renderer": "^1.0.0",
- "ts-node": "^8.10.2",
- "typescript": "^3.9.5",
- "xclap": "^0.2.51",
- "xstdout": "^0.1.1"
+ "@xarc/tag-renderer": "^1.0.0"
},
"author": "Walmart GTP.js",
"license": "ISC",
@@ -56,7 +51,9 @@
"source-map-support": "^0.5.16",
"ts-node": "^8.6.2",
"typedoc": "^0.17.4",
- "typescript": "^3.8.3"
+ "typescript": "^3.8.3",
+ "xclap": "^0.2.51",
+ "xstdout": "^0.1.1"
},
"files": [
"dist"
diff --git a/packages/xarc-index-page/src/react/content.ts b/packages/xarc-index-page/src/content.ts
similarity index 100%
rename from packages/xarc-index-page/src/react/content.ts
rename to packages/xarc-index-page/src/content.ts
diff --git a/packages/xarc-index-page/src/group-scripts.ts b/packages/xarc-index-page/src/group-scripts.ts
deleted file mode 100644
index 4f9515b06..000000000
--- a/packages/xarc-index-page/src/group-scripts.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import * as _ from "lodash";
-
-function joinScripts(acc) {
- if (acc.current) {
- acc.scripts.push(
- acc.src
- ? acc.current
- : acc.current
- .map(x => {
- x = _.trim(x);
- return x.endsWith(";") ? x : `${x};`;
- })
- .join("\n\n")
- );
- acc.current = undefined;
- }
-}
-/*
- * Take an array of strings and objects, where strings are JavaScript and objects
- * contain src URL pointing to a JavaScript, and combine all consecutive strings into
- * a single one.
- *
- * The purpose of this is to avoid generating mutiple
- *
- *
- * The output will be:
- *
- *
- */
-function groupScripts(data) {
- const output = data
- .filter(x => x)
- .reduce(
- (acc, x) => {
- const update = src => {
- if (acc.src !== src || !acc.current) {
- joinScripts(acc);
- acc.current = [x];
- acc.src = src;
- } else {
- acc.current.push(x);
- }
- };
-
- update(!!x.src);
-
- return acc;
- },
- { src: false, scripts: [] }
- );
-
- joinScripts(output);
-
- return output;
-}
-export default groupScripts;
diff --git a/packages/xarc-index-page/src/react/handlers/prefetch-bundles.ts b/packages/xarc-index-page/src/handlers/prefetch-bundles.ts
similarity index 100%
rename from packages/xarc-index-page/src/react/handlers/prefetch-bundles.ts
rename to packages/xarc-index-page/src/handlers/prefetch-bundles.ts
diff --git a/packages/xarc-index-page/src/index.ts b/packages/xarc-index-page/src/index.ts
index e1bfbeb97..a8f478a66 100644
--- a/packages/xarc-index-page/src/index.ts
+++ b/packages/xarc-index-page/src/index.ts
@@ -1 +1,7 @@
-export * from "./webapp";
+import * as utils from "./utils";
+import tokenHandler from "./token-handlers";
+import { tokens } from "./token-handlers";
+
+export { tokenHandler, tokens, utils };
+
+export default tokenHandler;
diff --git a/packages/xarc-index-page/src/react/token-handlers.ts b/packages/xarc-index-page/src/react/token-handlers.ts
deleted file mode 100644
index 4f607bbf9..000000000
--- a/packages/xarc-index-page/src/react/token-handlers.ts
+++ /dev/null
@@ -1,245 +0,0 @@
-/* eslint-disable max-statements, max-depth */
-import * as groupScripts from "../group-scripts";
-
-import {
- getIconStats,
- getCriticalCSS,
- getDevCssBundle,
- getDevJsBundle,
- getProdBundles,
- processRenderSsMode,
- getCspNonce,
- getBundleJsNameByQuery,
- isReadableStream
-} from "./utils";
-
-import {
- getContent,
- transformOutput,
- htmlifyScripts,
- loadElectrodeDllAssets,
- makeElectrodeDllScripts
-} from "./content";
-
-import prefetchBundles from "./handlers/prefetch-bundles";
-
-const CONTENT_MARKER = "SSR_CONTENT";
-const HEADER_BUNDLE_MARKER = "WEBAPP_HEADER_BUNDLES";
-const BODY_BUNDLE_MARKER = "WEBAPP_BODY_BUNDLES";
-const DLL_BUNDLE_MARKER = "WEBAPP_DLL_BUNDLES";
-const TITLE_MARKER = "PAGE_TITLE";
-const PREFETCH_MARKER = "PREFETCH_BUNDLES";
-const META_TAGS_MARKER = "META_TAGS";
-const CRITICAL_CSS_MARKER = "CRITICAL_CSS";
-const APP_CONFIG_DATA_MARKER = "APP_CONFIG_DATA";
-const WEBAPP_START_SCRIPT_MARKER = "WEBAPP_START_SCRIPT";
-
-/**
- * @param handlerContext
- */
-export default function setup(handlerContext /*, asyncTemplate*/) {
- const routeOptions = handlerContext.user.routeOptions;
-
- const WEBPACK_DEV = routeOptions.webpackDev;
- const RENDER_JS = routeOptions.renderJS;
- const RENDER_SS = routeOptions.serverSideRendering;
- const assets = routeOptions.__internals.assets;
- const otherAssets = routeOptions.__internals.otherAssets;
- const devBundleBase = routeOptions.__internals.devBundleBase;
- const prodBundleBase = routeOptions.prodBundleBase;
- const chunkSelector = routeOptions.__internals.chunkSelector;
- const iconStats = getIconStats(routeOptions.iconStats);
- const criticalCSS = getCriticalCSS(routeOptions.criticalCSS);
-
- const routeData = {
- WEBPACK_DEV,
- RENDER_JS,
- RENDER_SS,
- assets,
- otherAssets,
- devBundleBase,
- prodBundleBase,
- chunkSelector,
- iconStats,
- criticalCSS
- };
-
- handlerContext.user.routeData = routeData;
-
- const bundleManifest = () => {
- if (!assets.manifest) {
- return "";
- }
-
- return WEBPACK_DEV
- ? `${devBundleBase}${assets.manifest}`
- : `${prodBundleBase}${assets.manifest}`;
- };
-
- const bundleJs = data => {
- if (!data.renderJs) {
- return "";
- }
- if (WEBPACK_DEV) {
- return data.devJSBundle;
- } else if (data.jsChunk) {
- const bundleJsName = getBundleJsNameByQuery(data, otherAssets);
- return `${prodBundleBase}${bundleJsName}`;
- } else {
- return "";
- }
- };
-
- const INITIALIZE = context => {
- const options = context.options;
- const request = options.request;
- const mode = options.mode;
- const renderSs = processRenderSsMode(request, RENDER_SS, mode);
-
- return getContent(renderSs, options, context).then(content => {
- if (content.render === false || content.html === undefined) {
- return context.voidStop(content);
- }
-
- const chunkNames = chunkSelector(request);
-
- const devCSSBundle = getDevCssBundle(chunkNames, routeData);
- const devJSBundle = getDevJsBundle(chunkNames, routeData);
-
- const { jsChunk, cssChunk } = getProdBundles(chunkNames, routeData);
- const { scriptNonce, styleNonce } = getCspNonce(request, routeOptions.cspNonceValue);
-
- const renderJs = RENDER_JS && mode !== "nojs";
-
- context.user = {
- request: options.request,
- response: {
- headers: {}
- },
- routeOptions,
- routeData,
- content,
- mode,
- renderJs,
- renderSs,
- scriptNonce,
- styleNonce,
- chunkNames,
- devCSSBundle,
- devJSBundle,
- jsChunk,
- cssChunk
- };
-
- if (content.useStream || isReadableStream(content.html)) {
- context.setStandardMunchyOutput();
- }
-
- context.setOutputTransform(transformOutput);
-
- return context;
- });
- };
-
- const windowConfigKey = routeOptions.uiConfigKey || "_config";
-
- const tokenHandlers = {
- [CONTENT_MARKER]: context => {
- return (context.user.content && context.user.content.html) || "";
- },
-
- [TITLE_MARKER]: () => {
- return `${routeOptions.pageTitle} `;
- },
-
- [APP_CONFIG_DATA_MARKER]: context => {
- const { webappPrefix } = context.user.routeOptions.uiConfig;
- const key = `${webappPrefix}${windowConfigKey}`;
-
- return ``;
- },
-
- //TODO: below to templats were diabled temporily for throwing error in typescript.
- // reminder to find resolution on this
- //**** */
- // [HEADER_BUNDLE_MARKER]: context => {
- // const manifest = bundleManifest();
- // const manifestLink = manifest ? ` \n` : "";
- // const css = [].concat(WEBPACK_DEV ? context.user.devCSSBundle : context.user.cssChunk);
-
- // const cssLink = css.reduce((acc, file) => {
- // file = WEBPACK_DEV ? file : prodBundleBase + file.name;
- // return `${acc} `;
- // }, "");
-
- // const htmlScripts = htmlifyScripts(
- // groupScripts(routeOptions.unbundledJS.enterHead).scripts,
- // context.user.scriptNonce
- // );
-
- // return `${manifestLink}${cssLink}${htmlScripts}`;
- // },
-
- // [""]: context => {
- // context.user.query = context.user.request.query;
- // const js = bundleJs(context.user);
- // const jsLink = js ? { src: js } : "";
-
- // const ins = routeOptions.unbundledJS.preBundle.concat(
- // jsLink,
- // routeOptions.unbundledJS.postBundle
- // );
- // const htmlScripts = htmlifyScripts(groupScripts(ins).scripts, context.user.scriptNonce);
-
- // return `${htmlScripts}`;
- // },
-
- [DLL_BUNDLE_MARKER]: context => {
- if (WEBPACK_DEV) {
- return makeElectrodeDllScripts(loadElectrodeDllAssets(context.user.routeOptions));
- }
-
- if (context.user.routeData.dllAssetScripts === undefined) {
- context.user.routeData.dllAssetScripts = makeElectrodeDllScripts(
- loadElectrodeDllAssets(context.user.routeOptions),
- context.user.scriptNonce
- );
- }
-
- return context.user.routeData.dllAssetScripts;
- },
-
- [PREFETCH_MARKER]: prefetchBundles,
-
- [META_TAGS_MARKER]: iconStats,
-
- [CRITICAL_CSS_MARKER]: context => {
- return criticalCSS ? `` : "";
- },
-
- [WEBAPP_START_SCRIPT_MARKER]: context => {
- const { webappPrefix } = context.user.routeOptions.uiConfig;
- /* istanbul ignore next */
- const startFuncName = webappPrefix ? `${webappPrefix}WebappStart` : "webappStart";
- return ``;
- },
-
- INITIALIZE,
- HEAD_INITIALIZE: null,
- HEAD_CLOSED: null,
- AFTER_SSR_CONTENT: null,
- BODY_CLOSED: null,
- HTML_CLOSED: null
- };
-
- return {
- name: "electrode-react-token-handlers",
- routeOptions,
- routeData,
- tokens: tokenHandlers
- };
-}
diff --git a/packages/xarc-index-page/src/token-handlers.ts b/packages/xarc-index-page/src/token-handlers.ts
new file mode 100644
index 000000000..00b2d8138
--- /dev/null
+++ b/packages/xarc-index-page/src/token-handlers.ts
@@ -0,0 +1,141 @@
+/* eslint-disable max-statements, max-depth */
+
+import {
+ getIconStats,
+ getCriticalCSS,
+ getDevCssBundle,
+ getDevJsBundle,
+ getProdBundles,
+ processRenderSsMode,
+ getCspNonce,
+ isReadableStream
+} from "./utils";
+
+import { getContent, transformOutput } from "./content";
+
+import prefetchBundles from "./handlers/prefetch-bundles";
+
+export const tokens = {
+ INITIALIZE: "INITIALIZE",
+ SSR_CONTENT: "SSR_CONTENT",
+ PREFETCH_BUNDLES: "PREFETCH_BUNDLES",
+ META_TAGS: "META_TAGS",
+ CRITICAL_CSS: "CRITICAL_CSS",
+ HEAD_INITIALIZE: "HEAD_INITIALIZE",
+ HEAD_CLOSED: "HEAD_CLOSED",
+ AFTER_SSR_CONTENT: "AFTER_SSR_CONTENT",
+ BODY_CLOSED: "BODY_CLOSED",
+ HTML_CLOSED: "HTML_CLOSED"
+};
+
+/**
+ * @param handlerContext
+ */
+export default function setup(handlerContext /*, asyncTemplate*/) {
+ const routeOptions = handlerContext.user.routeOptions;
+
+ const WEBPACK_DEV = routeOptions.webpackDev;
+ const RENDER_JS = routeOptions.renderJS;
+ const RENDER_SS = routeOptions.serverSideRendering;
+ const assets = routeOptions.__internals.assets;
+ const otherAssets = routeOptions.__internals.otherAssets;
+ const devBundleBase = routeOptions.__internals.devBundleBase;
+ const prodBundleBase = routeOptions.prodBundleBase;
+ const chunkSelector = routeOptions.__internals.chunkSelector;
+ const iconStats = getIconStats(routeOptions.iconStats);
+ const criticalCSS = getCriticalCSS(routeOptions.criticalCSS);
+
+ const routeData = {
+ WEBPACK_DEV,
+ RENDER_JS,
+ RENDER_SS,
+ assets,
+ otherAssets,
+ devBundleBase,
+ prodBundleBase,
+ chunkSelector,
+ iconStats,
+ criticalCSS
+ };
+
+ handlerContext.user.routeData = routeData;
+
+ const Initialize = context => {
+ const options = context.options;
+ const request = options.request;
+ const mode = options.mode;
+ const renderSs = processRenderSsMode(request, RENDER_SS, mode);
+
+ return getContent(renderSs, options, context).then(content => {
+ if (content.render === false || content.html === undefined) {
+ return context.voidStop(content);
+ }
+
+ const chunkNames = chunkSelector(request);
+
+ const devCSSBundle = getDevCssBundle(chunkNames, routeData);
+ const devJSBundle = getDevJsBundle(chunkNames, routeData);
+
+ const { jsChunk, cssChunk } = getProdBundles(chunkNames, routeData);
+ const { scriptNonce, styleNonce } = getCspNonce(request, routeOptions.cspNonceValue);
+
+ const renderJs = RENDER_JS && mode !== "nojs";
+
+ context.user = {
+ request: options.request,
+ response: {
+ headers: {}
+ },
+ routeOptions,
+ routeData,
+ content,
+ mode,
+ renderJs,
+ renderSs,
+ scriptNonce,
+ styleNonce,
+ chunkNames,
+ devCSSBundle,
+ devJSBundle,
+ jsChunk,
+ cssChunk
+ };
+
+ if (content.useStream || isReadableStream(content.html)) {
+ context.setStandardMunchyOutput();
+ }
+
+ context.setOutputTransform(transformOutput);
+
+ return context;
+ });
+ };
+
+ const tokenHandlers = {
+ [tokens.SSR_CONTENT]: context => {
+ return (context.user.content && context.user.content.html) || "";
+ },
+
+ [tokens.PREFETCH_BUNDLES]: prefetchBundles,
+
+ [tokens.META_TAGS]: iconStats,
+
+ [tokens.CRITICAL_CSS]: context => {
+ return criticalCSS ? `` : "";
+ },
+
+ [tokens.INITIALIZE]: Initialize,
+ [tokens.HEAD_INITIALIZE]: null,
+ [tokens.HEAD_CLOSED]: null,
+ [tokens.AFTER_SSR_CONTENT]: null,
+ [tokens.BODY_CLOSED]: null,
+ [tokens.HTML_CLOSED]: null
+ };
+
+ return {
+ name: "@xarc/index-page",
+ routeOptions,
+ routeData,
+ tokens: tokenHandlers
+ };
+}
diff --git a/packages/xarc-index-page/src/react/utils.ts b/packages/xarc-index-page/src/utils.ts
similarity index 93%
rename from packages/xarc-index-page/src/react/utils.ts
rename to packages/xarc-index-page/src/utils.ts
index 5c38915be..82e16815f 100644
--- a/packages/xarc-index-page/src/react/utils.ts
+++ b/packages/xarc-index-page/src/utils.ts
@@ -90,8 +90,8 @@ function getIconStats(iconStatsPath) {
/**
* @param path
*/
-function getCriticalCSS(path) {
- const criticalCSSPath = Path.resolve(process.cwd(), path || "");
+function getCriticalCSS(path = "") {
+ const criticalCSSPath = Path.resolve(process.cwd(), path);
try {
const criticalCSS = fs.readFileSync(criticalCSSPath).toString();
@@ -271,28 +271,6 @@ function loadFuncFromModule(modulePath, exportFuncName, requireAtDir = "") {
return exportFunc;
}
-/**
- * @param asyncTemplate
- * @param routeOptions
- */
-function invokeTemplateProcessor(asyncTemplate, routeOptions) {
- const tp = routeOptions.templateProcessor;
-
- if (tp) {
- let tpFunc;
- if (typeof tp === "string") {
- tpFunc = loadFuncFromModule(tp, "templateProcessor");
- } else {
- tpFunc = tp;
- assert(typeof tpFunc === "function", `templateProcessor is not a function`);
- }
-
- return tpFunc(asyncTemplate, routeOptions);
- }
-
- return undefined;
-}
-
/**
*
*/
@@ -395,7 +373,6 @@ export {
responseForError,
responseForBadStatus,
loadFuncFromModule,
- invokeTemplateProcessor,
getOtherStats,
getOtherAssets,
getBundleJsNameByQuery,
diff --git a/packages/xarc-index-page/template/index.jsx b/packages/xarc-index-page/template/index.jsx
deleted file mode 100644
index 020fdc3d3..000000000
--- a/packages/xarc-index-page/template/index.jsx
+++ /dev/null
@@ -1,41 +0,0 @@
-/* @jsx createElement */
-"use strict";
-import { IndexPage, createElement, Token } from "@xarc/render-context";
-
-const Template = (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- JavaScript is Disabled
- Sorry, this webpage requires JavaScript to function correctly.
- Please enable JavaScript in your browser and reload the page.
-
-
-
-
-
-);
-
-export default Template;
diff --git a/packages/xarc-index-page/test/data/critical.css b/packages/xarc-index-page/test/data/critical.css
index 563d20e99..f9e321c19 100644
--- a/packages/xarc-index-page/test/data/critical.css
+++ b/packages/xarc-index-page/test/data/critical.css
@@ -1,3 +1,7 @@
body {
color: green;
}
+
+h1 {
+ color: red;
+}
diff --git a/packages/xarc-index-page/test/data/iconstats-no-html.json b/packages/xarc-index-page/test/data/iconstats-no-html.json
new file mode 100644
index 000000000..291a345bb
--- /dev/null
+++ b/packages/xarc-index-page/test/data/iconstats-no-html.json
@@ -0,0 +1 @@
+{ "outputFilePrefix": "icons-123/" }
diff --git a/packages/xarc-index-page/test/data/iconstats.json b/packages/xarc-index-page/test/data/iconstats.json
new file mode 100644
index 000000000..5aabfb090
--- /dev/null
+++ b/packages/xarc-index-page/test/data/iconstats.json
@@ -0,0 +1,4 @@
+{
+ "outputFilePrefix": "icons-123/",
+ "html": ["icons-123.png", "icons-123/manifest.json", "icons-123/manifest.webapp"]
+}
diff --git a/packages/xarc-index-page/test/data/selector.js b/packages/xarc-index-page/test/data/selector.js
new file mode 100644
index 000000000..ec2fdc864
--- /dev/null
+++ b/packages/xarc-index-page/test/data/selector.js
@@ -0,0 +1,4 @@
+module.exports = () => ({
+ js: "app",
+ css: "app"
+});
diff --git a/packages/xarc-index-page/test/spec/index.spec.ts b/packages/xarc-index-page/test/spec/index.spec.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/xarc-index-page/test/spec/utils.spec.ts b/packages/xarc-index-page/test/spec/utils.spec.ts
new file mode 100644
index 000000000..1f660dcc7
--- /dev/null
+++ b/packages/xarc-index-page/test/spec/utils.spec.ts
@@ -0,0 +1,58 @@
+"use strict";
+
+import { describe, it } from "mocha";
+import { resolveChunkSelector, getIconStats, getCriticalCSS } from "../../src/utils";
+import { expect } from "chai";
+
+describe("subapp-server utils", () => {
+ describe("resolveChunkSelector", () => {
+ it("should return specified chunk selector if the chunk selector file exists", () => {
+ const selector = resolveChunkSelector({ bundleChunkSelector: "./test/data/selector" });
+ expect(selector).to.be.a("function");
+ const { js, css } = selector();
+ expect(js).to.equal("app");
+ expect(css).to.equal("app");
+ });
+
+ it("should return default chunk selector if the bundleChunkSelector does not exists", () => {
+ const selector = resolveChunkSelector({});
+ expect(selector).to.be.a("function");
+ const { js, css } = selector();
+ expect(js).to.equal("main");
+ expect(css).to.equal("main");
+ });
+ });
+
+ describe("getIconStats", () => {
+ it("should load iconStats", () => {
+ const html = ["icons-123.png", "icons-123/manifest.json", "icons-123/manifest.webapp"];
+ const iconStats = getIconStats("./test/data/iconstats.json");
+ expect(iconStats).to.equal(html.join(""));
+ });
+
+ it("should load iconStats as object if html field not exists", () => {
+ const iconStats = getIconStats("./test/data/iconstats-no-html.json");
+ expect(iconStats).to.be.an("object");
+ expect(Object.keys(iconStats).length).to.equal(1);
+ });
+
+ it("should return empty string if iconstats.json does not exist", () => {
+ expect(getIconStats("some path")).to.equal("");
+ });
+
+ it("should return empty string if iconstats file cannot be parsed as js object", () => {
+ expect(getIconStats("./test/data/selector")).to.equal("");
+ });
+ });
+
+ describe("getCriticalCSS", () => {
+ it("should get criticalCss", () => {
+ const criticalCss = getCriticalCSS("./test/data/critical.css");
+ expect(criticalCss).to.include("h1");
+ });
+
+ it("should return empty string if criticalCss file does not exist", () => {
+ expect(getCriticalCSS()).to.equal("");
+ });
+ });
+});
diff --git a/packages/xarc-index-page/test/spec/webapp.spec.ts b/packages/xarc-index-page/test/spec/webapp.spec.ts
deleted file mode 100644
index 2df0394e6..000000000
--- a/packages/xarc-index-page/test/spec/webapp.spec.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { expect } from "chai";
-import xstdout = require("xstdout");
-import { describe } from "mocha";
-import * as Webapp from "../../src/Webapp";
-
-describe("resolveContent", function () {
- it("should require module with relative path", () => {
- const f = "./test/data/foo.js";
- expect(Webapp.resolveContent({ module: f }).content).to.equal("hello");
- });
-
- it("should log error if resolving content fail", () => {
- const intercept = xstdout.intercept(true);
- const f = "./test/data/bad-content.js";
- const content = Webapp.resolveContent({ module: f });
- intercept.restore();
- expect(content.content).includes("test/data/bad-content.js failed");
- expect(intercept.stderr.join("")).includes("Error: Cannot find module 'foo-blah'");
- });
- it("should require module", () => {
- let mod;
- const fooRequire = x => (mod = x);
- fooRequire.resolve = x => x;
- const f = "test";
- const content = Webapp.resolveContent({ module: f }, fooRequire);
- expect(content.content).to.equal(f);
- expect(content.fullPath).to.equal(f);
- expect(mod).to.equal(f);
- });
- it("should require module", () => {
- let mod;
- const fooRequire = x => (mod = x);
- fooRequire.resolve = x => x;
- const f = "test";
- const content = Webapp.resolveContent({ module: f }, fooRequire);
- expect(content.content).to.equal(f);
- expect(content.fullPath).to.equal(f);
- expect(mod).to.equal(f);
- });
-});
diff --git a/packages/xarc-jsx-renderer/package.json b/packages/xarc-jsx-renderer/package.json
index 949c11f39..581404eba 100644
--- a/packages/xarc-jsx-renderer/package.json
+++ b/packages/xarc-jsx-renderer/package.json
@@ -31,7 +31,7 @@
"@types/sinon-chai": "^3.2.4",
"@typescript-eslint/eslint-plugin": "^2.21.0",
"@typescript-eslint/parser": "^2.21.0",
- "@xarc/module-dev": "^2.1.1",
+ "@xarc/module-dev": "^2.1.2",
"babel-eslint": "^10.1.0",
"chai": "^4.2.0",
"eslint": "^6.8.0",
@@ -106,7 +106,9 @@
},
"fyn": {
"dependencies": {
- "@xarc/render-context": "../xarc-render-context"
- }
+ "@xarc/render-context": "../xarc-render-context",
+ "xaa": "../../../xaa"
+ },
+ "devDependencies": {}
}
}
diff --git a/packages/xarc-jsx-renderer/src/Component.ts b/packages/xarc-jsx-renderer/src/Component.ts
index 116f0050f..061325a36 100644
--- a/packages/xarc-jsx-renderer/src/Component.ts
+++ b/packages/xarc-jsx-renderer/src/Component.ts
@@ -1,6 +1,6 @@
/**
* @packageDocumentation
- * @module index
+ * @module @xarc/jsx-renderer
*/
/* eslint-disable filenames/match-regex */
diff --git a/packages/xarc-jsx-renderer/src/JsxRenderer.ts b/packages/xarc-jsx-renderer/src/JsxRenderer.ts
index f5fc277dd..7eec16056 100644
--- a/packages/xarc-jsx-renderer/src/JsxRenderer.ts
+++ b/packages/xarc-jsx-renderer/src/JsxRenderer.ts
@@ -1,11 +1,13 @@
/**
* @packageDocumentation
- * @module index
+ * @module @xarc/jsx-renderer
*/
/* eslint-disable max-statements, max-params, prefer-template, complexity */
-/* eslint-disable comma-dangle, arrow-parens, filenames/match-regex */
+/* eslint-disable filenames/match-regex */
+import * as requireAt from "require-at";
+import * as Path from "path";
import * as assert from "assert";
import * as _ from "lodash";
import {
@@ -30,6 +32,7 @@ export class JsxRenderer {
private _beforeRenders: any;
private _afterRenders: any;
private _handlersLookup: any;
+ private _templateRequire: any;
constructor(options: any) {
this._options = options;
@@ -48,6 +51,7 @@ export class JsxRenderer {
options
);
this._template = options.template;
+ this._templateRequire = requireAt(Path.resolve(this._options.templateFullPath || ""));
}
get insertTokenIds() {
@@ -206,8 +210,31 @@ export class JsxRenderer {
}
}
+ registerTokenHandler(name, handlerMod, call) {
+ let mod = handlerMod;
+ const isStr = typeof mod === "string";
+ if (isStr && !name) {
+ name = this._templateRequire.resolve(mod);
+ }
+
+ const id = name + (call || "");
+ if (this._handlersMap[id]) {
+ return;
+ }
+ if (typeof mod === "string") {
+ mod = loadTokenModuleHandler(mod, this.templateFullPath, call);
+ mod = (call && mod[call]) || mod;
+ }
+ let handler = mod(this._handlerContext, this);
+ if (!handler.tokens) {
+ handler = { name, tokens: handler };
+ }
+ this._handlersMap[id] = handler;
+ this._handlersLookup = [handler].concat(this._handlersLookup);
+ }
+
private _loadTokenHandler(path) {
- const mod = loadTokenModuleHandler(path);
+ const mod = loadTokenModuleHandler(path, this.templateFullPath);
return mod(this._handlerContext, this);
}
diff --git a/packages/xarc-jsx-renderer/src/index.ts b/packages/xarc-jsx-renderer/src/index.ts
index c9118e151..325c26b9c 100644
--- a/packages/xarc-jsx-renderer/src/index.ts
+++ b/packages/xarc-jsx-renderer/src/index.ts
@@ -1,18 +1,19 @@
-/* eslint-disable comma-dangle, arrow-parens, no-param-reassign */
+/* eslint-disable no-param-reassign */
/**
* @packageDocumentation
- * @module index
+ * @module @xarc/jsx-renderer
*/
import { Component } from "./Component";
+import { JsxRenderer } from "./JsxRenderer";
import { Token } from "./tags/Token";
import { IndexPage } from "./tags/IndexPage";
import { Require } from "./tags/Require";
import { Literal } from "./tags/Literal";
-import { JsxRenderer } from "./JsxRenderer";
+import { LoadTokenHandler } from "./tags/LoadTokenHandler";
/** @ignore */
-export { Component, Token, IndexPage, Require, Literal, JsxRenderer };
+export { Component, Token, IndexPage, Require, Literal, JsxRenderer, LoadTokenHandler };
let ELEMENT_ID = 0;
@@ -43,7 +44,7 @@ export function createElement(type: any, props: any, ...children: any[]) {
id: ELEMENT_ID++,
type,
children,
- props,
+ props
};
const literal = typeof type === "string";
diff --git a/packages/xarc-jsx-renderer/src/process-token.ts b/packages/xarc-jsx-renderer/src/process-token.ts
index 84f3a089b..e916228b6 100644
--- a/packages/xarc-jsx-renderer/src/process-token.ts
+++ b/packages/xarc-jsx-renderer/src/process-token.ts
@@ -1,7 +1,6 @@
/** @ignore */ /** */
-/* eslint-disable max-params */
-/* eslint-disable comma-dangle, arrow-parens */
+/* eslint-disable max-params, max-statements */
import { TOKEN_HANDLER } from "@xarc/render-context";
@@ -33,7 +32,7 @@ export function processToken(props, context, scope, forRequire = false) {
}
const r = tokenInst[TOKEN_HANDLER](context, tokenInst);
- return context.handleTokenResult(tokenInst.id, r, (err) => {
+ return context.handleTokenResult(tokenInst.id, r, err => {
if (insertTokenIds) {
scope.output.add(``);
}
diff --git a/packages/xarc-jsx-renderer/src/tags/IndexPage.ts b/packages/xarc-jsx-renderer/src/tags/IndexPage.ts
index 3c365e080..555bc858c 100644
--- a/packages/xarc-jsx-renderer/src/tags/IndexPage.ts
+++ b/packages/xarc-jsx-renderer/src/tags/IndexPage.ts
@@ -1,6 +1,6 @@
/**
* @packageDocumentation
- * @module index
+ * @module @xarc/jsx-renderer
*/
/* eslint-disable filenames/match-regex */
diff --git a/packages/xarc-jsx-renderer/src/tags/Literal.ts b/packages/xarc-jsx-renderer/src/tags/Literal.ts
index afd473e5e..b51fb103f 100644
--- a/packages/xarc-jsx-renderer/src/tags/Literal.ts
+++ b/packages/xarc-jsx-renderer/src/tags/Literal.ts
@@ -1,9 +1,9 @@
/**
* @packageDocumentation
- * @module index
+ * @module @xarc/jsx-renderer
*/
-/* eslint-disable comma-dangle, arrow-parens, filenames/match-regex */
+/* eslint-disable filenames/match-regex */
/* eslint-disable no-unused-vars, no-magic-numbers */
import * as Path from "path";
diff --git a/packages/xarc-jsx-renderer/src/tags/LoadTokenHandler.ts b/packages/xarc-jsx-renderer/src/tags/LoadTokenHandler.ts
new file mode 100644
index 000000000..a7fe22aea
--- /dev/null
+++ b/packages/xarc-jsx-renderer/src/tags/LoadTokenHandler.ts
@@ -0,0 +1,47 @@
+/**
+ * @packageDocumentation
+ * @module @xarc/jsx-renderer
+ */
+/* eslint-disable filenames/match-regex */
+
+import { JsxRenderer } from "../JsxRenderer";
+
+/**
+ * Load and register classic template token handler for the JSX template.
+ * After loading the handler, its tokens can be used in the JSX template
+ *
+ * Usage: (*This is a JSX tag, don't call the function directly.*)
+ *
+ * - With function: ` {...}}`
+ * - Load from source file: ` `
+ * - If the handler string starts with `"."` then it will be resolved to the full
+ * path relative to the template's location, else it's a module for require.
+ * - The full path of the handler will be used as `name`, but you can provide
+ * a name prop if desired.
+ *
+ *
+ *
+ * Example:
+ *
+ * ```js
+ * import { IndexPage, Token, LoadTokenHandler } from "@xarc/jsx-renderer"
+ *
+ * export const Template = () => (
+ * {
+ * return {
+ * TOKEN1: context => {
+ * return "result from TOKEN1"
+ * }
+ * }
+ * }} />
+ *
+ * )
+ * ```
+ *
+ * @param props - JSX tag props
+ * @param context - rendering context
+ */
+export const LoadTokenHandler = (props, context) => {
+ const renderer: JsxRenderer = context.asyncTemplate;
+ renderer.registerTokenHandler(props.name, props.handler, props.call);
+};
diff --git a/packages/xarc-jsx-renderer/src/tags/Require.ts b/packages/xarc-jsx-renderer/src/tags/Require.ts
index d30588882..f0b5767c2 100644
--- a/packages/xarc-jsx-renderer/src/tags/Require.ts
+++ b/packages/xarc-jsx-renderer/src/tags/Require.ts
@@ -1,6 +1,6 @@
/**
* @packageDocumentation
- * @module index
+ * @module @xarc/jsx-renderer
*/
/* eslint-disable filenames/match-regex */
diff --git a/packages/xarc-jsx-renderer/src/tags/Token.ts b/packages/xarc-jsx-renderer/src/tags/Token.ts
index 972bdecad..e5f80a13c 100644
--- a/packages/xarc-jsx-renderer/src/tags/Token.ts
+++ b/packages/xarc-jsx-renderer/src/tags/Token.ts
@@ -1,6 +1,6 @@
/**
* @packageDocumentation
- * @module index
+ * @module @xarc/jsx-renderer
*/
/* eslint-disable filenames/match-regex */
diff --git a/packages/xarc-jsx-renderer/src/utils.ts b/packages/xarc-jsx-renderer/src/utils.ts
index 7e5739ab5..892a82807 100644
--- a/packages/xarc-jsx-renderer/src/utils.ts
+++ b/packages/xarc-jsx-renderer/src/utils.ts
@@ -1,7 +1,5 @@
/** @ignore */ /** */
-/* eslint-disable comma-dangle, arrow-parens */
-
export function expandProps(props, context) {
let s = "";
@@ -38,6 +36,6 @@ export const omittedCloseTags = {
param: true,
source: true,
track: true,
- wbr: true,
+ wbr: true
// NOTE: menuitem's close tag should be omitted, but that causes problems.
};
diff --git a/packages/xarc-jsx-renderer/test/fixtures/token-handler.js b/packages/xarc-jsx-renderer/test/fixtures/token-handler.js
index 5d93eb256..0175532ab 100644
--- a/packages/xarc-jsx-renderer/test/fixtures/token-handler.js
+++ b/packages/xarc-jsx-renderer/test/fixtures/token-handler.js
@@ -65,3 +65,13 @@ module.exports = () => {
}
};
};
+
+module.exports.handler2 = () => {
+ return {
+ tokens: {
+ TOKEN_HANDLER2: () => {
+ return "test token handler2";
+ }
+ }
+ };
+};
diff --git a/packages/xarc-jsx-renderer/test/jsx-templates/index-intercept.jsx b/packages/xarc-jsx-renderer/test/jsx-templates/index-intercept.jsx
index a827a4a63..53d4d49f9 100644
--- a/packages/xarc-jsx-renderer/test/jsx-templates/index-intercept.jsx
+++ b/packages/xarc-jsx-renderer/test/jsx-templates/index-intercept.jsx
@@ -1,6 +1,6 @@
/* @jsx createElement */
-const { IndexPage, createElement, Token, Require } = require("../../lib/jsx");
+const { IndexPage, createElement, Token } = require("../../src");
const Test = (props, context) => {
context.intercept({
diff --git a/packages/xarc-jsx-renderer/test/jsx-templates/test-load-token-handler.tsx b/packages/xarc-jsx-renderer/test/jsx-templates/test-load-token-handler.tsx
new file mode 100644
index 000000000..2bcf8ddf3
--- /dev/null
+++ b/packages/xarc-jsx-renderer/test/jsx-templates/test-load-token-handler.tsx
@@ -0,0 +1,35 @@
+/* @jsx createElement */
+
+import {
+ IndexPage,
+ createElement,
+ Token,
+ Require,
+ Literal,
+ Component,
+ LoadTokenHandler
+} from "../../src";
+
+const Template = () => {
+ return (
+
+ {
+ return {
+ FOO: () => "this is a test"
+ };
+ }}
+ />
+
+
+
+
+ {/* Test re-entry */}
+
+
+
+ );
+};
+
+export default Template;
diff --git a/packages/xarc-jsx-renderer/test/jsx-templates/test1.tsx b/packages/xarc-jsx-renderer/test/jsx-templates/test1.tsx
index e26864b26..19b37f083 100644
--- a/packages/xarc-jsx-renderer/test/jsx-templates/test1.tsx
+++ b/packages/xarc-jsx-renderer/test/jsx-templates/test1.tsx
@@ -26,7 +26,7 @@ class TestComponent1 extends Component {
}
function AsyncComponent(props, context, scope) {
- return new Promise((resolve) => {
+ return new Promise(resolve => {
setTimeout(() => {
scope.output.add(`${props.indent}async component ${props.key}\n`);
resolve(async component children: {props.children}
);
diff --git a/packages/xarc-jsx-renderer/test/spec/LoadTokenHandler.spec.js b/packages/xarc-jsx-renderer/test/spec/LoadTokenHandler.spec.js
new file mode 100644
index 000000000..8d0ab7682
--- /dev/null
+++ b/packages/xarc-jsx-renderer/test/spec/LoadTokenHandler.spec.js
@@ -0,0 +1 @@
+/* eslint-disable filenames/match-regex */
diff --git a/packages/xarc-jsx-renderer/test/spec/jsx-renderer.spec.tsx b/packages/xarc-jsx-renderer/test/spec/jsx-renderer.spec.tsx
index 8c7371656..725af4b96 100644
--- a/packages/xarc-jsx-renderer/test/spec/jsx-renderer.spec.tsx
+++ b/packages/xarc-jsx-renderer/test/spec/jsx-renderer.spec.tsx
@@ -16,6 +16,7 @@ import Template7 from "../jsx-templates/test7";
import Template8 from "../jsx-templates/test8";
import Template9 from "../jsx-templates/test9";
import Template91 from "../jsx-templates/test91";
+import TemplateLoadTokenHandler from "../jsx-templates/test-load-token-handler";
describe("IndexPage", function () {
it("should have static memoize", () => {
@@ -30,7 +31,7 @@ describe("Jsx Renderer", function () {
insertTokenIds: true,
templateFullPath: Path.dirname(require.resolve("../jsx-templates/test1")),
template: Template,
- tokenHandlers: "./test/fixtures/token-handler"
+ tokenHandlers: "../fixtures/token-handler"
});
expect(renderer.getTokenInst({ props: { _id: "blah" } })).to.equal(undefined);
});
@@ -40,7 +41,7 @@ describe("Jsx Renderer", function () {
insertTokenIds: true,
templateFullPath: Path.dirname(require.resolve("../jsx-templates/test1")),
template: Template,
- tokenHandlers: "./test/fixtures/token-handler"
+ tokenHandlers: "../fixtures/token-handler"
});
renderer.initializeRenderer();
renderer.initializeRenderer(true);
@@ -65,7 +66,7 @@ describe("Jsx Renderer", function () {
),
- tokenHandlers: "./test/fixtures/token-handler"
+ tokenHandlers: "../fixtures/token-handler"
});
renderer.initializeRenderer();
return renderer.render({}).then(context => {
@@ -115,7 +116,7 @@ describe("Jsx Renderer", function () {
),
- tokenHandlers: "./test/fixtures/token-handler"
+ tokenHandlers: "../fixtures/token-handler"
});
renderer.initializeRenderer();
@@ -151,7 +152,7 @@ describe("Jsx Renderer", function () {
insertTokenIds: true,
templateFullPath: Path.dirname(require.resolve("../jsx-templates/test1")),
template: Template,
- tokenHandlers: "./test/fixtures/token-handler"
+ tokenHandlers: "../fixtures/token-handler"
});
const verify = context => {
@@ -161,7 +162,7 @@ describe("Jsx Renderer", function () {
.map(x => x.trimRight())
.join("\n");
- expect(r).equal(test1ExpectedOutput);
+ expect(r).contains(test1ExpectedOutput);
};
renderer.initializeRenderer();
@@ -176,7 +177,7 @@ describe("Jsx Renderer", function () {
it("should handle failure in nesting async components", async () => {
const renderer = new JsxRenderer({
insertTokenIds: true,
- templateFullPath: Path.dirname(require.resolve("../jsx-templates/test2")),
+ templateFullPath: null, // test passing no templateFullPath so CWD would be used
template: Template2,
tokenHandlers: "./test/fixtures/token-handler"
});
@@ -191,7 +192,7 @@ describe("Jsx Renderer", function () {
const renderer = new JsxRenderer({
templateFullPath: Path.dirname(require.resolve("../jsx-templates/test4")),
template: Template4,
- tokenHandlers: "./test/fixtures/token-handler"
+ tokenHandlers: "../fixtures/token-handler"
});
renderer.initializeRenderer();
@@ -213,7 +214,7 @@ describe("Jsx Renderer", function () {
insertTokenIds: true,
templateFullPath: Path.dirname(require.resolve("../jsx-templates/test3")),
template: Template3,
- tokenHandlers: "./test/fixtures/token-handler"
+ tokenHandlers: "../fixtures/token-handler"
});
renderer.initializeRenderer();
@@ -235,7 +236,7 @@ describe("Jsx Renderer", function () {
insertTokenIds: false,
templateFullPath: Path.dirname(require.resolve("../jsx-templates/test5")),
template: Template5,
- tokenHandlers: "./test/fixtures/token-handler"
+ tokenHandlers: "../fixtures/token-handler"
});
const verify = context => {
@@ -263,7 +264,7 @@ describe("Jsx Renderer", function () {
insertTokenIds: false,
templateFullPath: Path.dirname(require.resolve("../jsx-templates/test6")),
template: Template6,
- tokenHandlers: "./test/fixtures/token-handler"
+ tokenHandlers: "../fixtures/token-handler"
});
const verify = context => {
@@ -288,7 +289,7 @@ World`
insertTokenIds: true,
templateFullPath: Path.dirname(require.resolve("../jsx-templates/test7")),
template: Template7,
- tokenHandlers: "./test/fixtures/token-handler"
+ tokenHandlers: "../fixtures/token-handler"
});
const verify = context => {
@@ -310,7 +311,7 @@ World`
insertTokenIds: true,
templateFullPath: Path.dirname(require.resolve("../jsx-templates/test8")),
template: Template8,
- tokenHandlers: "./test/fixtures/token-handler"
+ tokenHandlers: "../fixtures/token-handler"
});
const verify = context => {
@@ -332,7 +333,7 @@ World`
insertTokenIds: true,
templateFullPath: Path.dirname(require.resolve("../jsx-templates/test9")),
template: Template9,
- tokenHandlers: "./test/fixtures/token-handler"
+ tokenHandlers: "../fixtures/token-handler"
});
const verify = context => {
@@ -355,7 +356,7 @@ World`
templateFullPath: Path.dirname(require.resolve("../jsx-templates/test91")),
template: Template91,
tokenHandlers: [
- "./test/fixtures/token-handler",
+ "../fixtures/token-handler",
{
name: "test1",
beforeRender: () => {
@@ -381,4 +382,18 @@ World`
verify(context);
});
});
+
+ it("should handle LoadTokenHandler from template", async () => {
+ const renderer = new JsxRenderer({
+ insertTokenIds: true,
+ templateFullPath: Path.dirname(require.resolve("../jsx-templates/test91")),
+ template: TemplateLoadTokenHandler
+ });
+
+ renderer.initializeRenderer();
+ const context = await renderer.render({});
+ const result = await context.result;
+ expect(result).contains("this is a test");
+ expect(result).contains("
user-token-1
");
+ });
});
diff --git a/packages/xarc-jsx-renderer/test/spec/test1-output.txt b/packages/xarc-jsx-renderer/test/spec/test1-output.txt
index 4e211ef73..17b232f29 100644
--- a/packages/xarc-jsx-renderer/test/spec/test1-output.txt
+++ b/packages/xarc-jsx-renderer/test/spec/test1-output.txt
@@ -17,7 +17,7 @@ my test
user-handler-title
-token process module subapp-web/lib/init not found
+@xarc/render-context: token process module subapp-web/lib/init not found