Skip to content

Commit

Permalink
more work to support subapp inlining subapp (#1524)
Browse files Browse the repository at this point in the history
  • Loading branch information
jchip authored Feb 7, 2020
1 parent 3cda7f2 commit a985431
Show file tree
Hide file tree
Showing 36 changed files with 957 additions and 28 deletions.
18 changes: 18 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,24 @@
],
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "node",
"request": "launch",
"name": "subapp-pbundle Mocha Tests",
"cwd": "${workspaceFolder}/packages/subapp-pbundle",
"env": { "BABEL_ENV": "-src-node" },
"program": "${workspaceFolder}/packages/subapp-pbundle/node_modules/mocha/bin/_mocha",
"args": [
"-u",
"tdd",
"--timeout",
"999999",
"--colors",
"${workspaceFolder}/packages/subapp-pbundle/test/spec"
],
"internalConsoleOptions": "openOnSessionStart"
},

{
"type": "node",
"request": "launch",
Expand Down
50 changes: 40 additions & 10 deletions packages/subapp-pbundle/lib/framework-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ class FrameworkLib {
return "";
}

handleSSRSync() {
const { subApp, options } = this.ref;

assert(this._prepared, "subapp's data must've been prepared to run handleSSRSync");

if (!this.StartComponent) {
return `<!-- serverSideRendering ${subApp.name} has no StartComponent -->`;
} else if (subApp.__redux) {
return this.doReduxBundlerSSR();
} else if (options.serverSideRendering === true) {
return this.doSSR();
}

return "";
}

renderTo(element, options) {
if (options.streaming) {
throw new Error("render to stream is not yet supported for preact");
Expand Down Expand Up @@ -109,14 +125,13 @@ class FrameworkLib {
return this._initialProps;
}

async doSSR() {
return await this.renderTo(this.createTopComponent(this._initialProps), this.ref.options);
doSSR() {
return this.renderTo(this.createTopComponent(this._initialProps), this.ref.options);
}

async prepareReduxData() {
const { subApp, subAppServer, context } = this.ref;
const { request } = context.user;
const container = request.container || (request.container = {});

// subApp.reduxReducers || subApp.reduxCreateStore) {
// if sub app has reduxReducers or reduxCreateStore then assume it's using
Expand All @@ -133,6 +148,8 @@ class FrameworkLib {

if (!reduxData) {
reduxData = { initialState: {} };
} else {
this.store = reduxData.store;
}

this.initialState = reduxData.initialState || reduxData;
Expand All @@ -142,28 +159,41 @@ class FrameworkLib {
this.initialStateStr = JSON.stringify(this.initialState);
}
// next we take the initial state and create redux store from it
this.store =
reduxData.store ||
(subApp.reduxCreateStore && (await subApp.reduxCreateStore(this.initialState, container)));
return await this.prepareReduxStore();
}

async prepareReduxStore() {
const { subApp, context } = this.ref;

// next we take the initial state and create redux store from it
if (!this.store && subApp.reduxCreateStore) {
const storeContainer =
context.user.xarcReduxStoreContainer || (context.user.xarcReduxStoreContainer = {});

this.store = await subApp.reduxCreateStore(this.initialState, storeContainer);
}

assert(
this.store,
`redux subapp ${subApp.name} didn't provide store, reduxCreateStore, or reducers`
`redux subapp ${subApp.name} didn't provide store, reduxCreateStore, or redux bundles`
);

const reduxStoreReady = subAppServer.reduxStoreReady || subApp.reduxStoreReady;
const reduxStoreReady = this.ref.subAppServer.reduxStoreReady || subApp.reduxStoreReady;

if (reduxStoreReady) {
await reduxStoreReady({ store: this.store });
}

return this.store;
}

async doReduxBundlerSSR() {
doReduxBundlerSSR() {
const { options } = this.ref;

if (options.serverSideRendering === true) {
assert(Provider, "subapp-web: react-redux Provider not available");
// finally render the element with Redux Provider and the store created
return await this.renderTo(
return this.renderTo(
preact.createElement(Provider, { store: this.store }, this.createTopComponent()),
options
);
Expand Down
23 changes: 23 additions & 0 deletions packages/subapp-pbundle/test/spec/redux-bundler.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use strict";

const { reduxBundlerLoadSubApp } = require("../../lib/redux-bundler");

describe("redux-bundler load subapp", function() {
it("should initialize reduxCreateStore if it's not provided", () => {
const subapp = reduxBundlerLoadSubApp({
name: `test-${Date.now()}-1`
});
expect(subapp.__redux).to.equal(true);
expect(subapp._genReduxCreateStore).to.equal("subapp");
expect(subapp.reduxCreateStore).to.be.ok;
});

it("should keep user provided reduxCreateStore", () => {
const subapp = reduxBundlerLoadSubApp({
name: `test-${Date.now()}-2`,
reduxCreateStore: () => {}
});
expect(subapp.__redux).to.equal(true);
expect(subapp._genReduxCreateStore).to.equal(undefined);
});
});
44 changes: 42 additions & 2 deletions packages/subapp-pbundle/test/spec/ssr-framework.spec.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/** @jsx h */

"use strict";

/** @jsx h */
// const url = require("url");
const { h } = require("preact"); // eslint-disable-line
const lib = require("../../lib");

const { expectErrorHas, asyncVerify } = require("run-verify");

describe("SSR Preact framework", function() {
it("should setup React framework", () => {
expect(lib.preact).to.be.ok;
Expand Down Expand Up @@ -141,4 +143,42 @@ describe("SSR Preact framework", function() {
expect(res).contains(`<div>IS_SSR: true HAS_REQUEST: yes</div>`);
expect(request.foo).equals("bar");
});

it("handlePrepare should throw error if trying to use react router", () => {
const framework = new lib.FrameworkLib({
subApp: {
useReactRouter: true
},
subAppServer: {}
});
return asyncVerify(
expectErrorHas(
async () => await framework.handlePrepare(),
"react router is not yet supported"
)
);
});

it("handlePrepare should prepare redux data and store", async () => {
const framework = new lib.FrameworkLib({
subApp: {
__redux: true,
reduxCreateStore() {
return { testStore: true };
}
},
subAppServer: {
StartComponent: () => {},
async prepare() {
return { hello: "world" };
}
},
context: {
user: {}
}
});

const store = await framework.handlePrepare();
expect(store.testStore).to.equal(true);
});
});
3 changes: 2 additions & 1 deletion packages/subapp-server/src/index-page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const RenderSubApps = (props, context) => {
const { routeOptions } = context.user;
const { subApps } = routeOptions.__internals;

const elementId = props.inline ? undefined : `subapp-${subapp.name}-${ix}`;
return (
subApps &&
subApps.length > 0 &&
Expand All @@ -19,7 +20,7 @@ const RenderSubApps = (props, context) => {
Object.assign(
{
_concurrent: true,
elementId: `subapp-${subapp.name}-${ix}`,
elementId,
timestamp: true,
streaming: false,
async: true,
Expand Down
51 changes: 37 additions & 14 deletions packages/subapp-web/lib/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,14 +201,18 @@ module.exports = function setup(setupContext, { props: options }) {
// add the div for the Node and the SSR content to it, and add JS to start the
// sub app on load.
let elementId = "";
if (options.elementId) {
if (!options.inline && options.elementId) {
elementId = `elementId:"${options.elementId}",\n `;
outputSpot.add(`<div id="${options.elementId}">`);
outputSpot.add(ssrContent); // must add by itself since this could be a stream
outputSpot.add(`</div>`);
} else {
outputSpot.add("<!-- no elementId for starting subApp on load -->\n");
outputSpot.add(ssrContent);
outputSpot.add("<!-- inline or no elementId for starting subApp on load -->");
if (ssrContent) {
outputSpot.add("\n");
outputSpot.add(ssrContent);
outputSpot.add("\n");
}
}

let dynInitialState = "";
Expand All @@ -227,11 +231,13 @@ ${initialStateStr}
initialStateScript = `JSON.parse(document.getElementById("${dataId}").innerHTML)`;
}

const inlineStr = options.inline ? `inline:${options.inline},\n ` : "";
const groupStr = options.group ? `group:"${options.group}",\n ` : "";
outputSpot.add(`
${dynInitialState}<script>${xarc}.startSubAppOnLoad({
name:"${name}",
${elementId}serverSideRendering:${Boolean(options.serverSideRendering)},
clientProps:${clientProps},
${inlineStr}${groupStr}clientProps:${clientProps},
initialState:${initialStateScript}
});</script>
`);
Expand Down Expand Up @@ -280,15 +286,32 @@ ${stack}
const lib = util.getFramework(ref);
ssrInfo.awaitData = lib.handlePrepare();

ssrInfo.renderSSR = async () => {
try {
outputSSRContent(await lib.handleSSR(ref), lib.initialStateStr);
} catch (err) {
handleError(err);
} finally {
closeOutput();
}
};
ssrInfo.defer = true;

if (!options.inline) {
ssrInfo.renderSSR = async () => {
try {
outputSSRContent(await lib.handleSSR(ref), lib.initialStateStr);
} catch (err) {
handleError(err);
} finally {
closeOutput();
}
};
} else {
ssrInfo.saveSSRInfo = () => {
try {
// output load without SSR content
outputSSRContent("", lib.initialStateStr);
ssrInfo.lib = lib;
_.set(request.app, ["xarcInlineSSR", name], ssrInfo);
} catch (err) {
handleError(err);
} finally {
closeOutput();
}
};
}
} else {
outputSSRContent("");
}
Expand All @@ -305,7 +328,7 @@ ${stack}
} catch (err) {
handleError(err);
} finally {
if (!ssrInfo.renderSSR) {
if (!ssrInfo.defer) {
closeOutput();
}
}
Expand Down
5 changes: 4 additions & 1 deletion packages/subapp-web/lib/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ module.exports = function setup() {
// and then wait for it to complete data prepare
// awaitData should be available once ready is awaited
await info.awaitData;
if (info.saveSSRInfo) {
info.saveSSRInfo();
}
},
{ concurrency }
);

// finally kick off rendering for every subapp in the group
await xaa.map(
queue,
queue.filter(x => x.renderSSR),
async ({ renderSSR }) => {
if (renderSSR) await renderSSR();
},
Expand Down
4 changes: 4 additions & 0 deletions samples/poc-subapp-pbx/.browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Browsers that we support
last 2 versions
ie >= 11
> 5%
11 changes: 11 additions & 0 deletions samples/poc-subapp-pbx/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
root = true

[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
11 changes: 11 additions & 0 deletions samples/poc-subapp-pbx/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
var path = require("path");
var archetype = require("electrode-archetype-react-app/config/archetype");
var archetypeEslint = path.join(archetype.config.eslint, ".eslintrc-react");

function dotify(p) {
return path.isAbsolute(p) ? p : "." + path.sep + p;
}

module.exports = {
extends: dotify(path.relative(__dirname, archetypeEslint))
};
1 change: 1 addition & 0 deletions samples/poc-subapp-pbx/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto
Loading

0 comments on commit a985431

Please sign in to comment.