Skip to content

Commit

Permalink
use render pipeline for subapp version 2
Browse files Browse the repository at this point in the history
  • Loading branch information
jchip committed Jan 19, 2021
1 parent cbdb541 commit f71798d
Show file tree
Hide file tree
Showing 47 changed files with 1,325 additions and 363 deletions.
8 changes: 4 additions & 4 deletions packages/xarc-react-redux/src/common/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type ReduxFeatureOptions = {
* This is needed for the redux feature to wrap subapp's component inside
* the Redux Provider component.
*/
React: any;
React: Partial<{ createElement: unknown }>;

/**
* Configure the redux store to use
Expand Down Expand Up @@ -95,7 +95,7 @@ export function reduxFeature(options: ReduxFeatureOptions): SubAppFeatureFactory
};
redux.prepare = options.prepare;

redux.execute = async function ({ input, startOptions, reload }) {
redux.execute = async function ({ input, csrData, reload }) {
let initialState: any;

let reducers = options.reducers;
Expand All @@ -115,7 +115,7 @@ export function reduxFeature(options: ReduxFeatureOptions): SubAppFeatureFactory
redux._store.replaceReducer(reducers);
}
} else {
const props = startOptions && (await startOptions.getInitialState());
const props = csrData && (await csrData.getInitialState());
if (reducers === true) {
reducers = subapp._module.reduxReducers;
}
Expand All @@ -130,7 +130,7 @@ export function reduxFeature(options: ReduxFeatureOptions): SubAppFeatureFactory
return {
Component: () =>
this.wrap({
Component: input.Component || subapp._module.subapp.Component,
Component: input.Component || subapp._getExport()?.Component,
store: redux._store
}),
props: initialState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function reactRouterFeature(options: ReactRouterFeatureOptions): SubAppFe
id,
subId,
execute({ input }) {
const Component = input.Component || subapp._module.subapp.Component;
const Component = input.Component || subapp._getExport()?.Component;
return {
Component: (props: any) => {
return (
Expand Down
2 changes: 1 addition & 1 deletion packages/xarc-react-router/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type ReactRouterFeatureOptions = {
* This is needed for the react router feature to wrap subapp's component inside
* the Router component.
*/
React: any;
React: Partial<{ createElement: unknown }>;
/**
* A custom browser history object and control which Router to use.
*
Expand Down
2 changes: 1 addition & 1 deletion packages/xarc-react-router/src/node/react-router-node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function reactRouterFeature(options: ReactRouterFeatureOptions): SubAppFe
id,
subId,
execute({ input, ssrData }) {
const Component = input.Component || subapp._module.subapp.Component;
const Component = input.Component || subapp._getExport()?.Component;
const routerContext = {};
ssrData.context.user.routerContext = routerContext;

Expand Down
4 changes: 2 additions & 2 deletions packages/xarc-react/src/browser/feat-static-props-browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export function staticPropsFeature(_options: StaticPropsFeatureOptions): SubAppF
subapp._features.staticProps = {
id,
subId,
execute({ input, startOptions }) {
const props = startOptions.getInitialState();
execute({ input, csrData }) {
const props = csrData.getInitialState();
return {
Component: () => {
return <input.Component {...props} />;
Expand Down
17 changes: 15 additions & 2 deletions packages/xarc-react/src/browser/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
// import { ReactLib } from "./react-lib";
import { declareSubApp as dsa, SubAppDef, SubAppOptions, SubAppFeatureFactory } from "@xarc/subapp";
import {
declareSubApp as dsa,
SubAppDef,
SubAppOptions,
SubAppFeatureFactory,
PipelineFactoryParams
} from "@xarc/subapp";
import { __createDynamicComponent, CreateComponentOptions } from "../common/create-component";
import { BrowserReactLib } from "./react-lib-browser";
import { __reactFrameworkFeature, __addFeature } from "../common";
import { appContextFeature } from "./feat-app-context-browser";
import { ReactClientRenderPipeline } from "./react-render-pipeline";
//
// re-exports
//
export * from "../common";
export * from "./feat-static-props-browser";
export * from "./subapp-as-component";
export { appContextFeature };

/**
Expand All @@ -29,7 +37,12 @@ function __declareSubApp(options: SubAppOptions): SubAppDef {
// add framework feature if it's not exist
let opts = __addFeature(options, "framework", reactFrameworkFeature);
opts = __addFeature(opts, "app-context-provider", appContextFeature);
return dsa(opts);
const def = dsa(opts);
def._pipelineFactory = function ({ csrData }: PipelineFactoryParams) {
return new ReactClientRenderPipeline(csrData);
};

return def;
}

export { __declareSubApp as declareSubApp };
Expand Down
81 changes: 55 additions & 26 deletions packages/xarc-react/src/browser/react-lib-browser.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
/* eslint-disable @typescript-eslint/no-unused-vars, max-statements, @typescript-eslint/ban-ts-comment */
/* eslint-disable max-params, @typescript-eslint/no-unused-vars, max-statements, @typescript-eslint/ban-ts-comment */

import { SubAppDef, SubAppStartOptions, FrameworkLib } from "./index";
import { render, hydrate } from "react-dom";
import { createElement } from "react"; // eslint-disable-line
import { createElement, Component } from "react"; // eslint-disable-line
import { SubAppFeatureResult, xarcV2, ClientFrameworkLib, envHooks } from "@xarc/subapp";
import { SubAppCSRData } from "./index";
import { ReactClientRenderPipeline } from "./react-render-pipeline";
import { SubAppStartComponent } from "./subapp-start-component";
import { SubAppFeatureResult, xarcV2 } from "@xarc/subapp";

/**
* The implementation of Framework Lib for React for the browser.
*/
export class BrowserReactLib implements FrameworkLib {
export class BrowserReactLib implements ClientFrameworkLib {
constructor() {
//
}
Expand All @@ -19,56 +20,84 @@ export class BrowserReactLib implements FrameworkLib {
}

/**
* Start a subapp on the browser
*
* Note: this is called by the SubAppDef._start method that declareSubApp
* from @xarc/subapp module creates, with separate versions for browser
* and node.js.
*
* @param subapp
* @param options
* @param reload
* Prepare subapp for client side rendering
*/
async startSubApp(subapp: SubAppDef, options: SubAppStartOptions, reload?: boolean) {
async prepareCSR(csrData: SubAppCSRData, pipeline: ReactClientRenderPipeline, reload?: boolean) {
const subapp = envHooks.getContainer().get(csrData.name);

if (!subapp._module) {
xarcV2.debug("startSubApp", subapp.name, "module is not yet loaded.");
await subapp._getModule();
}
const mod = subapp._module;

let Comp = subapp._getExport().Component;
//
// in dev mode, use a wrapper component to wrap the subapp's component so we can
// re-render it when there' hot module update
//
// @ts-ignore module.hot
const Component = module.hot
? (props: any) => <SubAppStartComponent __subapp={subapp} __props={props} />
: mod.Component || mod.subapp?.Component;
if (module.hot) {
Comp = (props: any) => (
<SubAppStartComponent subapp={subapp} __props={props} pipeline={pipeline} />
);
}

const featNames = Object.keys(subapp._features);

let result: SubAppFeatureResult = { Component };
let result: SubAppFeatureResult = { Component: Comp };

// the order feature providers will be invoked
const featIds = ["state-provider", "router-provider", "app-context-provider"];

xarcV2.debug("subapp features of", subapp.name, featNames);

for (const featId of featIds) {
const featName = featNames.find(x => subapp._features[x].id === featId);
if (featName) {
const feat = subapp._features[featName];
xarcV2.debug("executing subapp feature", featName, "id", featId, "subId", feat.subId);
let nextRes = feat.execute({ input: result, startOptions: options, reload });
let nextRes = feat.execute({ input: result, csrData, reload });
if ((nextRes as any).then) {
nextRes = await nextRes;
}
result = Object.assign({}, result, nextRes);
}
}

if (!reload && options.ssr) {
hydrate(<result.Component />, options.element);
return result;
}

/**
* Start a subapp on the browser
*
* Note: this is called by the SubAppDef._start method that declareSubApp
* from @xarc/subapp module creates, with separate versions for browser
* and node.js.
*
* @param subapp
* @param options
* @param reload
*/
async startSubApp(csrData: SubAppCSRData, pipeline: ReactClientRenderPipeline, reload?: boolean) {
const result = await this.prepareCSR(csrData, pipeline, reload);
if (!reload && csrData.ssr) {
hydrate(<result.Component />, csrData.element);
} else {
render(<result.Component />, csrData.element);
}
}

startSubAppSync(csrData: SubAppCSRData, pipeline: ReactClientRenderPipeline, reload?: boolean) {
const subapp = envHooks.getContainer().get(csrData.name);
if (!subapp._module) {
console.error("startSubAppSync can't start because subapp._module is not available"); // eslint-disable-line
return;
}

const prepResult = pipeline.getPrepResult();

if (!reload && csrData.ssr) {
hydrate(<prepResult.Component />, csrData.element);
} else {
render(<result.Component />, options.element);
render(<prepResult.Component />, csrData.element);
}
}
}
94 changes: 94 additions & 0 deletions packages/xarc-react/src/browser/react-render-pipeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { SubAppCSRData, xarcV2, ClientRenderPipeline, envHooks } from "@xarc/subapp";
import { SubAppMountInfo } from "../common";
import { BrowserReactLib } from "./react-lib-browser";

const _dynamics = [];

export class ReactClientRenderPipeline extends ClientRenderPipeline {
/** The component instance subapp mounted on */
startComponent: any;
framework?: BrowserReactLib;

constructor(csrData: SubAppCSRData) {
super(csrData);
// In production build, webpack will replace module.hot with false and the code will be optimized out
// eslint-disable-next-line
// @ts-ignore
if (module.hot) {
this._mount = function (info: SubAppMountInfo) {
xarcV2.debug(
"subapp pipeline _mount for",
info.subapp.name,
info,
info.component.constructor.name
);
if (info.type === "start" || info.type === "inline") {
this.startComponent = info.component;
}
if (_dynamics.indexOf(info) < 0) {
_dynamics.push(info);
}
};

this._unmount = function (info: SubAppMountInfo) {
xarcV2.debug(
"subapp pipeline _unmount for",
info.subapp.name,
info,
info.component.constructor.name
);
const subapp = envHooks.getContainer().get(info.subapp.name);
const plIx = subapp._renderPipelines.indexOf(this);
// if this pipeline's component is the same as the mount component, then
// it's no longer needed, so remove it from subapp.
if (plIx >= 0) {
if (this.startComponent === info.component) {
xarcV2.debug(
"removing unmounted pipeline because its startComponent is same as unmount component"
);
subapp._renderPipelines.slice(plIx, 1);
} else if (this.startComponent) {
xarcV2.debug(
"keeping unmounted pipeline and reloading because its startComponent has changed"
);
subapp._getModule().then(() => {
this.startComponent.reload();
});
} else {
xarcV2.debug(
"keeping unmounted pipeline because its startComponent has changed to null"
);
}
} else {
console.error("unmounted pipeline not found in subapp"); // eslint-disable-line
}
const ix = _dynamics.indexOf(info);
if (ix >= 0) {
_dynamics.splice(ix, 1);
}
};

this._reload = function () {
return Promise.resolve().then(() => {
if (this.csrData) {
if (this.csrData.inlineId) {
xarcV2.debug("Reloading an inline subapp", this.csrData.name);
setTimeout(() => this.startComponent.reload(), 1);
} else if (this.startComponent) {
xarcV2.debug("Reloading a subapp with its start component");
// restart a rendered subapp
setTimeout(() => this.startComponent.reload(), 1);
} else {
// eslint-disable-next-line
console.error(
"Subapp pipeline has no startComponent to handle hot reload",
this.csrData.name
);
}
}
return null;
});
};
}
}
}
Loading

0 comments on commit f71798d

Please sign in to comment.