evanesce
Version:
Evanesce disappears your web application framework for desired routes by managing AOT page builds on dependency change.
151 lines (143 loc) • 4.36 kB
text/typescript
export type SignalKeyString = string;
export interface SignalKeyObject {
key: SignalKeyString;
keys: Array<SignalKeyString>;
}
export type SignalKey = SignalKeyString | SignalKeyObject;
export interface Signal {
key: SignalKeyString;
[key: SignalKeyString]: any | (() => Promise<void>);
rebuildDependencies: () => Promise<void>;
parent?: Signal;
children?: SignalsList; // eslint-disable-line no-use-before-define
}
export type SignalsList = Array<Signal>;
export type SignalOrFunction = Signal | ((obj: any) => Signal);
export type SignalsOrFunctions = Array<SignalOrFunction>;
export interface SignalRebuildTargets {
rebuildTargets: Array<any>;
[key: SignalKeyString]: SignalRebuildTargets | undefined | Array<any>;
}
export interface SignalsRebuildTargets {
[key: SignalKeyString]: SignalRebuildTargets;
}
export interface Signals {
[key: SignalKeyString]: Signal;
}
export const access = (obj: any, path: Array<any>) => {
let index = 0;
let length = path.length;
while (obj != null && index < length) {
obj = obj[path[index++]];
}
return index && index === length ? obj : void 0;
};
export const buildSignals = <Render>({
signalKeys,
onRouteRebuild,
render
}: {
signalKeys: Array<SignalKey>;
onRouteRebuild: ({ route, html }: { route: string; html: string }) => void;
render: Render;
}) => {
const signalsRebuildTargets: SignalsRebuildTargets = signalKeys.reduce(
(accum: SignalsRebuildTargets, signalKey: SignalKey) => {
if (typeof signalKey !== 'string') {
accum[signalKey.key] = {
rebuildTargets: []
};
for (const key of signalKey.keys) {
accum[signalKey.key][key] = {
rebuildTargets: []
};
}
} else {
accum[signalKey] = {
rebuildTargets: []
};
}
return accum as SignalsRebuildTargets;
},
{} as SignalsRebuildTargets
);
const signals: Signals = signalKeys.reduce((accum: Signals, signalKey) => {
if (typeof signalKey !== 'string') {
const parent: any = {};
const children = signalKey.keys.map((key) => {
const child = {
key: key,
[key]: key,
rebuildDependencies: async ({ skip }: { skip?: boolean } = {}) => {
for (const { controller, params, route } of access(
signalsRebuildTargets,
[signalKey.key, key]
).rebuildTargets) {
const html = await controller(params, { render });
await onRouteRebuild({
route,
html
});
}
if (!skip) {
await parent.rebuildDependencies({ skip: true });
}
},
parent
};
return child;
});
parent.key = signalKey.key;
parent[signalKey.key] = signalKey.key;
parent.children = children;
parent.nested = {};
children.forEach((child) => (parent.nested[child.key] = child));
parent.rebuildDependencies = async ({
skip
}: { skip?: boolean } = {}) => {
for (const { controller, params, route } of signalsRebuildTargets[
signalKey.key
].rebuildTargets) {
const html = await controller(params, { render });
await onRouteRebuild({
route,
html
});
}
if (!skip) {
for (const child of parent.children) {
await child.rebuildDependencies({ skip: true });
}
}
};
accum[signalKey.key] = parent;
} else {
accum[signalKey] = {
key: signalKey,
[signalKey]: signalKey,
rebuildDependencies: async () => {
for (const { controller, params, route } of signalsRebuildTargets[
signalKey
].rebuildTargets) {
const html = await controller(params, { render });
await onRouteRebuild({
route,
html
});
}
}
};
}
return accum;
}, {});
const addToSignalsRebuildTargets = (
signalKeys: Array<SignalKeyString>,
{ controller, route, params }: any
) =>
access(signalsRebuildTargets, signalKeys).rebuildTargets.push({
controller,
route,
params
});
return { signals, addToSignalsRebuildTargets };
};